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.

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 | validator_unavailable.
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). Currently credit = insufficient credit.
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 stagedIddoesn'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.