Skip to main content
POST
Create/update invoice

Creation retries and invoice number

The current endpoint has no idempotency key or company-unique externalId. The input validator rejects the unknown top-level field externalId. A timeout or lost response does not mean that saving failed: retrying creation can produce another invoice. The response returns the full saved invoice in data, as in the application save flow. Keep data.invoiceId and read the assigned number from data.basicInformation.invoiceNumber. Other saved fields, including lines, totals and payment data, are returned in the same snapshot. There is no data.invoice wrapper in the save response. If reloading the saved document fails, the response uses the in-memory save result, as the application does. When the result of creation is unknown, reconcile existing invoices before issuing another creation request. Sales search includes additionalDetails, so an external reference stored there can help locate a document. It is substring search and does not enforce uniqueness or prevent concurrent duplicates.

Payment fields

For a non-draft invoice, provide payments. Whenever the object is present, include all four required fields: paymentMethod, dueDate, isSplitPayment and isPaid. Send explicit false values when those flags do not apply:

Corrections and full returns

Each correction is a separate invoice with its own identifier and invoice number, linked to the document it corrects. For basicInformation.invoiceType: "CORRECTION", supply line amounts representing the state after the correction, rather than the amount of the reduction. When no explicit before rows are supplied, Altera derives and subtracts the rows from the first invoice in basicInformation.relatedInvoicesIds. quantity must be greater than zero on an ordinary invoice. On a CORRECTION that references a source invoice in basicInformation.relatedInvoicesIds, zero is accepted for the after-correction state, as in the application editor. Negative quantities are rejected. For a full return, set quantity to zero and keep the unit prices and VAT rate. For example, a fully returned line that originally had quantity 1 and a net unit price of 100 has this post-correction state:
For a correction of a correction, reference the previous correction in basicInformation.relatedInvoicesIds and supply the new after-correction state. Altera stores the after-correction rows on each correction and keeps the financial difference in its totals. For example, if invoice A has a line worth 100 and correction B changes it to 80, correction C should reference B and provide 60 to reduce it further. B and C each have a difference of -20. GET /sales/invoice?related={previousCorrectionId}&invoiceType=CORRECTION therefore prepares the rows stored on that correction, such as 80 in this example. Referencing the original invoice A prepares its original rows, such as 100; it does not apply later corrections automatically. Select the document you actually intend to correct and provide the full state after the new correction. This describes the local invoice calculation; submission to KSeF remains subject to KSeF validation.

Currency and document language

basicInformation.currency and basicInformation.language are supported on both create and update requests, including in production. These values are saved on the invoice and returned by Get invoice data. The language controls the invoice document. It does not translate the email body or select an email template. On update, resend these fields to retain their values; omission applies the defaults above. The following request fragment sets an English invoice in EUR:

Non-subject VAT rates and GTU

invoicePosition[].vat supports the distinct values np I and np II in production. They remain distinct when saved and exported to KSeF: np I maps to the P_13_8 summary bucket, and np II maps to P_13_9. The legacy value np maps to np I; use the explicit value for new invoices. Neither is the zw VAT-exemption rate described below. invoicePosition[].gtu is an optional string with a maximum of 10 characters. Use "1" through "13", for example "2". Zero-padded values such as "02" and KSeF values such as "GTU_02" are also supported. KSeF export converts the numeric value to GTU_01 through GTU_13. Omit it, pass null or an empty string when no GTU applies. Set the classification separately on each applicable line. The following request fragment shows the field locations; select the VAT rate and GTU appropriate for the actual transaction:

Calendar dates

Send basicInformation.issueDate, basicInformation.saleDate, payments.dueDate and optional payments.paymentDate as strings in YYYY-MM-DD format, for example "2026-09-14". These fields represent calendar dates without a time or time zone. Do not convert them to UTC or serialize them with toISOString(). For example, if the invoice date is September 15, send "2026-09-15", rather than "2026-09-14T22:00:00Z". Use the same date regardless of the client’s time zone. Timestamps representing an event time have different semantics and can include a time zone. Legacy date-time inputs may still be accepted for compatibility. Non-midnight values can be converted to the calendar date in Europe/Warsaw; the save response does not include a warning about that conversion. With strict date validation enabled, non-midnight date-time values are rejected with HTTP 400 and validationErrors[].code = "DATE_REQUIRES_LOCAL_DATE". Legacy midnight values are still accepted without changing their written date. Always send YYYY-MM-DD for new integrations. Validation errors include an English description in errorMessage, a Polish description in errorMessagePL, and the affected field in validationErrors[].field.

VAT-exempt invoices

If at least one invoice item uses the "vat": "zw" rate, provide vatExemptionReason at the top level of the request, alongside invoicePosition, additionalDetails, and payments. Altera resolves the legal basis from vatExemptionReason on the invoice, then from the company’s VAT-exemption setting. An ISSUED or SENT sales invoice containing a zw line requires a nonempty basis before it can be saved. Missing basis returns HTTP 400 with validationErrors[].code = "KSEF_VAT_EXEMPTION_LEGAL_BASIS_REQUIRED". override does not bypass this check. A DRAFT can remain incomplete. Providing the invoice basis explicitly makes it independent of the company default. KSeF submission checks generated XML and previously stored XML. Exempt invoices require P_19 = 1 and exactly one nonempty P_19A, P_19B or P_19C, without the no-exemption marker P_19N. Newly generated Altera XML uses P_19A for the selected basis. A successful save alone does not confirm acceptance by KSeF. The field accepts exactly one of these three values:
  • Zwolnienie podmiotowe z art. 113 ust. 1 i 9
  • Zwolnienie przedmiotowe z art. 43 ust. 1
  • Zwolnienie przedmiotowe z art. 82 ust. 3
vatExemptionReasonDetailed is optional. It accepts up to 500 characters of additional wording displayed on the invoice below the selected legal basis. It does not replace vatExemptionReason. Altera submits vatExemptionReason to KSeF as P_19A. The following request fragment shows an invoice item using the zw rate:
When updating an existing invoice, use the same fields and provide the document identifier in basicInformation.invoiceId. If the invoice has no item using the zw rate, omit both fields.

Separate recipients and other additional parties

Keep the buyer in contractor. Add a separate recipient to the top-level additionalParties array, alongside contractor, basicInformation and payments. These are structured invoice parties (Podmiot3 in KSeF), rather than text in additionalDetails or contractor.houseNumber. The following request fragment adds a recipient. Replace the illustrative name, address and tax identity with the actual party data:
semanticRole accepts the following values: Use RECIPIENT for an ordinary separate recipient. Do not automatically select JST_RECIPIENT just because the recipient is a school: choose the role that matches the actual local government arrangement, because it changes the KSeF mapping. internalId is optional. When used, send a string containing exactly five digits, including any leading zeros. It is the suffix, not the complete KSeF identifier. For Podmiot3 export it requires a Polish 10-digit NIP and Polish country; the example produces IDWew equal to 5260250274-00123. Omit internalId when the party does not use an internal identifier. Address fields belong to this additional party. houseNumber contains only the building number (maximum 20 characters), and postCode contains the postal code. Do not combine buyer and recipient addresses in one field. For an update, provide basicInformation.invoiceId together with the intended invoice data and additional parties. This does not retroactively correct previously issued documents; invoices already submitted to or accepted by KSeF have content-edit restrictions and may require a correction invoice. The legacy alias additionalContractors is also accepted; use additionalParties for new integrations and avoid sending both.

Assigning a project to an invoice

Use the singular top-level project object in POST /sales/invoice, alongside basicInformation, contractor and payments. One project is assigned to the invoice. The following fragment must be combined with the remaining invoice data:
Use the actual project object from the same company. tagId identifies the existing project; name, enabled and color are also required by invoice validation. Sending only { "tagId": 161 } is insufficient. The name must be nonempty, enabled is a boolean, and color is the project’s numeric ARGB value (4278190080–4294967295). Fetch existing projects using List projects (GET /company/projects, or GET /company/projects?onlyActive=1 for active projects), find the desired project by name in data, and use its complete object as project. Saving an invoice does not create a project from its name. If necessary, first create the project using Add/modify a project (POST /company/projects), then use the returned project data including its tagId when saving the invoice. For an existing invoice, include basicInformation.invoiceId and the intended invoice data:
  • To retain the assignment, send the current project again.
  • To change it, send the new project object.
  • To remove it, omit project from the update payload. This endpoint is not a partial PATCH that preserves every omitted field. project: null is not accepted.
The sales save flow copies the header project to all invoice line items, replacing line-level project values. Removing the header project also clears line-level assignments. Set the project at the invoice level rather than trying to assign different projects per sales line. Do not send projects: [], an array of projects, or a project name as a plain string. The current request validator expects project to be an object and rejects the unknown top-level field projects.

Immediate KSeF submission

Set the top-level boolean sendToKSEF to true to request submission during the save flow, after the invoice has been persisted. To issue a new invoice and request immediate submission, use this fragment together with the remaining required buyer, line-item and payment data:
  • The default is false. Omitting the flag or sending false does not request immediate submission, but does not disable normal automatic KSeF processing. Eligible invoices can still enter the pending workflow.
  • The flag does not issue a draft. The invoice must be ISSUED or SENT; a draft is not submitted.
  • KSeF integration and outgoing submission must be enabled for the company. The flag does not override company settings, private-person invoice policy or KSeF validation. If the integration or outgoing submission is disabled, the save flow can skip submission without returning a KSeF warning.
  • sendToKSEF: true is rejected for PRO_FORMA, ORDER and DEBIT_NOTE.
  • The compatibility spelling sendToKsef is also accepted. Prefer sendToKSEF; if both are sent, their values must agree.

Save success is not KSeF acceptance

A normal save returns HTTP 200 with the saved invoice identifier:
When immediate submission returns an unsuccessful result after saving, the endpoint still returns HTTP 200 and result: "OK", with optional KSeF details. For example:
Inspect ksefWarning, ksefErrorMessage and ksefReason even on HTTP 200. Reason codes are an open set; another possible value is KSEF_SUBMISSION_FAILED. Absence of these fields is not proof of KSeF acceptance. Retrieve the saved invoice through Get invoice data and inspect ksef.ksefStatus, ksef.ksefId and any ksef.ksefErrorMessage. Keep the returned invoiceId. Do not create the same invoice again because KSeF submission failed. Resolve the KSeF issue and use the existing invoice’s submission/retry workflow. For request validation errors, correct the input before retrying; do not repeatedly resend the same invalid payload. This warning response describes unsuccessful submission results after save; other validation or processing failures can still return an error response.

Authorizations

Authorization
string
header
required

OpenApi Key created within Altera.app for a certain company

Body

application/json
override
boolean
default:false

Defines whether we wish to override split payments warning, in Machine-machine integrations should be set to true

sendToKSEF
boolean
default:false

Request immediate KSeF submission after saving an eligible ISSUED or SENT invoice. Defaults to false; false does not disable normal automatic KSeF processing. Requires KSeF and outgoing submission enabled for the company and an eligible invoice. Does not issue a DRAFT or guarantee KSeF acceptance. Inspect data.ksefWarning, data.ksefErrorMessage and data.ksefReason even on HTTP 200, then check the saved invoice KSeF status. Not allowed for PRO_FORMA, ORDER or DEBIT_NOTE. See the endpoint guide for examples and retry handling.

basicInformation
object
contractor
object
invoicePosition
object[]
additionalDetails
string

Describes extra information that we wish to present on the invoice

correctionReason
string

Additional information regarding the reason behing correcting your invoice

vatExemptionReason
enum<string>

Legal basis for a zw line. Required before issuing a sales invoice unless a nonempty company default supplies the basis. Drafts may omit it. Missing basis returns validation code KSEF_VAT_EXEMPTION_LEGAL_BASIS_REQUIRED, which override cannot bypass. New XML uses P_19A. Submission also checks stored XML for P_19=1, exactly one nonempty P_19A/B/C, and no P_19N.

Available options:
Zwolnienie podmiotowe z art. 113 ust. 1 i 9,
Zwolnienie przedmiotowe z art. 43 ust. 1,
Zwolnienie przedmiotowe z art. 82 ust. 3
Maximum string length: 50
vatExemptionReasonDetailed
string

Optional detailed wording shown on the invoice below vatExemptionReason. It does not replace the required vatExemptionReason value.

Maximum string length: 500
payments
object

Required when statusId is not DRAFT. Whenever this object is supplied, include paymentMethod, dueDate, isSplitPayment and isPaid, including explicit false values for the booleans.

additionalParties
object[]

Additional parties, including a separate recipient (Podmiot3). Each entry is a snapshot; contractor continues to identify the buyer.

project
object

Single project assigned to the invoice. Pass an existing project from the same company, including tagId, name, enabled and color. Assignment is persisted by tagId; saving an invoice does not create a project. On update, resend project to retain the assignment; omitting it removes the assignment. The header project is propagated to sales line items. Use project, not projects.

Example:

Response

Invoice saved. HTTP 200 confirms the save, not acceptance by KSeF. A failed immediate submission can be reported in optional data.ksefWarning, data.ksefErrorMessage and data.ksefReason fields.

Regular format of success response in Altera API Retrieved information will always be stored in the 'data' node

data
Invoice · object

Invoice model for objects returned from server

warnings
object[]

Optional non-blocking validation warnings.

result
enum<string>
default:OK
Available options:
OK
Example:

"OK"