Skip to content

Rate limits and quota

Two separate mechanisms, two separate error codes, two separate fixes. Plus the upload size each kind of target accepts.

Rate limit

A short operational window, per key, measured in requests per minute. It protects the service from a burst.

rate_limited · HTTP 429

Clears within a minute. Slow down.

Daily beta allowance

A durable entitlement, per account, measured in validations per UTC day. It is what your account is allowed to run.

quota_exceeded · HTTP 429

Clears at 00:00 UTC. Waiting is the fix.

They share a status code because 429 is right for both — stop, and come back — and they are otherwise unrelated. Branch on code, never on the status or on the prose: an integration that treats a quota refusal as a rate limit will retry in sixty seconds, every sixty seconds, until midnight.

The rate limit

Counted per API key, over a rolling window of one minute. The allowance is a property of the key and is shown in the dashboard. Exceeding it is answered with rate_limited and a Retry-After header in seconds.

It is checked before the upload is read, so a rate-limited request costs you nothing else: no validation runs and nothing is counted against your daily allowance.

The usual cause is a worker pool with more concurrency than the key allows. Serialising per key, or giving each worker its own key within one project, both fix it.

The daily allowance

Counted per account, not per key or per project — every key you own draws on one allowance. It resets at 00:00 UTC, which is a fixed instant rather than midnight where you are, so a job scheduled for local midnight may find the allowance already partly spent or not yet reset.

Three properties are worth knowing before you build against it.

  • A unit is taken before the document runs, not after. That is what makes the limit a limit: deciding afterwards would let any number of concurrent requests past the check at once.
  • A document that fails validation still consumes a unit. The API ran and answered; a FAILED report is a result, not a refusal. The allowance counts validations executed, not documents that turned out to be clean.
  • Work that never started is refunded. If the queue refuses the request after a unit was taken, the unit goes back. That is the only case that is given back — a validation that reached the engine is spent whatever it concluded.

Your current position comes back on every validate response that reached the gate:

  • X-Quota-Limit — validations per day for this account.
  • X-Quota-Remaining — how many are left today.
  • X-Quota-Reset — seconds until 00:00 UTC.

The dashboard shows the same numbers, alongside per-day usage for each project. It is an entitlement, not a purchase: there is no billing, no credit balance, and nothing to top up. Beta allowances are set per account, so ask if the one you have is the wrong size for what you are building.

Repeating a request costs a unit

There is no idempotency key. The same document sent twice is two validations and two units. A client that retries automatically on any failure will spend its allowance on failures — see the retry guidance on the errors page, which says for each code whether the request was executed.

Upload size

The limit depends on what the target accepts, because a PDF container and an XML document are not comparable sizes. An upload above the limit is refused with payload_too_large and HTTP 413, before anything is parsed.

InputMaximumTargets
XML 12 MiB26
PDF (Factur-X) 25 MiB3

Each target's own limit is listed in the catalogue. A deployment also applies a global cap on the request body, which is never smaller than the largest target's limit. Factur-X targets additionally refuse containers above 100 pages.

Concurrency and timeouts

Validations run in a bounded worker pool with a bounded queue. When both are full the request is refused with capacity_unavailable and HTTP 503 rather than queued indefinitely — a caller waiting sixty seconds for a slot is worse served than one told to come back.

A validation that starts but does not finish within the time limit is answered with validation_timeout and HTTP 504. That one did reach the engine, so it consumed a unit.