Skip to content
bookey Docs

REST · v1 · https://bookeypro.com

Start building
with Bookey

JSON for a POS or warehouse. People sign in. Machines send a bey_ Bearer on /api/v1 only. Machine spec: /docs.json.

curl -s https://bookeypro.com/api/v1/sales \
  -H "Authorization: Bearer $BOOKEY_TOKEN"

Browse by product

Pick a surface. Tokens and CSRF rules stay the same.

Quickstart

  1. Issue a token Owner or admin opens Settings → API, or POST /api/v1/developer/tokens.
  2. Copy it once Bookey stores a hash. Paste bey_… into the POS vault immediately.
  3. Call JSON Send Authorization: Bearer $BOOKEY_TOKEN. Cookie sessions still need CSRF; Bearer does not.

Authentication

Send Authorization: Bearer <token> on every JSON call. A user session from /api/v1/auth/login works for people. Unattended POS and warehouse clients must use a Bookey API token that starts with bey_. Cookie sessions still need X-CSRF-Token; Bearer skips CSRF. API tokens work only on /api/v1/… — they cannot open Settings or mint other tokens.

KindHeaderUse
User session Authorization: Bearer People. From /api/v1/auth/login.
API token Authorization: Bearer bey_… Unattended POS and warehouse. JSON /api/v1 only.

Issuance

Owner or admin issues a token from Settings → API or POST /api/v1/developer/tokens with a name, scopes, and expiry (30 / 90 / 365 days). Bookey shows the secret once. Copy it into the POS or vault immediately. At most 20 active tokens per workspace.

Roles and scopes

People keep Bookey roles (owner, admin, accountant, member). Tokens do not inherit a human role. Each token has scopes only — sales.read / sales.write / inventory.read / inventory.write / receipts.read / receipts.ingest / banking.read / banking.write. Write or ingest implies the matching read. A stolen sales or ingest token cannot open QuickBooks, billing, Team, or approve receipts.

ScopeCan
sales.read List tickets and hourly sales. Does not post tickets or sync Clover.
sales.write Create sales tickets. Includes sales.read. Never syncs Clover or loads sample data.
inventory.read Read stock, locations, and movement. Does not receive, transfer, or waste.
inventory.write Create items and post receive / transfer / waste. Includes inventory.read.
receipts.read List and fetch expense receipts and images. Does not upload or approve.
receipts.ingest Upload JPEG / PNG / PDF and poll the OCR job. Includes receipts.read. Never approve, reject, or post to QuickBooks.
banking.read List bac_ accounts and btx_ feed transactions. Not the QBO ledger.
banking.write Register accounts, ingest feed lines, match or ignore. Includes banking.read. Unchecked by default. Never a QBO Banking Match write.

Revoke

Revoke from Settings → API or POST /api/v1/developer/tokens/{id}/revoke. The secret stops working on the next request. Revoke immediately if a device is lost or a contractor leaves. The row is archived and leaves Settings → API. Bookey never shows the secret again.

Renew

Renew rotates the secret in place (POST /api/v1/developer/tokens/{id}/renew). The old secret dies immediately. The same token id and scopes stay. Expiry restarts from now using 90 days unless you pass another allowed value. Update the POS before you renew, then paste the new secret.

Expiry

Every token expires. Allowed lifetimes are 30, 90, or 365 days — there is no forever token. Expired secrets return 401. Renew before the date, or issue a replacement and revoke the old one.

Developer

GET /api/v1/developer session · owner/admin

Specification plus the token list (prefix only).

Accepts No body. Session Bearer only — API tokens cannot mint tokens.

curl
curl -s https://bookeypro.com/api/v1/developer \
  -H "Authorization: Bearer $SESSION"
POST /api/v1/developer/tokens session · owner/admin

Issue a token. Secret returned once.

Accepts JSON: name, scopes[] (sales / inventory / receipts), expires_in_days (30 / 90 / 365).

JSON body
{
  "name": "Kitchen POS",
  "scopes": ["sales.write", "receipts.ingest"],
  "expires_in_days": 90
}
POST /api/v1/developer/tokens/{id}/renew session · owner/admin

Rotate the secret. Old Bearer dies immediately.

Accepts path id. Optional JSON expires_in_days. Session Bearer only.

POST /api/v1/developer/tokens/{id}/revoke session · owner/admin

Revoke immediately. The secret dies; the row is archived and leaves the Tokens list.

Accepts path id. No body. Session Bearer only.

Receipts

POST /api/v1/receipts/upload receipts.ingest

Store the image, start OCR, land in pending_review. Returns a job. Poll GET /api/v1/jobs/{id}. SHA256 duplicate is not a new receipt. Not JSON. Does not approve or post to QBO.

Accepts multipart field file — JPEG, PNG, or PDF. One file per request.

curl
curl -s https://bookeypro.com/api/v1/receipts/upload \
  -H "Authorization: Bearer $BOOKEY_TOKEN" \
  -F "file=@lunch.jpg;type=image/jpeg"
GET /api/v1/jobs/{id} receipts.ingest

Poll upload/OCR progress. When done, the receipt is in the expense queue.

Accepts path id from the upload response. No body.

curl
curl -s https://bookeypro.com/api/v1/jobs/{id} \
  -H "Authorization: Bearer $BOOKEY_TOKEN"
GET /api/v1/receipts receipts.read

Expense queue. Not sales tickets. Filter with state=pending_review.

Accepts query state (all, pending_review, …) and limit (1–100). No body.

curl
curl -s "https://bookeypro.com/api/v1/receipts?state=pending_review&limit=20" \
  -H "Authorization: Bearer $BOOKEY_TOKEN"
GET /api/v1/receipts/{id} receipts.read

One receipt after OCR: vendor, amounts, tax, lines, state.

Accepts path id. No body.

curl
curl -s https://bookeypro.com/api/v1/receipts/{id} \
  -H "Authorization: Bearer $BOOKEY_TOKEN"
GET /api/v1/receipts/evidence receipts.read

Stored JPEG / PNG / PDF files. One file downloads as itself. Two or more are a zip that Bookey deletes after the download.

Accepts optional query from and to (YYYY-MM-DD). Omit both for every stored file.

curl
curl -sO -J "https://bookeypro.com/api/v1/receipts/evidence" \
  -H "Authorization: Bearer $BOOKEY_TOKEN"
GET /api/v1/receipts/{id}/image receipts.read

Original JPEG / PNG / PDF. Use after list or get.

Accepts path id. Returns the stored file bytes, not JSON.

curl
curl -sO https://bookeypro.com/api/v1/receipts/{id}/image \
  -H "Authorization: Bearer $BOOKEY_TOKEN"

Sales

POST /api/v1/sales/tickets sales.write

Create one sales ticket. Same external_id + body is idempotent. Never the expense queue.

Accepts JSON object. Decimal strings. amount is pre-tax.

JSON body
{
  "external_id": "pos-123",
  "occurred_at": "2026-09-08T14:32:00Z",
  "currency": "CAD",
  "amount": "10.00",
  "tax": "1.50",
  "tip": "2.00",
  "tender": "card",
  "store": "Store 1",
  "pos": "2",
  "lines": [
    {"external_id": "line-1", "name": "Latte", "quantity": "2", "unit_price": "5.00", "line_total": "10.00"}
  ]
}
GET /api/v1/sales sales.read

List tickets for a date range.

Accepts query from and to (YYYY-MM-DD). No body.

curl
curl -s "https://bookeypro.com/api/v1/sales?from=2026-09-01&to=2026-09-30" \
  -H "Authorization: Bearer $BOOKEY_TOKEN"

Inventory

GET /api/v1/inventory inventory.read

Stock snapshot, locations, and movement.

Accepts No body. Feature flag inventory must be on.

curl
curl -s https://bookeypro.com/api/v1/inventory \
  -H "Authorization: Bearer $BOOKEY_TOKEN"
POST /api/v1/inventory/items inventory.write

Create a catalog item.

Accepts JSON: sku, name. Optional item_type, base_uom, reorder_point. Not a stock movement.

JSON body
{
  "sku": "BEANS-1KG",
  "name": "Coffee beans",
  "base_uom": "KG"
}
POST /api/v1/inventory/receive inventory.write

Receive stock. Replay of the same key does not double qty.

Accepts JSON. idempotency_key required. sku or item_id, quantity, location_id or location_slug.

JSON body
{
  "idempotency_key": "recv-2026-09-08-1",
  "sku": "BEANS-1KG",
  "quantity": "12",
  "location_slug": "central_kitchen"
}
POST /api/v1/inventory/transfers inventory.write

Transfer between locations.

Accepts JSON. idempotency_key, from_location_id or from_location_slug, to_*, lines[] with sku or item_id and quantity.

JSON body
{
  "idempotency_key": "xfer-2026-09-08-1",
  "from_location_slug": "central_kitchen",
  "to_location_slug": "store_1",
  "lines": [{"sku": "BEANS-1KG", "quantity": "2"}]
}
POST /api/v1/inventory/adjust inventory.write

Record waste.

Accepts JSON. idempotency_key, sku or item_id, quantity, location_id or location_slug, reason.

JSON body
{
  "idempotency_key": "waste-2026-09-08-1",
  "sku": "BEANS-1KG",
  "quantity": "0.25",
  "location_slug": "central_kitchen",
  "reason": "spill"
}

Banking feed

POST /api/v1/banking/accounts banking.write

Register a bac_ bank or card account. last4 only — never a full PAN.

Accepts JSON: name, account_kind (bank or card). Optional currency, last4, institution, nickname.

JSON body
{
  "name": "Visa ops",
  "account_kind": "card",
  "currency": "CAD",
  "last4": "9195"
}
GET /api/v1/banking/accounts banking.read

List Bookey feed accounts. Not QBO ledger accounts.

Accepts query active=1, limit, offset. No body.

curl
curl -s https://bookeypro.com/api/v1/banking/accounts \
  -H "Authorization: Bearer $BOOKEY_TOKEN"
POST /api/v1/banking/feed/transactions banking.write

Batch upsert btx_ lines. Same external_id + body is exists. Different body is conflict. Soft lookalikes stay and are flagged.

Accepts JSON. account_id plus transactions[]. amount is a signed decimal string.

JSON body
{
  "account_id": "bac_…",
  "transactions": [{
    "external_id": "visa-1001",
    "booked_on": "2026-09-08",
    "amount": "-42.50",
    "currency": "CAD",
    "payee": "Costco",
    "last4": "9195"
  }]
}
GET /api/v1/banking/feed/transactions banking.read

List API-feed rows. Do not send these to GET /api/v1/banking/transactions.

Accepts query from, to, account_id, unmatched=1, duplicate, limit, offset.

curl
curl -s "https://bookeypro.com/api/v1/banking/feed/transactions?from=2026-09-01&to=2026-09-30&unmatched=1" \
  -H "Authorization: Bearer $BOOKEY_TOKEN"
POST /api/v1/banking/feed/transactions/{id}/match banking.write

Link a btx_ line to a receipt and stamp Banking cleared (API feed). Not Bank matched.

Accepts JSON: receipt_id. Session or banking.write token.

JSON body
{
  "receipt_id": "RECEIPT_ID"
}

Errors

StatusMeaning
400 Validation failed
401 Missing, expired, or revoked token
403 Scope or role cannot do that
404 Add-on off, or unknown id
409 Idempotency or id conflict

Guardrails

  • Never log the full token or store the plaintext after issuance.
  • Partners post Bookey JSON, not BFDP envelopes.
  • Tickets never enter the expense receipt queue.
  • Receipt upload is a file, not JSON. Approve / reject / QBO post stay human.
  • Clover sync stays a Clover connection — API tokens cannot call /sales/sync.

Need help integrating?