Submit a document
Submits one UBL 2.1 document for delivery.
By default the response does not wait for processing: status 210 means “received by us”.
Send Prefer: wait=N (seconds, at most 30) to wait for the outcome of our processing up to
the submission to UJP. The answer then carries the status reached and, once UJP accepted the
document, its identifier in ujp.euid. If the time runs out first, the answer carries status
210 or 220 and ujp is null — follow the document with GET outbound/{docUid}. Later
changes on UJP’s side (the recipient accepting or refusing) are not waited for. The answer
names the wait that was applied in the Preference-Applied header.
Repeating the call with the same X-Idempotency-Key and the same body within one hour returns
the same docUid with HTTP 200 instead of creating a second document.
The example applies to the demo environment only.
Request
curl -i -X POST "https://sandbox.api.earhiva.mk/api/integration/v1/outbound" \
-H "X-Api-Key: <YOUR KEY>" \
-H "X-Software-Id: <NAME AND VERSION OF YOUR APPLICATION>" \
-H "X-Idempotency-Key: <X-Idempotency-Key>" \
-H "Prefer: <Prefer>" \
-H "Content-Type: application/xml" \
--data-binary @invoice.xml
Every call carries X-Api-Key with a key that has the IntegrationApi scope, and X-Software-Id with the name and version of your software. How to request a key is on Authentication.
Parameters
All parameters (3)
| Parameter | In | Type | Presence | Description |
|---|---|---|---|---|
X-Software-Id | header | string | always | Identifier of the calling software, e.g. SSBFocus/4.2.1. Free text, recorded in the audit trail. Required on every call; missing means EARH-1008. |
X-Idempotency-Key | header | string | always | Client-chosen key that makes the submission repeatable. The same key with the same body within one hour returns the same docUid instead of creating a second document; the same key with a different body is rejected with EARH-2010. Missing means EARH-2013. |
Prefer | header | string | optional | Optional. wait=N makes the answer wait up to N seconds (at most 30) for the outcome of our processing up to the submission to UJP, and return the status reached together with ujp.euid. Omit it and the answer returns at once with status 210. |
Request Body
One UBL 2.1 document as raw XML. Exactly one document per request — a payload carrying more is rejected with EARH-2011. The bytes are stored as received and are returned unchanged by GET outbound/{docUid}/file?format=ubl.
Type: application/xml. A working example that passes as it is: invoice-ubl21-en.xml — what it contains and what you may replace is on Formats.
Error codes
EARH-1001 · EARH-1002 · EARH-1003 · EARH-1004 · EARH-1005 · EARH-1006 · EARH-1007 · EARH-1010 · EARH-1011 · EARH-2001 · EARH-2002 · EARH-2003 · EARH-2004 · EARH-2005 · EARH-2006 · EARH-2007 · EARH-2010 · EARH-2011 · EARH-2012 · EARH-2013 · EARH-9001
The full list of codes, what each means and what to do, is in the Error catalogue.
Responses
Section titled “ Responses ”The same key and body were already submitted; the original docUid is returned and
alreadyExisted is true.
{
"docUid": "eah_o_01M0ZZB88FT117S27PM8ZEK9RY",
"status": 210,
"receivedAt": "2026-08-26T23:24:26+02:00",
"alreadyExisted": true
}
All fields in the body (6)
| Field | Type | Presence | Description |
|---|---|---|---|
docUid | string | always | Identifier of the document. Every later call about this document uses it. |
status | integer · int32 | always | Status of the document at the moment of the answer. Without Prefer: wait it is 210 (Received). With it, it is the status our processing reached — 210 or 220 when the time ran out first; then follow the document with GET outbound/{docUid}. |
receivedAt | string · date-time | always | Time the document was taken over. |
alreadyExisted | boolean | always | true when the same idempotency key and the same body were already submitted, so this call returned the original document instead of creating a second one. Then the answer carries HTTP 200; a newly created document carries 201. |
failureCode | string | nullable | The EARH-4xxx code when processing has already failed — the same field as on the document detail; null otherwise. |
ujp | — | nullable | The answer received from UJP, with the identifier UJP assigned (euid). null while the document has not been forwarded to UJP — always the case without Prefer: wait. |
The document was accepted and now carries a docUid.
{
"docUid": "eah_o_01M0ZZB88FT117S27PM8ZEK9RY",
"status": 210,
"receivedAt": "2026-08-26T23:24:17+02:00",
"alreadyExisted": false
}
All fields in the body (6)
| Field | Type | Presence | Description |
|---|---|---|---|
docUid | string | always | Identifier of the document. Every later call about this document uses it. |
status | integer · int32 | always | Status of the document at the moment of the answer. Without Prefer: wait it is 210 (Received). With it, it is the status our processing reached — 210 or 220 when the time ran out first; then follow the document with GET outbound/{docUid}. |
receivedAt | string · date-time | always | Time the document was taken over. |
alreadyExisted | boolean | always | true when the same idempotency key and the same body were already submitted, so this call returned the original document instead of creating a second one. Then the answer carries HTTP 200; a newly created document carries 201. |
failureCode | string | nullable | The EARH-4xxx code when processing has already failed — the same field as on the document detail; null otherwise. |
ujp | — | nullable | The answer received from UJP, with the identifier UJP assigned (euid). null while the document has not been forwarded to UJP — always the case without Prefer: wait. |
The submission cannot be processed — EARH-1007 (a tenant value in the body),
EARH-2001 (not well-formed XML), EARH-2002 (not UBL 2.1), EARH-2003 (no recipient),
EARH-2004 (malformed recipient tax number), EARH-2005 (no document type),
EARH-2006 (no amount), EARH-2007 (the issuer is not the organization this key belongs
to), EARH-2011 (more than one document),
EARH-2013 (the X-Idempotency-Key header is missing).
{
"type": "https://earhiva.mk/errors/EARH-1007",
"title": "Request body must not contain a tenant-identifying field.",
"status": 400,
"code": "EARH-1007"
}
All fields in the body (6)
| Field | Type | Presence | Description |
|---|---|---|---|
type | string | always | Address of this code in the error catalogue (RFC 7807). |
title | string | always | Short, stable English summary of the error — the same string that appears in your log. |
status | integer · int32 | always | HTTP status code, repeated in the body (RFC 7807). |
detail | string | nullable | What went wrong in this particular call, when there is more to say than the title. Absent when the title already says everything. |
code | string | always | The EARH-xxxx code. This is the contract: match on it, not on the text. |
eventId | string | nullable | Only on EARH-9000. Quote it when reporting the problem — it is the same reference we record in our log, so one number identifies the event on both sides. |
The API key is missing, unknown, expired or revoked — EARH-1001, EARH-1002, EARH-1003. The body is the standard error body — the same on every call. What to do about each code is in the Error catalogue.
The caller’s address is outside the allowed list, the key does not carry the IntegrationApi scope, the organization’s plan does not include integration access, or the key has been switched off by its owner — EARH-1004, EARH-1005, EARH-1010, EARH-1011. The last two are returned on every call and carry no Retry-After: waiting does not change the state. The body is the standard error body — the same on every call. What to do about each code is in the Error catalogue.
EARH-2010 — the same idempotency key was already used with different content;
EARH-9001 — outbound over the API is not enabled for this tenant.
{
"type": "https://earhiva.mk/errors/EARH-2010",
"title": "This idempotency key was already used with different content.",
"status": 409,
"code": "EARH-2010"
}
All fields in the body (6)
| Field | Type | Presence | Description |
|---|---|---|---|
type | string | always | Address of this code in the error catalogue (RFC 7807). |
title | string | always | Short, stable English summary of the error — the same string that appears in your log. |
status | integer · int32 | always | HTTP status code, repeated in the body (RFC 7807). |
detail | string | nullable | What went wrong in this particular call, when there is more to say than the title. Absent when the title already says everything. |
code | string | always | The EARH-xxxx code. This is the contract: match on it, not on the text. |
eventId | string | nullable | Only on EARH-9000. Quote it when reporting the problem — it is the same reference we record in our log, so one number identifies the event on both sides. |
EARH-2012 — the document exceeds the working size limit; the response names the allowed one.
{
"type": "https://earhiva.mk/errors/EARH-2012",
"title": "Document exceeds the configured size limit.",
"status": 413,
"code": "EARH-2012"
}
All fields in the body (6)
| Field | Type | Presence | Description |
|---|---|---|---|
type | string | always | Address of this code in the error catalogue (RFC 7807). |
title | string | always | Short, stable English summary of the error — the same string that appears in your log. |
status | integer · int32 | always | HTTP status code, repeated in the body (RFC 7807). |
detail | string | nullable | What went wrong in this particular call, when there is more to say than the title. Absent when the title already says everything. |
code | string | always | The EARH-xxxx code. This is the contract: match on it, not on the text. |
eventId | string | nullable | Only on EARH-9000. Quote it when reporting the problem — it is the same reference we record in our log, so one number identifies the event on both sides. |
The traffic limit for this key has been reached — EARH-1006. The response carries Retry-After. The limit is counted per key, and its size comes from the organization’s plan. The body is the standard error body — the same on every call. What to do about each code is in the Error catalogue.
Unexpected error on our side — EARH-9000. The body carries eventId; quote it when reporting the problem. The body is the standard error body — the same on every call. What to do about each code is in the Error catalogue.