Skip to main content

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.

Discounts never go below the item's minimum price

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:

CustomerInvoiceZATCA
companystandard (tax invoice)cleared before the reply
personsimplifiedreported after the reply

For a standard invoice:

  • ZATCA accepts it: 201 with zatca.status: "cleared" and ZATCA's QR code.
  • ZATCA rejects it: 422 zatca_rejected with ZATCA's messages in zatcaErrors. Nothing is saved.
  • ZATCA does not answer in time: 201 with zatca.status: "pending". Kosoor sends it again later, and it turns cleared, or rejected with ZATCA's messages in zatca.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 sendThe invoice
no paymentgoes on the customer's account (company customers only; a person gets 422 on_account_not_allowed)
payment without amountis paid in full
payment.amount below the totalis 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 totalis 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.