Quickstart¶
From an API key to a ZATCA-cleared invoice with a PDF.
sequenceDiagram
participant App as Your system
participant Clix
participant ZATCA
App->>Clix: GET /api/usage/current/
Clix-->>App: 200, invoices_allowed
App->>Clix: GET /api/devices/, /api/buyers/
Clix-->>App: device_id, seller_id, buyer uuid
App->>Clix: POST /api/invoices/preview/
Clix-->>App: 201, computed totals
App->>Clix: POST /api/invoices/
Clix-->>App: 202, location
Clix->>ZATCA: clearance or reporting
ZATCA-->>Clix: CLEARED / REPORTED / NOT_CLEARED
loop until final
App->>Clix: GET location
Clix-->>App: zatca_response_status
end
App->>Clix: GET …/pdf-a3/
Clix-->>App: PDF/A-3
Before you start¶
- An API key with role
CreateInvoiceorAccountant— see Authentication. - A device registered with ZATCA in the organisation. Invoices are signed by a device; without one, creation fails. See Devices and certificates.
- At least one client and one item, or create them through the API below.
Your base URL is the API gateway for your environment and already includes the version segment. It is given to you with your key; this page writes it as $CLIX_API.
1. Check the key works¶
A 200 with your plan and invoice counters means the key, the plan and the organisation are all good.
2. Look up the codes you will need¶
The code lists are static and safe to cache:
{
"STANDARD_RATED": { "code": "S", "description": "Standard Rated", "description_ar": "..." },
"ZERO_RATED": { "code": "Z", "description": "Zero Rated", "description_ar": "..." }
}
These are objects, not arrays
Every reference endpoint returns a map keyed by the enum member name, not a list. Index into it by name, or take Object.values(). See Reference data.
3. Build the payload¶
Three ids come from your own organisation, and one read gives you two of them:
Each row in devices carries device_id and seller_id. Use a device whose device_status is pcsid_generated, the Active connection in the app. Then take a buyer uuid from GET /api/buyers/ and, if you use the catalogue, an item name from GET /api/items/.
invoice.json, a standard invoice for one line, verified on the sandbox 2026-09-27:
{
"device": "<device_id from /api/devices/>",
"seller": "<seller_id from the same row>",
"buyer": "<uuid from /api/buyers/>",
"type_code": 388,
"transaction_code": "0100000",
"issue_date": "2026-09-27",
"issue_time": "10:00:00",
"supply_date": "2026-09-27",
"supply_end_date": "2026-09-27",
"currency": "SAR",
"exchange_rate": 1,
"payment_means_type_code": "10",
"is_sample_invoice": false,
"add_prepaid_amount": false,
"reason_id": null,
"notes": [{ "language_id": "en", "note": "Order 1042" }],
"form_lines": [
{ "item_name": "Consulting", "invoiced_quantity": 1,
"invoiced_quantity_unit_of_measure": "PCE", "item_net_price": "100",
"vat_category_code": "S", "vat_rate": 15.0,
"vat_exemption_reason_code": "", "vat_exemption_reason_text": "" }
],
"lines": [], "original_invoice_reference": [], "prepaid_invoices": [],
"document_level_allowances": [], "document_level_charges": []
}
Rules the server enforces on this body:
| Field | Rule |
|---|---|
transaction_code |
0100000 for a standard invoice to a business, 0200000 for a simplified invoice to a consumer. The device must allow that type. |
type_code |
388 tax invoice. Credit and debit notes go through the notes endpoint, not here. |
payment_means_type_code |
a string. "30" credit transfer and other credit means require payment_terms; the server answers 400 {"message": "Payment terms are required for credit payment."} without it. "10" cash needs nothing more. |
buyer |
a business buyer needs a VAT number on its record for a standard invoice, or ZATCA rejects the invoice. |
form_lines |
the lines you write. lines stays an empty list. |
4. Preview before you commit¶
POST /api/invoices/preview/ runs the same validation and calculation as a create and returns the computed invoice with its totals and a ref_num, but submits nothing to ZATCA and uses no invoice number. Use it to check your payload.
curl -s -X POST "$CLIX_API/api/invoices/preview/" \
-H "Authorization: Bearer $CLIX_KEY" \
-H "Content-Type: application/json" \
-d @invoice.json
Expect 201 with {"message": "Invoice preview created successfully", "invoice": {…}}. A 400 here is the same 400 a create would give, so fix it now.
5. Create the invoice¶
curl -s -X POST "$CLIX_API/api/invoices/" \
-H "Authorization: Bearer $CLIX_KEY" \
-H "Content-Type: application/json" \
-d @invoice.json
Expect 202 Accepted — submission to ZATCA is asynchronous:
{
"message": "Tax Invoice creation request completed successfully, it will be cleared or reported to ZATCA soon",
"task_id": "…",
"location": "/api/invoices/8f3c…/",
"status_code": 202
}
Keep location. Ignore task_id — it is informational, and there is no task-status endpoint.
6. Poll until it is final¶
Watch zatca_response_status. It reaches CLEARED for standard invoices or REPORTED for simplified ones, usually within a few seconds. Poll with a sensible backoff — a second or two between attempts, not a tight loop.
If it reads NOT_CLEARED or NOT_REPORTED, ZATCA rejected the invoice. The reason is in zatca_response_data.validationResults.errorMessages[], each with a code, category and message; zatca_response_code carries ZATCA's HTTP status. Fix the cause and resubmit with PATCH /api/invoices/{invoice_id}/clearance-reporting/. A rejected invoice has no PDF.
There are no outbound webhooks. Polling is the supported pattern.
7. Fetch the PDF¶
Once the invoice is final and invoice_pdf_url is set:
PDF/A-3 with the ZATCA XML embedded — the archival form. Before the invoice is final the endpoint answers 404 {"message": "Invoice PDF not found"}.
Do not retry a create blindly¶
Clix fingerprints invoice payloads for five minutes. Resending an identical body returns 400:
You just submitted an invoice with the exact same data. Please wait {n} seconds before retrying.
{n} is in seconds, up to 300. If a create times out, poll before retrying — the invoice may already exist. Retrying on a network error is how integrations produce duplicate invoices, and a cleared invoice cannot be deleted.
What you can call¶
21 paths in total — invoices, notes, buyers, items, devices, usage and the code lists. The authoritative list is the machine-readable spec:
GET /api/integrations/openapi.json- Browsable at
/api/integrations/docs
Both are public; no key needed to read them.