Alya Books

    Developer docs

    REST API, webhooks, MCP server and CLI for UAE e-invoicing

    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

    Codestatus in the bodyMeaningWhat your side does
    202processedMapped, validated and created. invoiceNumber and invoiceId are set.Record invoiceId; wait for the status update
    200duplicateA message with the same key was processed before. Nothing was created; the reply points at the existing invoice.Treat as delivered
    400failed (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, 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.