# Limits & quotas

URL: https://postman.slovakodata.com/developers/en/limity/
Updated: 2026-09-29

Request limits per plan, the RateLimit and RateLimit-Policy headers, the 429 response, document size and idempotency.

<dl class="facts">
  <div class="fact">
    <dt>Document size</dt>
    <dd>10<small> MB</small></dd>
  </div>
  <div class="fact">
    <dt>Idempotency</dt>
    <dd>24<small> h</small></dd>
  </div>
</dl>

Every organisation has a limit on how many requests it can send in a given time. The limit
depends on your plan and applies separately to each group of endpoints. Every response
tells you what your limit is and how much of it is left. Once you exceed it you get a
**429** with the exact time after which you can try again.

**Do not hard-code the limits — read them from the headers.** When your plan changes or an
individual limit is agreed, the limit changes without you having to touch the integration.

## What is counted

| Call | The limit is counted per |
|---|---|
| Enterprise API (`/api/v1/…`) with an API key or a session | the whole organisation — all its API keys and users together |
| SAPI-SK (`/sapi/document…`) | each SAPI client separately (`X-Peppol-Participant-Id`) |
| Unauthenticated calls (registration, the SAPI token endpoint) | IP address |

## Endpoint groups

Expensive operations have their own counter and do not count towards the general limit —
validating in a loop will not block you from sending invoices.

| Group | Endpoints |
|---|---|
| `api` | everything in the Enterprise API that is not in another group (sending and reading invoices…) |
| `validate` | `POST /api/v1/documents/validate` — [pre-flight validation](/developers/en/predbezna-validacia/) |
| `reachability` | `GET /api/v1/participants/:id/reachability` — receiver reachability |
| `company-lookup` | `GET /api/v1/invoicing/company/:ico` — buyer details by company ID (IČO) |
| `invoicing-channel` | `GET /api/v1/invoicing/channel` — delivery channel before issuing an invoice |
| `sapi` | SAPI-SK `/sapi/document…` |

## Limits per plan

| Group | MINI | START | PRO | CORPORATE | ENTERPRISE |
|---|---|---|---|---|---|
| `api` | 20 / s | 50 / s | 100 / s | 100 / s | 200 / s |
| `sapi` (per client) | 10 / s | 25 / s | 50 / s | 50 / s | 100 / s |
| `validate` | 10 / min | 30 / min | 60 / min | 150 / min | 300 / min |
| `reachability` | 30 / min | 30 / min | 60 / min | 60 / min | 120 / min |
| `company-lookup` | 30 / min | 30 / min | 30 / min | 30 / min | 30 / min |
| `invoicing-channel` | 30 / min | 30 / min | 60 / min | 60 / min | 120 / min |

If a limit is too tight, get in touch — an individual one can be agreed. It shows up in the
headers of your responses right away.

## How the limit is consumed

The limit is not a window that resets all at once at the top of the minute. Capacity is
**refilled continuously** — with `validate` at 60 per minute, one call frees up every second.

On top of that you have a **burst**: several calls you can send at once without waiting.
On the PRO plan that is 10 validations at once, then one per second on average. An unused
burst refills while you are idle.

- evenly spread calls within the limit never hit it,
- a short spike is absorbed by the burst,
- sustained load above the limit ends in a 429.

## Headers

```http
RateLimit-Policy: "validate";q=60;w=60
RateLimit: "validate";r=9;t=1
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 9
X-RateLimit-Reset: 1790668801
```

| Header | Meaning |
|---|---|
| `RateLimit-Policy` | your limit: the group, quota `q` and window `w` in seconds — here 60 requests per 60 s |
| `RateLimit` | the current state: `r` calls that would pass right now, `t` seconds until the burst is full again |
| `X-RateLimit-Limit` | same as `q` |
| `X-RateLimit-Remaining` | same as `r` |
| `X-RateLimit-Reset` | when the burst is full again, as a Unix timestamp in seconds |

`RateLimit` and `RateLimit-Policy` follow the IETF draft
[RateLimit header fields for HTTP](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/).
`X-RateLimit-*` is sent for older clients — the content is the same, reading one pair is enough.

The headers are on every response to a request that passed authentication and input
checks — successful or not. They are missing from a response with which authentication
rejects the request (**401**, **403** for a suspended account) and from **400**/**413** raised
by the request shape check: those are answered before the request is counted, and they do
not count towards the limit.

## Exceeding the limit

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 1
RateLimit-Policy: "validate";q=60;w=60
RateLimit: "validate";r=0;t=10
```

```json
{
  "statusCode": 429,
  "error": "RATE_LIMIT_EXCEEDED",
  "code": "ERR-429",
  "message": "Too many requests. Retry after 1 seconds.",
  "retry_after": 1
}
```

`Retry-After` is the **exact** number of seconds until the next call frees up, not an
estimate. The `retry_after` field carries the same value. Do not confuse it with `t` in the
`RateLimit` header — that is the time until the whole burst is back (10 validations on the
PRO plan in this example). On SAPI-SK the 429 uses the
SAPI-SK envelope with code [`SAPI-TEMP-002`](/developers/en/chyby/) and `retryable: true`.

A rejected call is not processed — no invoice is sent and no credit is used. Retrying it is
safe.

## Recommended client behaviour

1. **On a 429, wait `Retry-After` seconds** and retry. Do not retry immediately or in a
   tight loop.
2. **Watch `RateLimit` during batch processing** — slow down as `r` approaches zero, before
   a 429 arrives.
3. **Parallel processes share one organisation limit.** Split the capacity between them or
   send through a single queue.
4. **Derive your pace from `RateLimit-Policy`** — `q / w` is the average calls per second.
5. **Do not use pre-flight validation for bulk checks.** A sent invoice is validated
   anyway; pre-flight validation is for checking before you send.

```js
async function callWithLimit(url, options, attempts = 5) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(url, options);
    if (res.status !== 429) return res;
    const wait = Number(res.headers.get("Retry-After") ?? 1);
    await new Promise((r) => setTimeout(r, wait * 1000));
  }
  throw new Error("Rate limit still exceeded after retries");
}
```
