Skip to main content
Glama

RS.ge Waybill MCP Server

RS_GE_API_BEST_PRACTICES.md16 kB
# RS.ge Waybill API - Best Practices & Lessons Learned ## Table of Contents 1. [Critical API Requirements](#critical-api-requirements) 2. [Common Pitfalls & Solutions](#common-pitfalls--solutions) 3. [Correct Implementation Patterns](#correct-implementation-patterns) 4. [Testing Strategy](#testing-strategy) 5. [Production Checklist](#production-checklist) --- ## Critical API Requirements ### 1. Use the Correct SOAP Operation **❌ WRONG:** ```xml <get_waybills_v1 xmlns="http://tempuri.org/"> ``` **✅ CORRECT:** ```xml <get_waybills xmlns="http://tempuri.org/"> ``` **Why:** The `get_waybills_v1` operation exists but has different behavior and stricter limitations. The base `get_waybills` operation is more reliable and better documented. --- ### 2. Date Parameters Must Use ISO DateTime Format **❌ WRONG:** ```xml <create_date_s>2025-10-19</create_date_s> <create_date_e>2025-10-21</create_date_e> ``` **❌ ALSO WRONG:** ```xml <last_update_date_s>2025-10-19</last_update_date_s> <last_update_date_e>2025-10-21</last_update_date_e> ``` **✅ CORRECT:** ```xml <create_date_s>2025-10-19T00:00:00</create_date_s> <create_date_e>2025-10-22T00:00:00</create_date_e> ``` **Key Points:** - Use `create_date_s/e` NOT `last_update_date_s/e` - Format: `YYYY-MM-DDTHH:MM:SS` (19 characters) - End date should be **next day at 00:00:00** to include the full end day - Example: To get Oct 19-21, use start=`2025-10-19T00:00:00` end=`2025-10-22T00:00:00` --- ### 3. Include seller_un_id Parameter **❌ WRONG:** ```xml <su>4053098841:405309884</su> <sp>Password1@</sp> ``` **✅ CORRECT:** ```xml <su>4053098841:405309884</su> <sp>Password1@</sp> <seller_un_id>405309884</seller_un_id> ``` **How to Extract:** ```typescript const credentials = "4053098841:405309884"; const sellerUnId = credentials.split(':')[1]; // "405309884" ``` **Why:** Without `seller_un_id`, the API may return error `-101` or return incomplete data. This parameter filters waybills to only those belonging to your organization. --- ### 4. XML Parsing - Handle Attributes Correctly **❌ WRONG:** ```typescript const resultKeys = Object.keys(methodResponse); if (resultKeys.length === 1 && resultKeys[0].endsWith('Result')) { return methodResponse[resultKeys[0]]; } ``` **Problem:** When parsed, the response has TWO keys: - `get_waybillsResult` (the data) - `@_xmlns` (XML attribute) So `length === 1` fails! **✅ CORRECT:** ```typescript // Filter out XML attributes (keys starting with @_) const resultKeys = Object.keys(methodResponse).filter(k => !k.startsWith('@_')); // Find the Result key const resultKey = resultKeys.find(k => k.endsWith('Result')); if (resultKey) { return methodResponse[resultKey]; } ``` --- ### 5. Waybill ID Field Name **❌ WRONG:** ```typescript const waybillId = waybill.WAYBILL_ID; ``` **✅ CORRECT:** ```typescript const waybillId = waybill.ID; // Note: field is called "ID" not "WAYBILL_ID" ``` **Why:** The XML response uses `<ID>974175216</ID>`, not `<WAYBILL_ID>`. --- ## Common Pitfalls & Solutions ### Pitfall 1: Getting 0 Waybills When Data Exists **Symptoms:** - API returns HTTP 200 - XML contains `<WAYBILL>` tags - But your parser extracts 0 waybills **Root Causes:** 1. Using wrong date parameters (`last_update_date` instead of `create_date`) 2. Using wrong date format (YYYY-MM-DD instead of ISO datetime) 3. XML parser not handling attributes correctly 4. Missing `seller_un_id` parameter **Solution:** ```typescript // Correct SOAP request const soapRequest = ` <get_waybills xmlns="http://tempuri.org/"> <su>${credentials.user}</su> <sp>${credentials.password}</sp> <seller_un_id>${sellerUnId}</seller_un_id> <create_date_s>${startDate}T00:00:00</create_date_s> <create_date_e>${nextDayDate}T00:00:00</create_date_e> <buyer_tin></buyer_tin> </get_waybills> `; ``` --- ### Pitfall 2: ERROR -1072 (Date Range Too Large) **Symptoms:** ```xml <RESULT> <STATUS>-1072</STATUS> </RESULT> ``` **Causes:** 1. Date range exceeds 3 days 2. Using wrong parameters that trigger stricter validation 3. Not adding +1 day correctly to end date **Solution:** ```typescript // For Oct 19-21 (3 days): const startDate = '2025-10-19T00:00:00'; const endDateObj = new Date('2025-10-21'); endDateObj.setDate(endDateObj.getDate() + 1); // Oct 22 const endDate = endDateObj.toISOString().slice(0, 19); // 2025-10-22T00:00:00 // This gives us Oct 19, 20, 21 = 3 days ✅ ``` **Note:** The range is from Oct 19 00:00:00 to Oct 22 00:00:00, which is technically 3 days worth of data. --- ### Pitfall 3: Client-Side Filtering Reducing Results **Problem:** ```typescript // API returns 72 waybills // But filtering reduces to 37 ``` **Why:** - API might return waybills modified during the date range, not created - Client-side filtering by CREATE_DATE removes valid results **Solution:** ```typescript // Don't use client-side filtering anymore! // The correct API parameters return the right data // ❌ OLD WAY: const waybills = apiResults.filter(wb => wb.CREATE_DATE >= startDate && wb.CREATE_DATE <= endDate ); // ✅ NEW WAY: // Just use the API results as-is with correct parameters const waybills = apiResults; ``` --- ### Pitfall 4: Single-Day Queries Return ERROR **Symptoms:** ```typescript // Query for just Oct 19: create_date_s: '2025-10-19' create_date_e: '2025-10-19' // Result: ERROR -1072 ``` **Why:** The API interprets this as 0 days or rejects single-day queries. **Solution:** ```typescript // For Oct 19 only: create_date_s: '2025-10-19T00:00:00' create_date_e: '2025-10-20T00:00:00' // Next day! ``` --- ## Correct Implementation Patterns ### Pattern 1: Date Range Preparation ```typescript function prepareDateRange(startDate: string, endDate: string) { // Input: YYYY-MM-DD format // Output: ISO datetime with +1 day on end const startDateTime = `${startDate}T00:00:00`; const endDateObj = new Date(endDate); endDateObj.setDate(endDateObj.getDate() + 1); const endDateTime = endDateObj.toISOString().slice(0, 19); return { startDateTime, endDateTime }; } // Usage: const { startDateTime, endDateTime } = prepareDateRange('2025-10-19', '2025-10-21'); // Result: // startDateTime = '2025-10-19T00:00:00' // endDateTime = '2025-10-22T00:00:00' ``` --- ### Pattern 2: Building SOAP Request ```typescript function buildWaybillRequest( credentials: { user: string; password: string }, startDateTime: string, endDateTime: string, buyerTin: string = '' ): string { // Extract seller_un_id const sellerUnId = credentials.user.includes(':') ? credentials.user.split(':')[1] : ''; return `<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <get_waybills xmlns="http://tempuri.org/"> <su>${xmlEscape(credentials.user)}</su> <sp>${xmlEscape(credentials.password)}</sp> <seller_un_id>${xmlEscape(sellerUnId)}</seller_un_id> <create_date_s>${startDateTime}</create_date_s> <create_date_e>${endDateTime}</create_date_e> <buyer_tin>${xmlEscape(buyerTin)}</buyer_tin> </get_waybills> </soap:Body> </soap:Envelope>`; } function xmlEscape(str: string): string { return str .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&apos;'); } ``` --- ### Pattern 3: Parsing SOAP Response ```typescript import { XMLParser } from 'fast-xml-parser'; function parseSoapResponse<T>(xmlData: string): T { const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_', removeNSPrefix: true, // Remove soap: prefix }); const parsed = parser.parse(xmlData); // Navigate to response const envelope = parsed.Envelope; const body = envelope.Body; const methodResponse = body[Object.keys(body)[0]]; // e.g., get_waybillsResponse // Filter out XML attributes const resultKeys = Object.keys(methodResponse).filter(k => !k.startsWith('@_')); // Find Result element const resultKey = resultKeys.find(k => k.endsWith('Result')); if (!resultKey) { throw new Error('No Result element found in SOAP response'); } const result = methodResponse[resultKey]; // Check for RS.ge API errors if (result.RESULT && result.RESULT.STATUS) { const status = Number(result.RESULT.STATUS); if (status < 0) { throw new Error(`RS.ge API Error ${status}`); } } return result as T; } ``` --- ### Pattern 4: Extracting Waybills ```typescript function extractWaybills(response: any): Waybill[] { if (!response.WAYBILL_LIST) { return []; } const waybillData = response.WAYBILL_LIST.WAYBILL; if (!waybillData) { return []; } // Handle both single waybill and array const waybills = Array.isArray(waybillData) ? waybillData : [waybillData]; return waybills.map(wb => ({ id: wb.ID, // Note: ID not WAYBILL_ID type: wb.TYPE, createDate: wb.CREATE_DATE, buyerTin: wb.BUYER_TIN, sellerTin: wb.SELLER_TIN, fullAmount: wb.FULL_AMOUNT, status: wb.STATUS, // ... other fields })); } ``` --- ## Testing Strategy ### 1. Unit Tests for Date Preparation ```typescript describe('prepareDateRange', () => { it('should add 1 day to end date', () => { const { startDateTime, endDateTime } = prepareDateRange('2025-10-19', '2025-10-21'); expect(startDateTime).toBe('2025-10-19T00:00:00'); expect(endDateTime).toBe('2025-10-22T00:00:00'); }); it('should handle single day', () => { const { startDateTime, endDateTime } = prepareDateRange('2025-10-19', '2025-10-19'); expect(startDateTime).toBe('2025-10-19T00:00:00'); expect(endDateTime).toBe('2025-10-20T00:00:00'); }); }); ``` --- ### 2. Integration Tests with Real API ```typescript describe('RS.ge API Integration', () => { it('should retrieve waybills for Oct 19-21, 2025', async () => { const client = new RsWaybillClient({ user: process.env.RS_SERVICE_USER, password: process.env.RS_SERVICE_PASSWORD, }); const waybills = await client.getWaybills('2025-10-19', '2025-10-21'); expect(waybills.length).toBeGreaterThan(0); expect(waybills[0]).toHaveProperty('ID'); expect(waybills[0]).toHaveProperty('FULL_AMOUNT'); }); }); ``` --- ### 3. Test with Known Data ```typescript // Keep a record of known waybill IDs for validation const KNOWN_OCT_19_WAYBILLS = [ '974175216', '974175698', '974175705', '974175712', '974175730', '974175763', '974175769', '974175774', '974175784', '974175793', '974175798', '974175801' ]; it('should find all known Oct 19 waybills', async () => { const waybills = await client.getWaybills('2025-10-19', '2025-10-19'); const foundIds = waybills.map(wb => wb.ID.toString()); KNOWN_OCT_19_WAYBILLS.forEach(knownId => { expect(foundIds).toContain(knownId); }); }); ``` --- ## Production Checklist ### Before Deployment - [ ] Using `get_waybills` operation (not `get_waybills_v1`) - [ ] Date parameters use ISO datetime format (`YYYY-MM-DDTHH:MM:SS`) - [ ] Using `create_date_s/e` (not `last_update_date_s/e`) - [ ] End date incremented by +1 day - [ ] `seller_un_id` parameter included and extracted correctly - [ ] XML parser filters out `@_` attributes - [ ] Using `ID` field (not `WAYBILL_ID`) - [ ] Error handling for negative STATUS codes - [ ] Retry logic for network errors - [ ] Logging for debugging - [ ] Environment variables for credentials - [ ] Rate limiting / request throttling - [ ] Timeout configuration (recommend 60-120 seconds) ### Performance Considerations ```typescript // Good: Parallel requests for multiple date ranges const ranges = [ { start: '2025-10-01', end: '2025-10-03' }, { start: '2025-10-04', end: '2025-10-06' }, { start: '2025-10-07', end: '2025-10-09' }, ]; const results = await Promise.all( ranges.map(range => client.getWaybills(range.start, range.end)) ); const allWaybills = results.flat(); ``` ### Error Handling ```typescript try { const waybills = await client.getWaybills(startDate, endDate); } catch (error) { if (error.message.includes('-1072')) { console.error('Date range too large. Split into smaller ranges.'); } else if (error.message.includes('-101')) { console.error('Missing seller_un_id parameter.'); } else { console.error('Unknown error:', error); } } ``` --- ## Summary: Quick Reference | Aspect | Wrong | Correct | |--------|-------|---------| | SOAP Operation | `get_waybills_v1` | `get_waybills` | | Date Parameter | `last_update_date_s/e` | `create_date_s/e` | | Date Format | `YYYY-MM-DD` | `YYYY-MM-DDTHH:MM:SS` | | End Date | Same as user input | User input + 1 day | | Seller ID | Not included | `seller_un_id` from credentials | | ID Field | `WAYBILL_ID` | `ID` | | XML Attributes | Not filtered | Filter keys starting with `@_` | --- ## Real Example: Complete Working Code ```typescript import axios from 'axios'; import { XMLParser } from 'fast-xml-parser'; async function getWaybills( credentials: { user: string; password: string }, startDate: string, // YYYY-MM-DD endDate: string // YYYY-MM-DD ): Promise<any[]> { // 1. Prepare dates const startDateTime = `${startDate}T00:00:00`; const endDateObj = new Date(endDate); endDateObj.setDate(endDateObj.getDate() + 1); const endDateTime = endDateObj.toISOString().slice(0, 19); // 2. Extract seller_un_id const sellerUnId = credentials.user.split(':')[1]; // 3. Build SOAP request const soapRequest = `<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <get_waybills xmlns="http://tempuri.org/"> <su>${credentials.user}</su> <sp>${credentials.password}</sp> <seller_un_id>${sellerUnId}</seller_un_id> <create_date_s>${startDateTime}</create_date_s> <create_date_e>${endDateTime}</create_date_e> <buyer_tin></buyer_tin> </get_waybills> </soap:Body> </soap:Envelope>`; // 4. Send request const response = await axios.post( 'https://services.rs.ge/WayBillService/WayBillService.asmx', soapRequest, { headers: { 'Content-Type': 'text/xml; charset=utf-8', 'SOAPAction': 'http://tempuri.org/get_waybills' }, timeout: 60000 } ); // 5. Parse response const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_', removeNSPrefix: true, }); const parsed = parser.parse(response.data); const result = parsed.Envelope.Body.get_waybillsResponse.get_waybillsResult; // 6. Extract waybills const waybillData = result.WAYBILL_LIST?.WAYBILL; if (!waybillData) return []; return Array.isArray(waybillData) ? waybillData : [waybillData]; } // Usage: const waybills = await getWaybills( { user: '4053098841:405309884', password: 'Password1@' }, '2025-10-19', '2025-10-21' ); console.log(`Retrieved ${waybills.length} waybills`); // Output: Retrieved 61 waybills ``` --- ## Lessons Learned 1. **Don't assume API documentation is complete** - The working ERP code was more reliable than documentation 2. **Test with real data** - Having specific waybill IDs to verify was crucial 3. **XML parsing is tricky** - Attributes can break simple parsers 4. **Date handling is critical** - Small differences in format cause total failures 5. **Compare with working code** - Looking at the ERP application revealed all the answers 6. **Log everything during development** - Raw XML responses helped debug issues 7. **Test edge cases** - Single-day queries, 3-day limits, etc. --- **Last Updated:** 2025-01-XX **Version:** 1.0 **Verified With:** RS.ge Waybill Service (Production API)

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/BorisSolomonia/MCPWaybill'

If you have feedback or need assistance with the MCP directory API, please join our Discord server