Connector (send in one call)

Single-call sending via POST /v1/agent/peppol/connector/send. One call does everything: it validates the supplied UBL/CII XML (+ optional auto-repair via normalization), reserves credit, and queues the document for sending via Peppol. A replacement for chaining preflight → send, with three safety guards.

This endpoint is part of the Agent API. It authenticates with the X-API-Key and X-Organization-Id headers, scope invoice:send. A test key (efk_pk_test_…) routes to the sandbox AP and never charges credit; a live key sends for real and consumes credit.
Prerequisite: the organization in X-Organization-Id must hold an active Peppol account. A sandbox organization created via POST /v1/agent/organizations with a test key is enrolled automatically (see peppol_sandbox in the response); older or manually created organizations must be enrolled via POST /v1/agent/peppol/enroll. Without an account the document passes validation but the send ends with status: rejected, reason: ingest.
Bookkeeping and payments: a sent document is recorded under "Invoices" with the number from cbc:ID. The variable symbol is taken from cac:PaymentMeans/cbc:PaymentID (digits only), otherwise from the digits of the document number — banks carry the VS as a number, so "2026-000001" only pairs through PaymentID 2026000001.

Endpoint

MethodPathScopeHeaders
POST/v1/agent/peppol/connector/sendinvoice:sendX-API-Key, X-Organization-Id, Idempotency-Key

Safety guards

Three guards make a single call safe even on retries or partial failure:

GuardBehavior
Idempotency-Key is requiredOmitting the header returns 400. The same key with a different request body returns 409. A replay (same key and body) returns the stored response and never sends twice.
Credit only when readyCredit is reserved only when the document is send_ready: true. A document with errors is rejected before reservation. Credit is never over-consumed and the charge happens exactly once, at send time (no double billing).
Auto-repair is opt-inNormalization changes are applied only when options.autoRepair: true, and each one is listed in repair_applied: a modified document is never sent silently. Without autoRepair, a document that needs changes is rejected as needs_repair.

Request body

FieldTypeRequiredDescription
receiverPeppolIdstringNoPeppol identifier of the recipient (e.g. 0245:DIČ). When provided, an SMP lookup also runs and fills in the recipient field.
documentobjectYesSource document: see below.
document.formatubl | ciiYesFormat of the source XML.
document.xmlBase64string (base64)YesBase64-encoded UBL / CII XML.
options.autoRepairbooleanNoWhen true, normalization changes (BIS 3.0 identifiers, CII→UBL) are applied and the document is sent. Defaults to false.
options.validateOnlybooleanNoWhen true, performs only validation (status validated), and doesn't reserve credit or send.
options.dispatchnow | laterNoDefaults to now (reserves credit and queues immediately). later only stages the document without charging credit and returns staged_id: see "Prepare now, send later" below.
POST /v1/agent/peppol/connector/send
X-API-Key: efk_pk_test_...
X-Organization-Id: 0a2c1f3e-…
Idempotency-Key: 7d1f2c9a-3b4e-4f5a-8c6d-1e2f3a4b5c6d
Content-Type: application/json

{
  "receiverPeppolId": "0245:2123456789",
  "document": {
    "format": "ubl",
    "xmlBase64": "PD94bWwgdmVyc2lvbj0iMS4wIj8+..."
  },
  "options": { "autoRepair": true }
}

Response

The response is 200 whenever the operation ran. The actual result is in the status field (queued / rejected / validated) and send_ready. The invoice_id, document_id, and job_id fields are populated only when status: queued.

{
  "data": {
    "status": "queued",
    "invoice_id": "b1f0d7a2-…",
    "document_id": "0245:2123456789#OF2026001",
    "job_id": "0245:2123456789#OF2026001",
    "send_ready": true,
    "validator_unavailable": false,
    "validation": {
      "ran": true,
      "valid": true,
      "error_count": 0,
      "warning_count": 1
    },
    "repair": [],
    "repair_applied": [
      { "change": "Added Peppol BIS 3.0 identifiers (CustomizationID/ProfileID)." }
    ],
    "recipient": {
      "peppol_id": "0245:2123456789",
      "found": true,
      "sml_state": "active"
    }
  }
}
FieldTypeDescription
statusqueued | rejected | validatedqueued = credit reserved + queued for sending; rejected = not sent (see reason); validated = validation only (validateOnly).
reasonstring | nullRejection reason (only for rejected): validation | needs_repair | credit | prepayment_overdue | validator_unavailable | ingest (the document passed validation but the organization refused it — e.g. no Peppol account, or the document number already exists as a native invoice; details in error).
errorstring | nullHuman-readable rejection detail (only for reason = ingest).
invoice_idstring | nullID of the created invoice (only for queued).
document_idstring | nullDocument identifier in the transmission (only for queued).
job_idstring | nullID of the queued sending job (only for queued).
send_readyboolean | nulltrue = the document was ready to send. null when the validator wasn't available.
validator_unavailablebooleantrue if the validation runtime wasn't available, and send_ready is then null.
validationobjectRun summary: ran, valid, error_count, warning_count.
repairarray of objectsFindings / suggested fixes: field, code, message, severity (error | warning).
repair_appliedarray of objectsSpecific changes applied by auto-repair: field, change. Populated only when autoRepair=true and the document was sent.
recipientobject | nullResult of the recipient SMP lookup, if receiverPeppolId was provided; otherwise null.

Prepare now, send later

When you want to first prepare (validate) a document and send it only later (for example after approval), send options.dispatch: "later". A staged document doesn't reserve or charge credit; the response has status: staged and returns staged_id, which you use to send the document in a separate call. The default dispatch: "now" behaves as before (reserves credit and queues immediately).

POST /v1/agent/peppol/connector/send
{
  "document": { "format": "ubl", "xmlBase64": "PD94bWwg..." },
  "options": { "dispatch": "later" }
}

→ 200
{
  "data": {
    "status": "staged",
    "staged_id": "5f2a9c1b-…",
    "send_ready": true,
    "validation": { "ran": true, "valid": true, "error_count": 0, "warning_count": 0 }
  }
}

You send a staged document via POST /v1/agent/peppol/connector/dispatch/{stagedId} (scope invoice:send). Only now is credit checked and reserved, and the document is queued for sending. The call is naturally idempotent: the staged document is deleted on first send, so retrying with the same stagedId returns 404. Staged documents are held temporarily (7 days) and are scoped to the organization.

MethodPathScopeHeaders
POST/v1/agent/peppol/connector/dispatch/{stagedId}invoice:sendX-API-Key, X-Organization-Id
POST /v1/agent/peppol/connector/dispatch/5f2a9c1b-…

→ 200
{
  "data": {
    "status": "queued",
    "invoice_id": "b1f0d7a2-…",
    "document_id": "0245:2123456789#OF2026001",
    "job_id": "0245:2123456789#OF2026001"
  }
}
FieldTypeDescription
statusqueued | rejectedqueued = credit reserved + queued for sending; rejected = not sent (see reason).
reasonstring | nullRejection reason (only for rejected): credit | prepayment_overdue | ingest (details in error). On ingest the staged document is kept — fix the organization (e.g. enroll) and call dispatch with the same stagedId again.
invoice_idstring | nullID of the created invoice (only for queued).
document_idstring | nullDocument identifier in the transmission (only for queued).
job_idstring | nullID of the queued sending job (only for queued).
You get 404 both when stagedId doesn't exist and when it has already been sent once: a staged document is deleted after sending, so dispatch never sends twice.

Errors

Validation findings about the document itself are not a 4xx error; they're always part of the 200 response, in the status / repair fields. 4xx status codes are only for protocol errors:

CodeMeaning
400The Idempotency-Key header is missing, or the body / document is invalid.
401Invalid or missing API key.
403Missing invoice:send scope or invalid organization.
409The Idempotency-Key was already used with a different request body.
Idempotency-Key is required. Use a stable key derived from the invoice number (not a random one on every retry): otherwise a retry after a timeout could create a second send. A replay with the same key and body is safe and returns the original result.

Related

For bulk sending from a table, use CSV ingest. You can look up the recipient separately via Recipient lookup and get delivery evidence after sending via Delivery evidence.