Qoyod
Pricing

Knowledge Base

The Sandbox Guide for Developers

After you understand the concept of the Sandbox and its role in phase two of e-invoicing, the practical step arrives: sitting down at the command line and running your real integration with the Fatoora platform. This guide is the hands-on part. We do not explain here what the Sandbox is or why ZATCA requires it; instead we take you by the hand step by step: how to get access to the environment, how to obtain the Compliance CSID, how to send your first test invoice, which four documents you must test, how to read the platform’s responses, and the common mistakes that stall developers on day one.

The target audience here is clear: a developer or a technical integration team connecting their system to the Fatoora platform for the first time. We assume you deal with HTTP requests, know what JSON is, and can run a curl command or its equivalent. Everything else we explain in detail.

Before you start: what you actually need

Integrating with the Sandbox does not require a paid subscription or a special Fatoora account in the traditional sense. What you need is simpler than many developers imagine. The following list defines your requirements before the first request:

  • The organization’s VAT Registration Number, made up of 15 digits, or a valid test number for testing.
  • The one-time password (OTP) you generate from the Fatoora portal to bind your device.
  • A tool to generate the certificate signing request (CSR) and the key pair, such as OpenSSL or a cryptography library in your language.
  • An HTTP client that supports Basic Authentication and custom headers (curl, Postman, or code in your application).
  • A UBL 2.1 invoice sample (we will explain where it comes from).

Note that the Sandbox is completely isolated from production. Any VAT number or data you use here carries no legal obligations. This gives you complete freedom to experiment and make mistakes.

Before we proceed, remember that the Sandbox is not a goal in itself, but a bridge you cross toward production. Every minute you spend in it tests an assumption that could collapse in production had you not verified it early. So treat every rejection or warning as valuable information, not an obstacle. The developer who rushes out of the Sandbox often returns to it, forced back after the first invoice rejected in front of a client.

Three environments you must not confuse

ZATCA provides three separate environments, each with a different base URL. Confusing them is the first source of errors. Here is the essential difference:

  • Sandbox / Developer Portal: For free experimentation and learning. It does not require actual device binding, and it returns realistic responses so you understand how the APIs behave.
  • Compliance environment (Simulation): Here you obtain the Compliance CSID and run the mandatory compliance checks on documents before production.
  • Production environment: The actual legal invoices. You only reach it after passing the compliance checks and obtaining the production certificate.

This guide focuses on the first two environments, because that is where the developer spends most of their time before moving to production.

From Sandbox to production
The three environments and the gate between each one.
1

Accessing the Sandbox

2

Compliance CSID and passing the check

3

Upgrading to production

No move to production until you pass the compliance check in the Sandbox.

Step 1: Getting access to the Sandbox

The journey begins from the developer portal that ZATCA provides within the Fatoora platform. First access requires nothing more than generating a one-time password (OTP) from the portal, then using it to bind your device and obtain the first certificate.

The core idea: your system does not authenticate with a username and password. Instead, it generates a cryptographic key pair (private and public), then sends a certificate signing request (CSR) to ZATCA along with the OTP. ZATCA responds with a compliance certificate that you use afterward to sign every request. This mechanism is explained in detail in the authentication guide for the invoicing API, and it is the foundation everything below rests on.

Generating the key pair and the signing request (CSR)

The first technical step is generating the private key and the certificate signing request. ZATCA uses the encryption curve secp256k1. Here is the command using OpenSSL:

openssl ecparam -name secp256k1 -genkey -noout -out ec-private-key.pem

openssl req -new -sha256 -key ec-private-key.pem -out taxpayer.csr \
  -config csr-config.cnf

The configuration file csr-config.cnf contains mandatory fields that ZATCA requires, most notably the organizational unit name, the VAT number, and the supported invoice type. A simplified example of its content:

[req]
distinguished_name = req_dn
prompt = no
req_extensions = v3_req

[req_dn]
CN = TST-MyCompany
O = My Company LLC
OU = 1234567890
C = SA

[v3_req]
subjectAltName = dirName:alt_names

[alt_names]
SN = 1-MyApp|2-1.0|3-test-device-001
UID = 312345678900003
title = 1100
registeredAddress = Riyadh
businessCategory = Technology

Note the field UID: it is the VAT number made up of 15 digits. And the field title with the value 1100 means your device will issue both standard and simplified tax invoices. These numbers carry a specific meaning for ZATCA, so do not invent them randomly.

Exchanging the CSR for the Compliance CSID

After generating the CSR, you send it to the compliance endpoint along with the OTP. The request looks like this:

POST /compliance HTTP/1.1
Host: gw-fatoora.zatca.gov.sa
OTP: 123456
Accept-Version: V2
Content-Type: application/json

{
  "csr": "<BASE64_ENCODED_CSR>"
}

If the request succeeds, ZATCA returns a response containing three decisive elements: the compliance certificate itself (binarySecurityToken), the accompanying secret (secret), and the request ID (requestID). These three are your key to everything that follows.

{
  "requestID": 1234567890123,
  "dispositionMessage": "ISSUED",
  "binarySecurityToken": "TUlJRD...<truncated>",
  "secret": "Xy9...<truncated>",
  "errors": null
}

Save the value of binarySecurityToken andsecret in a safe place. Together, these two values form the Basic Authentication credentials: you put the token in the username position, the secret in the password position, and encode them in Base64.

Step 2: Understanding the Compliance CSID

Before we send the first invoice, the relationship between the certificates must be clear in your mind. Many developers confuse the compliance certificate with the production certificate, and fall into an incomprehensible rejection when they move over.

The Compliance CSID is a certificate with a temporary purpose. Its sole job is to let you run compliance checks on a sample of documents. It is not valid for issuing invoices in production. After you pass these checks successfully, you ask ZATCA to upgrade your certificate to a Production CSID via a separate endpoint, and only then do you begin issuing legal invoices.

The logical sequence, then:

  • You generate the keys and the CSR.
  • You obtain the Compliance CSID in exchange for the OTP.
  • You run compliance checks on the four documents.
  • You request the production certificate after success.
  • You begin actual issuance in production.

This naming matters: the correct abbreviation is CSID (Cryptographic Stamp Identifier), which compliant systems manage automatically on behalf of the organization, as explained by the overview page for the e-invoicing API.

The certificate lifecycle in the Sandbox
Five steps to issue certificates and test.
Certificate cycle
1

Generate keys

2

Create the CSR signing request

3

Obtain the Compliance CSID

4

Pass the compliance checks

5

Upgrade to the Production CSID

Each step is a condition for moving to the one after it.

Step 3: Sending your first test invoice

Now we have reached the moment every developer waits for: sending the first actual document. There are two main APIs you deal with:

  • The Compliance API (/compliance/invoices): you use it during the checks phase with the Compliance CSID.
  • The Clearance and Reporting APIs (/invoices/clearance/single and/invoices/reporting/single): you use them in production, the first for standard invoices (B2B) and the second for simplified ones (B2C).

While working in the Sandbox, you focus on the Compliance API. The shape of the request:

POST /compliance/invoices HTTP/1.1
Host: gw-fatoora.zatca.gov.sa
Authorization: Basic <BASE64(binarySecurityToken:secret)>
Accept-Version: V2
Content-Type: application/json

{
  "invoiceHash": "<SHA256_HASH_BASE64>",
  "uuid": "3cf5ee18-ee25-44ea-a444-2c37ba7f28be",
  "invoice": "<BASE64_ENCODED_UBL_XML>"
}

The three mandatory fields: the invoice hash (invoiceHash) computed with the SHA-256 algorithm, the unique identifier (uuid), and the invoice content in UBL 2.1 format encoded in Base64. Generating the correct XML is the hardest part, so we strongly recommend using a spec-compliant generator instead of writing it by hand.

Building the authentication header

The value of the Authorization header is built by joining the certificate token and the secret with a colon between them, then encoding them in Base64:

# in simplified command-line form
CREDENTIALS=$(printf '%s:%s' "$BINARY_SECURITY_TOKEN" "$SECRET" | base64 -w0)

curl -X POST "https://gw-fatoora.zatca.gov.sa/e-invoicing/simulation/compliance/invoices" \
  -H "Authorization: Basic ${CREDENTIALS}" \
  -H "Accept-Version: V2" \
  -H "Content-Type: application/json" \
  -d @invoice-request.json

If you see the status code 401, the problem is usually in the authentication header: either an encoding error, or using the production certificate in the compliance environment or vice versa.

Try it without writing a single line

Let Qoyod handle the connection with the Fatoora platform on your behalf

If you do not want to build the integration yourself, Qoyod manages the certificate lifecycle, invoice signing, and reporting to ZATCA automatically. It issues your phase-two compliant invoices from day one.

Start your free trial and issue a compliant invoice

Step 4: The four documents you must test

The compliance check is not a check of a single invoice, but a series of checks on the document types your organization supports. ZATCA requires you to pass a sample representing each document type you declared support for in the CSR. If you declared in the title=1100 field that you support both standard and simplified, then you must test both with all their derivatives.

The four core documents:

  • Standard tax invoice (Standard / B2B): The invoice directed at another organization, which passes through Clearance in production. It must carry a full cryptographic signature and a stamp from ZATCA.
  • Simplified tax invoice (Simplified / B2C): Directed at the end consumer, and passes through Reporting within 24 hours in production. It carries a QR code.
  • Credit Note: Issued to correct a previous invoice downward, such as a return or a subsequent discount.
  • Debit Note: Issued to increase the amount of a previous invoice, such as additional charges not originally accounted for.

For each type you must send the standard version and the simplified version if you support them. In practice the number of check samples may reach six or eight documents. ZATCA verifies that each one carries the correct fields, the correct hash chain sequence, and a valid signature.

The hash chain in the check sample

A subtle point many overlook: each invoice carries the hash of the invoice before it in the PIH field (Previous Invoice Hash). The first document in the chain carries a fixed initial value:

PIH (for the first invoice):
NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZj... (the standard initial value)

Each subsequent document must carry the hash of the document that preceded it. If you break this sequence, the check rejects the invoice with a message concerning chain integrity. Make sure your system generates these values automatically and do not tamper with them by hand.

The four tested documents
The document types that must be tested in the Sandbox.
Test documents

Standard tax invoice (Clearance)

Simplified invoice (Reporting)

Credit Note

Debit Note

Passing the four documents is a condition for moving to production.

Step 5: Reading the platform’s responses

Every check request returns a JSON response carrying ZATCA’s decision. Reading these responses correctly is what separates an integration that works from one that spins in a loop of blind attempts. The pivotal field is clearanceStatus or reportingStatus, in addition to the two arrays warnings anderrors.

There are three possible outcomes for each document:

  • Full acceptance (CLEARED / REPORTED): The invoice is completely valid and passed every check. No action required.
  • Acceptance with warnings (CLEARED_WITH_WARNINGS): The invoice was accepted but has non-critical notes. Read the warnings array and address them before production.
  • Rejection (NOT_CLEARED / NOT_REPORTED): The invoice failed a critical check. The errors array carries the reason for rejection and its code.

An example of a rejection response:

{
  "validationResults": {
    "infoMessages": [],
    "warningMessages": [],
    "errorMessages": [
      {
        "type": "ERROR",
        "code": "BR-KSA-15",
        "category": "KSA",
        "message": "Invoice type code must be a valid value"
      }
    ],
    "status": "ERROR"
  },
  "clearanceStatus": "NOT_CLEARED"
}

The code BR-KSA-15 here tells you exactly where the flaw is: the invoice type code is invalid. Every code starting with BR-KSA is a business rule specific to Saudi Arabia, while the one starting with BR- is a general rule from the UBL specification. Learning to read these codes saves you hours of guessing.

A table of the status codes you will encounter

Alongside the check decision inside the JSON, every response carries an HTTP status code that tells you the nature of the result at the gateway level before the check logic. Understanding these codes shortens diagnosis time:

  • 200: The request arrived and was processed, and the invoice was accepted in full. Still, read the warnings array.
  • 202: The request was processed but the invoice was accepted with warnings. The result is valid, but review the notes.
  • 303: An indication in some APIs that the document was previously uploaded, or of a redirect. Read the response body, not the code alone.
  • 400: The invoice failed a critical check. The reason is in the errorMessages.
  • 401: array. Authorization Authentication failure. Review the
  • 500: header and the certificate used.

A server-side error. Retry after a short delay, and do not treat it as a rejection of the invoice. 200 An important rule: do not build your logic on the status code alone. An invoice may be rejected inside a response carrying the code NOT_CLEAREDif the decision field is

. Always read both fields together: the HTTP code and the decision field inside the body.

Step 6: Building the request inside your code step by step curl So far we have used

  • for illustration. In practice you will build the request inside your application in a programming language. The sequence is fixed regardless of the language, and we present it here as logical steps you can implement with any HTTP and cryptography library: Prepare the invoice document (XML):
  • Generate a UBL 2.1 document from the invoice data, and insert ZATCA’s mandatory extensions. Compute the hash: invoiceHash.
  • Apply SHA-256 to the canonicalized document and save the result in Base64. This is the value of Sign the standard invoice: UBLExtensionsCreate an XML signature using your private key, and insert it within the
  • . The simplified invoice does not pass through Clearance but needs a signature for Reporting. Encode the invoice: invoice.
  • Convert the full document to Base64 so it becomes the value of the field Build the body and send: invoiceHash anduuid andinvoice Assemble

into a JSON object, and send it with the correct authentication header.

{
  "invoiceHash": "QtTr6QyW...<hash truncated>=",
  "uuid": "3cf5ee18-ee25-44ea-a444-2c37ba7f28be",
  "invoice": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgi...<truncated>"
}

If you set up these five steps correctly once, then every subsequent invoice follows the same path. The real effort is in the first successful document, and after it is repetition.

Requesting the certificate upgrade to production

After you pass the check sample successfully, you request the production certificate via a dedicated endpoint. In this request you use the compliance certificate itself for authentication, and the request ID (requestID) you obtained when you extracted the Compliance CSID:

POST /production/csids HTTP/1.1
Host: gw-fatoora.zatca.gov.sa
Authorization: Basic <BASE64(complianceToken:secret)>
Accept-Version: V2
Content-Type: application/json

{
  "compliance_request_id": "1234567890123"
}

If the request succeeds, ZATCA returns a new production certificate (a newbinarySecurityToken andsecret pair). From this moment, you stop using the compliance credentials entirely and use only the production credentials. Confusing them after the upgrade is a common cause of the first rejection messages in production.

Step 7: Common mistakes in setup

Here we have gathered the most recurring obstacles on the first day of integration, with the cause of each and its direct fix. Reviewing this list before you start saves you most of the obscure rejection messages.

Confusing the environment URLs

The most common mistake: using the compliance (Simulation) environment URL to send a request meant for the Sandbox, or vice versa. Each environment has a different path within the same domain. Verify that the path contains simulation or developer-portal according to the intended environment before every request.

Encoding the certificate in the wrong format

The value of binarySecurityToken that ZATCA returns is already Base64-encoded. Some developers decode it and then re-encode it before placing it in the authentication header, producing a broken token. The rule: use the value as is when building the Basic Authentication credentials.

XML not conforming to the UBL 2.1 specification

The invoice is not simple JSON, but an XML document precisely structured according to UBL 2.1 with ZATCA’s extensions. A missing mandatory element or a wrong element order causes an immediate rejection. Do not write the XML by hand; rely on a compliant generator that performs validation locally before sending.

An incorrect cryptographic signature

The standard invoice needs a cryptographic signature (XML Signature) built on your private key and certificate. An error in computing the signature or in inserting it within the UBLExtensions extension fails the check. Make sure your signing library follows ZATCA’s algorithm precisely.

Ignoring warnings before production

Acceptance of an invoice with warnings sometimes deceives you, so you think everything is fine. Warnings in compliance may become critical errors in production after the rules tighten. Address every warning before you request the production certificate.

The QR code in the simplified invoice

The simplified invoice directed at the end consumer carries a mandatory QR code. This code is not a decorative image, but a data field structured in TLV (Tag-Length-Value) format containing the invoice’s essential information encoded in Base64. ZATCA verifies its structure in the compliance check, so do not treat it lightly.

The mandatory fields inside the phase-two QR code:

  • Seller name: The legal name of the issuing organization.
  • Seller VAT number: The VAT registration number (15 digits).
  • Timestamp: The invoice issuance date and time in ISO 8601 format.
  • Total including tax: The total amount of the invoice.
  • VAT amount: The computed tax amount.
  • Invoice hash: The hash value in Base64.
  • Cryptographic signature and public key: To prove the invoice’s authenticity.

If your system issues simplified invoices, test the QR code in the Sandbox by reading it with any scanner app, then verify that all the fields appear correctly. A common mistake: ordering the TLV tags incorrectly, so the scanner reads distorted data even though the check may pass. Visual verification is a necessary step you do not skip.

Automating the test cycle

Re-running the steps manually for each document is exhausting and error-prone. We advise developers to build a simple script that repeats the test cycle automatically. The idea is to prepare sample invoice files, then pass each one through the same cycle and log the result.

A logical structure for the test script:

# for each invoice file in the samples folder:
#   1) compute the hash
#   2) sign the document if it is standard
#   3) encode it Base64
#   4) build the JSON body
#   5) send it to /compliance/invoices
#   6) log clearanceStatus + any errorMessages in a report

for invoice in samples/*.xml; do
  ./run-compliance-check.sh "$invoice" >> compliance-report.log
done

An aggregated report like this quickly reveals which document type is still failing, so you focus your effort where the problem really is. It also serves as a basis for regression tests when you update your XML generator in the future. Investing in automation from day one saves hours later, especially with multiple documents and repeated trials.

A final tip before you move to the next section: keep a copy of every complete request and response during development. The log of requests and responses is your first tool when diagnosing any obscure rejection, and it spares you from rebuilding the context from scratch every time.

How Qoyod helps you connect with the Fatoora platform

If everything above seems like a heavy technical burden, that is natural. Direct integration with the Fatoora platform requires a development team, key and certificate management, and continuous tracking of specification updates. Qoyod carries this burden for you entirely through a system compliant with phase two of e-invoicing in Saudi Arabia:

  • Managing the certificate lifecycle automatically: Qoyod handles generating the keys, requesting the Compliance CSID, upgrading it to a production certificate, and renewing it upon expiry, without any intervention from you.
  • Signing and stamping every invoice: Every standard invoice is cryptographically signed and passes through Clearance, and every simplified invoice is reported within the statutory period, with the QR code, the unique UUID, and the hash chain automatically.
  • Generating UBL 2.1-compliant XML: Qoyod builds the invoice document in the correct format, so you do not deal with the complexity of the specification or the obscure rejection messages.
  • The four document types ready: The standard invoice, the simplified invoice, the credit note, and the debit note, all supported directly from the user interface without writing code.

This way you get the fruit of everything we explained in this guide, without building or maintaining the integration yourself. You focus on your business, and Qoyod takes care of compliance.

When to build the integration yourself and when to rely on a ready-made solution

The decision between building a direct integration with the Fatoora platform and relying on a compliant system is not a purely technical decision, but a decision of resources and priorities. Each path has its suitable context:

  • Build it yourself if the integration is part of your product: If you are developing a software system you sell to other organizations and you want full control over every detail, then direct building is logical. But dedicate a team that continuously tracks the specification updates.
  • Rely on a ready-made solution if invoicing is a means, not an end: If your organization only needs to issue compliant invoices without invoicing being your product, then building the integration from scratch is a waste of resources. A compliant system achieves the same goal faster and cheaper.
  • Weigh the maintenance cost: Direct integration is not a project that ends at the first successful invoice. The specification changes, certificates expire, and rules tighten. The ongoing maintenance cost is often higher than the initial build cost.

Whatever your decision, understanding the practical path we explained here makes you a better negotiator and a more accurate evaluator, whether you lead the technical team yourself or choose a partner to handle it for you.

A practical summary before you move to production

You have now reached the end of the practical path. Before you request the production certificate, review this short list as a final gate:

  • You generated a valid key pair and obtained the Compliance CSID in exchange for a valid OTP.
  • You sent a sample covering every document type you declared support for in the CSR.
  • You obtained an acceptance result for every document, and addressed every warning that appeared in the errorMessages array. warnings.
  • You verified the integrity of the hash chain across the entire sample.
  • You confirmed you are using the correct URL for each environment, and that the authentication header is built with the values as ZATCA returns them.

After you pass this gate, you request the upgrade of your certificate to a production certificate, and begin actual issuance. Remember that the Sandbox exists so you discover errors before they cost you an invoice rejected in front of a real client, so invest enough time in it. And if you prefer to skip this whole path, Qoyod is ready to handle the connection on your behalf from day one.

Guides

Continue your learning journey

Explore the rest of Qoyod’s guides, or start applying what you’ve learned.

Live webinars hosted by the Qoyod team to help you use the software easily and answer your questions.

Discover Qoyod’s latest updates, ongoing improvements, and new features in one place.

Our team is ready to help you and provide instant support for any issue you face, around the clock.