Quickstart: Your First Invoice in 5 Minutes¶
This is a guided, copy-paste first run. You will get an access token, create a draft invoice from a couple of line items, freeze it (the platform computes the totals, renders the compliant document, and submits it to the tax authority), and finally read it back to see the QR code and signed artifact.
Every request below runs against Staging — a safe sandbox — so you can follow along without touching production data. Each step shows the request and a realistic response so you always know what success looks like and where the next step's input comes from.
We use the Simple invoicing API (/api/v1/simple/...): you send only the
business essentials — buyer, line items, tax rates — and the platform computes
every total for you. For the full field-by-field UBL surface, see the
Documents API instead.
Prerequisites¶
- An Invora account — register at stg-dashboard.invora.app.
- A machine-to-machine
client_idandclient_secretfrom the dashboard.
Set them as shell variables so the snippets below are copy-pasteable:
1. Get an access token¶
Exchange your credentials for a token with the OAuth 2.0 client_credentials
grant:
TOKEN=$(curl -s -X POST https://stg-auth.invora.app/oauth/v2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET" \
--data-urlencode "scope=openid urn:zitadel:iam:org:project:id:372376692817133647:aud urn:zitadel:iam:user:resourceowner" \
| jq -r '.access_token')
{
"access_token": "iIDF3v0p9X8q2nReferenceTokenOpaqueValue",
"token_type": "Bearer",
"expires_in": 43200
}
The access_token is an opaque reference token, not a JWT — pass it through
verbatim; do not try to decode it. Every request from here carries
Authorization: Bearer $TOKEN. Your token is automatically scoped to your own
tenant, so no tenancy header is needed. (Full details in
Authentication.)
2. Create a draft invoice¶
Create an invoice with two standard-rate line items. You send quantities, unit
prices, and tax rates — not totals; the platform computes those. Omitting
freezeImmediately leaves the invoice as an editable draft.
Monetary values and rates are exact decimals shaped { "units": …, "nanos": … }
(both numbers, value = units + nanos / 1,000,000,000); a 15% VAT rate is
{ "units": 15, "nanos": 0 }. See
decimal values.
curl -s -X POST https://stg-gateway.invora.app/api/v1/simple/invoices \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"changes": {
"issueAt": "2026-06-11T10:30:00Z",
"supplyDate": { "year": 2026, "month": 6, "day": 11 },
"currencyCode": "SAR",
"buyer": {
"inline": {
"legalName": "Beta Corp",
"vatRegistrationNumber": "310122393500003",
"address": {
"streetName": "King Fahd Road",
"buildingNumber": "1234",
"citySubdivisionName": "Al Olaya",
"cityName": "Riyadh",
"postalZone": "12345",
"countryCode": "SA"
}
}
},
"lines": [
{
"description": "Consulting services",
"quantity": { "value": { "units": 10, "nanos": 0 }, "unitCode": "HUR" },
"unitPrice": { "units": 100, "nanos": 0 },
"tax": { "taxRate": { "units": 15, "nanos": 0 } }
},
{
"description": "Onboarding setup fee",
"quantity": { "value": { "units": 1, "nanos": 0 }, "unitCode": "EA" },
"unitPrice": { "units": 500, "nanos": 0 },
"tax": { "taxRate": { "units": 15, "nanos": 0 } }
}
]
}
}'
{
"details": {
"key": "siv_01J7ZK8QF3EXAMPLE",
"frozen": false,
"concurrencyStamp": "8f2c1a44",
"calculations": {
"lineExtensionTotal": { "units": 1500, "nanos": 0 },
"taxTotal": { "units": 225, "nanos": 0 },
"payableAmount": { "units": 1725, "nanos": 0 }
}
}
}
Two things to keep from this response drive the rest of the tutorial — the
server-generated key and the concurrencyStamp. Save them:
The invoice is a draft (frozen: false), so calculations are already computed
but nothing has been submitted yet. Because the buyer has a
vatRegistrationNumber, this is a B2B invoice that ZATCA will clear when you
freeze it.
3. (Optional) Preview the totals with Calculate¶
Before committing, you can preview the fully computed document — per-line and
document totals — without persisting anything. Send the same changes you
sent on Create:
curl -s -X POST https://stg-gateway.invora.app/api/v1/simple/invoices:calculate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"changes": {
"currencyCode": "SAR",
"lines": [
{
"description": "Consulting services",
"quantity": { "value": { "units": 10, "nanos": 0 }, "unitCode": "HUR" },
"unitPrice": { "units": 100, "nanos": 0 },
"tax": { "taxRate": { "units": 15, "nanos": 0 } }
},
{
"description": "Onboarding setup fee",
"quantity": { "value": { "units": 1, "nanos": 0 }, "unitCode": "EA" },
"unitPrice": { "units": 500, "nanos": 0 },
"tax": { "taxRate": { "units": 15, "nanos": 0 } }
}
]
}
}'
{
"details": {
"calculations": {
"lineExtensionTotal": { "units": 1500, "nanos": 0 },
"taxTotal": { "units": 225, "nanos": 0 },
"payableAmount": { "units": 1725, "nanos": 0 }
}
}
}
Calculate never stores anything and needs no key — it is a pure preview. The
main flow continues with the draft you created in step 2.
4. Freeze the invoice¶
Freezing finalizes the draft and runs the regulation pipeline: the platform
signs the document, generates the QR code, and submits it to ZATCA. Pass the
concurrencyStamp from step 2 so the platform can reject a conflicting edit
(concurrencyStamp is the optimistic-concurrency guard — see
Entity versioning).
curl -s -X POST https://stg-gateway.invora.app/api/v1/simple/invoices/$KEY/freeze \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{ \"concurrencyStamp\": \"$STAMP\" }"
{
"details": {
"key": "siv_01J7ZK8QF3EXAMPLE",
"frozen": true,
"concurrencyStamp": "d4e9b077",
"calculations": {
"lineExtensionTotal": { "units": 1500, "nanos": 0 },
"taxTotal": { "units": 225, "nanos": 0 },
"payableAmount": { "units": 1725, "nanos": 0 }
},
"regulation": {
"regulationId": "zatca",
"status": "SIMPLE_REGULATION_STATUS_ACCEPTED",
"qrCode": "AR5T...base64-TLV...==",
"authorityReferenceIds": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
"artifacts": [
{ "id": "qr-code", "contentType": "image/png", "sha256": "3a7bd3e2...42dd4f1b" },
{ "id": "signed-xml", "contentType": "application/xml", "sha256": "9f1c04a7...b30e5521" }
]
}
}
}
The invoice is now frozen: true and immutable. regulation.status
SIMPLE_REGULATION_STATUS_ACCEPTED means ZATCA cleared it, and
regulation.qrCode plus the two artifacts are ready. To adjust a frozen
invoice you never edit it — you issue a credit or debit note (see
Simple invoicing).
5. Read the invoice back¶
Fetch the finalized invoice to confirm the terminal state and inspect the regulation metadata:
curl -s -X GET https://stg-gateway.invora.app/api/v1/simple/invoices/$KEY \
-H "Authorization: Bearer $TOKEN"
{
"details": {
"key": "siv_01J7ZK8QF3EXAMPLE",
"frozen": true,
"concurrencyStamp": "d4e9b077",
"currencyCode": "SAR",
"calculations": {
"lineExtensionTotal": { "units": 1500, "nanos": 0 },
"taxTotal": { "units": 225, "nanos": 0 },
"payableAmount": { "units": 1725, "nanos": 0 }
},
"regulation": {
"regulationId": "zatca",
"status": "SIMPLE_REGULATION_STATUS_ACCEPTED",
"qrCode": "AR5T...base64-TLV...==",
"authorityReferenceIds": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
"artifacts": [
{ "id": "qr-code", "contentType": "image/png", "sha256": "3a7bd3e2...42dd4f1b" },
{ "id": "signed-xml", "contentType": "application/xml", "sha256": "9f1c04a7...b30e5521" }
]
}
}
}
That is a complete, ZATCA-cleared invoice: computed totals, a QR code, and a
signed XML artifact — the guaranteed terminal state for this walkthrough. The
artifacts list carries identity and integrity metadata only; download the
actual bytes (the signed XML or the QR image) by artifact id with the
GetArtifact operation, covered in
Simple invoicing → Artifacts.
Flow at a glance¶
flowchart LR
A[Get access token] --> B["Create invoice<br/>(draft)"]
B -. optional .-> C["Calculate<br/>(preview totals)"]
B --> D["Freeze<br/>(ZATCA clearance)"]
D --> E["Get / verify<br/>(QR + signed XML)"]
What's next¶
- All Simple operations — update, delete, list, credit and debit notes, artifact download: Simple invoicing.
- Environments and base URLs — moving from Staging to Production: Getting started.
- ZATCA compliance — onboarding, clearance vs. reporting, and simplified (B2C) invoices: ZATCA integration.