انتقل إلى المحتوى

لم تُترجم هذه الصفحة بعد

تعرض هذه الصفحة النص الإنجليزي حتى تصدر ترجمتها.

Public tools

A small set of endpoints under /api/public/ that need no API key and no account. The five tools read nothing from your organisation and store nothing. The shared-link endpoints are the exception: each one reads the single invoice or draft its token points to.

Base URL

The public tools are not on the Integration API base URL. The web app calls them through the gateway's v1-3 route:

export CLIX_PUBLIC="https://<your-gateway-host>/v1-3"

On the sandbox, GET …/v1/api/public/health/ answers 404 and GET …/v1-3/api/public/health/ answers 200. Checked 2026-09-29.

What is there

Method and path What it does Body
POST /api/public/scan-qr-code/ Decode the TLV string of a ZATCA invoice QR code JSON
POST /api/public/validate-zatca/ Run every ZATCA rule against an invoice and report each result JSON
POST /api/public/calculate-vat/ Work out net, VAT and gross totals per VAT category JSON
POST /api/public/parse-invoice/ Read the QR tags and UBL data out of a PDF (including PDF/A-3) or a UBL XML file multipart, field file
POST /api/public/export/ Render an invoice as JSON, CSV, UBL XML or PDF, if it passes every ZATCA rule JSON
GET /api/public/health/ Liveness none
GET /api/public/shared/{token}/ View a shared invoice or draft none
GET /api/public/shared/{token}/pdf/ Download the PDF of a shared invoice or draft none
POST /api/public/shared/{token}/otp/ Email a 6-digit approval code to the draft's recipient none
POST /api/public/shared/{token}/approve/ Approve a shared draft JSON

Every JSON body is strict: an unknown field is refused with 400. Every successful tool response carries "branding": "Powered by Clix" and an X-Powered-By: Powered by Clix header.

Calculate VAT

The body is a list of invoice lines in the same vocabulary as the authenticated invoice endpoints. vat_category_code is one of S, Z, E or O and defaults to S. invoiced_quantity defaults to 1.

curl -s -X POST "$CLIX_PUBLIC/api/public/calculate-vat/" \
  -H "Content-Type: application/json" \
  -d '{
    "lines": [
      { "item_name": "Consulting", "invoiced_quantity": "1",
        "item_net_price": "1000.00", "vat_rate": "15", "vat_category_code": "S" }
    ],
    "document_level_allowances": []
  }'

200, verified against the sandbox 2026-09-29:

{
  "invoice_total_line_net_amount": "1000.00",
  "invoice_total_document_level_allowance_amount": "0.00",
  "invoice_total_amount_without_vat": "1000.00",
  "invoice_total_vat_amount": "150.00",
  "invoice_gross_total": "1150.00",
  "vat_breakdown": [
    { "vat_category_code": "S", "vat_rate": "15",
      "vat_category_taxable_amount": "1000.00", "vat_category_tax_amount": "150.00",
      "vat_exemption_reason_code": null, "vat_exemption_reason_text": null }
  ],
  "branding": "Powered by Clix"
}

A document-level allowance must name a (vat_category_code, vat_rate) pair that a line already uses. When allowance_amount is above zero and both reason_code and reason are left out, the calculator uses code 95 (Discount).

Decode a QR code

Send the base64 string a QR reader returns from the code on a ZATCA invoice.

curl -s -X POST "$CLIX_PUBLIC/api/public/scan-qr-code/" \
  -H "Content-Type: application/json" \
  -d '{ "qr_base64": "AQ9UZXN0IFNlbGxlciBMTEMCDzMwMDAwMDAwMDAwMDAwMwMUMjAyNi0wMS0wMVQxMjowMDowMFoEBzExNTAuMDAFBjE1MC4wMA==" }'

200, verified against the sandbox 2026-09-29 with that made-up QR:

{
  "tags": {
    "seller_name": "Test Seller LLC",
    "vat_registration_number": "300000000000003",
    "timestamp": "2026-01-01T12:00:00Z",
    "invoice_total_with_vat": "1150.00",
    "vat_amount": "150.00"
  },
  "branding": "Powered by Clix"
}

A QR code from a simplified invoice also carries invoice_hash, invoice_hash_signature, egs_public_key and zatca_ca_signature_of_egs_csr. The last three come back as base64. A tag number the decoder does not know comes back as unknown_tag_<n>.

Validate, export and parse

validate-zatca and export take a full invoice: ref_num, issue_date, seller and lines are required, and the rest have defaults. export wraps it as {"invoice": {…}, "format": "pdf"}. format is json (the default), csv, ubl-xml or pdf.

  • validate-zatca answers 200 with total_rules, passed, failed, not_applicable and one results row per rule: rule_id, status (passed, failed or not_applicable) and message.
  • export runs the same rules first. If any rule fails it answers 400 with detail, failed_count and violations, and renders nothing. The CSV, XML and PDF formats come back as a file download. The UBL XML is unsigned.
  • parse-invoice answers 200 with qr_found, tags, invoice and warning. A scanned, image-only PDF gives qr_found: false; decode its QR code with scan-qr-code instead.

The full request schema, with an example for each endpoint, is in the Clix OpenAPI document at /api/openapi.json, under the Public API tag.

Errors

Status When Body
400 A field is missing, has the wrong type, or is unknown {"message": ["qr_base64: Field required"]}
400 The tool refused the input {"detail": "qr_base64 is not valid base64: Only base64 data is allowed"}
400 An upload is over the size cap {"detail": "File too large (… bytes). Maximum accepted: 5242880 bytes."}
404 A share token is unknown, expired or revoked {"message": "This share link is no longer available."}
429 A rate limit is reached see Limits
503 The rate limiter could not answer {"detail": "Rate-limit service temporarily unavailable."}

Schema errors use message with an array, one entry per field, as path: problem. Errors the tool raises itself use detail with a string. The first two rows and the 404 were checked against the sandbox 2026-09-29.

Limits

Separate from your API key's limits:

Caller Allowance
Anonymous 10 a day, per IP address
Signed in, Free plan or a card trial 100 a month, per organisation
Signed in, paid plan Not metered

"Signed in" means a Clix web session, sent as the session cookie or as a Bearer token. A clix_… API key is not a session: a call that carries one counts as anonymous.

The daily allowance covers every path under /api/public/, the shared-link endpoints included. A limit returns 429:

{ "detail": "Anonymous daily limit reached (11/10). Sign in for a higher limit, or wait until tomorrow.", "window": "daily", "limit": 10, "current": 11 }

window is daily, monthly or hourly. A daily 429 carries Retry-After: 86400, an hourly one Retry-After: 3600, and a monthly one none. A 503 means the limiter itself could not answer; retry shortly.

Uploads to parse-invoice are capped at 5 MiB.

A seller makes a share link in the app. The public view of it is GET /api/public/shared/{token}/. A link expires 30 days after it is made. Each link answers at most 60 requests an hour, counted across all four shared-link endpoints; the next one gets 429 with window: "hourly".

A shared draft can be approved without an account. The recipient calls POST /api/public/shared/{token}/otp/ to get a 6-digit code by email, valid for 10 minutes. Asking again within those 10 minutes sends nothing new. Then they call POST /api/public/shared/{token}/approve/ with {"name": "…", "code": "123456"}. Approving an already approved draft returns the first approval unchanged.

Anyone with the link can view that one invoice. Treat a share link as a bearer credential for a single document; do not put it somewhere indexable.

When to use these

Use the public tools for one-off checks and for features you offer your own users: scanning a supplier QR code in your app, validating a file before you submit it.

Do not build your integration on them. They are rate-limited for anonymous use, carry no organisation context, and cannot issue anything. Issuing invoices goes through the authenticated API; see Quickstart.