Recommended flow
How to put issuing and sending an invoice together with the Agent API: create, optionally check with one call, send, and track the status.
Order of calls
| Step | Call | Note |
|---|---|---|
| 1. Create | POST /v1/agent/invoices | Store the invoice id. |
| 2. Preflight (optional) | POST /v1/agent/invoices/{id}/preflight | Keep it out of the end-user click path. |
| 3. Send | POST /v1/agent/peppol/send/{id} | Answers 202, sending is asynchronous. |
| 4. Status | webhook peppol.document.* or a status GET | A webhook is better than polling. |
Preflight is a convenience. The send validates the document again, so you do not have to call preflight before every send.
Invoice preflight
POST /v1/agent/invoices/{id}/preflight (scope invoice:read) validates the exact document that will be sent (attachments and Peppol IDs included) and checks the recipient derived from the buyer DIČ. It never writes and never charges.
{
"data": {
"send_ready": true,
"validator_unavailable": false,
"validation": { "ran": true, "valid": true, "error_count": 0, "warning_count": 1 },
"repair": [
{ "field": "…", "code": "…", "message": "…", "severity": "warning" }
],
"recipient": { "peppol_id": "0245:2010101010", "found": true },
"assembly": { "attachments_bytes": 20480 }
}
}| Situation | Result |
|---|---|
| Buyer without DIČ / IČ DPH | 200 with send_ready: false and a repair item with code RECEIVER_UNRESOLVED. |
| The send would be refused (organisation on the other Peppol network than the key, inactive Peppol account) | 200 with send_ready: false and a repair item SENDER_WORLD_MISMATCH or PEPPOL_ACCOUNT_INACTIVE. |
| Attachments over 10 MB | 422 |
XML preview before sending
Before sending, GET /v1/agent/invoices/{id}/xml returns exactly the document that will be sent, attachments and the sender and receiver Peppol IDs included (header X-Efaktura-Ubl-Source: preview). If the buyer data is incomplete, the preview is still returned, just without the receiver EndpointID. An attachment problem returns 422.
Server-Timing
Every /v1/agent/* response carries the header Server-Timing: total;dur=<ms>. The two preflight endpoints add validate and recipient, and the invoice preflight also assemble (all in ms). The header is exposed to browsers via CORS.
Timeouts and retries
Set a client timeout of 10 s. Retry only with the same Idempotency-Key. On a non-2xx response, read our JSON error body.
<?php
$ch = curl_init('https://api.efaktura.sk/v1/agent/invoices/' . $id . '/preflight');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'X-API-Key: ' . $apiKey,
'X-Organization-Id: ' . $orgId,
'Content-Type: application/json',
],
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($raw === false) {
// network error or timeout: retry with the same Idempotency-Key
}
$body = json_decode($raw, true); // 2xx and error responses are both JSON
if ($status >= 200 && $status < 300) {
$sendReady = $body['data']['send_ready'];
} else {
// $body holds our error description
}file_get_contents without 'ignore_errors' => true returns false and hides our error body. Use cURL.