Replies to the ERP
What POST /erp/{connector}/messages answers: status codes, the reply body, duplicates, failures, always-acknowledge, retries and rate limits.
Every message gets an immediate reply. A processed message has created the invoice (and, when the connector's submit setting is on, handed it to the service provider); the FTA outcome follows by webhook or polling.
Status codes
| Code | status in the body | Meaning | What your side does |
|---|---|---|---|
202 | processed | Mapped, validated and created. invoiceNumber and invoiceId are set. | Record invoiceId; wait for the status update |
200 | duplicate | A message with the same key was processed before. Nothing was created; the reply points at the existing invoice. | Treat as delivered |
400 | failed (in extra.message) | The message could not be unwrapped, mapped or validated. The body explains each problem. The message is kept in the log. | Do not retry unchanged: fix the data in CargoWise (or the charge map in Alya Books) and resend, or replay from the log |
400 | — | Empty body, or larger than 2 MB. Nothing stored. | Fix the transport |
401 | — | Missing or invalid key. | Check the key and the Basic credentials |
403 | — | The key lacks the invoices:write scope. | Use a key with the scope |
409 | — | Live messages are refused until the charge map is signed off, or the invoice number already exists outside this connector. | Contact the organisation |
429 | — | More than 600 requests a minute for the key. Retry-After says when to retry. | Retry after the delay |
5xx | — | Alya Books could not finish. A stored message stays in the log. | Retry with back-off; a resend is safe (it answers duplicate once processed) |
With ?ack=always on the endpoint URL (or Always acknowledge in the connector settings), failures answer 202 with status: "failed" instead of 400, for transports that would otherwise resend a message that cannot succeed. The failure stays in the log and in the reply body.
The reply body
{
"id": "5f0c…", // Alya Books's message id
"connector": "cargowise",
"status": "processed", // processed | duplicate | failed
"documentNumber": "INV-2027-00412",
"documentType": "INV",
"idempotencyKey": "cargowise:DXB:INV:INV-2027-00412",
"messageId": "EAN-0001", // yours, from X-Message-Id or the envelope
"envelope": ["json:Payload"], // how the XML arrived; [] for bare XML
"contentType": "application/json",
"bytes": 5555,
"invoiceId": "ac20fac3-…", // the invoice
"invoiceNumber": "INV-2027-00412",
"error": null,
"notes": ["Received as json:Payload", "CargoWise key 7f3c…"],
"result": { "invoiceId": "…", "invoiceNumber": "…", "validation": { "validity": true, "errors": [] }, "submission": { … } },
"receivedAt": "2026-10-13T07:10:02Z",
"processedAt": "2026-10-13T07:10:03Z",
"replayOf": null
}
A failure (400) uses the error envelope with the stored message in extra.message:
{
"code": "E_VALIDATION",
"statusCode": 400,
"message": "Charge NEW has no mapping and VAT code XX is unknown: add it to the connector's charge map",
"field": "charges.NEW",
"extra": { "kind": "mapping", "message": { "id": "…", "status": "failed", "error": "…", "notes": [] } }
}
When validation fails, extra.errors[] lists each PINT AE rule with rule, field, path, the message and an explanation of how to fix it, in English and Arabic.
Duplicates and corrections
- The key is
cargowise:<branch>:<type>:<number>. Resending the same document, with or without the same message id, answersduplicate. - A failed message is not a duplicate: once the data is fixed, resend it (or replay it in Alya Books) and it is processed.
- A reported invoice cannot change. Post a credit note in CargoWise; it arrives as a 381 against the original.
The message log
GET {BASE}/erp/messages lists every message (filter by status, connector, or search by number, key or error), GET {BASE}/erp/messages/{id} returns one with its raw body, and POST {BASE}/erp/messages/{id}/replay processes a stored message again after a mapping fix. The same log is in the app under Connectors → CargoWise → Messages.