One call fiscalises a sale

Everything else on this page is optional. This is the whole integration.

POST /api/v1/sales
curl -X POST https://gateway.mikenyambe.com/api/v1/sales \
  -H "Authorization: Bearer $TENANT_TOKEN" \
  -H "Idempotency-Key: INV-1042" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
        {
            "name": "Maize meal 25kg",
            "classificationCode": "50221204",
            "quantity": 2,
            "price": 350
        }
    ]
}'
201 Created What comes back
{
    "data": {
        "id": 4821,
        "status": "uploaded",
        "invoiceNo": 424,
        "receiptNo": 424,
        "reference": "424",
        "totals": {
            "taxable": 603.45,
            "vat": 96.55,
            "total": 700
        },
        "taxBreakdown": {
            "A": {
                "taxbl": 603.45,
                "tax": 96.55,
                "rate": 16
            },
            "B": {
                "taxbl": 0,
                "tax": 0,
                "rate": 0
            },
            "C1": {
                "taxbl": 0,
                "tax": 0,
                "rate": 0
            },
            "C2": {
                "taxbl": 0,
                "tax": 0,
                "rate": 0
            },
            "C3": {
                "taxbl": 0,
                "tax": 0,
                "rate": 0
            },
            "D": {
                "taxbl": 0,
                "tax": 0,
                "rate": 0
            },
            "RVAT": {
                "taxbl": 0,
                "tax": 0,
                "rate": 0
            },
            "E": {
                "taxbl": 0,
                "tax": 0,
                "rate": 0
            }
        },
        "fiscal": {
            "sdcId": "SDC0010000941",
            "mrcNo": "MRC0010000941",
            "receiptSignature": "WVRE-M3BF-QPXT",
            "internalData": "3PGT-XKQZ-7YHD-2WMR",
            "vsdcDate": "20260901101422",
            "qrCodeUrl": "https://gateway.mikenyambe.com/verify/424"
        }
    }
}
status What it means What you may print
uploaded ZRA has it. A fiscal receipt. Render fiscal.qrCodeUrl as a QR code, never as text.
pending Queued offline; every fiscal.* is null. Mark it PROVISIONAL and reprint once it uploads.
failed ZRA refused it. Nothing fiscal. Branch on resultCode, never on the prose.

Idempotency-Key is optional, but it is the only place your own invoice reference can land on /api/v1 — and it is what GET /api/v1/sales?reference= matches. A 422 reading "No initialized ZRA device" is not a payload problem: that branch has not finished Go live.

Which endpoints are yours

Three ways in, and what each one costs you

One merchant

Authorization: Bearer <tenant token>

1 call

  1. POST /api/v1/sales

Then, when you need them: GET /api/v1/me, GET /api/v1/sales/{sale}, POST /api/v1/sales/{sale}/credit-note.

Jump to Sales →

Many merchants (partner)

Authorization: Bearer sk_live_…

5 calls

  1. POST /api/v1/tenants
  2. POST …/devices
  3. POST …/devices/{branch}/initialize
  4. POST …/tokens
  5. POST /api/v1/sales

Give every branch its own device serial — ZRA hands out an initialisation payload once per serial. The token from …/tokens is the credential that fiscalises; a pk_… key from branch keys will 401 on /api/v1.

Jump to Tenants →

Migrating a legacy till

X-Api-Key + X-SdcId

1 call

  1. POST /zm/api/Invoice

Your request payload carries over; responses come back bare, with no {success, message, data} wrapper. See the migration guide.

Jump to Zambia (compat) →

Registering a webhook needs a partner key, so a merchant holding only a tenant token polls GET /api/v1/sales/{sale} for the pending → uploaded transition instead.