Skip to content

Document Status

Track the delivery lifecycle of sent invoices and credit notes on the Peppol network.

GET/api/v1/invoices/:id#

Track the lifecycle of sent documents. After calling send(), use getStatus() to check delivery progress at any time.

import { Peppol } from "@getpeppr/sdk";

const peppol = new Peppol({ apiKey: "sk_live_..." });

// Send an invoice
const result = await peppol.invoices.send(invoiceData);
console.log(result.status); // "submitted"

// Check status later
const status = await peppol.invoices.getStatus(result.id);
console.log(status.status);

// Status flow:
//   "submitted" → "delivered" → "accepted" → "paid"
//                              → "rejected"
//               → "failed"

// The Peppol AS4 message ID is read from the network on demand, so ask for it
// explicitly. Without ?include=evidence the field is simply absent.
const withEvidence = await peppol.invoices.getStatus(result.id, { includeEvidence: true });
if (withEvidence.peppolMessageId) {
  console.log(`Peppol AS4 ID: ${withEvidence.peppolMessageId}`);
}

Example Response

200 OK
{
  "id": "inv_abc123",
  "submissionId": "8b3f2c1d-4a5e-4f6b-9c8d-7e6f5a4b3c2d",
  "providerDocumentId": "inv_abc123",
  "number": "INV-2026-001",
  "status": "delivered",
  "currency": "EUR",
  "totalAmount": 12100,
  "createdAt": "2026-03-01T10:00:00Z",
  "updatedAt": "2026-03-01T10:04:12Z"
}

Peppol AS4 message ID

The AS4 message ID is the receipt the receiving access point signed for your document. getpeppr reads it from the network on request rather than storing it, so it is opt-in: pass include=evidence (or { includeEvidence: true } with the SDK). If the document has not gone out yet, the field is simply absent — the rest of the response is unchanged.

200 OK — ?include=evidence
{
  "id": "inv_abc123",
  "status": "delivered",
  "peppolMessageId": "4381dde6-28b7-4d7b-a728-2148e1d470c9@phase4",
  "createdAt": "2026-03-01T10:00:00Z"
}

Status Flow

Every document follows a linear progression through these states:

submitteddeliveredacceptedpaid

After delivered, a document can also transition to:

rejected

At any point after submitted, delivery failures result in:

failedno_action

Status Details

  • submitted — document submitted to the Peppol network
  • delivered — confirmed received by buyer's access point
  • accepted — buyer accepted the document
  • rejected — buyer rejected the document
  • paid — buyer marked the invoice as paid
  • failed — delivery failed (network/protocol error)
  • no_action — not deliverable: no receiving capability found for the recipient on the Peppol network. Terminal for waitFor() and the CLI --watch — nothing ever follows it; register or correct the recipient, then send again
Use Webhooks for real-time status updates instead of polling getStatus().

Raw status and structured detail (SDK 2.2.0, required since 4.0.0)

  • rawStatus — the raw status string exactly as the gateway sent it, before any SDK coercion. When the gateway emits a status the SDK doesn't recognize, status falls back to unknown but rawStatus preserves the original value. Available on SendResult (e.g. getStatus()) and on invoices.list() rows. Since SDK 4.0.0 it is required on both, not optional: the one case where it used to be missing — a 2xx response carrying no status at all — is now refused outright.
  • detail — structured national status detail, one entry per axis (platformFiscal, delivery, businessDisposition, settlement), each carrying the native code and label of the jurisdiction (e.g. a French DGFiP lifecycle code) plus its codeSystem/codeVersion. Present when the jurisdiction provides one; exposed as the detail field on GET /api/v1/invoices list items and parsed by the SDK.

waitFor() terminal semantics

  • failed, rejected, no_action — terminal failures: waitFor() throws immediately instead of polling until the timeout (unless the terminal itself is the awaited target).
  • paid — terminal success. Since SDK 2.2.0, if the document reaches paid while you are waiting for an earlier progress status (e.g. delivered), waitFor() resolves with the real result (result.status === "paid") — the target was passed, not missed. Previously this path ended in a timeout error.