# Matheel API > Stateless ZATCA (Saudi Arabia) Phase 2 e-invoicing API. One request signs an invoice (UBL 2.1 XML, SHA-256 hash, XAdES/ECDSA secp256k1 signature, TLV QR), files it with ZATCA (clearance for standard/B2B, reporting for simplified/B2C) and returns ZATCA's answer plus the legal XML. Matheel stores nothing: the caller keeps the key, the invoice chain and the archive. Hosted in Riyadh. Base URL: https://matheel.app Human docs (Arabic): https://matheel.app/docs ## Authentication `Authorization: Bearer `. Keys are issued on subscription (free trial key available). Unknown or revoked key: 401 `{"error":"invalid_api_key"}`. ## POST /api/v1/invoices JSON body: - `environment` (required): `sandbox` (ZATCA developer portal, free) | `simulation` | `production` (billed). The CSID must come from the same environment. - `seller` (required unless instant sandbox): `vat_number` (15 digits), `legal_name_ar`, optional `legal_name_en`, `crn`, `address` {`street`, `building_number` (4 digits), `district`, `city`, `postal_code` (5 digits)}. - `csid` (required unless instant sandbox): `private_key` (base64 DER, secp256k1), `binary_security_token` (as ZATCA returned it), `secret`. - `invoice` (required): `type` (`simplified`|`standard`), `document_type` (`invoice`|`credit_note`|`debit_note`, default invoice), `counter` (ICV, positive integer, +1 per invoice per device), `uuid`, `number`, `previous_invoice_hash` (PIH, from the previous accepted invoice; omit on the first), `issued_at` (ISO 8601 with offset), `currency` (default SAR), `line_items` [{`name`, `quantity`, `price`, optional `tax_category` S|Z|E|O, `tax_exemption_reason_code`}], `subtotal`, `vat_amount`, `total` (decimal strings), `buyer` (for standard invoices: `name`, `vat_number`, address fields), `original_invoice_number` and `note` (credit/debit notes). - `mode` (optional): `clear` | `report`; default follows `type`. ### Instant sandbox `"environment": "sandbox"` with no `seller` and no `csid` files the invoice as Matheel's shared test taxpayer on ZATCA's developer portal. Real ZATCA answer, no certificate needed. Limited to 100 per hour per key (429 `sandbox_rate_limited` past it). ### Response 200 ```json {"status":"cleared|reported","zatca_uuid":"…","invoice_hash":"…","previous_invoice_hash":"…","qr":"","invoice_xml":"","warnings":[],"errors":[]} ``` Store `invoice_hash` as the next invoice's `previous_invoice_hash`. Archive `invoice_xml`; print `qr`. ### Errors - 401 `invalid_api_key` - 402 `quota_exhausted`: production invoices for this billing month are used up. - 422 `{"error":{"code":"missing_fields","fields":[…]}}` | `invalid` | `validation_failed` | `filing_failed` (network to ZATCA; retry with the same counter and uuid), or 422 with ZATCA's `errors` when ZATCA rejected the invoice. - 429 `sandbox_rate_limited` - 503 `billing_unavailable`: retry later with the same counter and uuid. ## Invoice chain rules - One chain and one CSID per device (EGS). - Advance the counter and the stored hash only after a 200 with status cleared/reported. - A rejected invoice does not consume a counter value: fix it and resend with the same counter. - Resending the same `uuid` is billed once. ## Getting a CSID (per taxpayer, per device) `POST /api/v1/onboarding` does the whole flow in one call: `{"environment": "sandbox"|"simulation"|"production", "seller": {...same fields as filing...}, "otp": "…"}` (no `otp` needed on sandbox). Response: `{"csid": {"private_key", "binary_security_token", "secret"}, "compliance_checks": 6}`, ready to send as `csid` when filing. Nothing is stored; 10 per hour per key. Errors: 422 `onboarding_failed` with the failing `step` (csr | compliance_csid | compliance_checks | production_csid). Manually: generate a CSR with a secp256k1 key; the taxpayer gets an OTP from their Fatoora portal account; exchange CSR + OTP for a compliance CSID; pass the six compliance checks; request the production CSID. On the developer portal the OTP is the published test value. ## Usage and billing Accepted production invoices count against the monthly plan. Sandbox and simulation are free. Rejected invoices are not counted.