Sales invoices
POST /v1/sales-invoices creates and issues a sales invoice in one call. There is no draft step: an issued invoice is corrected only by a credit note.
If an item has a minimum selling price in Kosoor, a unitDiscount that would sell a unit below it is reduced to that price. The request still succeeds, with no error.
For example, an item priced 100 SAR before VAT with a minimum price of 92 SAR including VAT (80 SAR before VAT) can take at most 20 SAR off. Send unitDiscount: 50 and the invoice applies 20.
Always read lines[].unitDiscount and total from the reply, not from your own request. If you send payment.amount, compare it with the reply's total.
Required fields
Every request sends customerId, branchId, pricesIncludeVat and lines, and each line sends itemId and quantity. When you send payment, it needs paymentMethodId. Every invoice names a customer: the API makes no walk-in sale.
Standard or simplified
The customer decides the invoice's kind:
| Customer | Invoice | ZATCA |
|---|---|---|
| company | standard (tax invoice) | cleared before the reply |
| person | simplified | reported after the reply |
For a standard invoice:
- ZATCA accepts it:
201withzatca.status: "cleared"and ZATCA's QR code. - ZATCA rejects it:
422 zatca_rejectedwith ZATCA's messages inzatcaErrors. Nothing is saved. - ZATCA does not answer in time:
201withzatca.status: "pending". Kosoor sends it again later, and it turnscleared, orrejectedwith ZATCA's messages inzatca.errors. Either way the invoice stays issued.
A standard invoice also needs the company customer's full address and VAT number or ID (customer_address_incomplete lists what is missing), and a branch registered with ZATCA (branch_not_onboarded). Its PDF and XML answer 409 invoice_not_cleared until ZATCA clears it, and for good if ZATCA rejects it. A simplified invoice's PDF is ready at once; its XML answers 409 invoice_not_cleared until ZATCA has accepted the report, and for good if ZATCA rejects it.
Prices
pricesIncludeVat says whether every unitPrice and unitDiscount in the request includes VAT. A line without unitPrice takes the item's catalogue price; an item priced per car size has no single catalogue price, so its line must send unitPrice or the call answers 400 missing_parameter on lines[i].unitPrice.
Kosoor keeps every price to the halala before VAT, and rounds VAT once per rate on the invoice, the way ZATCA totals it. A price that includes VAT is converted first, so the total can differ from what you sent by a halala: 12.00 including VAT becomes 10.43 before VAT and a total of 11.99. The reply always shows line amounts before VAT. To pay such an invoice in full, send payment without amount.
Payment
| You send | The invoice |
|---|---|
no payment | goes on the customer's account (company customers only; a person gets 422 on_account_not_allowed) |
payment without amount | is paid in full |
payment.amount below the total | is partly paid; the rest stays on the customer's account (company customers only; a person gets 422 on_account_not_allowed) |
payment.amount above the total | is refused: 400 invalid_parameter |
payment.amount takes at most two decimals. amountPaid and amountDue in the reply show where the invoice stands.
Reading invoices
GET /v1/sales-invoices lists every issued invoice in the key's branches, whichever channel created it. expand=customer adds the customer, and needs customers:read as well as sales_invoices:read.