Docs

Hand in an invoice the client received through a route

  • In the sandbox
POST
/received

Since 0.35.0 (17 Oct 2026). The body is the XML exactly as the route delivered it (UBL, CII or FA(3)); nothing is changed and nothing is sent. The file is stored first, sealed under the client's data key, because in law it is already received; then the route's own validators check it, as for a pass-through send, and the result arrives as a status event (received, then validated or validation_failed, with direction: in) on the client's webhook and on GET /received/{id}/events. A file that fails the rules is kept and reads validation_failed with the errors; it is never dropped. A file that is not an invoice to keep is refused with 422 and nothing is stored, as on POST /invoices/xml: a DOCTYPE (EI-XML-DTD, never parsed), XML that is not well-formed (EI-XML-SYNTAX), or a document that is not an invoice the route knows (EI-XML-TYPE). A check that could not run (a validator down) is retried, and after the last retry the invoice goes on the care list and stays received. A client hands in at most 120 documents a minute and 2,000 a day, whatever key it uses (429, not kept under the Idempotency-Key). The file is served back as a download (Content-Disposition: attachment).

One route document is one received invoice for the client: by rail_id when it is given, by the file's SHA-256 when it is not. The same document again answers 202 with duplicate_of; the same rail_id with other bytes answers 409 received-id-conflict, and nothing is stored. Received invoices are kept apart from the invoices the client sends: they are not in GET /invoices, the series, the metrics or the monthly report, and they never block a send of the same file. A received invoice is deleted with the client's retention once nothing is open on it (its check ended, or a person handled its care row), and is in the client's export under received/. The submit scope; a client key acts for its own client only.

Authorization

apiKey
headerAuthorizationBearer <token>

Send your key as a bearer token: Authorization: Bearer <your-api-key>. Health is the only call that needs no key.

Query parameters

route*string

The route the document came through.

Value in"PEPPOL""PL-KSEF""RO-EFACTURA""FR-PA""DE-XRECHNUNG"
rail_id?string

The route's own id for the document (the access point's document id, the KSeF number, ANAF's download id). 1 to 200 letters, digits and . _ : / @ + = # -.

Match^[A-Za-z0-9._:/@+=#-]{1,200}$
client?string

The client that received it. The operator's key names it (left out, the operator's own local client); a client key may leave it out or name its own client, and any other answers 403.

Match^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$

Header parameters

Idempotency-Key*string

A unique key per logical request (a UUID is fine). A key that begins erp: (an ERP transport recipe's erp:{connector}:{scope}:{entity}:{id}, with the operator's key or a client's) has no window; it is kept with its first answer until the client, or the invoice it made, is deleted, so a changed document handed again weeks later still gets 409 idempotency-conflict. Such a key is taken only on an ERP connector export (POST /invoices with a connector this build reads, an export that is a non-empty JSON object, and with the operator's key client); on any other request, a canonical document, POST /invoices/xml or a cancel, it answers 400 idempotency-key-not-allowed and nothing is kept, and an export with a number longer than 40 characters gets the 422 EI-SCHEMA it gets under any other key, also kept under no key. When the operator releases a dead letter (cancels it), the earlier answers about that invoice go with it, the submission's and, since 0.35.0, an earlier refused cancel's, so a retry of that cancel runs again and answers 200 with the invoice cancelled. Since 0.32.0 a 5xx answer and a 422 connector-settings-missing are not kept, so the same request with the same key can be sent again. With the operator's key it may not begin with client: (400), the form client keys are stored under.

Length8 <= length <= 100

Request body

body*stringapplication/xml

Response body

Stored, and queued for the route's check unless it already failed; or already held (duplicate_of).

application/json
  1. response

Since 0.35.0. An invoice the client received through a route, as POST /received took it. Like Invoice, it carries no invoice content: no seller, buyer or amounts.

id*string
Match^rcv_[0-9a-f]{24}$
invoice_ref*string

The file's own invoice number (BT-1; FA(3) Fa/P_2), else the rail_id, else the id.

invoice_number?string
route*Route

The country route. The same values as country_route in status events.

Value in"PEPPOL""PL-KSEF""RO-EFACTURA""FR-PA""DE-XRECHNUNG"
client*string
environment*string
Value in"sandbox""production"
state*string
Value in"received""validated""validation_failed"
direction*"in"
Value in"in"
rail_id?string

The route's own id for the document, as given on POST /received.

document_sha256*string
Match^[a-f0-9]{64}$
duplicate_of?string

Only on POST /received, when the document was already held for the client and route.

links?
errors?array<>
documents?array<>
created_at*string
Formatdate-time
updated_at*string
Formatdate-time
curl -X POST "https://example.com/received?route=PEPPOL" \  -H "Authorization: Bearer <your-api-key>" \  -H "Idempotency-Key: order-2026-0001" \  -H "Content-Type: application/xml" \  -d 'string'
{  "id": "string",  "invoice_ref": "string",  "invoice_number": "string",  "route": "PEPPOL",  "client": "string",  "environment": "sandbox",  "state": "received",  "direction": "in",  "rail_id": "string",  "document_sha256": "string",  "duplicate_of": "string",  "links": {    "self": "string",    "events": "string"  },  "errors": [    {      "code": "string"    }  ],  "documents": [    {      "kind": "received-xml",      "sha256": "string",      "href": "string"    }  ],  "created_at": "2019-08-24T14:15:22Z",  "updated_at": "2019-08-24T14:15:22Z"}