Errors
The API uses standard HTTP status codes and a consistent error envelope. Every error carries a stable, machine-readable message (the error code) and, where it matters, details in errors.
Error shape
{
"success": false,
"data": null,
"message": "AMBIGUOUS_PARTY",
"executionTime": 5,
"errors": ["Podaj albo buyerId, albo buyer, ale nie oba."]
}
Always branch on message (the code), never on the human-readable text. The text may change, the code will not. errors has two shapes: for field validation it is a list of { field, code?, message } objects, otherwise a list of explanatory sentences, e.g. ["REST API wymaga planu MAX."].
Status codes
| Status | Meaning |
|---|---|
200 / 201 |
success |
400 |
invalid query or path parameters, an operation not allowed for the state of the document, and when creating documents also a malformed request body (e.g. a missing documentType, issueDate, products or buyer) |
401 |
missing, invalid or expired token, or a revoked API account |
403 |
authenticated but not allowed: scope, MAX plan, IP allowlist |
404 |
resource not found (also when it belongs to another account) |
409 |
conflict (e.g. the document number already exists, a webhook for that URL is already registered) |
422 |
request body failed validation: missing field, bad format, type-dependent rules, conflicting annotations (when creating documents some of these checks answer 400, see VALIDATION_ERROR) |
429 |
rate limit exceeded or the PDF rendering queue is full |
500 |
unexpected server error |
503 |
an external register is temporarily unavailable (company lookup in GUS); retry in a moment |
Error codes
| Code | Status | When |
|---|---|---|
UNAUTHORIZED |
401 | missing or invalid token; also a token of a revoked API account |
TOKEN_EXPIRED |
401 | the token expired, get a new one from /v1/oauth/token |
NO_PERMISSIONS |
403 | the token lacks the required scope (errors[0] names it) |
SUBSCRIPTION_REQUIRED |
403 | the account has no active MAX plan (the whole REST API is a MAX module) |
FORBIDDEN |
403 | the caller IP is outside the allowlist of the API account |
DOCUMENT_LIMIT_EXCEEDED |
403 | the plan does not allow issuing or editing documents (errors[0] starts with "Subskrypcja nieaktywna" or "Subskrypcja wygasła") |
VALIDATION_ERROR |
422 / 400 | the request body failed validation; details in errors. Usually 422, but creating documents (POST /documents, POST /documents/validate) and warehouse documents (POST /warehouse/documents, POST /warehouse/documents/validate) answers 400 for request-shape problems (a missing documentType, issueDate or products, a bad date format, an invalid line). Treat both statuses with this code as a validation failure |
PARTIAL_PAYMENTS_INVALID |
422 | partial payments do not match the amount or the payment status |
DOCUMENT_DATES_INVALID |
422 | the document's dates contradict each other or the VAT Act (sale period, a sale date more than 60 days after the issue, a payment after the issue, a correction before the corrected invoice) |
PAYMENT_INSTALLMENTS_INVALID |
422 | instalments do not match the amount due, the due dates or the payment status, or paymentInstallmentsSplit cannot be made (e.g. the amount is too small for that many instalments) |
PARTY_IDENTIFIER_INVALID |
422 | the identifiers of a buyer or receiver given as data on a document contradict each other (e.g. nip with a DE… number, vatEu with a PL… or XI… number, a wrong NIP check digit, idWew for a buyer); errors names the field, e.g. buyer.vatUE. Contractors (/clients) report these as VALIDATION_ERROR. See Contractor identifiers |
ADVANCE_RECEIPTS_INVALID |
422 | receipts on an advance invoice do not match the advance amount, the dates or the rates |
INVALID_PARAMS |
400 | invalid query parameters (e.g. unknown documentType, bad date) |
BAD_REQUEST |
400 | a required part of the request is missing, outside field validation |
AMBIGUOUS_PARTY |
400 | both buyerId and buyer given (or receiverId and receiver) |
BUYER_REQUIRED |
400 | neither buyerId nor buyer given |
NOT_FOUND / DOCUMENT_NOT_FOUND |
404 | no such resource |
DOCUMENT_NUMBER_EXISTS |
409 | the number is taken in its series, or another document on the account already has the same full number |
CONFLICT |
409 | the resource state does not allow the operation (the only warehouse, a webhook for that URL exists, a finished recurring rule) |
KSEF_CONFIRMED |
400 | deleting/editing a document already confirmed in KSeF (it has a KSeF number) |
KSEF_SENT |
400 | deleting/editing a document sent to KSeF and awaiting confirmation |
DOCUMENT_CORRECTED |
409 | editing or deleting an invoice that has a correction (another correction changes it) |
CORRECTED_INVOICE_HAS_CORRECTION |
409 | a new correction of a document that already has one (issue the next change as a correction of its latest correction, number and id in errors[0]) |
ADVANCE_INVOICE_SETTLED |
409 | editing or deleting an advance invoice settled by a settlement invoice |
ORDER_HAS_ADVANCE_INVOICES |
400 | editing or deleting an order that has advance invoices |
WAREHOUSE_LINES_LOCKED |
409 | changing the lines of a warehouse document with PUT/PATCH (delete it and issue a new one) |
STOCK_SHORTAGE |
422 | a WZ/RW/MM would take stock below zero (allowNegativeStock: true issues it anyway) |
WAREHOUSE_INVALID |
422 | unknown or foreign warehouse, MM without a target or with the same warehouse twice |
PRODUCT_NOT_FOUND |
404 | a warehouse document line references an unknown product |
KSEF_CONNECTION_REQUIRED |
409 | a send without a configured KSeF connection |
KSEF_CONNECTION_NOT_AUTHORIZED |
422 | the connection exists but did not pass authorization |
KSEF_ALREADY_PROCESSING / KSEF_SEND_NOT_ALLOWED |
409 | a send is in progress, or the document is in a state that cannot be sent |
INVALID_PAYMENT_STATUS / PAYMENT_STATUS_UPDATE_NOT_ALLOWED |
400 | bad receivedPayment value, or the document is not "Do zapłaty" |
EMAIL_TAKEN / EMPLOYEE_LIMIT_REACHED |
409 | the employee e-mail is taken, or the limit of 20 accounts is reached |
INVALID_NIP / COMPANY_NOT_FOUND |
400 / 404 | company lookup by NIP |
GUS_UNAVAILABLE |
503 | the GUS register did not answer the company lookup by NIP; retry in a moment |
RATE_LIMIT_EXCEEDED |
429 | rate limit exceeded, retry after Retry-After (see Rate limits) |
WAIT_PDF_QUEUE |
429 | the PDF rendering queue is full, retry after Retry-After (see PDF rendering queue) |
INTERNAL_SERVER_ERROR |
500 | unexpected, safe to retry idempotently |
The full table of codes with statuses is also at the top of the OpenAPI specification (info.description).
Response headers
Every response carries an X-Request-ID header with a unique request id. Log it in your integration and quote it in support requests: it lets Fakturnia find that exact call. The X-RateLimit-* and Retry-After headers are described in Rate limits, Idempotent-Replayed in Idempotency.
Handling errors
const res = await fetch(url, opts);
const body = await res.json();
if (!body.success) {
switch (body.message) {
case 'TOKEN_EXPIRED': // refresh the token and retry once
case 'UNAUTHORIZED': // re-authenticate; a revoked API account needs a new one
case 'SUBSCRIPTION_REQUIRED': // the account needs the MAX plan; tell the user, do not retry
case 'RATE_LIMIT_EXCEEDED': // wait Retry-After seconds, then retry
case 'DOCUMENT_NUMBER_EXISTS': // pick a new number or let the API auto-number
case 'VALIDATION_ERROR': // 400 or 422: inspect body.errors
default:
throw new Error(`${body.message} [${res.headers.get('X-Request-ID')}]: ${JSON.stringify(body.errors ?? [])}`);
}
}
5xx and 429 errors are safe to retry with a backoff when the request carried an Idempotency-Key. See Idempotency.