Refund or correct a sale
An issued sales invoice never changes and is never deleted. To give money back, take it off what the customer owes, or correct a mistake, you issue a credit note against it with POST /v1/credit-notes. The key needs credit_notes:write.
A credit note credits items of one invoice. It takes its branch, customer and prices from the invoice, and the credited goods go back to stock. The money goes one of two ways:
| You send | The credit note's total |
|---|---|
refund with a paymentMethodId | is paid back to the customer through that payment method |
no refund | comes off what the customer owes on the invoice; anything above that stays as the customer's credit |
The credit note number values below are made up: each merchant sets its own numbering.
reason tells ZATCA why: cancellation (the sale is cancelled, fully or in part), refund (goods or services are returned and the money is given back) or informationChange (the invoice's details were wrong).
Refund a person
The person from Record a sale paid 115 SAR for a wash that did not happen. Credit the line and pay the money back:
curl https://api.kosoor.com.sa/v1/credit-notes \
-H "Authorization: Bearer ksr_test_..." \
-H "Idempotency-Key: 5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f" \
-H "Content-Type: application/json" \
-d '{
"invoiceId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
"reason": "refund",
"lines": [
{ "itemId": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f", "quantity": 1 }
],
"refund": { "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9" },
"notes": "Booking cancelled by the customer",
"referenceId": "refund-20417"
}'
201 Created:
{
"id": "e4f5a6b7-c8d9-4e0f-9a1b-3c4d5e6f7a8b",
"object": "creditNote",
"number": "CN-0007",
"invoiceId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
"customerId": "5a4db0c5-0000-4000-8000-000000000001",
"branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"issueDate": "2026-11-08",
"reason": "refund",
"currency": "SAR",
"lines": [
{
"itemId": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
"name": "Full wash",
"description": null,
"quantity": 1,
"unitPrice": 100,
"unitDiscount": 0,
"vatRate": 15,
"vatAmount": 15,
"lineTotal": 115
}
],
"subtotal": 100,
"discountTotal": 0,
"vatTotal": 15,
"total": 115,
"refund": {
"paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
"amount": 115
},
"notes": "Booking cancelled by the customer",
"referenceId": "refund-20417",
"metadata": {},
"createdVia": "api",
"zatca": {
"kind": "simplified",
"status": "pending",
"uuid": null,
"counter": null,
"hash": null,
"qr": null,
"submittedAt": null,
"clearedAt": null,
"reportedAt": null,
"errors": []
}
}
The invoice was simplified, so the credit note is too: it is reported to ZATCA after the reply and turns reported. refund.amount is the money paid back.
A person carries no balance. Without refund, a person can be credited only up to what the invoice still owes; above that the call answers 400 invalid_parameter on refund. A person who paid in full is therefore always refunded with refund.
Credit a company's account
The company from Record a sale has not paid its 230 SAR invoice yet, and one of the two washes is cancelled. Credit one unit without refund, so it comes off what the company owes:
curl https://api.kosoor.com.sa/v1/credit-notes \
-H "Authorization: Bearer ksr_test_..." \
-H "Idempotency-Key: 1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b" \
-H "Content-Type: application/json" \
-d '{
"invoiceId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f",
"reason": "cancellation",
"lines": [
{ "itemId": "8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a", "quantity": 1 }
],
"referenceId": "cancel-20418-1"
}'
201 Created:
{
"id": "f5a6b7c8-d9e0-4f1a-8b2c-4d5e6f7a8b9c",
"object": "creditNote",
"number": "CN-0008",
"invoiceId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f",
"customerId": "5a4db0c5-0000-4000-8000-000000000002",
"branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"issueDate": "2026-11-08",
"reason": "cancellation",
"currency": "SAR",
"lines": [
{
"itemId": "8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a",
"name": "Fleet wash",
"description": "Plates ABC 1234 and XYZ 5678",
"quantity": 1,
"unitPrice": 100,
"unitDiscount": 0,
"vatRate": 15,
"vatAmount": 15,
"lineTotal": 115
}
],
"subtotal": 100,
"discountTotal": 0,
"vatTotal": 15,
"total": 115,
"refund": null,
"notes": null,
"referenceId": "cancel-20418-1",
"metadata": {},
"createdVia": "api",
"zatca": {
"kind": "standard",
"status": "cleared",
"uuid": "a9b0c1d2-e3f4-4a5b-8c6d-7e8f9a0b1c2d",
"counter": 19,
"hash": "ZHXx9tKJ0Zr2b/RlF8NG4LYnWT15u5Rq0/+tYkccjjk=",
"qr": "ARBTYW5kYm94IE1lcmNoYW50Ag8zOTk5OTk5OTk5MDAwMDMDFDIwMjYtMTEtMDhUMDg6MTU6MDRaBAYxMTUuMDAFBTE1LjAw",
"submittedAt": "2026-11-08T08:15:03.902Z",
"clearedAt": "2026-11-08T08:15:04.611Z",
"reportedAt": null,
"errors": []
}
}
refund is null: nothing was paid back. The invoice's amountDue drops from 230 to 115. Had the company already paid in full, the 115 would stay as its credit.
The invoice was standard, so ZATCA cleared the credit note before the reply. An invoice ZATCA has not cleared yet cannot be credited: the call answers 409 invoice_not_cleared. Retry once GET /v1/sales-invoices/{id} shows zatca.status: "cleared".
Correct a mistake
An invoice with wrong details, such as the wrong customer, is corrected in two calls:
- Credit every line of it with
reason: "informationChange". If the customer paid, sendrefundto pay the money back; a company may instead keep it as credit. - Issue a new invoice with the right details, as in Record a sale.
How much can be credited
The credits on an invoice never add up to more than its total, and never more of an item than was sold. Refunds made at the POS or on the dashboard count too. Above that, the call answers 422 credit_exceeds_invoice. A refund can also not pass what the invoice has collected, less what was already paid back (422 refund_exceeds_collected).
When the API refuses
| Error | What to do |
|---|---|
409 invoice_not_cleared | the standard invoice is still pending; retry once it is cleared |
422 invoice_not_creditable | ZATCA rejected the invoice, or it was imported; the merchant corrects it on the dashboard |
422 loyalty_refund_blocked | retry once a few minutes later; if refused again, the merchant issues the credit note from the dashboard |
422 used_subscription_not_refundable | the sale was a subscription with visits used; the merchant handles it on the dashboard |
422 refund_method_not_supported | the sale was paid by card on the merchant's online store; the API cannot refund the card |
409 invoice_busy | another request held the invoice for more than 30 seconds; send it again |
Credit notes has every rule and every error.