Skip to content
Basmtak Books

Partner API and webhooks

Connect your store or system to your books and receive signed events.

A REST API for developers at https://books.basmtak.com/partner/v1. Read contacts, items, accounts, documents and reports; create contacts, invoices, notes, receipts, supplier bills and journals, under the same rules as the screens. Webhooks tell your system what happens in the books. Each key works on one business.

Before you start

  • API keys depend on your subscription plan, and their tab shows for someone who administers the business.
  • A developer who knows HTTP and JSON.
  • An HTTPS address to receive webhooks, if you want them.

Setting it up

Create a key

Open Businesses & people, then the API keys tab, and press New key. Pick the person under Acts as: the key can never do more than they can, and suspending them stops it. Grant only what your system needs (scroll the row of scopes sideways to see them all), set an expiry if you like, and press Create the key.
The key is shown once and stored only as a hash, so copy it right away. Never put it in code that reaches a browser. The webhooks, journals, stock and settlements scopes do not come with the general Read and Write options: they are granted by name.
Create a key

Test the connection

Send the key in the Authorization header. /ping returns the business, the subscription state, the key's scopes, its rate limit and the server clock. The full OpenAPI description is at /partner/v1/openapi.json, with the same key.
bash
curl https://books.basmtak.com/partner/v1/ping \
  -H "Authorization: Bearer bsk_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Accept: application/json"

Read data

Every list is paged, with per_page up to 200. Use updated_since to read only what changed, and send both cursor values on the next call. Paths: /contacts, /items, /accounts, /invoices, /credit-notes, /receipts, /bills, /journals, /reports/trial-balance and /reports/vat.
bash
# the first page of invoices changed since a moment
curl "https://books.basmtak.com/partner/v1/invoices?updated_since=2026-09-01T00:00:00%2B03:00&per_page=100" \
  -H "Authorization: Bearer $BOOKS_KEY"

# every list answers: data, page, per_page, total, last_page
# with updated_since it adds: cursor { updated_since, since_id } and has_more
# pass both cursor values on the next call

Write safely

Send your system's external_id with every document. If a reply is lost and you retry with the same id, the same document comes back with no duplicate, and created is false. Without issue: true the invoice stays a draft, and you issue it later with POST /invoices/{id}/issue. Issuing numbers it, posts its journal and starts ZATCA.
bash
curl -X POST https://books.basmtak.com/partner/v1/invoices \
  -H "Authorization: Bearer $BOOKS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "shop-order-10482",
    "contact_id": 118,
    "date": "2026-09-30",
    "issue": true,
    "lines": [
      { "item_id": 13, "quantity": 2 },
      { "description": "Delivery", "quantity": 1, "unit_price": 40 }
    ]
  }'

# answers { "invoice": { ... }, "created": true }
# the same external_id again answers the same invoice with "created": false

Subscribe to webhooks

The key needs webhooks:write. Working events: invoice.created, invoice.issued, credit_note.created, receipt.created, bill.created, contact.created and journal.posted.
invoice.cleared is registered but not sent today, so do not build on it. journal.posted covers journals sent through the API only.
bash
curl -X POST https://books.basmtak.com/partner/v1/webhooks \
  -H "Authorization: Bearer $BOOKS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/books",
    "events": ["invoice.issued", "receipt.created"],
    "description": "Online store"
  }'

# the answer holds the signing secret (whsec_...) once: store it now

Verify the signature

Each message carries X-Books-Signature as t=<unix>,v1=<hex>, the HMAC-SHA256 of t.body using the subscription secret. It also carries X-Books-Event, X-Books-Delivery and X-Books-Attempt. The body holds id, event, event_key, business_id, created_at, attempt and data.
javascript
import crypto from 'node:crypto';

// rawBody: the request body exactly as received, before any JSON parsing
function verify(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const signed = `${parts.t}.${rawBody}`;
  const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < toleranceSeconds;
  return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

// header: req.headers['x-books-signature'], e.g. t=1790000000,v1=9f2c...
// answer any 2xx to confirm delivery

What moves across

  • Read from Books: contacts:read, items:read, accounts:read, invoices:read, credit_notes:read, receipts:read, bills:read, reports:read and journals:read.
  • Write to Books: contacts:write, invoices:write, credit_notes:write, receipts:write, bills:write and journals:write.
  • Items and accounts cannot be created through the API: they are read-only there.
  • Books to your system: signed events to the address you choose.

Troubleshooting

I get a 401
The key is missing, wrong, revoked or expired. Check the Authorization: Bearer ... header.
I get a 403 with ScopeRequired
The key lacks the scope named in details.scope. FeatureNotInPlan means the business's plan does not include the API.
I get a 402
The books are read-only because the subscription lapsed. Renew it to write again.
I get a 429
You went over the limit. The default is 120 requests a minute per key, and there is a per-address limit too. Wait for the time in the Retry-After header.
A webhook did not arrive
Read the log at GET /webhooks/deliveries. A failure retries after one minute, then 5 and 15 minutes, an hour and 6 hours, and then stops as dead. Send it again with POST /webhooks/deliveries/{id}/replay.
A webhook subscription switched itself off
A subscription stops after 10 failures in a row. Fix the address, then switch it back on with POST /webhooks/{id}/active.

Still need a hand?

Our team is available Sunday to Thursday 8 AM – 4 PM and Saturday 10 AM – 4 PM.