Limits and rules
Some of the rules the Integration API works by cannot be read from the machine-readable description — it says which fields exist, but not how many requests are allowed, in what order the lists arrive, how to pull only what has changed, nor how long the protection against duplicates lasts. Those things are written down here.
| Rule | Value |
|---|---|
| traffic limit | per key; the number comes from the plan |
| document size | per organization; the ceiling comes from the plan |
| paging | 100 by default, 500 at most |
| list order | newest first |
| incremental pulling | by=updated + nextFrom |
| idempotency window | one hour |
waiting for the answer when submitting (Prefer: wait) | at most 30 seconds |
Traffic limit
Section titled “Traffic limit”Requests are counted per key — not per the address they come from. That means two things, and both matter when planning your schedule:
- One key used from several servers shares a single quota. If the same key is used by three instances of your product, their requests add up into the same count. The limit does not multiply by the number of servers.
- Several keys behind one outbound address do NOT share a quota. If two of your clients work from the same network and go out through the same address, each has its own count. One cannot use up the other’s quota.
When the limit is reached, the request is rejected with EARH-1006 · 429
and repeated after the time the Retry-After header states — neither the key nor the address is
changed.
How large the number is
Section titled “How large the number is”The number is not the same for everyone. It comes from the plan of the organization that owns the key, and the key’s owner may lower it further for that one key. The smaller of the two applies.
| Plan | Requests per minute |
|---|---|
| Free | the Integration API is not included |
| Starter | 60 |
| Professional | 300 |
| Enterprise | unlimited |
So the value that applies to you is known to the organization whose key you use. If you are not sure, ask them — it cannot be derived from the responses.
When access is closed rather than limited
Section titled “When access is closed rather than limited”Two states are not a limit but a closed door, and both answer 403 on every call:
| Code | What it means | Where it is resolved |
|---|---|---|
EARH-1010 | the organization’s plan does not include integration access | with the organization, through its subscription |
EARH-1011 | the key has been switched off by its owner | with the key’s owner |
Neither carries Retry-After, and that is deliberate: waiting changes nothing, so retrying in
a loop only burns calls. That is also the difference from EARH-1006, where time is the answer.
If the number, the basis it is counted on, or which plan carries how much changes, the change goes through the Changelog with an announced date before it takes effect. There is no silent change to the limit: what is published here holds until a new date for it appears in the Changelog.
Limits per operation
Section titled “Limits per operation”The traffic limit from the previous section applies equally to every address — no operation has a quota of its own and none is cheaper than another. What does differ per operation is how much a single request may carry:
| Operation | What is limited | When it is exceeded |
|---|---|---|
POST outbound | the size of the document | EARH-2012 · 413 |
GET outbound · GET inbound | page size — 100 by default, 500 at most | EARH-3005 · 400 |
GET codelists/{type} | ⛔ no paging — the whole active list is returned | — |
| all the others | the traffic limit only | EARH-1006 · 429 |
You do not have to know the number in advance. For both limits the response carries the allowed
value inside it — EARH-2012 states how many MB are allowed, EARH-3005 how many records per page.
So a single rejected call is enough to learn the limit; it is not guessed and not asked of us.
Document size
Section titled “Document size”What is measured is the body of POST outbound — the UBL XML itself. The excess usually comes from
embedded attachments and images, not from the invoice data.
As with traffic, two numbers apply: the ceiling that comes from the organization’s plan, and the working limit its owner may lower below it. The smaller of the two applies.
| Plan | Ceiling |
|---|---|
| Free | the Integration API is not included |
| Starter | 2 MB |
| Professional | 10 MB |
| Enterprise | 50 MB |
Incremental pulling — by=updated and nextFrom
Section titled “Incremental pulling — by=updated and nextFrom”The lists take a by parameter, which says what dateFrom and dateTo measure:
| Value | What it measures |
|---|---|
created — default | the time the document was created |
updated | the time we last recorded a change to it |
The difference is what updated solves: a document created last month whose status changed
yesterday is returned by by=updated and is not returned by by=created. With the parameter
omitted the behaviour is as before — existing calls do not change.
When you ask for by=updated, the answer also carries a nextFrom field: the point to
continue from next time. You pass it back as dateFrom in the following call, again with
by=updated. With by=created the field is null.
The overlap is expected
Section titled “The overlap is expected”Your next call will return some of the documents you have already seen. That is a contract, not a defect, and it has two reasons:
- the lower bound
dateFromis inclusive nextFromis deliberately set slightly in the past — a margin of 5 minutes
The margin exists because no source is monotonic with respect to the moment of writing: a record that commits after your read can carry an older timestamp. Without the margin, exactly those records would be lost silently.
So the client drops the repeats by docUid. That is all that is asked of it.
by=updated against onlyNotImported
Section titled “by=updated against onlyNotImported”Inbound documents have both, and they are not interchangeable:
| Question | Parameter |
|---|---|
| “What have I not fetched yet?” | onlyNotImported=true |
| “What has changed since my last pull?” | by=updated |
A document you already fetched and that was then approved comes back only with by=updated —
for onlyNotImported it no longer exists, because it has been fetched.
Resubmission and protection against duplicates
Section titled “Resubmission and protection against duplicates”Every request that creates a document is marked with the X-Idempotency-Key header. You choose
the mark yourself, and it is what tells the API that two requests are in fact one and the same.
| What you send | What you get |
|---|---|
| the first time, with a new mark | 201 — the document has been created |
| again, same mark and same content | 200 — the same answer as the first time, without a second document |
| same mark, different content | EARH-2010 · 409 |
The difference between 201 and 200 is how you tell whether the document was created now or
already existed. Both answers carry the same document, so repeating after a broken connection is
safe.
The third row is an error, not a repetition: the same mark with different content means either a
new document under an old mark, or a document changed after the first submission. What exactly to
do about it is at EARH-2010 in the error catalogue.
The idempotency window lasts one hour. After it, the same mark counts as a new one.
Synchronous submission — Prefer: wait
Section titled “Synchronous submission — Prefer: wait”By default POST outbound answers at once, with status 210: the document is received and
processing runs in the background. With the header Prefer: wait=N the answer waits up to N
seconds for our processing to reach the outcome of the submission to UJP, then returns that state —
together with ujp.euid, the number UJP assigned to the document, and ujp.qrLink, the QR code
link UJP returned.
| Rule | Value |
|---|---|
| longest wait | 30 seconds — a larger value is reduced to 30 |
| what is waited for | only the submission to UJP, not later changes on UJP’s side |
| when the time runs out | the same 201, with status 210 or 220 and "ujp": null — continue with GET outbound/{docUid} |
| how you know it was applied | the header Preference-Applied: wait=N in the answer |
Waiting changes nothing in the processing itself. An interrupted request does not interrupt the
processing — it goes on, and the outcome is read with GET outbound/{docUid}. The same header also
applies to a repeated call with the same idempotency mark.
⚠️ A request that waits counts against the traffic limit like any other — one, however many seconds it waited.
History boundary
Section titled “History boundary”Documents created before the docUid identifier was introduced do not appear through the
Integration API. They have no identifier the API can address them by, so no list returns them and
no request by docUid reaches them.
Those documents are not counted in the total (totalCount) the lists return either. So the
number in the response is not the client’s total number of documents — it is the number of
documents available through the Integration API. If your product compares that number with what the
client sees in eArhiva, the difference is expected and is not an error.
The older documents remain available through the application itself; what this limits is only the Integration API.