The Aqid ERP API

A versioned REST API for the books: issue invoices and ZATCA e-invoices from your own system, record payments, pull reports, and hear about every change by webhook. The full contract is an OpenAPI document you can read and call.

Base address
https://aqiderp.com/api/v1
Format
JSON over HTTPS. Money is always a decimal string such as "1150.00"; the server does every sum.
Authentication
An API key (Bearer mzk_live_… or mzk_test_…) scoped read and/or write, or an OAuth access token a company granted your app.
Plans
The API is included in the Growth and Premium plans. Pricing

Quick start

Create a key in the app under Sales → Invoicing API → API keys. It is shown once; we keep only its hash. Start with a Test key: its writes are checked and priced but nothing is saved or sent to ZATCA.

1. Check the key

curl -s https://aqiderp.com/api/v1/whoami \
  -H "Authorization: Bearer $AQID_API_KEY"

2. Find your company (entity)

curl -s https://aqiderp.com/api/v1/entities \
  -H "Authorization: Bearer $AQID_API_KEY"

3. Draft an invoice

curl -s -X POST https://aqiderp.com/api/v1/entities/$ENTITY_ID/invoices \
  -H "Authorization: Bearer $AQID_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8f14e45f-ceea-467a-9f9c-1b0c0f5a1e11" \
  -d '{"contactId":"'$CONTACT_ID'","issueDate":"2026-10-16",
       "lines":[{"description":"Consulting","qty":"2","unitPrice":"500.00","taxCode":"SA-STD"}]}'

4. Approve it: number, journal and, in Saudi Arabia, ZATCA validation and QR

curl -s -X POST https://aqiderp.com/api/v1/invoices/$INVOICE_ID/approve \
  -H "Authorization: Bearer $AQID_API_KEY"

Approval answers with the invoice's gapless number, its journal and the QR payload. A Saudi invoice is then cleared or reported to ZATCA, and the result arrives by webhook.

What every call shares

Test keys

A mzk_test_ key reads like a live one. Its writes to invoices, quotes, credit notes and e-invoices are validated and priced and answer "livemode": false; nothing is saved, numbered or sent.

Idempotency

Send an Idempotency-Key header on writes. A repeat returns the first response with Idempotency-Replayed: true, so a retry after a timeout never makes a second invoice.

Pagination

Lists are cursor based: pass limit, then the nextCursor from the page you have. Records added during a walk are never skipped or repeated.

Errors

RFC 7807 application/problem+json, with a machine-readable code (BAD_VAT, ZATCA_VALIDATION, UPGRADE_REQUIRED…). A ZATCA refusal lists ZATCA's own findings.

Rate limits

A token bucket per key: 120 requests refilling over 60 seconds by default. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining; a 429 adds Retry-After.

Money and tax

Amounts are decimal strings and tax codes name the rate (SA-STD for Saudi 15%). Foreign-currency invoices take a currency and an exchange rate; a Saudi invoice goes to ZATCA with its VAT in SAR.

Resources

InvoicesGET/POST /v1/entities/{entityId}/invoices · POST /v1/invoices/{id}/approve
ZATCA e-invoicesPOST /v1/entities/{entityId}/einvoices · POST …/einvoices/validate · GET /v1/einvoices/{id}/xml
ContactsGET/POST /v1/contacts · PATCH /v1/contacts/{id}
PaymentsPOST /v1/entities/{entityId}/payments · POST /v1/payments/{id}/allocations
Supplier billsGET/POST /v1/entities/{entityId}/bills · POST /v1/bills/{id}/approve
Manual journalsGET/POST /v1/entities/{entityId}/journals
Bank accounts and statementsGET/POST /v1/entities/{entityId}/bank-accounts · POST /v1/bank-accounts/{id}/statements
ReportsGET /v1/entities/{entityId}/reports/{profit-and-loss | balance-sheet | cash-flow | vat-return}
Employees and payslipsGET/POST /v1/entities/{entityId}/employees · GET /v1/payslips/{id}/pdf
Warehouses and stockGET/POST /v1/entities/{entityId}/warehouses · GET …/stock-levels

Also quotes, credit and debit notes, purchase orders, expenses, bank transfers, items, cost centres, projects, branches and tax rates. The interactive reference lists every route and field.

ZATCA e-invoicing from your own system

A billing system, POS or marketplace can send its sales to the API and get back ZATCA-compliant invoices: validated first (POST …/einvoices/validate shows ZATCA's findings without issuing), then numbered, signed and cleared (B2B) or reported (B2C) in one call, with the signed XML to download.

E-invoicing (ZATCA)

Webhooks

Register an endpoint on the Invoicing API page and choose events. Delivery is at least once, retried with backoff; deduplicate on the AqidERP-Event-Id header. The endpoint must be publicly reachable.

invoice.approved · invoice.paid · invoice.voided · credit_note.approved · bill.approved · payment.received · payment.sent · quote.accepted · quote.declined · einvoice.cleared · einvoice.reported · einvoice.rejected · contact.created · contact.updated · bank_transfer.posted

Each delivery is signed: AqidERP-Signature carries a timestamp and an HMAC-SHA256 of "<timestamp>.<raw body>" with your endpoint's secret. Verify it in constant time and reject old timestamps:

import { createHmac, timingSafeEqual } from "node:crypto";

// header: the AqidERP-Signature header, "t=<unix>,v1=<hex>"
export function verify(secret, rawBody, header, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
  const t = Number(parts.t);
  if (!Number.isFinite(t)) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(parts.v1 ?? "", "utf8");
  return a.length === b.length && timingSafeEqual(a, b);
}

OAuth apps

An app a company did not write, such as a POS or a marketplace connector, gets a token instead of a key: the company registers it under Settings → Developer, and the authorization-code flow with PKCE (required for every client) returns an access token with only the scopes asked for, such as invoices:write contacts:read.

GET https://aqiderp.com/oauth/authorize?response_type=code&client_id=mzc_…
    &redirect_uri=https://your-app.example/callback&scope=invoices:write%20contacts:read
    &state=…&code_challenge=…&code_challenge_method=S256

POST https://aqiderp.com/api/oauth/token   (grant_type=authorization_code, code_verifier=…)

Build on your own books

Start now for free API access comes with the Growth and Premium plans