Developer Guide - CIRS

API integration and automation

Last updated About 2 months ago

πŸ’» Developer Guide: Invoice Reconciliation API

Best for: API integration and automation
Time: 10 minutes
Technical level: Requires API access

Quick Start

This system provides an API for automated invoice reconciliation against your contracted courier rates.

Authentication

Example
Authorisation: Bearer YOUR_API_KEY

Get your API key from Settings > API Key

Core Workflow

1. Set up couriers/services/pricing (one-time) 
↓ 
2. Import invoice data (POST /api/invoices) 
↓ 
3. System auto-validates against expected rates 
↓ 
4. Retrieve discrepancies (GET /api/invoices/{id}/discrepancies)

API Endpoints

1. Create Courier

Example
POST /api/couriers Content-Type: application/json { "name": "DPD", "code": "dpd" }

Response:

Example
{ "id": "courier_123", "name": "DPD", "code": "dpd" }

2. Create Service

Example
POST /api/courier-services Content-Type: application/json { "courier_id": "courier_123", "name": "DPD Next Day", "code": "next_day" }

3. Set Pricing

Example
POST /api/courier-services/{service_id}/pricing Content-Type: application/json { "zone": "GB_MAINLAND", "weight_bands": [ {"min": 0, "max": 1, "price": 4.40}, {"min": 1, "max": 2, "price": 5.30}, {"min": 2, "max": 5, "price": 8.50} ], "surcharges": [ {"type": "fuel", "amount": 0.50}, {"type": "saturday", "amount": 2.50} ] }

4. Import Invoice

Example
POST /api/invoices Content-Type: multipart/form-data courier_id: courier_123 invoice_number: INV-2024-001 file: @invoice.csv

Column Mapping (JSON):

Example
{ "tracking_number": "Tracking Number", "weight": "Weight (kg)", "destination_postcode": "Postcode", "service_type": "Service", "base_cost": "Base Cost", "surcharges": { "fuel": "Fuel Surcharge", "saturday": "Saturday Fee" } }

Response:

Example
{ "invoice_id": "inv_456", "status": "processing", "lines_imported": 150 }

5. Get Invoice Status

Example
GET /api/invoices/inv_456

Response:

Example
{ "id": "inv_456", "status": "completed", "total_lines": 150, "matched": 145, "discrepancies": 3, "ignored": 2, "processing_time_ms": 2340 }

6. Get Discrepancies

Example
GET /api/invoices/inv_456/discrepancies

Response:

Example
{ "discrepancies": [ { "line_id": "line_789", "tracking_number": "DPD123456", "type": "overcharge", "expected_amount": 14.50, "invoiced_amount": 15.95, "variance_amount": 1.45, "variance_percent": 10.0, "reason": "Base cost exceeded contract rate", "details": { "weight": 8.5, "zone": "GB_MAINLAND", "expected_base": 14.00, "invoiced_base": 15.45, "fuel_surcharge": 0.50 } } ] }

7. Get Unmatched Lines

Lines not present in your billing system:

Example
GET /api/invoices/unmatched?status=not_matched

Response:

Example
{ "unmatched_lines": [ { "tracking_number": "DPD789012", "invoice_id": "inv_456", "amount": 12.40, "shipment_date": "2024-11-15", "billed_to_customer": false } ], "total": 15 }

Billing API Integration

Fetch Customer Data

Pull existing customer rates from Billing API:

Example
POST /api/customers/{customer_id}/fetch-billing-data

This imports:

  • Customer zones

  • Contracted rates

  • Weight classes

  • Surcharges

Response:

Example
{ "steps_completed": 9, "couriers_imported": 3, "services_imported": 12, "zones_imported": 5, "rates_imported": 156 }

Match to Billing API

Check if invoice line matches a charge in Billing API:

Example
GET /api/invoice-lines/{line_id}/billing-match

Response:

Example
{ "steps_completed": 9, "couriers_imported": 3, "services_imported": 12, "zones_imported": 5, "rates_imported": 156 }

Webhooks

Subscribe to real-time events:

Available Events

  • invoice.completed - Invoice processing finished

  • discrepancy.detected - Overcharge/undercharge found

  • line.unmatched - Charge not found in billing system

Subscribe

Example
POST /api/webhooks Content-Type: application/json { "url": "https://your-app.com/webhooks/invoice-reconciliation", "events": ["invoice.completed", "discrepancy.detected"] }

Webhook Payload

Example
{ "event": "discrepancy.detected", "timestamp": "2024-11-20T10:30:00Z", "data": { "invoice_id": "inv_456", "line_id": "line_789", "variance_amount": 1.45, "variance_percent": 10.0 } }

Automation Example

Node.js: Automated Daily Reconciliation

Example
const axios = require('axios'); const FormData = require('form-data'); const fs = require('fs'); async function reconcileInvoice(courierCode, invoiceFile) { const API_KEY = process.env.RECONCILIATION_API_KEY; const BASE_URL = 'https://api.yourplatform.com'; // 1. Upload invoice const form = new FormData(); form.append('courier_id', courierCode); form.append('invoice_number', `INV-${Date.now()}`); form.append('file', fs.createReadStream(invoiceFile)); const uploadRes = await axios.post( `${BASE_URL}/api/invoices`, form, { headers: { 'Authorization': `Bearer ${API_KEY}`, ...form.getHeaders() } } ); const invoiceId = uploadRes.data.invoice_id; console.log(`Invoice uploaded: ${invoiceId}`); // 2. Wait for processing let status = 'processing'; while (status === 'processing') { await new Promise(resolve => setTimeout(resolve, 2000)); const statusRes = await axios.get( `${BASE_URL}/api/invoices/${invoiceId}`, { headers: { 'Authorization': `Bearer ${API_KEY}` } } ); status = statusRes.data.status; } // 3. Get discrepancies const discrepancyRes = await axios.get( `${BASE_URL}/api/invoices/${invoiceId}/discrepancies`, { headers: { 'Authorization': `Bearer ${API_KEY}` } } ); const { discrepancies } = discrepancyRes.data; // 4. Alert if significant overcharges const significantOvercharges = discrepancies.filter( d => d.variance_percent > 5 && d.type === 'overcharge' ); if (significantOvercharges.length > 0) { console.log(`🚨 Found ${significantOvercharges.length} significant overcharges!`); // Send alert email/Slack/etc. } return { invoiceId, totalDiscrepancies: discrepancies.length, totalVariance: discrepancies.reduce((sum, d) => sum + d.variance_amount, 0) }; } // Run daily via cron reconcileInvoice('courier_123', './invoices/dpd-latest.csv');

Rate Limits

  • 100 requests per minute per API key

  • Invoice processing: Max 10,000 lines per file

  • Bulk operations: Max 50 invoices per batch

Error Handling

Common Errors

401 Unauthorized

Example
{ "error": "invalid_api_key", "message": "API key is invalid or expired" }

400 Bad Request - Invalid Column Mapping

Example
{ "error": "missing_required_column", "message": "Invoice missing required column: 'tracking_number'", "available_columns": ["Tracking", "Weight", "Cost"] }

422 Unprocessable - Pricing Not Set

Example
{ "error": "pricing_not_configured", "message": "No pricing configured for zone: GB_HIGHLANDS", "service_id": "service_123" }

Best Practices

  1. Set up pricing before importing invoices - System can't validate without expected rates

  2. Use postcode over zone for destination - More accurate matching

  3. Handle ignored lines - Invalid data won't be processed

  4. Monitor unmatched rates - Indicates missing customer billing

  5. Store invoice_id - Use for future reference and reporting

Testing

Sandbox Environment:

Base URL: https://sandbox-api.yourplatform.com 
Test API Key: Available in Settings (sandbox mode)

Test Invoice: Use /api/test/generate-sample-invoice to get test data

Support & Next Steps πŸ“ž

Need Help?

Related Guides