Rate limits
The API applies rate limits to keep things fast and fair. Limits are counted per application (API account), identified by the token, not per IP address. They are tightest on expensive endpoints:
| Endpoint | Limit |
|---|---|
POST /documents |
30 requests / 60 s |
POST /documents/validate |
30 requests / 60 s |
POST /warehouse/documents |
30 requests / 60 s |
GET /documents |
10 requests / 30 s |
GET /clients |
10 requests / 30 s |
GET /products |
10 requests / 30 s |
POST /documents/{id}/send-to-ksef |
10 requests / 60 s |
GET /company/nip-lookup |
10 requests / 60 s |
GET /documents/{id}/download |
5 requests / 60 s |
GET /documents/{id}/ksef-status |
5 requests / 60 s |
POST /ksef/connection/test |
5 requests / 60 s |
POST /webhooks/{id}/test |
5 requests / 60 s |
| Other endpoints | 45 requests / 60 s (one shared counter) |
Each application has its own counters, whatever IP address its requests come from. Several integrations running on one server therefore do not share a limit as long as each uses its own application, and one application called from several servers has a single limit. Each endpoint in the table has its own counter, and all other endpoints share one. Responses carry the X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers (Unix time of the window end); for a request with a valid token they describe the application's limit on the endpoint called. The limit of each endpoint is also listed in its description in the OpenAPI specification.
On top of that, a general limit of 240 requests per minute per IP address applies to all endpoints and applications together. It is checked before authentication, so it also counts requests with a missing, invalid or expired token. Such requests do not use up the application's limit. Normal traffic never gets near it. It matters when many applications call from one address (for example behind a shared NAT) or when a client keeps retrying with an invalid token.
When you exceed any limit you receive 429 with the code RATE_LIMIT_EXCEEDED and a Retry-After header (seconds). Back off and retry, ideally with exponential backoff and jitter. If your request carried an Idempotency-Key, retries are safe.
async function withRetry<T>(fn: () => Promise<Response>): Promise<T> {
for (let i = 0; i < 5; i++) {
const res = await fn();
if (res.status !== 429) return res.json() as Promise<T>;
const retryAfter = Number(res.headers.get('Retry-After') ?? 0) * 1000;
const wait = Math.max(retryAfter, Math.min(2 ** i * 500 + Math.random() * 250, 8000));
await new Promise(r => setTimeout(r, wait));
}
throw new Error('rate limited');
}
Design for limits: cache your OAuth token, batch where you can, and prefer webhooks over polling.
Fair use
Rate limits exist to keep the API fast for everyone. When you hit a limit, honor the Retry-After header and back off (exponential backoff with jitter). A client that ignores 429 and keeps hammering an endpoint is put on a short automatic cooldown that grows if the behavior continues, so the smart move is always to slow down, not to retry harder. Persistent or egregious abuse can lead to temporary suspension of the API client. If your use case genuinely needs higher limits, contact support instead of working around them.