Skip to content

Worked example: key to cleared invoice

Every call below was made on the sandbox on 2026-09-27 with a throwaway key, in this order, and the bodies are what came back with the ids shortened. The Quickstart explains each step; this page shows the wire.

The interactive reference for every endpoint here is Swagger UI on your gateway host, with an Authorize button that takes a clix_ key:

https://<your-gateway-host>/v1/api/integrations/docs          Swagger UI
https://<your-gateway-host>/v1/api/integrations/openapi.json  OpenAPI 3 document

Both are public. Point a client generator at the JSON; use the UI to try a call with your own key.

sequenceDiagram
    participant Admin as Admin, in the app
    participant App as Your system
    participant Clix
    participant ZATCA
    Admin->>Clix: API Keys, Create key
    Clix-->>Admin: key shown once
    App->>Clix: 1 GET /api/usage/current/
    App->>Clix: 2 GET reference data
    App->>Clix: 3 GET /api/devices/, /api/buyers/, /api/items/
    App->>Clix: 4 POST /api/invoices/preview/
    Clix-->>App: 201 computed invoice
    App->>Clix: 5 POST /api/invoices/
    Clix-->>App: 202 location
    Clix->>ZATCA: clearance
    ZATCA-->>Clix: verdict
    App->>Clix: 6 GET location (poll)
    Clix-->>App: zatca_response_status
    App->>Clix: 7 GET pdf-a3
    Admin->>Clix: 8 Revoke

0. Create the key

In the app, an Admin opens API Keys, clicks Create key, enters a Key name and an Access level (Create and view invoices is the default), and clicks Create key. The Copy your key now dialog shows the key once; I have stored it closes it. Steps with screenshots: API keys.

Every call below sends it as:

Authorization: Bearer clix_CdHmGYM_<43-character secret>

The 12-character prefix clix_CdHmGYM is the key's public name; it appears in listings and in the revoke dialog and is safe to log.

1. Check the key and the plan

curl -s "$CLIX_API/api/usage/current/" -H "Authorization: Bearer $CLIX_KEY"

200, with these headers and body:

X-RateLimit-Limit: 200
X-RateLimit-Remaining: 199
X-RateLimit-Reset: 60
{
  "month": "2026-09-01",
  "subscribed_plan": "business",
  "plan_name": "Business",
  "invoices_used": 0,
  "invoices_limit": -1,
  "api_calls_used": 0,
  "api_calls_limit": 0,
  "public_api_validations_used": 0,
  "public_api_validations_limit": 0,
  "grace_used": false,
  "grace_exceeded": false,
  "invoices_allowed": true
}
Field Type Meaning
month date first day of the current Riyadh calendar month
subscribed_plan, plan_name string plan slug and display name
invoices_used, invoices_limit int invoices submitted this month, and the allowance; -1 is unlimited
api_calls_used, api_calls_limit int always 0; API calls are not metered
grace_used, grace_exceeded bool whether the 10 percent grace above the limit is in use or spent
invoices_allowed bool the one field to branch on before a create

With a wrong or revoked key, the same call returns 401:

{"status": "false", "message": "Invalid or revoked API key"}

2. Read the codes

curl -s "$CLIX_API/api/invoices/vat-categories/"      -H "Authorization: Bearer $CLIX_KEY"
curl -s "$CLIX_API/api/invoices/payment-means-types/" -H "Authorization: Bearer $CLIX_KEY"
curl -s "$CLIX_API/api/invoices/invoice-type-codes/"  -H "Authorization: Bearer $CLIX_KEY"

Each is a map keyed by the enum name, never a list:

{"STANDARD_RATED": {"code": "S",  "description": "Standard Rated", "description_ar": "…"}, "ZERO_RATED": {…}}
{"IN_CASH":        {"code": 10,   "description": "In cash",        "description_ar": "نقداً"}, "CHEQUE": {…}}
{"TAX_INVOICE":    {"code": 388,  "description": "Tax Invoice",    "description_ar": "…"}, "CREDIT_NOTE": {…}}

code is a string for VAT categories and an integer for payment means and invoice types. Full lists: Reference data.

3. Find the device, the buyer and an item

curl -s "$CLIX_API/api/devices/" -H "Authorization: Bearer $CLIX_KEY"
{
  "devices": [
    {
      "device_id": "53bf5f28-…",
      "seller_id": "79ec7b95-…",
      "common_name": "Docs verification",
      "serial_number": "1-AZMX|2-V1|3-53bf5f28-…",
      "location": "Riyadh",
      "unit_name": "https://clix.azmx.sa",
      "industry": "Design",
      "invoice_type": "1100",
      "business_type": "BOTH",
      "device_status": "pcsid_generated",
      "ccsid_status": "ISSUED",
      "pcsid_status": "ISSUED",
      "CSIDCertificateData": {"not_valid_before": "2026-09-27T13:25:24Z", "not_valid_after": "2031-09-26T21:00:00Z", "days_until_expiry": 1825, "expiry_status": "valid"},
      "PSIDCertificateData": {"not_valid_before": "2024-01-11T09:19:30Z", "not_valid_after": "2029-01-09T09:19:30Z", "days_until_expiry": 834, "expiry_status": "valid"},
      "branch_id": null,
      "is_branch_device": false,
      "counter": "No invoices generated yet.",
      "created_at": "2026-09-27"
    }
  ],
  "total": 1, "num_pages": 1, "current_page": 1,
  "total_active_devices": 0, "total_ccsid_expired": 0, "total_pcsid_expired": 0
}
Field Use it for
device_id the device of the invoice; the field is not called uuid
seller_id the seller of the invoice; same row, so one read gives both
device_status pcsid_generated is the Active connection in the app; only such a device can issue
invoice_type 1000 standard only, 0100 simplified only, 1100 both
PSIDCertificateData.not_valid_after when the production certificate expires
curl -s "$CLIX_API/api/buyers/" -H "Authorization: Bearer $CLIX_KEY"
curl -s "$CLIX_API/api/items/"  -H "Authorization: Bearer $CLIX_KEY"

Buyers come back as {"buyers": [...]}, each with uuid, buyer_name, buyer_name_arabic, vat_registration_number, business_type, building_number, city, contact_email and the rest of the address; items as {"items": [...]} with uuid and item_name. A standard invoice needs a buyer with a vat_registration_number.

4. Preview

The body, invoice.json, exactly as sent:

{
  "device": "53bf5f28-…",
  "seller": "79ec7b95-…",
  "buyer": "efe0d610-…",
  "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": "Docs verification"}],
  "form_lines": [
    {
      "item_name": "QA Walk item 2026-09-21",
      "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": []
}
Field Type Rule
device, seller, buyer uuid strings from step 3
type_code int 388 here; notes use the notes endpoint
transaction_code string 0100000 standard, 0200000 simplified; must suit the device's invoice_type
issue_date, issue_time YYYY-MM-DD, HH:MM:SS set by the server, which discards what you send: this request carried 10:00:00 and the invoice came back 13:27:38. Required keys on release 0.108.7, optional after it
supply_date, supply_end_date YYYY-MM-DD yours: when the goods or service were delivered
currency, exchange_rate string, number SAR and 1 for a domestic sale
payment_means_type_code string "10" cash; "30" credit transfer additionally requires payment_terms
form_lines[] objects the lines you write; lines stays []
form_lines[].item_net_price string decimal
form_lines[].vat_category_code, vat_rate S/Z/E/O, number zero-rated and exempt lines need vat_exemption_reason_code
reason_id, original_invoice_reference, prepaid_invoices for notes and advance payments; empty here

What each of these constants means and what else it may be: Codes and constants.

curl -s -X POST "$CLIX_API/api/invoices/preview/" -H "Authorization: Bearer $CLIX_KEY" \
  -H "Content-Type: application/json" -d @invoice.json

201:

{
  "message": "Invoice preview created successfully",
  "invoice": {
    "uuid": "0f1ad997-…",
    "ref_num": "158-1",
    "device": "53bf5f28-…",
    "issue_date": "2026-09-27",
    "issue_time": "13:27:38",
    "is_sample_invoice": false,
    "add_prepaid_amount": false,
    "…": "totals, vat_breakdown and every other field of a full invoice"
  }
}

A preview runs the same validation as a create. The two refusals we hit while building this body, both 400:

{"message": ["invoice_schema_data: Value error, Missing required fields: device. Optional fields not provided: reason_id"]}
{"message": "Payment terms are required for credit payment."}

The first came from sending device: null; the second from payment_means_type_code: "30" without payment_terms.

5. Create

curl -s -X POST "$CLIX_API/api/invoices/" -H "Authorization: Bearer $CLIX_KEY" \
  -H "Content-Type: application/json" -d @invoice.json

202, verbatim:

{
  "message": "Tax Invoice creation request completed successfully, it will be cleared or reported to ZATCA soon",
  "task_id": "e0f94fed-…",
  "location": "/api/invoices/9f12304f-…/"
}

Keep location. task_id has no endpoint.

Sending the identical body again inside five minutes, 400:

{"message": "You just submitted an invoice with the exact same data. Please wait 295 seconds before retrying."}

6. Poll

curl -s "$CLIX_API/api/invoices/9f12304f-…/" -H "Authorization: Bearer $CLIX_KEY"

200. The response carries every field of the invoice; these are the ones a poller reads:

{
  "uuid": "9f12304f-…",
  "ref_num": "158-1",
  "zatca_submitted_at": "2026-09-27T13:27:39.026Z",
  "zatca_response_received_at": "2026-09-27T13:27:39.699Z",
  "zatca_response_status": "NOT_CLEARED",
  "zatca_response_code": 400,
  "zatca_response_data": {
    "clearedInvoice": null,
    "clearanceStatus": "NOT_CLEARED",
    "validationResults": {
      "status": "ERROR",
      "infoMessages": [
        {"code": "XSD_ZATCA_VALID", "type": "INFO", "status": "PASS", "category": "XSD validation",
         "message": "Complied with UBL 2.1 standards in line with ZATCA specifications"}
      ],
      "errorMessages": [
        {"code": "certificate-permissions",
         "message": "User only allowed to use the vat number that exists in the authentication certificate"}
      ]
    }
  },
  "payment_status": "Pending",
  "invoice_pdf_url": null,
  "qr_code": "…",
  "share_link": "…",
  "invoice_total_amount_without_vat": "100.00",
  "invoice_total_vat_amount": "15.00",
  "invoice_payable_amount": "115.00",
  "vat_breakdown": [ { "vat_category_code": "S", "vat_rate": "15", "vat_category_taxable_amount": "100.00", "vat_category_tax_amount": "15.00" } ],
  "lines": [ … ], "buyer": { … }, "seller": { … }, "device": { … }
}
Field Meaning
zatca_response_status CLEARED, REPORTED, NOT_CLEARED, NOT_REPORTED; stop polling on any of them
zatca_response_code ZATCA's HTTP status; 200 accepted, 400 rejected
zatca_response_data.validationResults.errorMessages[] why, each with code, category, message; infoMessages[] are the checks that passed
zatca_response_data.clearedInvoice the signed XML on success, null on rejection
invoice_pdf_url set once the invoice is final
payment_status Pending, Partially paid, Paid, Overdue; yours to move through payments, not ZATCA's

The answer came within a second. This run was rejected on purpose: the sandbox signs with one shared certificate, and certificate-permissions is what any other VAT number gets there. On app.goclix.ai the same body from an active device clears. A rejected invoice is fixed and resubmitted with PATCH /api/invoices/{invoice_id}/clearance-reporting/.

7. PDF

curl -s "$CLIX_API/api/invoices/9f12304f-…/pdf-a3/" -H "Authorization: Bearer $CLIX_KEY"

Before the invoice is final, 404:

{"message": "Invoice PDF not found"}

Once invoice_pdf_url is set the same call returns the PDF/A-3 bytes with the ZATCA XML embedded.

8. Revoke

In the app, API Keys, Revoke on the row, Revoke key in the Revoke this key? dialog. The very next call answered:

{"status": "false", "message": "Invalid or revoked API key"}

with status 401, within a second of the click.

What was not run

Notes (POST /api/invoices/{invoice_id}/notes/), client creation (POST /api/onboarding/organizations/clients/), item creation (POST /api/items/) and resubmission (PATCH …/clearance-reporting/) are on the surface and in Swagger but were not exercised in this run. Their schemas are in the OpenAPI document above.