Basmtak Cashier developer platform

Partner API v1: read sales and catalogue, write items, send orders to the till, receive signed webhooks.

العربية

Base URL https://cashier.basmtak.com/api/partner/v1 · OpenAPI 3.0.3 · Download the OpenAPI file

Read sales and the catalogue, write items, send orders to the till and receive signed webhooks. Money is always integer halalas (1 SAR = 100). Errors are JSON {error, message, details}; the message follows Accept-Language (ar or en). Lists answer {data, meta}.

Authentication

An account owner or manager creates an API client under Back office → Developers and chooses its abilities. The token is shown once. Send it on every call:

curl -H "Authorization: Bearer bmc_…" -H "Accept-Language: ar" https://cashier.basmtak.com/api/partner/v1/ping

Abilities

pos:readGET branches · categories · items · price-lists · customers · receipts · sessions/{id}/z-report · stock/levels
pos:writePOST /items · PUT /items/{id}
pos:ordersPOST /orders · GET /orders/{id}
pos:webhooksGET|POST|DELETE /webhooks

Rate limit

120 requests a minute per API client. A 429 answer carries Retry-After; every answer carries X-RateLimit-Limit and X-RateLimit-Remaining.

Errors

Every error is JSON {error, message, details}; the message follows Accept-Language (ar or en). Money is always integer halalas (1 SAR = 100).

{"error": "insufficient_ability", "message": "…", "details": {"required_ability": "pos:write"}}

Webhooks (REST hooks)

Subscribe with POST /webhooks {target_url, event} and unsubscribe with DELETE /webhooks/{id} — the Zapier and Make REST-hook pattern. Each delivery is a POST of {id, event, tenant_id, occurred_at, api_version, data}, retried with exponential backoff; 20 failures in a row (or a 410 answer) switch the subscription off.

Verifying a delivery

X-Basmtak-Signature = "sha256=" + hex HMAC-SHA256(secret, X-Basmtak-Timestamp + "\n" + "POST" + "\n" + path + "\n" + hex SHA-256(raw body)). Reject timestamps older than five minutes. X-Basmtak-Event-Id is unique per event — use it to drop duplicates.

// Node.js
const crypto = require('crypto');
const canonical = ts + "\nPOST\n" + new URL(targetUrl).pathname + "\n" + crypto.createHash('sha256').update(rawBody).digest('hex');
const ok = 'sha256=' + crypto.createHmac('sha256', secret).update(canonical).digest('hex') === req.headers['x-basmtak-signature'];

Events

receipt.issued

Sample data

{
    "id": "01J9ZK3Q7R8S9T0V1W2X3Y4Z5A",
    "number": "R1-418",
    "kind": "simplified",
    "branch_id": 1,
    "register_id": 3,
    "session_id": "01J9ZJ0000000000000000000S",
    "issued_at": "2027-04-02T18:21:07+03:00",
    "business_date": "2027-04-02",
    "currency": "SAR",
    "subtotal_halalas": 3130,
    "tax_halalas": 470,
    "total_halalas": 3600,
    "prepaid_applied": null,
    "amount_due_halalas": 3600,
    "lines": [
        {
            "line_no": 1,
            "variant_id": 21,
            "name": "كابتشينو كبير",
            "qty": "2",
            "unit_price_halalas": 1800,
            "total_halalas": 3600
        }
    ],
    "payments": [
        {
            "tender": "card",
            "amount_halalas": 3600,
            "scheme": "mada",
            "last4": "4242"
        }
    ]
}
credit_note.issued

Sample data

{
    "kind": "credit_note",
    "refund_of": "01J9ZK3Q7R8S9T0V1W2X3Y4Z5A",
    "total_halalas": 1800,
    "id": "01J9ZK3Q7R8S9T0V1W2X3Y4Z5A",
    "number": "R1-418",
    "branch_id": 1,
    "register_id": 3,
    "session_id": "01J9ZJ0000000000000000000S",
    "issued_at": "2027-04-02T18:21:07+03:00",
    "business_date": "2027-04-02",
    "currency": "SAR",
    "subtotal_halalas": 3130,
    "tax_halalas": 470,
    "prepaid_applied": null,
    "amount_due_halalas": 3600,
    "lines": [
        {
            "line_no": 1,
            "variant_id": 21,
            "name": "كابتشينو كبير",
            "qty": "2",
            "unit_price_halalas": 1800,
            "total_halalas": 3600
        }
    ],
    "payments": [
        {
            "tender": "card",
            "amount_halalas": 3600,
            "scheme": "mada",
            "last4": "4242"
        }
    ]
}
session.closed

Sample data

{
    "id": "01J9ZJ0000000000000000000S",
    "branch_id": 1,
    "register_id": 3,
    "status": "closed",
    "business_date": "2027-04-02",
    "receipts": 57,
    "total_halalas": 412300,
    "net_sales_halalas": 358522,
    "variance_halalas": -50,
    "z_report": {
        "sales": {
            "total_halalas": 412300
        }
    }
}
stock.low

Sample data

{
    "branch_id": 1,
    "variant_id": 21,
    "sku": "MILK-1L",
    "name_ar": "حليب ١ لتر",
    "qty": "3.0000",
    "reorder_point": "5.0000",
    "suggested_qty": "7.0000"
}
order.status_changed

Sample data

{
    "from": "received",
    "to": "accepted",
    "id": "01J9ZM0000000000000000000O",
    "external_id": "web-1001",
    "provider": "partner:4",
    "status": "accepted",
    "total_halalas": 5400
}
customer.created

Sample data

{
    "id": 77,
    "marketing_consent": true,
    "name": "نورة",
    "phone": "+966501234567",
    "email": null,
    "vat_number": null
}

Endpoints

meta Connectivity
GET /ping Check the token and see the client's abilities Ability: any

Responses

200The client, its abilities and the account
401{error, message, details}Error
catalogue Branches, categories, items, price lists and customers
GET /branches List branches Ability: pos:read

Parameters

pagequeryinteger
per_pagequeryinteger

Responses

200A page of branchesBranchPage
GET /categories List categories Ability: pos:read

Parameters

pagequeryinteger
per_pagequeryinteger

Responses

200A page of categories
GET /items List items with their variants Ability: pos:read

Each variant carries `plu` (V + variant id), the reference to use in orders.

Parameters

pagequeryinteger
per_pagequeryinteger
category_idqueryinteger
updated_sincequerystringISO-8601; only items changed since then
skuquerystring
barcodequerystring

Responses

200A page of itemsItemPage
POST /items Create an item (one payload with its variants) Ability: pos:write

Same payload as the back office. Barcodes and SKUs are unique per account. Without variants one default variant is made at default_price_halalas.

Request body ItemWrite

{
    "name_ar": "كابتشينو",
    "name_en": "Cappuccino",
    "category_id": 3,
    "variants": [
        {
            "name_ar": "صغير",
            "sku": "CAP-S",
            "price_halalas": 1400,
            "is_default": true
        },
        {
            "name_ar": "كبير",
            "sku": "CAP-L",
            "price_halalas": 1800
        }
    ]
}

Responses

201The itemobject
403{error, message, details}Error
422{error, message, details}Error
GET /items/{id} One item Ability: pos:read

Parameters

idpathinteger · required

Responses

200The item
404{error, message, details}Error
PUT /items/{id} Update an item Ability: pos:write

Arrays present in the body (variants, modifier_group_ids, combo_slots) replace what is stored; rows without id are created, missing ids are deleted. Arrays absent are untouched.

Parameters

idpathinteger · required

Request body ItemWrite

Responses

200The item
422{error, message, details}Error
GET /price-lists Price lists with their prices Ability: pos:read

Parameters

pagequeryinteger
per_pagequeryinteger

Responses

200A page of price lists
GET /customers Customers (consented fields only) Ability: pos:read

Name, phone, email and address are returned only while the customer's marketing consent is granted (PDPL); otherwise they are null. VAT and CR numbers are always returned.

Parameters

pagequeryinteger
per_pagequeryinteger
updated_sincequerystring
consented_onlyquerybooleanOnly customers whose latest marketing consent in the consent ledger is granted (never erased ones).
consent_channelquerystringWith consented_only, only consents that cover this channel.

Responses

200A page of customersobject
sales Receipts, Z-reports and stock
GET /receipts Receipts and credit notes, oldest first, with a since_id cursor Ability: pos:read

Pass the last receipt id you saw as since_id to get what arrived after it; meta.next_since_id is the value for the next call and meta.has_more says whether to call again now. The order is the server's arrival order, so receipts a till sold offline and synced later are never skipped. Receipts that arrived in the last few seconds are held back.

Parameters

since_idquerystring
branch_idqueryinteger
kindquerystring
per_pagequeryinteger

Responses

200Receipts after the cursorReceiptCursorPage
422{error, message, details}Error
GET /receipts/{id} One receipt or credit note Ability: pos:read

Parameters

idpathstring · required

Responses

200The receiptobject
404{error, message, details}Error
GET /sessions/{id}/z-report The Z-report of a closed till session Ability: pos:read

The Z as stored at close; deposits already invoiced are netted (prepayments_applied).

Parameters

idpathstring · required

Responses

200The session with z_report, expected and counted
409{error, message, details}Error
GET /stock/levels Stock on hand per branch and variant Ability: pos:read

Parameters

pagequeryinteger
per_pagequeryinteger
branch_idqueryinteger
variant_idqueryinteger
low_onlyqueryboolean

Responses

200A page of stock levels
orders Orders sent to the till's incoming-orders panel
POST /orders Send an order to the till Ability: pos:orders

The order appears on the branch till's incoming-orders panel; the cashier accepts it and the receipt is issued on the till. Idempotent on external_id per API client: a retry answers 200 with duplicate true. Lines reference a variant by variant_id, or plu / sku / barcode; a line nothing matches stays an open item with your name and price. Prices are VAT-inclusive halalas; a line without a price takes the catalogue price. Status changes come back as order.status_changed webhooks.

Request body OrderWrite

{
    "external_id": "web-1001",
    "branch_id": 1,
    "order_type": "pickup",
    "prepaid": true,
    "customer": {
        "name": "Noura",
        "phone": "+966501234567"
    },
    "lines": [
        {
            "variant_id": 21,
            "qty": 2,
            "unit_price_halalas": 1800
        }
    ]
}

Responses

201The orderobject
200The same order again (a retry)
422{error, message, details}Error
GET /orders/{id} The status of one of your orders Ability: pos:orders

Parameters

idpathstring · required

Responses

200The order
404{error, message, details}Error
POST /orders/{id}/cancel Cancel one of your orders Ability: pos:orders

Withdraws the order from the till's incoming-orders panel. Cancelling an order that is already cancelled answers the same order again. If the till already issued its receipt, the receipt stands and the cashier is asked to decide on a refund.

Parameters

idpathstring · required

Request body object

Responses

200The order, now cancelledobject
404{error, message, details}Error
409{error, message, details}Error
webhooks REST hooks (Zapier and Make)
GET /webhooks Your subscriptions Ability: pos:webhooks

Responses

200A page of subscriptions
POST /webhooks Subscribe (REST hook) Ability: pos:webhooks

The answer carries the subscription id and its signing secret. The secret is shown only here.

Request body object

{
    "target_url": "https://hooks.zapier.com/hooks/standard/123/abc",
    "event": "receipt.issued"
}

Responses

201{id, secret, data}
422{error, message, details}Error
GET /webhooks/events The events, each with sample data Ability: pos:webhooks

Responses

200{data: [{event, sample}]}
GET /webhooks/{id} One subscription Ability: pos:webhooks

Parameters

idpathinteger · required

Responses

200The subscription
DELETE /webhooks/{id} Unsubscribe (REST hook) Ability: pos:webhooks

Parameters

idpathinteger · required

Responses

204Removed
404{error, message, details}Error