This guide is written for developers and technical integration teams who connect their systems to the Fatoora platform as part of e-invoicing in Saudi Arabia. Its single focus: how to authenticate your requests against the Fatoora platform APIs (Fatoora API). Every request you send to ZATCA must carry a trusted identity, otherwise it is rejected at the gateway before it ever reaches business logic.
Authentication here is not a login with a username and password, nor is it OAuth with short-lived access tokens. The Fatoora invoicing APIs use Basic Authentication built on the Cryptographic Stamp Identifier certificate (CSID) and its accompanying secret. In this guide you will learn where this data comes from, how to build it into the Authorization header, when to move from the compliance certificate to the production certificate, and how to secure this sensitive data.
We assume you have already read the conceptual story of connecting your establishment to ZATCA. If not, go back to the onboarding with ZATCA (Zakat, Tax and Customs Authority) guide, then return here. This guide starts where that one ends: from the technical layer of authentication itself.
One pivotal point we put in front of you from the start so you don’t waste time in the wrong direction: the Fatoora invoicing APIs do not use OAuth. Many developers assume there is an endpoint to request an access token (Access Token), because that is the dominant pattern in most modern APIs. Here it is different. Authentication relies on the CSID certificate directly in every request through a Basic header. Keep this fact in mind throughout your reading.
What does authentication mean in the Fatoora API?
Authentication is proving that the device knocking at the gateway door is a known device registered with ZATCA. Every HTTP request that reaches the Fatoora platform first passes through an identity verification layer. If the request does not carry valid credentials, the gateway rejects it with status code 401 without even looking at the invoice content.
It helps to separate three concepts in your mind that many people confuse. Authentication answers the question “who are you?” Authorization answers the question “what are you allowed to do?” Document signing answers the question “is this invoice intact and unaltered?” This guide is about authentication, that is, proving identity at the transport layer (Transport Layer).
Separating authentication from invoice signing matters in practice. The CSID certificate plays two completely separate roles. The first role is authenticating the request at the gateway, and here the certificate and its secret are sent in the Authorization header to prove that the sender is a registered device. The second role is signing the invoice, and here the private key associated with the certificate is used to create the cryptographic stamp inside the XML. The first role is at the transport layer, the second at the document layer. Do not mix them.
This means the private key is never sent in the authentication header. What is sent in the header is the certificate identifier (which carries the public key) together with its accompanying secret. The private key stays local in your system, and you use it only to sign the invoice hash. Securing this key is your own responsibility, and any leak of it means another party can impersonate your device.
Why Basic and not OAuth?
The Fatoora platform chooses Basic Authentication because it is built on a stable device certificate rather than a changing user session. In the OAuth model, you request a short-lived access token, use it until it expires, then renew it. This pattern suits applications where human users log in. But invoice issuance is an automated process between a device and a government system, so a permanent device identity carried in a digital certificate is more appropriate for it.
In practice, this simplifies your system design. You do not need to manage the lifecycle of an access token, nor deal with expiry in the middle of sending an invoice, nor store a refresh token. You carry the same credentials in every request. In exchange for this simplicity, the burden falls on you to secure the credentials well, because they do not expire automatically the way a short-lived access token does.
For those who need to understand the OAuth pattern in detail, whether to compare it or because another part of their system uses it, we have dedicated a separate guide titled “OAuth”. But for invoices themselves, rely on Basic with CSID as this guide explains.
Where do the credentials come from? Compliance CSID versus production CSID
Simulation: CCSID certificate via OTP
Passing compliance
Production: PCSID certificate
The full picture of the credential source
The credentials you place in the Authorization header are not something you invent; you receive them from ZATCA in a step prior to sending any invoice. They pass through two stages, each with its own separate certificate and secret: the compliance stage in the simulation environment, then the production stage in the live environment.
In the first stage, you request a compliance certificate (Compliance CSID) through the ZATCA portal using a one-time password (OTP) that you obtain from the Fatoora platform. The compliance API responds with a certificate identifier and an accompanying secret. This data is for testing only, that is, to run a series of trial invoices that prove your system builds and signs the invoice correctly. The details of this certificate are in the Compliance CSID certificate.
After passing the compliance tests successfully, you request a production certificate (Production CSID, abbreviated PCSID). This is the certificate you use to run real invoices with tax effect. The production API responds with a completely new certificate identifier and secret. What concerns the production environment is explained in the PCSID certificate for production.
The takeaway that must stick in your system design: you have two pairs of credentials, one pair for simulation and one pair for production, and each pair is a certificate identifier with a secret. Never mix them. Sending an invoice with production credentials to the simulation environment means it was not actually approved for tax purposes, and you may think everything works while what you sent has no effect. To understand the difference between the two environments at the infrastructure level, see the sandbox environment (Sandbox) andproduction environment (Production).
The role of the CSID certificate itself
The CSID certificate is the cryptographic stamp identifier that ZATCA issues for each invoicing device. It carries the device’s public key and is linked to the establishment’s tax number. The certificate is what proves to the gateway that the device is approved, and it is the same one used to build the authentication header. This certificate and how to generate it are explained in the CSID certificate: the cryptographic stamp identifier.
Pay close attention to the certificate’s naming: CSID, not CCSI or any other order of letters. The abbreviation means Cryptographic Stamp Identifier. Misspelling the abbreviation is common and may confuse you when searching ZATCA’s documentation.
Building the Authorization header step by step
Basic Authentication in the HTTP protocol is simple at its core: you take the username and password, join them with a colon, encode the result with Base64, then place it in the Authorization header prefixed with the word Basic. What is new here is that the “username” is the CSID certificate identifier, and the “password” is its accompanying secret.
The general format is as follows:
That is, you take the certificate identifier, add a colon to it, then the secret, then encode the whole string with Base64. Note that the colon is part of the input before encoding, not after. If you encode first and then add the colon, the result is an invalid request that the server rejects with code 401.
To clarify with a concrete example, assume your certificate identifier is the string TUlJ... and the secret is aBcD1234. You build the intermediate string in this form before encoding:
then encode it with Base64 and place it in the header. The final result looks like this inside an actual request:
A subtle detail many get wrong: the certificate identifier value itself is already Base64-encoded because it is binary certificate data. This means you are encoding something already encoded, that is, two layers of Base64. The first layer is for the certificate itself as you received it from ZATCA, and the second layer is for the full “identifier:secret” string as Basic Authentication requires. To understand the encoding mechanism itself in depth, see the Base64 encoding in e-invoicing.
Practical examples of building the header
In most programming languages, building the header is one line or two. Here is an example in Python that shows the full step:
and the same logic in JavaScript on the Node.js runtime:
Note that the code does not request an access token from any endpoint before sending. You build the header locally from the credentials and send the request directly. This is the practical difference between Basic and OAuth in your system design.
The Accept-Version header and why it is mandatory
| Header | Purpose |
|---|---|
| Authorization | Basic authentication with the CSID certificate |
| Accept-Version | Specifies the API version (mandatory) |
| Content-Type | Content type application/json |
| Accept-Language | Message language (optional) |
The role of the Accept-Version header
Alongside the authentication header, the Fatoora platform APIs require an Accept-Version header in every request. Its value specifies the API version your system understands. The current version for Phase Two is V2. If you omit this header, the gateway may reject the request or handle it with a default version that does not match your expectations.
The purpose of this header is to protect your integration from future changes. When ZATCA develops its APIs and releases a new version, your system stays bound to the version you built it on as long as you explicitly send V2. This gives you a chance to test the newer version before moving to it, instead of having responses change under your feet suddenly.
Make the Accept-Version header a fixed part of your HTTP client setup in your system, exactly as you fix Content-Type. Do not leave it to each call individually, because forgetting it at a single endpoint is a common cause of errors that are hard to trace later.
The other accompanying headers
Besides authentication and versioning, the request carries standard headers. Content-Type with the value application/json tells the gateway that your payload is in JSON format. Accept-Language with the value ar or en specifies the language of the error messages in the response, and it is a useful header for Arabic support teams. Some endpoints add their own specific headers, such as Clearance-Status in clearance requests, but authentication and versioning are fixed in every request.
The request path from compliance to production at the authentication level
The credentials you use change according to the stage your integration is passing through. In the compliance stage, you build the Authorization header from the compliance certificate and its secret, and you send the requests to the sandbox environment address. The goal is to pass the series of trial invoices that verify the integrity of building and signing your invoice.
After compliance succeeds, you replace the credentials with the production certificate and its secret, and you switch the destination of the requests to the production environment address. The header structure itself has not changed, as it is Basic with an identifier and a secret, but the values are completely different and the destination is different. This is the essence of moving from testing to live operation at the authentication level.
The most common operational error here is combining the credentials of one environment with the address of the other environment. For example, sending a production certificate to the sandbox address, or vice versa. The result is an authentication rejection with code 401 even though the data is “correct” in itself, because it is correct for one environment and not for the environment you addressed. Make the address destination and the credentials paired in a single configuration to avoid this confusion.
The credential lifecycle after production
Production credentials are not eternal. The CSID certificate has a validity period, and as it nears expiry you renew it through ZATCA and obtain a new identifier and secret. Design your system to read the credentials from a central, updatable source, rather than hardcoding them in the code. This way, renewal is updating a value in one place, not redeploying the entire system.
Monitor the 401 status codes in your logs. A sudden rise in this code on the production environment is a likely indicator of certificate expiry or of the secret changing without being updated in your system. Catching this early saves you an outage in invoice issuance that could extend for hours before you discover the cause.
Securing the credentials
Do not put credentials in the source code
Use an encrypted secrets store
Do not log the Authorization header
Separate the sandbox environment from production
Reissue the certificate on suspicion of leakage
Why securing here matters more than with OAuth
In the OAuth model, the access token is short-lived, so even if it leaks its exploitation window is limited. But in Basic Authentication, the credentials are fixed and do not expire automatically before the certificate is renewed. This makes leaking them more dangerous, because whoever obtains them can impersonate your device’s identity until you notice and reissue the certificate. That is why strict security is not optional.
Rule one: do not write the certificate identifier or the secret inside the source code, and do not push them to the code repository. This is the most common source of leakage. Store them in an encrypted secrets store such as cloud secrets management services, or in protected environment variables on the server, and read them only at runtime.
Rule two: do not log the Authorization header in the system logs. Many tracing tools log full headers when debugging, so the credentials leak into log files unintentionally. Add an explicit redaction rule for this header in your logging configuration.
Rule three: separate the storage of the simulation credentials from production. Put them in two different keys in the secrets store, not in the same value swapped by a variable. This separation prevents the error of sending production data to simulation, and makes it easier to audit who accesses the sensitive production data.
Rule four: on any suspicion of leakage, reissue the certificate immediately from ZATCA. Do not wait for confirmation of the leak. Reissuance invalidates the old data and grants you a new pair, so even if the old secret is in another party’s hands, it becomes worthless. This action is much cheaper than dealing with invoices issued in your name without your knowledge.
Authentication is unified across the four APIs
The Fatoora platform deals with four APIs that cover the onboarding cycle: the Compliance API, the issuance and production API (Onboarding), the Clearance API, and the Reporting API. The reassuring news for the developer is that the authentication mechanism is the same across all of them: a Basic header built on the CSID certificate and its secret. You learn the build once and apply it at every endpoint.
What differs between the APIs is not the authentication, but which credential pair you use. The compliance and issuance APIs are called once when provisioning the device, and they use the compliance certificate first, then produce the production certificate. The clearance and reporting APIs are called with every invoice in daily operation, and they use the production certificate. The details of the two operational APIs are in the Clearance (Clearance) andReporting (Reporting).
This unification simplifies the design of the HTTP client in your system. You build a single function that takes the credential pair and produces the Authorization header, then call it before any request to any API. Do not repeat the authentication logic at every endpoint; instead, gather it in one place that reads the credentials from the secrets store and builds the header. This pattern makes renewing the certificate later a change in one location rather than in every call.
The shape of the request and response in an authenticated request
To cement the picture, here is the shape of a typical response to an invoice clearance request whose authentication succeeded. Note that the gateway responds in JSON format carrying the clearance status and follow-up identifiers:
When authentication fails before the request reaches business logic, the response is completely different. You will not find a clearance status or validation results in it, but rather a 401 code and a brief message concerning identity:
The difference between the two responses points you quickly to the layer of the problem. A response carrying validationResults means your authentication succeeded and the problem is in the invoice structure. A 401 response means the problem is prior to all of that, in your credentials or in building the header. Always start diagnosis from the status code before you dive into the response body.
Common authentication errors and how to read them
When your request fails at the authentication level, the server responds with a status code that points you to the cause. The most frequent is code 401 (Unauthorized), which means the gateway did not accept your credentials. Before you suspect the server, review the common causes in order.
The first cause is an error in building the string before encoding, such as forgetting the colon between the identifier and the secret, or adding an extra space. The second cause is mixing environments, that is, sending simulation credentials to the production address or vice versa. The third cause is using an expired certificate. The fourth cause is forgetting to encode the string with Base64 or encoding it twice by mistake.
Another code you may encounter is 403 (Forbidden), which means your identity was proven but is not entitled to perform this action, and this is a matter of authorization, not authentication. As for code 400 (Bad Request), it usually concerns the payload structure rather than authentication, but forgetting the Accept-Version header may sometimes produce it. Always read the response body, as it carries a message that explains the cause precisely, especially when you set Accept-Language to ar.
How Qoyod helps you with authentication with the Fatoora platform
Everything we explained in this guide, from building the Basic header to managing the compliance and production certificates and securing the secret, is managed by Qoyod’s e-invoicing system on your behalf. You do not write a line of Base64 nor manage the Authorization header manually; instead, you issue your invoice from the Qoyod interface and the system handles authentication with ZATCA in the background.
Specifically, Qoyod does the following at the authentication and onboarding level:
- It manages the cryptographic stamp certificate (CSID) automatically, from requesting the compliance certificate via the verification code, to generating the production certificate after passing compliance.
- It builds the Authorization header in Basic format for every request automatically, so you do not deal with Base64 encoding nor with the secret directly.
- It separates the sandbox environment from the production environment internally, so the error of mixing credentials between the two environments does not occur.
- It performs instant clearance for B2B invoices and reporting within 24 hours for B2C invoices, with cryptographic signing of the invoice and embedding of the QR code.
- It stores the invoice hash chain to verify compliance, and manages certificate renewal as it nears expiry.
This way, development teams focus on the business logic in your system, while the authentication layer with ZATCA remains Qoyod’s responsibility. This eliminates the need to build and maintain a direct integration with the Fatoora platform from scratch.
Let Qoyod handle authentication with the Fatoora platform
Don’t build the authentication integration from scratch. Issue your Phase Two compliant e-invoices, and let Qoyod manage the certificate, the headers, and the integration with ZATCA in the background.
Frequently asked questions about authentication in the Fatoora API
Do the Fatoora platform invoicing APIs use the OAuth protocol?
No. The invoice issuance APIs in the Fatoora platform use Basic Authentication built on the CSID certificate and its secret, not OAuth with short-lived access tokens. You carry the same credentials in the Authorization header with every request. For those who need to understand the OAuth pattern in its own right, we have dedicated a separate guide to it.
Where do I get the CSID identifier and the secret required for authentication?
You receive them from ZATCA in two stages. In compliance you request a Compliance CSID certificate via a one-time password, so you obtain an identifier and secret for testing. After passing compliance you request the production certificate (PCSID), so you obtain a new identifier and secret for live operation. Each environment has separate credentials.
How exactly do I build the Authorization header value?
You join the CSID identifier with the secret with a colon between them, then encode the resulting string with Base64, then place the result in the header prefixed with the word Basic. The format: Basic then the Base64 encoding of the string “identifier:secret”. The colon is part of the input before encoding, not after.
What is the purpose of the Accept-Version header?
It specifies the API version your system understands, and its value for Phase Two is V2. Its presence protects your integration from future changes, since your system stays bound to the version you built it on. Omitting it may lead to the request being rejected or to a response with an unexpected default version.
Why must the credentials be secured more strictly than with OAuth?
Because Basic credentials are fixed and do not expire automatically before the certificate is renewed, unlike the short-lived access token in OAuth. Leaking them allows impersonating your device’s identity for a longer time. Therefore do not write them in the code, store them in an encrypted secrets store, do not log them in the logs, and reissue the certificate immediately on any suspicion of leakage.
What does status code 401 mean in an authentication request?
It means the gateway did not accept your credentials. The common causes: an error in building the string before encoding, or mixing credentials between the sandbox and production environments, or an expired certificate, or forgetting the Base64 encoding. Read the response body, as it clarifies the cause, especially with Accept-Language set to ar.