Creating documents
This is the complete guide to POST /external/v1/documents, the single endpoint you need to issue an invoice, proforma, advance, settlement, correction or order. It covers every request field, how transaction parties are resolved, the per-type logic and the KSeF annotations. For the money math and the full type map see Document types & amounts; here we focus on how to build a correct request.
If you are just starting, run through the Quickstart first. The full machine-generated schema for every field lives in the API reference.
Before you start
- Scope: creating documents requires the
documents:createpermission. See Authentication. - Dictionaries are the source of truth: rather than hardcoding enum values, fetch them once from
GET /external/v1/dictionaries. It returns the live lists: document types, payment methods and statuses, VAT rates, GTU codes, currencies, country codes, margin procedures, VAT exemption types and date headers. Your integration then won't drift when we add a new entry. - Idempotency: send every create with an
Idempotency-Keyheader so a retry after a dropped connection never creates a duplicate. See Idempotency.
Full list of values. Every enum (document types, VAT rates, currencies, country codes, GTU, margin procedures, …) is described on the Dictionaries page, and you can fetch them live from
GET /dictionaries. The dictionary returns exactly the document types this endpoint accepts.
Minimal request
The endpoint will create a document from just documentType, issueDate, a buyer with nip and name, and one line item. That is not enough for real use though: for the invoice to pass KSeF submission, the buyer must have a full address (street, postalCode, city, countryCode). So we treat the example with an address as the sensible minimum. The seller is resolved automatically from your company profile.
curl -s -X POST https://prod.api.fakturnia.pl/external/v1/documents \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c1f1d6e-0b2a-4a1e-9b1a-2a4e9b1a2a4e" \
-d '{
"documentType": "FakturaVAT",
"issueDate": "2026-06-04",
"buyer": {
"nip": "5261040828",
"name": "Acme Corp Sp. z o.o.",
"street": "Marszałkowska 1",
"postalCode": "00-001",
"city": "Warszawa",
"countryCode": "PL"
},
"products": [
{ "name": "Development service", "unitOfMeasure": "szt.", "quantity": 1, "netPrice": 100, "vatRate": 23 }
]
}'
Buyer address and KSeF. Without an address the document is still created and you can download the PDF, but KSeF submission is rejected. If you issue invoices with KSeF in mind, treat the buyer address as required. For an individual (
type: "person") providefirstNameandlastNameinstead ofname.
Everything else is optional with sensible defaults. Below we break the request down.
Document numbering
A document number is made of three parts: a symbol, a sequence number (documentNo) and a date part (numberingFormat). The most common layout is the number followed by the date:
{symbol}{documentNo}{numberingFormat}
For example symbol FV/, number 4 and format /MM/YYYY for issue date 2026-05-07 give FV/4/05/2026. The date part can also come before the number, depending on the chosen format (see "Available formats" below).
The three parts of a number
| Part | Field | What it is |
|---|---|---|
| Symbol | symbol |
Prefix, e.g. FV/, KOR/. Defaults from your company settings for the given document type. |
| Sequence number | documentNo |
Integer ≥ 1. This is what increments within a period. |
| Format | numberingFormat |
The "date" part pattern, e.g. /MM/YYYY. Defaults from settings, fallback /MM/YYYY. |
The format substitutes only two tokens: MM (month, two digits) and YYYY (year), both from the issue date. Any other characters (slashes, dashes, letters) are inserted verbatim:
numberingFormat |
issueDate |
Date part | Full number (symbol FV/, no. 4) |
|---|---|---|---|
/MM/YYYY |
2026-05-07 |
/05/2026 |
FV/4/05/2026 |
/YYYY |
2026-05-07 |
/2026 |
FV/4/2026 |
-MM-YYYY |
2026-05-07 |
-05-2026 |
FV/4-05-2026 |
Available formats
The position of the date part depends on the pattern: in a prefix variant the date comes after the number, in a suffix variant before it. Both are supported. In the "Layout" column, NUMER stands for documentNo.
Monthly (contain MM):
| Format | Layout | Example (symbol FV/, no. 4, 2026-05) |
|---|---|---|
/MM/YYYY |
NUMER/MM/YYYY | FV/4/05/2026 |
/YYYY/MM |
NUMER/YYYY/MM | FV/4/2026/05 |
-MM-YYYY |
NUMER-MM-YYYY | FV/4-05-2026 |
-YYYY-MM |
NUMER-YYYY-MM | FV/4-2026-05 |
YYYY/MM/ |
YYYY/MM/NUMER | FV/2026/05/4 |
MM/YYYY/ |
MM/YYYY/NUMER | FV/05/2026/4 |
YYYY-MM- |
YYYY-MM-NUMER | FV/2026-05-4 |
MM-YYYY- |
MM-YYYY-NUMER | FV/05-2026-4 |
Yearly (YYYY only):
| Format | Layout | Example (symbol FV/, no. 4, 2026) |
|---|---|---|
/YYYY |
NUMER/YYYY | FV/4/2026 |
-YYYY |
NUMER-YYYY | FV/4-2026 |
YYYY/ |
YYYY/NUMER | FV/2026/4 |
YYYY- |
YYYY-NUMER | FV/2026-4 |
The default format is /MM/YYYY. numberingFormat is validated against the list above, so an unknown pattern is rejected with 400 VALIDATION_ERROR. When you send documentNumber manually, it must match the chosen format's layout (otherwise 422 NUMBER_MISMATCH).
Automatic numbering (default)
When you do not send documentNo, the backend assigns the number:
symbol,numberingFormatand the numbering mode come from your company settings (you can override each of them in the request).- The numbering mode is monthly (default) or yearly (the
numberingTypesetting). It decides when the counter resets: monthly keeps a separate counter per month (periodYYYY-MM), yearly per year (periodYYYY). - The counter is atomic and separate per
company + document type + period. The first document in a period gets1, the next2, and so on. - Corrections share one counter:
FakturaKorygujaca,FakturaKorygujacaZaliczkowaandFakturaKorygujacaRozliczeniowaare numbered together.
Keep the format consistent with the mode. With monthly numbering the format should contain
MM(e.g./MM/YYYY). Number uniqueness is checked within the period implied by the format, so using/YYYYwith the monthly mode collapses months into one period.
Manual numbering
To force your own number, send documentNo (≥ 1). The backend builds the full number from the symbol, the number and the date part following the chosen format's layout, and checks that the number is free: in its series (document type, numbering format, month or year of the issue date) and across the whole account, since two documents can never share the same full number, even of different types or from different months.
Optionally add documentNumber (the full string) as a safeguard: it must exactly match what the backend builds from symbol/documentNo/numberingFormat, otherwise you get 422 NUMBER_MISMATCH. This guards against your number drifting from ours.
{
"documentType": "FakturaVAT",
"issueDate": "2026-05-07",
"symbol": "FV/",
"documentNo": 4,
"numberingFormat": "/MM/YYYY",
"documentNumber": "FV/4/05/2026"
}
Manual numbering does not touch the automatic counter. If you mix both modes, keep your numbering continuous on your side so you don't create gaps or collisions.
Numbering fields
| Field | Type | Required | Notes |
|---|---|---|---|
symbol |
string | no | Prefix. Defaults from settings for the given document type. |
documentNo |
number | no | Sequence number ≥ 1. Omitted → automatic numbering. |
numberingFormat |
string | no | Date-part pattern (MM, YYYY tokens). Defaults from settings. |
documentNumber |
string | no | Full number for 1:1 validation against documentNo. |
Numbering errors
| Status | Code | Cause |
|---|---|---|
422 |
NUMBER_MISMATCH |
documentNumber does not match the number the API builds for the chosen format's layout (PREFIX {symbol}{documentNo}{date} or SUFFIX {symbol}{date}{documentNo}). |
409 |
DOCUMENT_NUMBER_EXISTS |
The number is taken in its series, or another document on the account already has the same full number. |
Request anatomy
Document header
| Field | Type | Required | Notes |
|---|---|---|---|
documentType |
string | yes | One of the ten types (see Document types). Immutable on edit. |
issueDate |
string | yes | Issue date, ISO 8601 YYYY-MM-DD. |
issuePlace |
string | no | Place of issue. Defaults from company settings. |
currency |
string | no | Currency code, defaults to PLN. List in the currencies dictionary. |
exchangeRate |
number | no | Exchange rate for non-PLN currencies: the NBP mid rate of the last business day before the tax point (usually before the sale date), or before the issue date when the invoice comes first (art. 31a of the VAT Act). |
exchangeRateDate |
string | no | Day of the NBP table exchangeRate comes from (YYYY-MM-DD). |
exchangeRateTable |
string | no | NBP table number, e.g. 190/A/NBP/2026. Printed on the PDF next to the rate. |
comments |
string | no | Notes shown on the document. In KSeF they map to the invoice footer: Stopka/Informacje/StopkaFaktury. |
invoiceName |
string | no | Display name. Defaults to type plus number. |
Transaction parties: seller, buyer, receiver
This is the most common source of integrator questions, so we cover it carefully.
The seller (seller) is never sent in the request. It always comes from the authenticated user's company profile (user.company): name, NIP, EU VAT, REGON, KRS, BDO, address, and the personal details for a sole proprietorship. To change seller data, update the company profile, not the request. The buyer's NIP must not equal the seller's NIP (422).
The buyer (buyer) is required, and you pass it in one of two ways:
buyerId(string): the id of an existing contractor. Fetch it fromGET /external/v1/clients.buyer(object): inline data. The backend looks up a saved contractor (by NIP, VAT UE number or name) and, if none is found, creates a new one from the fields you sent. So you don't have to create the contractor separately before invoicing. Identifiers follow the contractor identifier rules: send a contractor from another EU country as"identifierType": "vatEu", "vatUE": "DE123456789", not innip. An inferred type and moved numbers come back indata.warnings, contradictions as422 PARTY_IDENTIFIER_INVALID.
Send buyerId or buyer, not both.
The receiver (receiver) is optional and identifies a separate entity that actually receives the invoice, distinct from the buyer. The typical case is local-government structures: the buyer is the parent unit that holds the NIP (e.g. a municipality), while the receiver is the subordinate unit that actually receives the invoice (e.g. a department or a public kindergarten). You pass it the same way as the buyer: receiverId or an inline receiver object. If you omit the receiver, the invoice has no separate receiver and the buyer remains the recipient.
The receiver can additionally be identified by an internal identifier: set identifierType to idWew and provide idWew (format NIP-NNNNN, e.g. 5252248481-16006), which is useful for local-government units (JST), among others. The seller has no such option; in KSeF it is always identified by its NIP.
The contractor object (buyer / receiver)
The same field set applies to buyer and receiver. All fields except the type marker are optional, but for a valid invoice include identifying and address data.
| Field | Type | Notes |
|---|---|---|
id |
string | Id of an existing contractor (alternative to buyerId/receiverId). |
type |
string | company (default) or person. Decides whether you use name or firstName + lastName. |
name |
string | Company name (for company). |
firstName, lastName |
string | Given and family name (for person). |
nip |
string | Polish NIP (10 digits; spaces, dashes and the PL prefix are removed, the check digit is verified). Used to look up or create the contractor. Send a foreign VAT number in vatUE. |
vatUE |
string | EU VAT number with its country prefix, e.g. DE123456789; stored in uppercase without spaces. |
pesel |
string | Personal id of an individual. |
regon, krs, bdo |
string | Other registry identifiers. |
street, postalCode, city |
string | Address. |
country, countryCode |
string | ISO 3166-1 alpha-2 code (e.g. PL, DE), defaults to PL. List in the countries dictionary. Only countryCode matters for KSeF and the PDF; the country name is optional (the PDF derives it from the code anyway). |
email, phoneNumber, www |
string | Contact details. |
identifierType |
string | Which identifier identifies the party in KSeF: nip (field nip), vatEu (vatUE), other (countryCode and otherTaxIdentifier), idWew (idWew, receiver only), none (none). Without it the API infers the type from the data, see Contractor identifiers. |
otherTaxIdentifier |
string | Tax identifier for identifierType: "other", up to 50 characters, together with countryCode (KodKraju + NrID in KSeF). |
idWew |
string | Internal identifier (format NIP-NNNNN, e.g. 5252248481-16006), used when identifierType: "idWew". Only the receiver can have it: in the FA(3) schema IDWew exists only for the receiver (Podmiot3), not the buyer or seller. |
fieldsToShow |
string[] | Which fields to print on the document, e.g. ["nip","regon"]. |
Example of a buyer who is an individual:
{
"buyer": {
"type": "person",
"firstName": "Jan",
"lastName": "Kowalski",
"street": "Długa 5",
"postalCode": "80-001",
"city": "Gdańsk",
"countryCode": "PL"
}
}
Tip. If you issue many documents to the same contractor, create it once via the contractors API and then pass
buyerId. Faster, and no risk of drifting data from a typo in the name.
Line items (products)
The array of line items. Required for standard, settlement and correction invoices. For an advance invoice the items are derived from the linked order, so you don't send them (see below).
For each item you send a price and a VAT rate, and the backend fills in the values. Do not compute netValue, amountOfVat or grossValue yourself - the server is the single source of truth. The exact formulas and rounding rules are in Document types & amounts.
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | Product or service name. |
unitOfMeasure |
string | yes | Unit, e.g. szt., godz., kg. Free text, suggested values in the dictionary. |
quantity |
number | yes | Quantity. For corrections 0 is allowed (cancel a line). |
vatRate |
number | string | yes | VAT rate (see VAT rates). |
netPrice |
number | yes* | Net unit price. |
grossPrice |
number | yes* | Gross unit price (alternative to netPrice). |
discount |
number | no | Percentage discount 0-100. |
gtu |
string | no | GTU code (GTU_01 to GTU_13). Validated against the dictionary. |
pkwiu |
string | no | PKWiU symbol. |
marginPurchaseValue |
number | no | vatRate: "marza" only: gross purchase amount of the whole line in PLN. See Margin scheme. |
marginTaxRate |
number | no | vatRate: "marza" only: rate of the tax on the margin, 23, 8, 5 or 0. Default 23. |
* Send netPrice or grossPrice. If you pass grossPrice, the math runs "downward" from gross.
VAT rates
vatRate takes a numeric percentage or a special string code. The full list with descriptions is in the vatRates dictionary.
- Percentage rates:
23,22,8,7,5,4,3. - Special codes (VAT = 0, net = gross):
zw(exempt),np Iandnp II(not subject),oo(reverse charge),0 KR(domestic 0%),0 WDT(intra-EU supply),0 EX(export),marza(margin scheme, see below).
There is no bare 0 or bare np. An unknown rate returns 400 VALIDATION_ERROR.
Margin scheme
A margin-scheme invoice (art. 119 of the VAT Act for travel agencies, art. 120 for second-hand goods, works of art, collectors' items and antiques) shows the buyer gross amounts only. Mark a margin line with vatRate: "marza":
- send
grossPrice;netValue=grossValue,amountOfVat=0, - the document needs
annotations.marginProcedurewithenabled: trueand aprocedureId(1travel agencies,2second-hand goods,3works of art,4collectors' items and antiques); without it you get422 MARGIN_LINE_WITHOUT_PROCEDURE, - in KSeF the line has no rate (
P_12) and its sum goes toP_13_11, - margin lines and regular rates can share one invoice (e.g. a travel agency: services bought for the tourist under the margin scheme and its own service at 23%).
The tax is due on the margin, the difference between the price and the purchase amount. Fakturnia computes it for the VAT register and JPK_V7 (K_19 and K_20 for 23%, MR_T or MR_UZ), so a line can carry:
marginPurchaseValue: the gross purchase amount of the whole line in PLN (second-hand goods: the purchase price; travel agencies: the costs of services bought directly for the tourist). It never appears on the invoice or in KSeF. Without it the reports leave the VAT on the margin of that line out and show a warning, and MCP warns withMARGIN_PURCHASE_MISSING. You can add it later withPUT /documents/{id},marginTaxRate: the rate of the tax on the margin,23(default),8,5or0.
You can also set the purchase amount after the invoice is issued, including once it is in KSeF: PATCH /documents/{id}/margin-purchases with { "lines": [{ "index": 0, "marginPurchaseValue": 1500, "marginTaxRate": 23 }] }, where index is the position in products (from 0). Only these two fields change; the invoice content stays. A key you leave out keeps the stored value, null clears it. A line outside the margin scheme returns 422 MARGIN_LINE_NOT_FOUND, a wrong value 422 MARGIN_PURCHASES_INVALID. The MCP tool updateDocumentMarginPurchases does the same.
When PUT or PATCH /documents/{id} sends a margin line without marginPurchaseValue and marginTaxRate, the values stored on the line with the same name stay.
VAT on the margin = margin × rate / (100 + rate). A negative margin gives 0 tax and does not lower the margin of other lines. In a correction, margin fields you leave out are taken from the corrected line with the same name, the purchase amount in proportion to the quantity (returning one of three items lowers it by a third).
A margin line has no net, so the document's netSum counts it with its gross (KSeF reports it apart, in P_13_11). The POST /documents/validate preview gives this sale in preview.totals.marginSaleSum, and the net of the ordinary rates is netSum minus marginSaleSum. Without margin lines marginSaleSum is 0.
{
"documentType": "FakturaVAT",
"annotations": { "marginProcedure": { "enabled": true, "procedureId": 3 } },
"products": [
{ "name": "Oil painting", "unitOfMeasure": "szt.", "quantity": 1, "grossPrice": 5000, "vatRate": "marza", "marginPurchaseValue": 3000, "marginTaxRate": 23 }
]
}
Payment
| Field | Type | Notes |
|---|---|---|
paymentMethodId |
number | 1 Cash · 2 Card · 3 Voucher · 4 Cheque · 5 Credit · 6 Transfer · 7 Mobile payment |
paymentStatusId |
number | 0 Paid · 1 Unpaid |
paymentDeadlineDate |
string | Due date. Required when paymentStatusId = 1. |
additionalPaymentDate |
string | Payment date. Required when paymentStatusId = 0. |
additionalPaymentDateHeaderId |
number | Header above the payment date (paymentDateHeaders dictionary). |
paymentDescription |
string | Extra payment note. |
showPaymentSection |
boolean | Whether to show the payment section on the PDF. Defaults to true. |
Partial payments (partialPayments)
When the buyer paid part of the amount by the issue date (or the whole amount in several payments), send the list of payments. They go to KSeF as ZaplataCzesciowa and appear on the PDF as payment information.
| Field | Type | Meaning |
|---|---|---|
partialPayments[].amount |
number | gross payment amount |
partialPayments[].date |
string | payment date (YYYY-MM-DD), not later than issueDate |
partialPayments[].paymentMethodId |
number | payment form like paymentMethodId (optional) |
The rules depend on paymentStatusId:
1(Due): the payments must sum to less than the amount due; the rest is due onpaymentDeadlineDate.0(Paid): at least two payments summing exactly to the amount due;additionalPaymentDateis then not required.
Inconsistent payments answer 422 PARTIAL_PAYMENTS_INVALID. When the invoice has partial payments, the payment date of the whole (additionalPaymentDate) is not sent to KSeF: the dates go with the payments. In the response payment carries partialPayments (an empty list when there are none), paidAmount (paid according to the invoice) and amountDue (still due), so the integration does not have to compute them.
{
"paymentMethodId": 6,
"paymentStatusId": 1,
"paymentDeadlineDate": "2026-10-10",
"partialPayments": [
{ "amount": 500, "date": "2026-09-20", "paymentMethodId": 6 }
]
}
Instalments (paymentInstallments)
An invoice payable on several dates lists instalments, each with a due date and an amount. In KSeF every instalment is its own TerminPlatnosci, and the instalment amounts go to DodatkowyOpis (FA(3) has no field for an instalment amount). On the PDF the instalments replace the single due date.
| Field | Type | Meaning |
|---|---|---|
paymentInstallments[].date |
string | due date of the instalment (YYYY-MM-DD) |
paymentInstallments[].amount |
number | gross amount of the instalment, in the document currency |
Rules: only with paymentStatusId = 1, at least two instalments and at most 100, due dates in order and not before issueDate, adding up to the amount due. Without paymentDeadlineDate the document is due on the first instalment. Inconsistent instalments answer 422 PAYMENT_INSTALLMENTS_INVALID. An empty list ([]) in PUT or PATCH removes them. The API computes equal instalments for you: see Equal instalments.
{
"paymentStatusId": 1,
"paymentMethodId": 6,
"paymentInstallments": [
{ "date": "2026-10-20", "amount": 615 },
{ "date": "2026-11-20", "amount": 615 }
]
}
Equal instalments (paymentInstallmentsSplit)
When the instalments should be equal, do not compute the amounts yourself: send the number of instalments and the API splits the amount due once the document totals are computed. The document gets ordinary paymentInstallments (in the response, in the validate preview and in KSeF); the split itself is not stored.
| Field | Type | Meaning |
|---|---|---|
paymentInstallmentsSplit.count |
number | number of instalments, 2 to 100 (required) |
paymentInstallmentsSplit.firstDate |
string | due date of the first instalment (YYYY-MM-DD), not before issueDate; defaults to paymentDeadlineDate from the request, otherwise issueDate + 14 days |
paymentInstallmentsSplit.every |
string | month (monthly, the default), week (every 7 days) or 14days (every 14 days) |
How the API computes the instalments:
- The amount due (gross, in the document currency; on a settlement invoice the amount left after the advances) becomes grosze once, rounding halves away from zero.
- Every instalment gets the same number of grosze and the remainder goes to the last one: 100.00 in 3 instalments is 33.33 + 33.33 + 33.34. The instalments always add up to the amount due.
- Monthly instalments are counted from the first date: 31 January, 28 February, 31 March.
- The document's payment deadline (
paymentDeadlineDate) becomes the date of the first instalment.
Rules: only with paymentStatusId = 1. On create without paymentStatusId the document becomes unpaid (to pay); status 0 (paid) is an error. Do not combine it with paymentInstallments: instalments with different amounts (e.g. a larger first instalment) go in paymentInstallments. A PATCH without products splits the amount stored in the document and, without firstDate, starts on the stored payment deadline. Less than one grosz per instalment, a wrong count, date or interval answer 422 PAYMENT_INSTALLMENTS_INVALID, with the reason in errors[0] (in Polish, e.g. "Kwota jest za mała na tyle rat.", the amount is too small for that many instalments). null means no split.
Request (part of POST /external/v1/documents, gross total 1,230.00 PLN):
{
"documentType": "FakturaVAT",
"issueDate": "2026-10-07",
"paymentMethodId": 6,
"paymentStatusId": 1,
"paymentInstallmentsSplit": { "count": 3, "firstDate": "2026-10-31", "every": "month" }
}
Response (part of data.document):
{
"paymentDeadlineDate": "2026-10-31",
"paymentInstallments": [
{ "date": "2026-10-31", "amount": 410 },
{ "date": "2026-11-30", "amount": 410 },
{ "date": "2026-12-31", "amount": 410 }
]
}
POST /external/v1/documents/validate returns the same instalments in preview.paymentInstallments, so you can show them before issuing.
Due date in words (paymentDeadlineTerm)
The due date can also be described in words, e.g. "14 days from the invoice date". It goes to TerminOpis in KSeF and next to the due date on the PDF.
| Field | Type | Meaning |
|---|---|---|
paymentDeadlineTerm.count |
number | number of units, a whole number from 1 |
paymentDeadlineTerm.unit |
string | day, week or month |
paymentDeadlineTerm.event |
string | what the term counts from: issue (the issue of the invoice), invoice_delivery (delivery of the invoice), supply (supply of goods or services), acceptance (acceptance) |
paymentDeadlineDate still gives the due date; with event: issue it should follow from the description. null in PUT or PATCH removes the description.
Several receipts on an advance invoice (advanceReceipts)
An advance invoice can document several payments received before it was issued (ZaliczkaCzesciowa in KSeF, example 16 of the Ministry of Finance brochure). Each receipt is its own tax point (art. 19a ust. 8 of the VAT Act), so the VAT report and JPK place them separately.
| Field | Type | Meaning |
|---|---|---|
advanceReceipts[].date |
string | the day the payment arrived, not later than issueDate |
advanceReceipts[].amount |
number | gross amount of the payment |
advanceReceipts[].exchangeRate |
number | in a foreign currency: the NBP rate of the business day before the payment arrived (required) |
advanceReceipts[].exchangeRateDate |
string | day of the NBP table of that rate (optional) |
Rules: only FakturaZaliczka with paymentStatusId = 0, from two to 31 receipts, adding up to advancePaymentAmount. The document's payment date is the last receipt's. Inconsistent receipts answer 422 ADVANCE_RECEIPTS_INVALID.
Bank accounts
For a bank transfer (paymentMethodId = 6) supply bankAccounts, unless you have a default account in company settings. The first account becomes the primary one, the rest are shown as additional.
| Field | Type | Required | Notes |
|---|---|---|---|
accountNumber |
string | yes | Account number / IBAN. |
bank |
string | no | Bank name. |
swift |
string | no | SWIFT code for international transfers. |
accountDescription |
string | no | Account description on the document. |
bankOwnAccountType |
number | no | 1, 2 or 3. |
For split payment you can additionally provide a separate VAT account: vatAccountNumber, vatAccountBank, vatAccountSwift, vatAccountDescription. The MPP annotation itself is enabled via annotations.isSplitPayment (see below).
Sale dates
| Field | Type | Notes |
|---|---|---|
subDate |
string | Sale / delivery / service date (KSeF P_6); with subDateTo the start of a period. For KSeF types and orders it defaults to issueDate. A correction without subDate takes the sale date or period of the corrected invoice. |
subDateTo |
string | End of the sale period, e.g. a service for a whole month (KSeF OkresFa). null in PUT or PATCH goes back to a single date. |
subDateHeaderId |
number | Header above the sale date (additionalDateHeaders dictionary). |
taxPointRule |
string | Tax point at the issue of the invoice (art. 19a ust. 5 of the VAT Act): art19a5p3a construction services, art19a5p3b supply of books and press, art19a5p3c printing of books and press, art19a5p4 utilities, telecommunications, rent, lease, leasing, security, ongoing legal and office services. Not sent to KSeF; it decides the period in VAT reports and the day of the exchange rate. null means the usual rules. |
The dates of a document cannot contradict each other. The API refuses with 422 DOCUMENT_DATES_INVALID (messages in errors):
- a sale period whose start is after its end;
- a sale date more than 60 days after the issue date (art. 106i ust. 7; a billing period is exempt);
- a payment date (
additionalPaymentDate) later than the issue date withpaymentStatusId = 0. Mark a payment that arrived after the issue withPATCH /documents/{id}/payment-statusandpaymentDate; - a correction issued before the corrected invoice.
A due date earlier than the issue date is allowed (a late invoice may carry a due date that has passed).
Annotations (annotations)
Annotations are the modern, KSeF FA(3) compatible way to flag special VAT settlement modes. They replace the old, separate invoice types (such as FakturaMPP or FakturaVATMarza), which the API deliberately does not expose.
| Field | Type | Meaning |
|---|---|---|
isSplitPayment |
boolean | Split payment mechanism (MPP). Mutually exclusive with isCashMethod. |
isReverseCharge |
boolean | Reverse charge. |
isCashMethod |
boolean | Cash method. Mutually exclusive with isSplitPayment. |
isSelfInvoicing |
boolean | Self-invoicing. |
marginProcedure |
object | Margin scheme. |
vatExemption |
object | VAT exemption. |
Margin scheme (annotations.marginProcedure):
| Field | Type | Notes |
|---|---|---|
enabled |
boolean | Enables the scheme. |
procedureId |
number | Required when enabled = true. Values in the marginProcedures dictionary (e.g. travel agency, used goods, works of art, antiques). |
VAT exemption (annotations.vatExemption):
| Field | Type | Notes |
|---|---|---|
enabled |
boolean | Enables the exemption. |
exemptionType |
string | A (statute), B (Directive 2006/112/EC) or C (other legal basis). |
legalBasis |
string | Legal basis as text, e.g. art. 43 ust. 1 pkt 19 ustawy o VAT. |
showOnDocument |
boolean | Whether to show the note on the PDF. Does not affect the document sent to KSeF (the exemption still maps to P_19/P_19A/P_19B/P_19C) — it only controls the PDF display. |
{
"annotations": {
"isSplitPayment": true,
"vatExemption": {
"enabled": true,
"exemptionType": "A",
"legalBasis": "art. 113 ust. 1 ustawy o VAT"
}
}
}
Additional information (additionalInfo)
The KSeF FA(3) "Additional information" section — separate from annotations. For now it exposes a single field:
| Field | Type | Meaning |
|---|---|---|
isInvoiceToReceipt |
boolean | Invoice issued to a receipt ("faktura do paragonu", art. 109 ust. 3d of the Polish VAT Act). |
When you set isInvoiceToReceipt: true, the document sent to KSeF carries the <FP>1</FP> flag and the PDF shows the note "Faktura do paragonu, o której mowa w art. 109 ust. 3d ustawy". The document type (RodzajFaktury) does not change — this is a regular VAT invoice with an extra marker, not a separate document type. KSeF has no field for the receipt number, so the API does not accept one either.
{
"additionalInfo": {
"isInvoiceToReceipt": true
}
}
Signatures (signatures)
Optional signature blocks on the PDF.
{
"signatures": {
"issuerSignature": { "show": true, "value": "Jan Nowak" },
"receiverSignature": { "show": false }
}
}
Other fields
ksefJst(boolean): document for a local-government unit for KSeF purposes.
Account defaults
A document issued through the API looks like a new document from the app editor: fields you do not send get the account's settings. A field you send always wins (including an empty string "" or an empty object {}), and useAccountDefaults: false switches all account defaults off.
| Field | Default |
|---|---|
comments |
The comments from the document settings. |
issuePlace |
The place of issue from the settings, or the company address city when empty. |
signatures |
Signature fields per the signature setting; the issuer is the owner or the employee issuing the document. |
annotations |
Automatic annotations (when enabled): split payment with the VAT account (vatAccountNumber from the account marked as VAT) and the margin procedure. Corrections do not get them. |
annotations.vatExemption |
Lines with the zw rate get the exemption basis from the settings (type A/B/C and legal basis). |
paymentMethodId |
6 (bank transfer) with the default account. |
paymentStatusId |
The default status from the settings: paid (payment date = issue date) or to pay (due date = issue date). |
products[].vatRate |
The account's default VAT rate. |
taxPointRule |
The default rule of the setting „Obowiązek podatkowy z datą wystawienia faktury” (tax point at the issue of the invoice; Settings → Autocomplete), when it is on and has a rule chosen. Applies to KSeF sales invoices other than corrections and advance invoices; an explicit taxPointRule: null does not get it. |
| seller | The seller's visible fields from the settings (fieldsToShow). |
| PKWiU on the print | Per the PKWiU display setting. |
POST /documents/validate lists every applied default in preview.appliedDefaults (code, field, a sentence to show the user), and preview.resolved shows the payment, place, comments and annotations after them.
Corrections take the buyer, receiver, currency with its rate and annotations (with the VAT account) from the corrected invoice when you do not send them, so buyerId is optional for them. An invoice that has a correction cannot be changed or deleted (409 DOCUMENT_CORRECTED): another correction changes it. Once its last correction is deleted, the invoice is editable again.
FakturaProformaBezVAT always has 0 VAT on its lines, like in the app, whatever rate you send. FakturaZaliczka has no sale date.
Document types and their fields
The endpoint accepts ten types. Below are only the extra fields required per type; the sum-calculation rules for advances, settlements and corrections are in Document types & amounts.
| Type | What it is | Extra required |
|---|---|---|
Faktura |
Invoice outside KSeF (VAT-exempt entities, until end of 2026) | - |
FakturaVAT |
VAT invoice (the KSeF basic-invoice equivalent) | - |
FakturaProforma |
Proforma | - |
FakturaProformaBezVAT |
Proforma without VAT | - |
FakturaZaliczka |
Advance invoice | linkedOrderId, advancePaymentAmount (no products) |
FakturaRozliczeniowa |
Settlement (final) invoice | linkedAdvanceInvoiceIds, products |
FakturaKorygujaca |
Correction invoice | correctedInvoiceId, products (the full "after" state); buyer from the corrected invoice |
FakturaKorygujacaZaliczkowa |
Advance correction | correctedInvoiceId, correctionAdvanceAmountAfter |
FakturaKorygujacaRozliczeniowa |
Settlement correction | correctedInvoiceId, correctionSettlementAmountAfter |
Zamowienie |
Order | - |
Fields tied to special types:
| Field | Type | For |
|---|---|---|
linkedOrderId |
string | FakturaZaliczka: the linked Zamowienie. |
advancePaymentAmount |
number | FakturaZaliczka: advance amount (≥ 0.01, ≤ order value). |
linkedAdvanceInvoiceIds |
string[] | FakturaRozliczeniowa: the advance invoices being settled. |
settlementAmount |
number | FakturaRozliczeniowa: amount to settle (auto, unless isSettlementAmountManual). |
isSettlementAmountManual |
boolean | FakturaRozliczeniowa: forces a manual settlementAmount. |
correctedInvoiceId |
string | Corrections: the invoice being corrected. |
correctionReason |
string | Corrections: reason (free text). |
correctionAdvanceAmountAfter |
number | FakturaKorygujacaZaliczkowa: new advance amount after correction. |
correctionSettlementAmountAfter |
number | FakturaKorygujacaRozliczeniowa: new settlement amount after correction. |
Sums do not always equal
Σ products. For advances, settlements and corrections,grossSumfollows separate logic. If you mirror amounts on your side, take them from the API response, don't recompute them. Full formulas in Document types & amounts.
Dry-run validation
POST /external/v1/documents/validate runs the full pipeline (numbering, party resolution, sums) and returns a preview without saving the document. Ideal for showing a confirmation screen or testing an integration without cluttering the account.
Idempotency
Send an Idempotency-Key header (a UUID v4 is recommended). Repeating the same request with the same key returns the stored response instead of creating a duplicate (24 h cache). The same key with a different body returns an error. See Idempotency.
Common errors
| Status | Code | Cause |
|---|---|---|
400 |
VALIDATION_ERROR |
Invalid or missing type, date, VAT rate, field structure. |
403 |
NO_PERMISSIONS |
Token without the documents:create scope. |
404 |
DOCUMENT_NOT_FOUND |
Unknown buyerId, receiverId, correctedInvoiceId or linkedOrderId. |
409 |
DOCUMENT_NUMBER_EXISTS |
The document number already exists. |
422 |
DOCUMENT_DATES_INVALID |
The document's dates contradict each other or the VAT Act (see Sale dates). |
422 |
PAYMENT_INSTALLMENTS_INVALID |
Instalments do not match the amount, the due dates or the payment status, or paymentInstallmentsSplit cannot be made. |
422 |
ADVANCE_RECEIPTS_INVALID |
Advance receipts do not match the advance amount, the dates or the rates. |
422 |
- | Domain rules: advance larger than the order, isSplitPayment together with isCashMethod, missing required payment date, buyer NIP equal to seller NIP. |
The full list of codes and the error envelope are in Errors.
What's next
- Document types & amounts: calculation rules, rounding, advance / settlement / correction logic.
- Sending to KSeF: submit a finished document and wait for the result.
- Idempotency: safely retry writes.
- Rate limits and Pagination: create limits and list paging.
- Errors: codes and handling.
- API reference: the full schema for every field.