eFakturuj / API Docs
eFakturuj Guides

SAPI-SK connector

The SAPI-SK connector is an alternative front door for integrators who already generate their own UBL. Instead of posting structured JSON to /api/v1/invoices and letting eFakturuj build the UBL, you send a ready-made UBL 2.1 document and eFakturuj validates, delivers, and files it — while remaining your Access Point and Service Provider.

Use it if your ERP or accounting system already emits compliant UBL. If you want eFakturuj to generate the UBL from your data, use the native API instead — start at Getting started.

SAPI routes are mounted at the API host root (/sapi), not under /api/v1:

  • Production: https://api.efakturuj.sk/sapi
  • Sandbox: https://api.sandbox.efakturuj.sk/sapi

The base_url shown on the connector install screen is the API host — append /sapi to it when configuring your ERP.

Sandbox delivery is a closed loop: documents sent in sandbox never reach a real recipient's Access Point, even though the status reaches SENT. To test delivery end-to-end, send between two sandbox companies — see End-to-end delivery in sandbox.

Auth is a Bearer access token obtained from client credentials — not the efk_ API key used by the native API.

The flow at a glance

POST /sapi/auth/token                                  → access_token (15 min)
POST /sapi/document/send                               → 202 { providerDocumentId, status: ACCEPTED }
GET  /sapi/document/status/{providerDocumentId}        → ACCEPTED → PROCESSING → SENT
GET  /sapi/document/receive                            → inbound documents
POST /sapi/document/receive/{id}/acknowledge           → mark inbound as ACKNOWLEDGED

A single send performs the same full Slovak 5-corner exchange as the native API: the invoice to the buyer's Access Point, plus the SK Tax Data Document leg and the Finančná správa copy. You never build the TDD.

1. Get credentials

Install the SAPI-SK Connector for your company in the dashboard (Firmy → Apps → SAPI-SK Connector). Installing issues:

  • client_id
  • client_secret — shown once; store it securely
  • the connector base_url
  • the participant ID the credentials are locked to (taken from the company's configured Peppol participant ID)

Credentials are scoped to one company and its participant ID. A send whose metadata or X-Peppol-Participant-Id header names a different participant is rejected with SAPI-AUTH-PARTICIPANT.

2. Get an access token

POST /sapi/auth/token takes a JSON body (not form encoding).

curl -X POST https://api.efakturuj.sk/sapi/auth/token \
  -H 'Content-Type: application/json' \
  -d '{
    "client_id": "your-client-id",
    "client_secret": "…",
    "grant_type": "client_credentials"
  }'
# → { "access_token": "…", "token_type": "Bearer", "expires_in": 900,
#     "refresh_token": "…", "scope": "document:send document:receive" }
FactValue
Access token lifetime900 s (15 minutes)
Refresh token lifetime30 days
Scopesdocument:send, document:receive (both granted unless you request narrower)

Optionally pass scope to narrow the grant. document:send covers both sending and reading status; document:receive covers the inbound endpoints.

Renew with POST /sapi/auth/renew ({"refresh_token": "…"}) and revoke with POST /sapi/auth/revoke ({"token": "…"}).

GET /sapi/auth/token/status reports whether the current token is still valid and when to refresh — it sets should_refresh: true once fewer than 180 seconds remain.

3. Send a document

POST /sapi/document/send returns 202 Accepted. Two headers are required:

  • Idempotency-Key — your unique key for this submission
  • X-Peppol-Participant-Id — must match your credentials' participant

The body carries the UBL as a plain UTF-8 XML string in payload — it is not base64. Wire field names are camelCase.

curl -X POST https://api.efakturuj.sk/sapi/document/send \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 5f2b…' \
  -H 'X-Peppol-Participant-Id: 9925:sk2020111111' \
  -d '{
    "metadata": {
      "documentId": "2026-0001",
      "documentTypeId": "busdox-docid-qns::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",
      "processId": "cenbii-procid-ubl::urn:fdc:peppol.eu:2017:poacc:billing:01:1.0",
      "senderParticipantId": "9925:sk2020111111",
      "receiverParticipantId": "9925:sk2020222222",
      "creationDateTime": "2026-07-16T10:30:00Z"
    },
    "payload": "<Invoice xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\">…</Invoice>",
    "payloadFormat": "XML",
    "checksum": "…"
  }'
# → 202 { "providerDocumentId": "efk_sapi_doc_…", "status": "ACCEPTED",
#         "receivedAt": "…", "timestamp": "…" }

Save providerDocumentId — it is your handle for status lookups.

FieldNotes
payloadPlain UTF-8 XML string. Max 10 MB.
payloadFormat"XML" — the only accepted value.
payloadEncodingOptional, defaults to UTF-8. Only UTF-8 is supported.
checksumOptional. Lowercase SHA-256 hex of the payload bytes. If present it must match, or you get SAPI-VAL-CHECKSUM.
metadata.documentTypeIdMust agree with the UBL root element — an Invoice payload declared as a CreditNote is rejected.

Only UBL Invoice and CreditNote roots are accepted. The declared senderParticipantId / receiverParticipantId must match the EndpointID values inside the UBL.

Idempotency

Idempotency-Key is required and scoped to your SAPI client. Replaying the same key with the same body returns the original response. Replaying it with a different body fails with SAPI-IDEMPOTENCY-CONFLICT.

4. Track delivery

curl https://api.efakturuj.sk/sapi/document/status/efk_sapi_doc_… \
  -H 'Authorization: Bearer <access_token>' \
  -H 'X-Peppol-Participant-Id: 9925:sk2020111111'
# → { "providerDocumentId": "…", "clientDocumentId": "2026-0001",
#     "status": "SENT", "direction": "outbound",
#     "peppolMessageId": "…", "fs": { "status": "ACKNOWLEDGED", "submissionId": "…" },
#     "updatedAt": "…", "timestamp": "…" }

Outbound status walks:

ACCEPTED → PROCESSING → SENT        (or FAILED)

The fs object reports the Finančná správa leg separately: NOT_SUBMITTED, SUBMITTED, ACKNOWLEDGED, or FAILED, with submissionId, reportDeadline, acknowledgedAt, and errorMessage.

5. Receive inbound documents

curl 'https://api.efakturuj.sk/sapi/document/receive?limit=20' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'X-Peppol-Participant-Id: 9925:sk2020222222'
# → { "documents": [ { "documentId": "efk_sapi_in_…", "status": "RECEIVED",
#                      "metadata": {…}, "receivedAt": "…" } ],
#     "nextPageToken": "…" }

Paginate with pageToken; filter with status. Fetch one document — the response carries the full UBL in payload:

curl https://api.efakturuj.sk/sapi/document/receive/efk_sapi_in_… \
  -H 'Authorization: Bearer <access_token>' \
  -H 'X-Peppol-Participant-Id: 9925:sk2020222222'

Acknowledge it. The request takes no body:

curl -X POST https://api.efakturuj.sk/sapi/document/receive/efk_sapi_in_…/acknowledge \
  -H 'Authorization: Bearer <access_token>' \
  -H 'X-Peppol-Participant-Id: 9925:sk2020222222'
# → { "documentId": "…", "status": "ACKNOWLEDGED", "acknowledgedDateTime": "…" }

Acknowledgement is idempotent — re-acknowledging keeps the original timestamp.

Errors

SAPI errors use their own envelope — not the native API's error shape:

{
  "error": {
    "category": "VALIDATION",
    "code": "SAPI-VAL-001",
    "message": "Document payload is not well-formed XML.",
    "retryable": false,
    "correlation_id": "…",
    "details": [{ "field": "payload", "issue": "…" }]
  }
}

category is one of AUTH, VALIDATION, PROCESSING, QUOTA, TEMPORARY, PERMANENT. Retry only when retryable is true. Quote correlation_id in support requests.

CodeMeaning
SAPI-AUTH-001Authentication failed (401)
SAPI-AUTH-LOCKEDClient locked after repeated auth failures (15 minutes)
SAPI-AUTH-PARTICIPANTParticipant not permitted for these credentials
SAPI-AUTH-SCOPE / SAPI-VAL-SCOPEToken lacks the required scope
SAPI-AUTH-IPRequest from an IP outside the client's allowlist
SAPI-VAL-001Generic validation failure (bad XML, unsupported root, metadata mismatch, missing header)
SAPI-VAL-CHECKSUMchecksum does not match the payload
SAPI-VAL-VALIDATION-UNAVAILABLEValidator bundles unavailable and strict validation is on — fail-closed
SAPI-IDEMPOTENCY-CONFLICTIdempotency-Key reused with a different body
SAPI-DOC-NOT-FOUNDUnknown document id (404)
SAPI-QUOTA-INVOICE-LIMITMonthly plan invoice quota exceeded (402)
SAPI-DISABLEDSAPI facade disabled (503, retryable)

Quotas

Sends count against your plan's monthly invoice limit, the same counter the native API uses. Exceeding it returns 402 with SAPI-QUOTA-INVOICE-LIMIT and the plan tier and limit in details — unless your plan permits overage, in which case the send proceeds and is billed.

Testing

Use the sandbox host (https://api.sandbox.efakturuj.sk/sapi). It has the same shape and runs against isolated test data without delivering to the real Peppol network or billing.

Next steps