This guide is aimed at developers and technical integration teams building a real integration with the Fatoora platform as part of e-invoicing in Saudi Arabia. It has one specific goal: to put the catalog of the Fatoora platform’s endpoints in a single place. The relative path of each endpoint, its request method (HTTP Method), the headers it requires, and the shape of the expected response.
This guide does not explain the conceptual story of integrating with the Zakat, Tax and Customs Authority (ZATCA) as a business step; that is a story with its own dedicated guide. Here we assume you are a developer writing the code, and you want to know exactly which address to call, which header to send with it, and which JSON shape to expect in return. For the broader picture of the full API integration, go back to the Fatoora platform API integration guide, and this guide picks up where that one ends: from the endpoint table itself.
The Fatoora platform works with four groups of APIs that cover the entire integration lifecycle: Compliance, Onboarding / Production CSID, Clearance, and Reporting. We go through each group with its endpoint, its method, its headers, and the shape of its response, then close with a quick reference table that brings them all together.
General principles before any request
Before you call any endpoint, fix in your mind four principles that apply to all of the platform’s APIs without exception. Ignoring any one of them means the request is rejected at the gateway level before it even reaches the business logic.
First, all endpoints follow the REST pattern over HTTPS and exchange their metadata in JSON format. The invoice itself is carried as XML compliant with UBL 2.1, but it is sent encoded in Base64 inside a JSON field named invoice. Any request that does not adhere to this envelope is rejected outright.
Second, the APIs split functionally into two groups. The device digital-identity setup group (Compliance and Onboarding) is called once when each device is onboarded. The daily invoice-operation group (Clearance and Reporting) is called with every invoice. This split affects your system design: the setup APIs run in a one-time flow, whereas the two operational APIs must be fast and non-blocking for the user experience, especially at high-frequency points of sale.
Third, the two environments are completely separate. There is a test environment (Sandbox) for compliance and development, and a Production environment for real invoices. Each environment has a different base URL (Base URL) and a different Cryptographic Stamp Identifier (CSID). Do not mix them: compliance runs on the Compliance CSID, while real issuance runs on the Production CSID.
Fourth, the four common headers. Every request mostly carries Accept-Version to pin the API version, andContent-Type: application/json, andAccept-Language to set the language of error messages, in addition to the authentication header specific to each endpoint. We detail authentication in the section dedicated to it below.
The Accept-Version header deserves special attention. The platform evolves its APIs over time, and pinning the version in the header protects your integration from unexpected changes. If you neglect this header, the API behavior may change when the platform is upgraded, breaking your system without you touching a single line. Make the version value an easily updatable configuration variable, not a value buried in the code.
Also, remember that the UBL 2.1 document requires mandatory Arabic content by the Authority’s requirements, with the option to add English alongside it. This is not a matter for the Accept-Language header, which concerns the language of error messages only, but a matter for the invoice content itself inside the XML. Confuse the two and you will get language warnings that have nothing to do with the API messages.
Base URLs
All the paths mentioned in this guide are relative, and they are appended after the base URL of the environment you are working on. Fix the base URL in your system settings and do not embed it directly in the code, so that switching between test and production is easy.
Note that the same relative path (such as /invoices/clearance/single) is called under both base URLs; the difference between test and production is the base URL and the identifier used, not the path. Document the base URL as a single environment variable and change it when moving to production.
Request and response encoding
Every request carries a body in JSON format, even if its core field is an encoded XML document. This separation between the transport layer and the document layer is intentional: JSON is the transport envelope that carries the metadata such as the hash and the unique identifier, while XML is the official tax document that the Authority inspects and signs.
In practice, this means you build the invoice document in UBL 2.1 format first, then compute its hash (Hash) with the SHA-256 algorithm, then encode the entire document with Base64, and place the result in the invoice field. You place the hash in the invoiceHash field and the unique identifier in the uuid field. These three fields are the backbone of every operational request.
In return, the response is always JSON. From it you read the status code at the transport level, then dive into the body to read validationResults , which carries the actual result of the validation. Relying on the status code alone without reading the body is a common cause of silent integration errors.
The unique identifier (UUID) and the invoice counter (ICV)
Before you call any operational endpoint, you need to understand two fields that link your invoices into a coherent chain. The unique identifier (UUID) is a random value unique to each invoice, which you generate and which never repeats. The invoice counter (ICV) is a sequential number that increments across all your invoices without gaps.
In addition to them, every invoice carries the hash of the previous invoice (Previous Invoice Hash), forming a chain that makes any later tampering detectable. If the chain breaks or the counter repeats, the Authority returns an error and rejects the invoice. Design your storage so that you keep the hash of the last invoice for each device and use it as the input for the next invoice.
| Criterion | One-time setup | Recurring operation |
|---|---|---|
| APIs | Compliance and Onboarding | Clearance and Reporting |
| Frequency | Once per unit | Per invoice |
| Purpose | Setup and certificates | Invoice issuance |
Authentication on every endpoint
All operational endpoints on the Fatoora platform are protected by Basic Authentication over HTTPS. There is no separate access-token endpoint (no OAuth token endpoint in the traditional sense). Instead, you build the Authorization header from the Cryptographic Stamp Identifier (CSID) and its Secret, which you obtained from the onboarding API.
The practical form: you combine the identifier and the secret with a colon between them, then encode the result with Base64, and place it after the word Basic. In the test environment you use the Compliance CSID, and in production you use the Production CSID. This header recurs in the Compliance, Clearance, and Reporting requests, so we show it once here and refer back to it in each section.
The full detail of the authentication flow and handling identifier expiry is in the Fatoora platform API integration guide. Here it is enough to know that the absence of the correct Authorization header always returns the status 401 Unauthorized before any other check.
First: the Compliance API
The Compliance API is your first stop. Its purpose is to prove that your system generates invoices compliant with the phase-two specification before it is allowed to operate in production. It is called in the test environment only, and on the Compliance CSID.
Requesting the Compliance CSID
You start by sending a certificate signing request (CSR) with the one-time password (OTP) that the taxpayer generates from the Fatoora portal. The platform returns the Compliance CSID, which you use in the rest of the compliance checks.
Checking compliance invoices
After obtaining the Compliance CSID, you send sample invoices (standard and simplified, with their credit and debit notes) to the compliance-check endpoint. Every sample must pass successfully before you qualify to request the Production CSID.
When every sample returns the status PASS, your system is ready to move on to requesting the Production CSID. Any sample that returns a non-empty errorMessages halts the transition until you fix the cause.
In the compliance phase, the platform expects you to send a full set of samples covering every document type your device will issue: the standard tax invoice, the simplified invoice, the credit note, and the debit note for each type. The goal is to prove that your system generates every type in a correct format before it enters production. If your device will only issue B2B invoices, you will not need to submit B2C samples, because the platform measures the device capabilities declared in the certificate request.
Pay attention to distinguishing warning messages (Warnings) from error messages (Errors) at this stage. A warning does not halt compliance, but it reveals a weakness in your data that will recur in production, so address it now. An error halts compliance entirely until you fix it. Read both arrays in every response, because an empty errorMessages array alongside a populated warningMessages means a conditional success worth reviewing.
Second: the Onboarding / Production CSID API
After all compliance checks succeed, you request the Production CSID. This identifier is the one that signs every real invoice afterward, and each device has its own Production CSID. This endpoint is called on the Compliance CSID you obtained in the previous stage.
You later use the same endpoint with a PATCH method to renew an expired identifier, and with a GET method to query the identifier’s status. Store the identifier and its secret in encrypted form, as they are the key to signing your real invoices and cannot be retrieved if lost; instead, a new identifier must be issued.
Generate the CSR and request an OTP
Receive the Compliance CSID certificate
Check the sample invoices until they pass
Receive the Production CSID certificate
Third: the Clearance API
Clearance is the path for the business-directed tax invoice (B2B). The mechanism: you send the invoice to the Authority first, it validates it, signs it, and returns a cleared XML copy to you, and only then are you entitled to hand it to the buyer. The invoice is not considered valid before clearance.
This endpoint is called on the Production CSID, and with every tax invoice individually. Note the Clearance-Status header that asks the platform to perform full clearance.
The essential field in the response is clearedInvoice: this is the cleared copy that carries the Authority’s stamp, and it is what you must hand to the buyer, not the copy you sent. As for the status NOT_CLEARED with a populated errorMessages , it means the invoice was rejected, and you must not hand it to the buyer until you fix the error and re-clear it.
Operationally, clearance is a synchronous operation (Synchronous): your request stays pending until the Authority responds with the result. This means the endpoint’s response time enters directly into the time to complete the sale, so make sure to set a reasonable timeout (Timeout) and to ensure the wait does not fully block the user interface. In B2B invoices this is usually acceptable because their frequency is lower than that of point-of-sale invoices.
Note also that clearance may succeed with warnings. In this case the platform returns the status CLEARED array alongside a populated warningMessages, and the cleared copy remains valid to hand over. A warning does not mean rejection, but it points to data that is better corrected in the coming invoices before it turns into errors after the Authority tightens its rules.
For the full detail of the clearance path, its states, and its common errors, see the Clearance in e-invoicing.
Fourth: the Reporting API
Reporting is the path for the consumer-directed simplified tax invoice (B2C). The mechanism differs from clearance: you issue the invoice and hand it to the buyer immediately, then you have up to 24 hours to report it to the Authority. There is no waiting for clearance before delivery.
This endpoint is called on the Production CSID, and with every simplified invoice. You use the Clearance-Status header with a value of zero to indicate that the operation is reporting and not clearance.
The essential difference in the response is that the Reporting API does not return a clearedInvoice copy to you, because the invoice was already delivered before reporting. What matters here is the status REPORTED that confirms the Authority received it. The status may come with a populated warningMessages without the reporting being rejected, since warnings do not stop delivery but do call for handling.
The 24-hour window gives you important design flexibility. You are not obliged to report every invoice the moment it is issued; instead, you can batch invoices and report them in groups within the deadline, or report them in the background without the customer waiting at the point of sale. This separation between delivery and reporting makes the B2C path faster and more suitable for high-frequency environments such as stores and restaurants.
But do not squander the deadline. If reporting fails within the 24 hours due to a transient error, you need a reliable retry mechanism that ensures the invoice arrives before the window closes. The simplest safe design: a queue (Queue) for not-yet-reported invoices, with periodic retries and an alert when any invoice approaches the deadline limit without successful reporting.
For the full detail of the reporting path, the 24-hour window, and warning handling, see the Reporting in e-invoicing.
The essential difference between Clearance and Reporting at the API level
Many integration errors stem from confusing the two endpoints. The three practical differences you must build into the code are clear.
The first difference is in the endpoint itself: Clearance on /invoices/clearance/single for the B2B tax invoice, and Reporting on /invoices/reporting/single for the B2C simplified invoice. Do not call the reporting endpoint for a tax invoice, because the type determines the endpoint.
The second difference is in timing: clearance precedes delivery (the Authority first, then the buyer), and reporting follows delivery (the buyer first, then the Authority within 24 hours). This difference completely changes the flow design at the point of sale.
The third difference is in the response: clearance returns the clearedInvoice that must be handed over, and reporting suffices with a REPORTED status without a cleared copy. Rely on the correct field in each path, because relying on clearedInvoice in the reporting path is a common error that breaks the integration.
| Criterion | Clearance (B2B) | Reporting (B2C) |
|---|---|---|
| Endpoint | /invoices/clearance/single | /invoices/reporting/single |
| Clearance-Status | 1 | 0 |
| Timing | Before delivery | Within 24 hours |
The status codes (Status Codes) you deal with
All endpoints return standard HTTP status codes, and it matters that you handle them programmatically rather than ignore them. The most notable ones you will encounter:
- 200 OK: The request succeeded and was processed. Inspect the
validationResults.statusfield inside the body, as it may bePASSor carry warnings. - 202 Accepted: The request was accepted with warnings (Warnings) that do not halt the operation but are worth reviewing and handling.
- 303 See Other: The invoice was rejected in the clearance path. Do not hand it to the buyer; read the
errorMessagesand correct it, then resend. - 400 Bad Request: The request itself is structurally invalid, such as broken JSON, a missing field, or XML not compliant with UBL 2.1.
- 401 Unauthorized: The authentication header is missing, incorrect, or the identifier is expired.
- 500 Internal Server Error: An error on the platform side. Retry later with a graduated retry mechanism (Exponential backoff).
The practical rule: do not settle for the HTTP code alone. An invoice may return 200 at the transport level while carrying errorMessages inside the body. Always read validationResults before considering the invoice successful.
Retries and handling transient errors
The operational APIs are called with every sale, so the chance of encountering a transient network error or gateway slowness is real. Design your communication layer to handle these cases without breaking the user experience or duplicating the invoice.
For errors of the 5xx category (an error on the platform side) and for timeout cases (Timeout), use a graduated retry (Exponential backoff) with increasing intervals between attempts. Do not retry immediately in a tight loop, as that increases pressure on the gateway and prolongs the outage.
For errors of the 4xx (an error on your side, such as missing data or a wrong identifier), do not retry automatically, because the request itself is faulty and resending it without correction will reproduce the same error. Log the error, surface it to the user or the support team, and fix the source.
The most important principle in the operational path: do not issue a new unique identifier (UUID) when retrying an invoice that failed due to a transient error. Resend the same invoice with its original identifier, because issuing a new identifier for an invoice that already reached the Authority may produce two duplicate invoices in its records. Tie the send status to the identifier, and do not generate a new identifier except for a genuinely new invoice.
Test-to-production migration checklist
Before you switch your system from the test environment to production, review this checklist. Every item on it is a common cause of the first real invoice failing.
- You switched the base URL: make sure the environment variable points to the production URL, not the test URL.
- You use the Production CSID: The
Authorizationin production it is built from the Production CSID and its secret, not from the Compliance CSID. - Each device has its own identifier: if you have more than one branch or point of sale, verify that each device signs with its own Production CSID.
- The type routing is correct: the tax invoice goes to the clearance path, and the simplified one to the reporting path. A routing error breaks the integration silently.
- The hash chain is continuous: the invoice counter (ICV) increments without gaps, and the previous invoice hash is stored for each device.
- Reading the full response: your system reads
validationResultsinside the body, not the HTTP code alone.
Passing this checklist successfully means your API layer is ready. What remains after it is operational tuning: monitoring the error rate, alerts for identifier expiry, and an audit log of every request and response.
A quick reference table for all the endpoints
This table brings the endpoint catalog together into a single view you can return to while building. All paths are relative, appended after the environment’s base URL.
| API | Relative path | Method | Identifier used | Purpose |
|---|---|---|---|---|
| Compliance | /compliance |
POST | CSR + OTP | Requesting the Compliance CSID |
| Compliance | /compliance/invoices |
POST | Compliance CSID | Check sample invoices |
| Onboarding | /production/csids |
POST | Compliance CSID | Request the Production CSID |
| Clearance | /invoices/clearance/single |
POST | Production CSID | Clear a B2B tax invoice |
| Reporting | /invoices/reporting/single |
POST | Production CSID | Report a B2C simplified invoice |
How Qoyod helps you integrate with the Fatoora platform
You may build all this integration yourself, or rely on an accounting system that handles it on your behalf. Qoyod is an accounting software compliant with phase two of e-invoicing, and it manages this entire API layer without you writing a single line of code.
- Managing the Cryptographic Stamp Identifier (CSID) automatically: Qoyod handles the flow of requesting the Compliance CSID then the Production CSID for each device, stores them securely, and renews them when needed.
- Generating UBL 2.1-compliant XML: Qoyod builds the invoice document in the required format with the QR code, the cryptographic stamp, and the hash chain, so you do not hand-craft XML.
- Both the clearance and reporting paths together: Qoyod routes the tax invoice to the Clearance path and the simplified invoice to the Reporting path automatically according to its type.
- Validating the identifier format before sending: Qoyod validates the format of the identification numbers (such as the commercial registration and the tax number) at entry, catching one of the most common causes of the Authority’s warnings before the request reaches it.
- A PDF/A-3 invoice with embedded XML: Qoyod issues a single PDF file that embeds the tax XML document within it in line with the phase-two presentation requirement, so you hand the buyer one file instead of two.
An important note: Qoyod transmits what you issue to the Authority, and the Authority is the party that validates the invoice and clears it. Qoyod does not filter tax business-rule errors on the Authority’s behalf before the invoice reaches it.
Frequently asked questions about the Fatoora platform endpoints
Do all endpoints use the POST method?
The core issuance and operational endpoints (Compliance, Clearance, Reporting, and the Production CSID request) use POST. The Production CSID endpoint also accepts PATCH for renewal andGET for querying the status.
What is the difference between the Compliance CSID and the Production CSID?
The Compliance CSID works in the test environment to check your system’s compliance only. The Production CSID is the one that signs every real invoice. Each device has its own Production CSID.
Which header distinguishes clearance from reporting?
The Clearance-Status: its value is 1 in the Clearance path, and0 in the Reporting path. In addition to the difference in the path itself between the two endpoints.
Why is the invoice sent in Base64 format?
Because the invoice document is in XML compliant with UBL 2.1, and it is carried encoded in Base64 inside the invoice field in the JSON body. This separates the transport envelope (JSON) from the official tax document (XML).
What do I hand to the buyer in the clearance path?
You hand over the clearedInvoice field from the response, which is the copy cleared with the Authority’s stamp, and not the copy you sent.
Do I need to build this integration myself?
No. An accounting system compliant with phase two such as Qoyod manages all these endpoints on your behalf, from managing the identifiers to generating XML and routing every invoice to its correct path.
Connect your invoices to the Fatoora platform without writing a single line of code
Qoyod manages the Cryptographic Stamp Identifier, XML generation, and both the clearance and reporting paths on your behalf. Try phase-two-compliant invoicing straight from your account.