Receiving and statuses

How to track the status of sent invoices (GET /peppol/status), read inbound documents received over Peppol (GET /peppol/received), and how that relates to the peppol.document.received webhook. Both read operations require the invoice:read scope and the X-Organization-Id header.

Sent invoice status

MethodPathScope
GET/v1/agent/peppol/status/{invoiceId}invoice:read

Returns the latest (most recent) transmission for the given invoice. If no transmission exists yet for the invoice, it returns a synthetic not_sent status with no other fields.

GET /v1/agent/peppol/status/b1f0d7a2-…
X-API-Key: efk_pk_test_...
X-Organization-Id: 0a2c1f3e-…

{
  "data": {
    "invoice_id": "b1f0d7a2-…",
    "state": "SENT",
    "error_message": null,
    "receiver_identifier": "9915:2010101010",
    "document_id": "…",
    "updated_at": "2026-06-22T10:00:05.000Z"
  }
}
FieldTypeDescription
invoice_idUUIDThe invoice's ID.
stateenumThe transmission status (see the state machine below).
error_messagestring | nullThe failure reason for the ERROR state.
receiver_identifierstring | nullThe recipient's Peppol identifier (e.g. 9915:DIČ or 0245:DIČ).
document_idstring | nullThe document identifier within the transmission.
updated_atISO 8601Time of the last status change.

State machine

StateMeaningAction / when to retry
not_sentNo transmission exists for the invoice yet: it has not been sent.Call POST /peppol/send/{invoiceId}.
QUEUEDThe send is queued and waiting for a worker.Wait and keep polling (or listen for the webhook).
SENDINGA worker is currently sending the document via the AP.Wait: this is a transient state.
SENTThe document was accepted by the Peppol network (AS4 receipt).Done at the send level. Delivery to the recipient is signaled by the delivered webhook.
DEFERREDThe send is temporarily deferred (e.g. transient unavailability) and will be retried.Don't retry the send manually: the system retries on its own. Keep polling.
ERRORThe send failed permanently (see error_message).Fix the cause (often a missing address / VAT ID) and send again.
The DELIVERED state (document delivered to the recipient) is carried by a separate peppol.document.delivered webhook event, at the GET /peppol/status level, the terminal successful send state is SENT. In the phoss-ap sandbox, delivery is confirmed right at submit time, so the transition to SENT is fast.

While GET /peppol/status gives you one summary state, a detailed layer-by-layer delivery breakdown (AS4 transport / MLS / business response) and the full transmission history for audit and archiving are available via Delivery evidence (GET /v1/agent/peppol/sent/{invoiceId}/evidence).

Inbound documents

MethodPathScope
GET/v1/agent/peppol/receivedinvoice:read

Query parameters:

ParameterTypeDefaultDescription
limitint 1–10050Number of records.
offsetint ≥ 00Pagination offset.
fromYYYY-MM-DD–Received from this day inclusive (UTC day of received_at) – incremental sync.
toYYYY-MM-DD–Received up to this day inclusive.
acknowledgedtrue | false–false = only documents not yet acknowledged via POST …/acknowledge.

Records are sorted from newest first.

GET /v1/agent/peppol/received?limit=20&offset=0
X-API-Key: efk_pk_test_...
X-Organization-Id: 0a2c1f3e-…

{
  "data": [
    {
      "id": "f3a1…",
      "sender_participant_id": "9915:9999999999",
      "sender_name": "Supplier a.s.",
      "sender_ico": "11112222",
      "document_type": "invoice",
      "document_number": "VF2026123",
      "total": "240.00",
      "vat_total": "40.00",
      "currency": "EUR",
      "status": "new",
      "issue_date": "2026-06-20",
      "received_at": "2026-06-20T08:15:00.000Z",
      "acknowledged_at": null,
      "sbdh_instance_id": "8f2c1c0e-…",
      "attachments_count": 1,
      "is_test": false
    }
  ]
}
FieldTypeDescription
idUUIDThe received document's ID.
sender_participant_idstringThe sender's Peppol identifier.
sender_namestring | nullThe sender's name.
sender_icostring | nullThe sender's IČO.
document_typestringThe document type (e.g. invoice, credit_note).
document_numberstringThe sender's document number.
totalstringThe total amount including VAT.
vat_totalstringThe VAT amount.
currencystringThe currency.
statusenumProcessing status: new, reviewed, matched, rejected, partially_paid, paid.
issue_datedateThe issue date.
received_atISO 8601Time the document was received.
acknowledged_atISO 8601 | nullWhen pickup was acknowledged via POST …/acknowledge; null = not yet picked up.
sbdh_instance_idstring | nullSBDH InstanceIdentifier – immutable transmission id (null for documents without an SBDH).
attachments_countintNumber of embedded attachments.
is_testbooleanSandbox (test) document.

Downloading a received document (PDF / XML)

You can download an individual received document as a PDF (generated from the document's data, including a pay-by-square QR code when there's an IBAN) or as the original UBL XML. Both operations require the invoice:read scope and return the file as an attachment (Content-Disposition: attachment). id is the value from GET /peppol/received.

MethodPathScopeContent-Type
GET/v1/agent/peppol/received/{id}/pdfinvoice:readapplication/pdf
GET/v1/agent/peppol/received/{id}/xmlinvoice:readapplication/xml

If the original XML is not available for a document, the /xml endpoint returns 404. A nonexistent id (or a document belonging to another organization) also returns 404.

GET /v1/agent/peppol/received/f3a1…/pdf
X-API-Key: efk_pk_test_...
X-Organization-Id: 0a2c1f3e-…

HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="prijata-faktura-VF2026123.pdf"

%PDF-1.7 …

Detail, attachments and pickup acknowledgement

MethodPathScope
GET/v1/agent/peppol/received/{id}invoice:read
GET/v1/agent/peppol/received/{id}/attachmentsinvoice:read
GET/v1/agent/peppol/received/{id}/attachments/{attachmentId}invoice:read
POST/v1/agent/peppol/received/{id}/acknowledgeinvoice:read

The detail returns the document metadata incl. SBDH identifiers (receiver_participant_id, document_type_id, process_id, sbdh_instance_id), the SHA-256 of the archived UBL (ubl_sha256), the attachment list and links to the XML, PDF, attachments and receipt evidence. Attachments are the files the sender embedded in the UBL (PDF, XLSX, DOCX, XML…); /attachments lists them, /attachments/{attachmentId} returns the content with its original Content-Type.

POST /v1/agent/peppol/received/f3a1…/acknowledge
X-API-Key: efk_pk_live_...
X-Organization-Id: 0a2c1f3e-…

{
  "data": {
    "id": "f3a1…",
    "acknowledged_at": "2026-06-20T08:20:11.000Z",
    "already_acknowledged": false
  }
}
Recommended pattern against double processing: fetch GET /peppol/received?acknowledged=false, process the document in your system and call POST …/acknowledge. The call is idempotent (a second call returns the original stamp and already_acknowledged: true) and does not change the in-app review status. Documents stay available for re-download for the whole archiving period (10 years).

Receipt evidence

MethodPathScope
GET/v1/agent/peppol/received/{id}/evidenceinvoice:read

Immutable identifiers of the inbound transmission, receipt time, UBL integrity and the Message Level Status (MLS) we sent back to the sender as the "accepted for processing" proof. mls.state is sent (with the AS4 message_id, when this API dispatched the MLS), sent_by_access_point (the Access Point answers MLS itself – the production default, no message id available), pending while API-side MLS is not dispatched yet, or not_applicable for non-Peppol documents. The counterpart for sent documents is Delivery evidence.

{
  "data": {
    "document_id": "f3a1…",
    "transport": "peppol",
    "mode": "live",
    "sender_participant_id": "iso6523-actorid-upis::0245:2020202020",
    "receiver_participant_id": "iso6523-actorid-upis::0245:3030303030",
    "document_type_id": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1",
    "process_id": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0",
    "sbdh_instance_id": "8f2c1c0e-…",
    "document_number": "VF2026123",
    "received_at": "2026-06-20T08:15:00.000Z",
    "ubl_sha256": "3b7c…",
    "ubl_bytes": 18422,
    "mls": { "state": "sent", "message_id": "…@phase4.phoss-ap", "sent_at": "2026-06-20T08:15:03.000Z" },
    "acknowledged_at": "2026-06-20T08:20:11.000Z"
  }
}

Audit trail (event export)

MethodPathScope
GET/v1/agent/peppol/eventsinvoice:read

Exportable audit trail derived from the immutable transmission records, for sent and received documents alike. Every event carries occurred_at, event, result, document_id, the AS4 message_id, sbdh_instance_id, counterparty_participant_id and ubl_sha256. Parameters: from/to (UTC days, default last 31 days), direction (sent | received), format (json | csv), limit (max 1000) and offset. The window selects events by their own time: a document received in August and acknowledged in September belongs to the September export. The X-Events-Truncated: true header (JSON and CSV) means the window exceeded 5,000 source rows of one direction – narrow it.

EventDirectionMeaning
sent.queuedsentTransmission still awaiting the Access Point outcome.
sent.acceptedsentAS4 receipt from the receiving Access Point.
sent.deferredsentSending temporarily deferred (retry).
sent.failedsentSending failed – at the Access Point or already before dispatch (detail = reason).
sent.deliveredsentReceiver confirmed receipt (MLS accepted).
sent.rejectedsentReceiver rejected the document (MLS rejected).
receivedreceivedDocument received and archived.
received.mls_sentreceivedWe sent the MLS to the sender.
received.acknowledgedreceivedPickup acknowledged via the API.
GET /v1/agent/peppol/events?from=2026-06-01&to=2026-06-30&format=csv
X-API-Key: efk_pk_live_...
X-Organization-Id: 0a2c1f3e-…

HTTP/1.1 200 OK
Content-Type: text/csv; charset=utf-8
Content-Disposition: attachment; filename="peppol-events-2026-06-01_2026-07-01.csv"

occurred_at,event,direction,result,mode,document_id,invoice_id,message_id,sbdh_instance_id,counterparty_participant_id,ubl_sha256,detail
2026-06-20T08:15:03.000Z,received.mls_sent,received,success,live,f3a1…,,…@phase4.phoss-ap,8f2c1c0e-…,0245:2020202020,3b7c…,Message Level Status sent to the sender
2026-06-20T08:15:00.000Z,received,received,success,live,f3a1…,,,8f2c1c0e-…,0245:2020202020,3b7c…,invoice VF2026123

Testing inbound documents (sandbox)

With a test key (efk_pk_test_…), a send addressed to a 9915: participant that lives on our platform — including the sender itself — is also materialized as a real received document for that organization, and fires the same peppol.document.received webhook the live inbound pipeline uses. That lets you verify the whole receive path on your own, without us sending you anything.

Register the receiving organization with your test key (POST /v1/agent/peppol/enroll, which creates its 9915:{DIČ} participant), subscribe your webhook endpoint to peppol.document.received, then send a test invoice whose buyer DIČ is that organization’s DIČ — your own for a self-send, or a second test organization for an A → B test.

POST /v1/agent/peppol/send/b1f0d7a2-…
X-API-Key: efk_pk_test_...
X-Organization-Id: <sender org>

GET /v1/agent/peppol/received
X-API-Key: efk_pk_test_...
X-Organization-Id: <receiver org>
In test mode the recipient is derived only from the buyer’s DIČ (10 digits, or an SK VAT id we strip to it); an explicit Peppol identifier on the buyer contact is ignored. Documents produced this way are flagged as test documents and are never billed.

Relation to the webhook

When a document arrives for you over Peppol, the system stores it (it appears in GET /peppol/received) and simultaneously fires the peppol.document.received webhook. Polling and the webhook are two views of the same event: the webhook is a push notification, and the received endpoint is the authoritative list.

{
  "event": "peppol.document.received",
  "timestamp": "2026-06-20T08:15:00.000Z",
  "data": {
    "senderName": "Supplier a.s.",
    "senderParticipantId": "9915:9999999999",
    "documentNumber": "VF2026123",
    "documentType": "invoice",
    "total": "240.00",
    "currency": "EUR",
    "orgId": "0a2c1f3e-…"
  }
}
Recommended pattern: listen for the webhook and, upon receiving it (or periodically as a safety net), fetch GET /peppol/received. Deduplicate webhooks by X-Webhook-Id: details in Webhooks.

Documents from the API to the accounting program

Received documents created through POST /v1/agent/expenses reach the firm's accounting program through the Konektor (Omega, Pohoda, Money S3, MRP) the same way as the click on "Zaúčtovať" in eDoklad. Without this step a document created through the API never reaches the program. The scope needed is receipt:approve:

MethodPathScope
POST/v1/agent/expenses/{id}/approve-for-accountingreceipt:approve
POST/v1/agent/expenses/bulkreceipt:update + receipt:approve for action: approve_for_accounting

The first call marks the document as approved for accounting and hands it to the firm's active connector. Repeat calls are no-ops. No request body.

POST /v1/agent/expenses/5b7e91c4-…/approve-for-accounting
X-API-Key: efk_pk_test_...
X-Organization-Id: 0a2c1f3e-…

HTTP/1.1 200 OK

{
  "data": {
    "id": "5b7e91c4-…",
    "approved_for_accounting_at": "2026-10-13T08:15:00.000Z",
    "sent_to_accounting": true
  }
}

sent_to_accounting: false means the document was already approved earlier or is locked by a file export - nothing was sent again. A document in status processing, needs_review or rejected returns 409 EXPENSE_NOT_POSTABLE (details.status carries the status); a missing document returns 404.

In bulk: POST /v1/agent/expenses/bulk with { "ids": [...], "action": "approve_for_accounting" } (max 100 IDs). Only documents approved for accounting for the first time and not in status processing, needs_review or rejected are sent; the others are skipped and not counted in processed_count.