# Replies to the ERP

> What POST /erp/{connector}/messages answers: status codes, the reply body, duplicates, failures, always-acknowledge, retries and rate limits.

Source: /docs/reference/erp-replies

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](/docs/guides/erp-status-updates).

## 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

```jsonc
{
  "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](/docs/reference/errors) with the stored message in `extra.message`:

```json
{
  "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, answers `duplicate`.
- 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.
