blueQAPI

Developer API · v1

blueQ API

Connect blueQ to your website, accounting tools, delivery apps or your own dashboards. Every module you use in blueQ — clients, invoices, payments, sales, expenditure, stock, production orders, online stores, POS and reports — is available over a simple JSON REST API.

23resources
148endpoints
JSONover HTTPS
OpenAPI 3+ Postman

Base URL

https://www.blueq.app/api/v1

All requests and responses use JSON (Content-Type: application/json). Form-encoded bodies are also accepted. Dates are YYYY-MM-DD; timestamps are UTC YYYY-MM-DD HH:MM:SS; money is a number in your workspace currency (GHS).

Response envelope

{
    "ok": true,
    "data": {
        "id": 2381,
        "name": "Akosua Boateng"
    },
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 182,
        "total_pages": 8
    }
}
{
    "ok": false,
    "error": {
        "code": "validation_failed",
        "message": "phone is required.",
        "fields": {
            "phone": "Required."
        }
    }
}

meta is only present on lists. Every response carries an X-Request-Id header — quote it when contacting support.

Quick start

  1. In blueQ, open Settings → Developer API and click Generate sandbox key. Copy the key — it is shown only once.
  2. Call GET /me to check the key, your plan and rate limit.
  3. Paste the key in the box at the top of this page to use Try it on any endpoint.
  4. When your integration works, generate a live key (Business or Pro) and swap it in. Nothing else changes.
curl https://www.blueq.app/api/v1/me \
  -H "Authorization: Bearer bq_test_xxxxxxxxxxxxxxxxxxxxxxxx"

Authentication

Each workspace has one live key (bq_live_…) and one sandbox key (bq_test_…). Send it as a Bearer token on every request:

Authorization: Bearer bq_live_3f9a1c2e…

X-API-Key: bq_live_… also works. Keys are never accepted in the URL — a key sent as a query parameter is refused with 400 key_in_url so it doesn't leak into logs; regenerate it if that happens.

  • Activate / deactivate — the owner or an admin can switch the key off at any time in Settings → Developer API. Requests then fail with 401 api_key_inactive until it is switched back on.
  • Regenerate — issues a new key and immediately invalidates the old one (401 invalid_api_key).
  • blueQ stores only a SHA-256 hash of the key. If you lose it, regenerate.
  • The key acts as the workspace owner. Keep it on your server — never ship it in a mobile app or browser code that customers can read.

Sandbox & live

Both keys use the same base URL and the same endpoints. The key decides which data you reach:

Sandbox key bq_test_…Live key bq_live_…
DataA separate sandbox copy of your business. Your real clients, invoices and sales are never visible or changed.Your real business data.
PlansEvery plan, including Free and Growth.Business (read-only) and Pro (read/write).
AccessFull read / write.As your plan allows.
Rate limit60 requests / minute.60 (Business) or 300 (Pro) requests / minute.
Sample dataA category, two products, a client, a draft invoice and a cash sale are created on the first request.—
SMS, email, WhatsAppReally sent and charged to your SMS credits, so you can see exactly what customers receive. The sample client uses your own phone and email.Sent and charged as usual.

Every response has an X-API-Mode header (sandbox or live), and GET /me returns mode. Reset sandbox in Settings → Developer API deletes everything in the sandbox and reloads the sample data; the sandbox key keeps working. Record IDs from the sandbox do not exist in live, so don't copy IDs between the two.

Plan limits (clients, invoices, branches…) and business-type modules follow your real plan in both modes. If blueQ support disables API access for a workspace, the sandbox key stops too.

Plans & access

PlanLive keyLive rate limitSandbox key
Free Not included — Full · 60 / minute
Growth Not included — Full · 60 / minute
Business Read-only (GET) 60 requests / minute Full · 60 / minute
Pro Full read / write 300 requests / minute Full · 60 / minute

Read-only keys can call every GET endpoint (and report runs). Any create, update or delete returns 403 plan_read_only. 85 of the 148 endpoints need full access; they are marked Pro below.

Plan limits still apply through the API — e.g. creating a client beyond your plan's client limit returns 403 plan_limit_reached. Modules that depend on your business type (such as Recipes) return 403 feature_not_enabled when they are switched off.

Errors

Errors use standard HTTP status codes and a machine-readable error.code. For 422 responses, error.fields maps each invalid field to a message.

StatusCodeMeaning
400bad_requestThe request is malformed (invalid JSON, bad parameter or Idempotency-Key).
400key_in_urlThe key was sent in the query string. Send it in the Authorization header instead, and regenerate it.
401missing_api_keyNo API key was sent.
401invalid_api_keyThe key is wrong or was replaced by Regenerate.
401api_key_inactiveThe key has been deactivated in Settings → Developer API.
402subscription_expiredThe workspace subscription has expired.
403plan_no_apiThe workspace plan does not include live API access (Free and Growth). Sandbox keys still work.
403plan_read_onlyA Business (read-only) key tried to create, update or delete.
403api_disabledAPI access was switched off for this workspace by blueQ support.
403feature_not_enabledThe module is not enabled for this business type (e.g. recipes).
403plan_limit_reachedCreating the record would exceed a plan limit (clients, invoices, branches...).
404not_foundThe record does not exist or belongs to another workspace.
405method_not_allowedThe path exists but not for this HTTP method. See the Allow header.
409conflictThe request conflicts with the record's state (duplicate, locked, in use). The code is often more specific, e.g. items_locked, invalid_status.
422validation_failedOne or more fields are invalid. "fields" maps each field to a message.
409idempotency_key_reusedThe Idempotency-Key was used before with a different request.
429rate_limitedToo many requests this minute. Wait for Retry-After seconds.
500server_errorSomething went wrong on our side. Retry later; include X-Request-Id if you contact support.

Pagination & filters

List endpoints return 25 records per page by default and accept these query parameters:

pagePage number, from 1.
per_pageRecords per page, 1–100 (default 25).
qFree-text search where supported (names, numbers, phone, email).
from, toDate range (YYYY-MM-DD, inclusive) on the record's main date.
updated_sinceSame as from; handy for incremental sync.
statusStatus filter; allowed values are listed per endpoint.
sortField to sort by; prefix with - for descending, e.g. sort=-created_at.

Use meta.total_pages to know when to stop.

Rate limits

Limits are per key, per calendar minute: 60/min on Business and 300/min on Pro for live keys, and 60/min for sandbox keys on any plan. The sandbox and live keys have separate counters. Every response includes:

X-RateLimit-LimitRequests allowed this minute.
X-RateLimit-RemainingRequests left this minute.
X-RateLimit-ResetUnix time when the window resets.

Over the limit you get 429 rate_limited with a Retry-After header (seconds). Back off and retry after that time.

Idempotency

Network failures happen. To retry a POST safely — recording a payment or a POS sale, for example — send a unique Idempotency-Key header (a UUID works well):

Idempotency-Key: 6f1d0c7e-2b8a-4f43-9d1e-0b6c0f3a1e55

If the same key is sent again within 24 hours with the same request, blueQ returns the original response (with Idempotent-Replayed: true) instead of acting twice. Reusing a key with a different request returns 409 idempotency_key_reused.

Testing

  • Try it — every endpoint below has a console. Paste your key at the top of the page; it is kept in this browser tab only (sessionStorage) and sent straight to the API. Use your sandbox key here: write requests with a live key change real data.
  • Postman — download the collection, then set the apiKey variable.
  • OpenAPI 3 — openapi.json for code generators, Insomnia or Swagger UI.
  • Command-line test runner — php tests/api/run.php --base=https://www.blueq.app/api/v1 --key=bq_test_… [--module=invoices] [--readonly] runs a create → read → update → delete pass over every module and cleans up after itself. Point it at your sandbox key. Add --key=bq_live_… --sandbox-key=bq_test_… to check that sandbox and live data stay apart.

Changelog & versioning

The version is part of the URL (/api/v1). Within a version we only make additive changes — new endpoints, new optional fields, new error codes. Breaking changes ship as a new version (/api/v2) with at least 6 months' overlap. Every response carries an API-Version header.

v1 2026-10-09
  • Sandbox and live keys. Every workspace, on every plan, can create a sandbox key (bq_test_...) that works on a separate sandbox copy of the business with sample data and full read/write access at 60 requests/minute.
  • Responses carry an X-API-Mode header (sandbox or live); GET /me returns mode and live_workspace_id.
  • Reset sandbox in Settings → Developer API deletes all sandbox data and reloads the sample data. The sandbox key keeps working.
v1 2026-10-08
  • First public release covering every blueQ module: clients, suppliers, catalogue and stock, invoices, payments, sales, expenditure, production orders, taxes, branches, users and roles, recipes, online stores, POS and reports.
  • One API key per workspace with activate, deactivate and regenerate in Settings → Developer API.
  • Business plan: read-only, 60 requests/minute. Pro plan: full read/write, 300 requests/minute.
  • Idempotency-Key support on all POST requests, OpenAPI 3 file and Postman collection.

Getting started

Account /me

The workspace that owns the API key, its plan, API access level and rate limit.

The account object
moderead-onlystring"live" for bq_live_ keys, "sandbox" for bq_test_ keys.
workspace_idread-onlyintegerWorkspace (tenant) ID. For sandbox keys this is the sandbox workspace.
live_workspace_idread-onlyintegerThe real workspace. Same as workspace_id for live keys.
business_nameread-onlystringBusiness name.
business_typeread-onlystringIndustry slug.
ownerread-onlyobjectOwner name, email and phone.
planread-onlystringCurrent plan name.
api_accessread-onlystring"read" (Business) or "full" (Pro).
rate_limit_per_minuteread-onlyintegerRequests allowed per minute.
key_prefixread-onlystringFirst characters of the key in use.
limitsread-onlyobjectPlan limits (null = unlimited).
usageread-onlyobjectCurrent usage counted against plan limits.

Get the current workspace

GET/me

Use this to test a key. Returns the workspace, plan, access level and rate limit.

Response 200

{
    "ok": true,
    "data": {
        "mode": "live",
        "workspace_id": 1042,
        "live_workspace_id": 1042,
        "business_name": "Kente Prints Ltd",
        "business_type": "printing",
        "owner": {
            "name": "Ama Mensah",
            "email": "ama@kenteprints.com",
            "phone": "0244000000"
        },
        "plan": "Pro",
        "api_access": "full",
        "rate_limit_per_minute": 300,
        "key_prefix": "bq_live_3f9a1c2e",
        "limits": {
            "clients": null,
            "invoices": null,
            "sales": null,
            "branches": 5
        },
        "usage": {
            "clients": 182,
            "invoices": 940,
            "sales": 2210,
            "branches": 2
        }
    }
}
curl 'https://www.blueq.app/api/v1/me' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Customers

Clients /clients

Customers you invoice and sell to. Creating a client counts against the plan's client limit.

The clients object
idread-onlyintegerClient ID.
namestringClient or company name.
contact_personstringContact person (for companies).
phonestringPhone number. Must be unique in the workspace.
emailstringEmail address. Must be unique in the workspace.
genderstring"male" or "female".
date_of_birthdateDate of birth (YYYY-MM-DD).
balanceread-onlynumberAccount balance cache (credit on account).
created_atread-onlydatetimeWhen the client was added (UTC).

List clients

GET/clients

Query parameters

page, per_pagePagination (default 25, max 100).
qSearch name, phone or email.
fromCreated on/after (YYYY-MM-DD).
toCreated on/before.
sortname, -created_at (default).

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 2381,
            "name": "Akosua Boateng",
            "contact_person": "Kwame Boateng",
            "phone": "0244123456",
            "email": "akosua@example.com",
            "gender": "female",
            "date_of_birth": "1992-04-18",
            "balance": 0,
            "created_at": "2026-09-14 10:22:05"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/clients?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create a client Pro

POST/clients

Body

namerequiredstringClient or company name.
contact_personstringContact person (for companies).
phonerequiredstringPhone number. Must be unique in the workspace.
emailstringEmail address. Must be unique in the workspace.
genderstring"male" or "female".
date_of_birthdateDate of birth (YYYY-MM-DD).

Response 201

{
    "ok": true,
    "data": {
        "id": 2381,
        "name": "Akosua Boateng",
        "contact_person": "Kwame Boateng",
        "phone": "0244123456",
        "email": "akosua@example.com",
        "gender": "female",
        "date_of_birth": "1992-04-18",
        "balance": 0,
        "created_at": "2026-09-14 10:22:05"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/clients' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Akosua Boateng","contact_person":"Kwame Boateng","phone":"0244123456","email":"akosua@example.com","gender":"female","date_of_birth":"1992-04-18"}'
Try it Changes live data

Get a client

GET/clients/{id}

Path parameters

idrequiredClients ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 2381,
        "name": "Akosua Boateng",
        "contact_person": "Kwame Boateng",
        "phone": "0244123456",
        "email": "akosua@example.com",
        "gender": "female",
        "date_of_birth": "1992-04-18",
        "balance": 0,
        "created_at": "2026-09-14 10:22:05"
    }
}
curl 'https://www.blueq.app/api/v1/clients/2381' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a client Pro

PATCH/clients/{id}

Path parameters

idrequiredClients ID.

Body

namestringClient or company name.
contact_personstringContact person (for companies).
phonestringPhone number. Must be unique in the workspace.
emailstringEmail address. Must be unique in the workspace.
genderstring"male" or "female".
date_of_birthdateDate of birth (YYYY-MM-DD).

Response 200

{
    "ok": true,
    "data": {
        "id": 2381,
        "name": "Akosua Boateng",
        "contact_person": "Kwame Boateng",
        "phone": "0244123456",
        "email": "akosua@example.com",
        "gender": "female",
        "date_of_birth": "1992-04-18",
        "balance": 0,
        "created_at": "2026-09-14 10:22:05"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/clients/2381' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Akosua Boateng","contact_person":"Kwame Boateng","phone":"0244123456","email":"akosua@example.com","gender":"female","date_of_birth":"1992-04-18"}'
Try it Changes live data

Delete a client Pro

DELETE/clients/{id}

Refused with 409 if the client has invoices.

Path parameters

idrequiredClients ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/clients/2381' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

List a client's invoices

GET/clients/{id}/invoices

Path parameters

idrequiredClients ID.

Query parameters

page, per_pagePagination (default 25, max 100).

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 2381,
            "name": "Akosua Boateng",
            "contact_person": "Kwame Boateng",
            "phone": "0244123456",
            "email": "akosua@example.com",
            "gender": "female",
            "date_of_birth": "1992-04-18",
            "balance": 0,
            "created_at": "2026-09-14 10:22:05"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/clients/2381/invoices?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

List a client's payments

GET/clients/{id}/payments

Path parameters

idrequiredClients ID.

Query parameters

page, per_pagePagination (default 25, max 100).

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 2381,
            "name": "Akosua Boateng",
            "contact_person": "Kwame Boateng",
            "phone": "0244123456",
            "email": "akosua@example.com",
            "gender": "female",
            "date_of_birth": "1992-04-18",
            "balance": 0,
            "created_at": "2026-09-14 10:22:05"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/clients/2381/payments?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Customers

Suppliers /suppliers

Vendors you buy stock and services from. Deleting a supplier archives it.

The suppliers object
idread-onlyintegerSupplier ID.
namestringSupplier name.
phonestringPhone number.
emailstringEmail address.
notesstringInternal notes.
created_atread-onlydatetimeWhen the supplier was added (UTC).

List suppliers

GET/suppliers

Query parameters

page, per_pagePagination (default 25, max 100).
qSearch name, phone or email.
sortname, -created_at (default).

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 57,
            "name": "Accra Paper Mills",
            "phone": "0302123456",
            "email": "sales@accrapaper.com",
            "notes": "Delivers on Tuesdays.",
            "created_at": "2026-08-02 09:12:44"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/suppliers?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create a supplier Pro

POST/suppliers

Body

namerequiredstringSupplier name.
phonestringPhone number.
emailstringEmail address.
notesstringInternal notes.

Response 201

{
    "ok": true,
    "data": {
        "id": 57,
        "name": "Accra Paper Mills",
        "phone": "0302123456",
        "email": "sales@accrapaper.com",
        "notes": "Delivers on Tuesdays.",
        "created_at": "2026-08-02 09:12:44"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/suppliers' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Accra Paper Mills","phone":"0302123456","email":"sales@accrapaper.com","notes":"Delivers on Tuesdays."}'
Try it Changes live data

Get a supplier

GET/suppliers/{id}

Path parameters

idrequiredSuppliers ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 57,
        "name": "Accra Paper Mills",
        "phone": "0302123456",
        "email": "sales@accrapaper.com",
        "notes": "Delivers on Tuesdays.",
        "created_at": "2026-08-02 09:12:44"
    }
}
curl 'https://www.blueq.app/api/v1/suppliers/57' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a supplier Pro

PATCH/suppliers/{id}

Path parameters

idrequiredSuppliers ID.

Body

namestringSupplier name.
phonestringPhone number.
emailstringEmail address.
notesstringInternal notes.

Response 200

{
    "ok": true,
    "data": {
        "id": 57,
        "name": "Accra Paper Mills",
        "phone": "0302123456",
        "email": "sales@accrapaper.com",
        "notes": "Delivers on Tuesdays.",
        "created_at": "2026-08-02 09:12:44"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/suppliers/57' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Accra Paper Mills","phone":"0302123456","email":"sales@accrapaper.com","notes":"Delivers on Tuesdays."}'
Try it Changes live data

Archive a supplier Pro

DELETE/suppliers/{id}

Path parameters

idrequiredSuppliers ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/suppliers/57' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Catalogue & stock

Products /products

Catalogue items: sellable products, services, menu items and stock (inventory) items, with optional variations (sizes). Stock quantity changes go through /stock/adjustments.

The products object
idread-onlyintegerProduct ID.
namestringItem name.
descriptionstringDescription shown on invoices and the store.
pricenumberSelling price.
category_idintegerProduct category ID.
kindread-onlystring"product", "service" or "menu" (from the category).
sellablebooleantrue = catalogue item you sell; false = stock/inventory item.
activebooleanWhether the item is active.
unitstringStock unit, e.g. pcs, kg, l, m.
stock_qtynumberQuantity on hand. Set on create only; use /stock/adjustments afterwards.
reorder_levelnumberLow-stock warning level (inventory items).
expiry_datedateExpiry date (inventory items).
supplier_idintegerDefault supplier ID.
supplier_costnumberCost price from the supplier.
variationsarraySizes/options: [{id, label, price, stock_qty}]. Send on create to add variations.
created_atread-onlydatetimeWhen the item was added (UTC).

List products

GET/products

Query parameters

page, per_pagePagination (default 25, max 100).
qSearch name or description.
category_idFilter by category.
kindproduct, service or menu.
sellabletrue for catalogue items, false for stock items.
low_stocktrue to list items at or below reorder level.
sortname, price, -created_at (default).

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 812,
            "name": "A2 Flyer (glossy)",
            "description": "Full-colour, 150gsm",
            "price": 2.5,
            "category_id": 14,
            "kind": "product",
            "sellable": true,
            "active": true,
            "unit": "pcs",
            "stock_qty": 500,
            "reorder_level": 50,
            "expiry_date": null,
            "supplier_id": 57,
            "supplier_cost": 1.1,
            "variations": [
                {
                    "id": 813,
                    "label": "Pack of 100",
                    "price": 220,
                    "stock_qty": 20
                }
            ],
            "created_at": "2026-07-20 15:40:11"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/products?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create a product Pro

POST/products

Body

namerequiredstringItem name.
descriptionstringDescription shown on invoices and the store.
pricerequirednumberSelling price.
category_idrequiredintegerProduct category ID.
sellablebooleantrue = catalogue item you sell; false = stock/inventory item.
activebooleanWhether the item is active.
unitstringStock unit, e.g. pcs, kg, l, m.
stock_qtynumberQuantity on hand. Set on create only; use /stock/adjustments afterwards.
reorder_levelnumberLow-stock warning level (inventory items).
expiry_datedateExpiry date (inventory items).
supplier_idintegerDefault supplier ID.
supplier_costnumberCost price from the supplier.
variationsarraySizes/options: [{id, label, price, stock_qty}]. Send on create to add variations.

Response 201

{
    "ok": true,
    "data": {
        "id": 812,
        "name": "A2 Flyer (glossy)",
        "description": "Full-colour, 150gsm",
        "price": 2.5,
        "category_id": 14,
        "kind": "product",
        "sellable": true,
        "active": true,
        "unit": "pcs",
        "stock_qty": 500,
        "reorder_level": 50,
        "expiry_date": null,
        "supplier_id": 57,
        "supplier_cost": 1.1,
        "variations": [
            {
                "id": 813,
                "label": "Pack of 100",
                "price": 220,
                "stock_qty": 20
            }
        ],
        "created_at": "2026-07-20 15:40:11"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/products' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"name":"A2 Flyer (glossy)","description":"Full-colour, 150gsm","price":2.5,"category_id":14,"sellable":true,"active":true,"unit":"pcs","stock_qty":500,"reorder_level":50,"expiry_date":null,"supplier_id":57,"supplier_cost":1.1,"variations":[{"id":813,"label":"Pack of 100","price":220,"stock_qty":20}]}'
Try it Changes live data

Get a product

GET/products/{id}

Path parameters

idrequiredProducts ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 812,
        "name": "A2 Flyer (glossy)",
        "description": "Full-colour, 150gsm",
        "price": 2.5,
        "category_id": 14,
        "kind": "product",
        "sellable": true,
        "active": true,
        "unit": "pcs",
        "stock_qty": 500,
        "reorder_level": 50,
        "expiry_date": null,
        "supplier_id": 57,
        "supplier_cost": 1.1,
        "variations": [
            {
                "id": 813,
                "label": "Pack of 100",
                "price": 220,
                "stock_qty": 20
            }
        ],
        "created_at": "2026-07-20 15:40:11"
    }
}
curl 'https://www.blueq.app/api/v1/products/812' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a product Pro

PATCH/products/{id}

stock_qty and variations are ignored here. Use /stock/adjustments to change quantities.

Path parameters

idrequiredProducts ID.

Body

namestringItem name.
descriptionstringDescription shown on invoices and the store.
pricenumberSelling price.
category_idintegerProduct category ID.
sellablebooleantrue = catalogue item you sell; false = stock/inventory item.
activebooleanWhether the item is active.
unitstringStock unit, e.g. pcs, kg, l, m.
stock_qtynumberQuantity on hand. Set on create only; use /stock/adjustments afterwards.
reorder_levelnumberLow-stock warning level (inventory items).
expiry_datedateExpiry date (inventory items).
supplier_idintegerDefault supplier ID.
supplier_costnumberCost price from the supplier.
variationsarraySizes/options: [{id, label, price, stock_qty}]. Send on create to add variations.

Response 200

{
    "ok": true,
    "data": {
        "id": 812,
        "name": "A2 Flyer (glossy)",
        "description": "Full-colour, 150gsm",
        "price": 2.5,
        "category_id": 14,
        "kind": "product",
        "sellable": true,
        "active": true,
        "unit": "pcs",
        "stock_qty": 500,
        "reorder_level": 50,
        "expiry_date": null,
        "supplier_id": 57,
        "supplier_cost": 1.1,
        "variations": [
            {
                "id": 813,
                "label": "Pack of 100",
                "price": 220,
                "stock_qty": 20
            }
        ],
        "created_at": "2026-07-20 15:40:11"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/products/812' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"A2 Flyer (glossy)","description":"Full-colour, 150gsm","price":2.5,"category_id":14,"sellable":true,"active":true,"unit":"pcs","stock_qty":500,"reorder_level":50,"expiry_date":null,"supplier_id":57,"supplier_cost":1.1,"variations":[{"id":813,"label":"Pack of 100","price":220,"stock_qty":20}]}'
Try it Changes live data

Delete a product Pro

DELETE/products/{id}

Deletes the item, its variations and its store links.

Path parameters

idrequiredProducts ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/products/812' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Catalogue & stock

Product categories /product-categories

Groups for catalogue items. Each category has a kind: product, service or menu.

The product categories object
idread-onlyintegerCategory ID.
namestringCategory name.
kindstring"product", "service" or "menu".
store_visiblebooleanShown on the online store.
product_countread-onlyintegerItems in this category.

List categories

GET/product-categories

Query parameters

page, per_pagePagination (default 25, max 100).
qSearch by name.
kindproduct, service or menu.

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 14,
            "name": "Banners",
            "kind": "product",
            "store_visible": true,
            "product_count": 12
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/product-categories?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create a category Pro

POST/product-categories

Body

namerequiredstringCategory name.
kindstring"product", "service" or "menu".
store_visiblebooleanShown on the online store.

Response 201

{
    "ok": true,
    "data": {
        "id": 14,
        "name": "Banners",
        "kind": "product",
        "store_visible": true,
        "product_count": 12
    }
}
curl -X POST 'https://www.blueq.app/api/v1/product-categories' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Banners","kind":"product","store_visible":true}'
Try it Changes live data

Get a category

GET/product-categories/{id}

Path parameters

idrequiredProduct categories ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 14,
        "name": "Banners",
        "kind": "product",
        "store_visible": true,
        "product_count": 12
    }
}
curl 'https://www.blueq.app/api/v1/product-categories/14' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a category Pro

PATCH/product-categories/{id}

Path parameters

idrequiredProduct categories ID.

Body

namestringCategory name.
kindstring"product", "service" or "menu".
store_visiblebooleanShown on the online store.

Response 200

{
    "ok": true,
    "data": {
        "id": 14,
        "name": "Banners",
        "kind": "product",
        "store_visible": true,
        "product_count": 12
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/product-categories/14' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Banners","kind":"product","store_visible":true}'
Try it Changes live data

Delete a category Pro

DELETE/product-categories/{id}

Refused with 409 while products are still in the category.

Path parameters

idrequiredProduct categories ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/product-categories/14' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Catalogue & stock

Stock /stock

Quantities on hand for product-kind items, the stock movement history, and adjustments. Adjustments made through the API are applied immediately and recorded in the stock history as approved, with "API" as the requester.

The stock object
product_idread-onlyintegerProduct ID.
nameread-onlystringItem name.
unitread-onlystringStock unit.
stock_qtyread-onlynumberQuantity on hand.
reorder_levelread-onlynumberLow-stock level.
low_stockread-onlybooleantrue when stock_qty is at or below reorder_level.
sellableread-onlybooleanCatalogue item (true) or inventory item (false).
unit_costread-onlynumberSupplier cost per unit, if known.
stock_valueread-onlynumberstock_qty x unit_cost.
Stock movement object
idread-onlyintegerMovement ID.
product_idread-onlyintegerProduct ID.
nameread-onlystringItem name at the time.
typeread-onlystringadd, discard, edit or create.
reasonread-onlystringDiscard reason: expired, broken, obsolete.
qtyread-onlynumberQuantity changed.
qty_beforeread-onlynumberQuantity before.
qty_afterread-onlynumberQuantity after (null while pending).
statusread-onlystringpending, approved or rejected.
noteread-onlystringNote.
requested_byread-onlystringWho requested it.
requested_atread-onlydatetimeWhen (UTC).

List stock levels

GET/stock

Query parameters

page, per_pagePagination (default 25, max 100).
qSearch item name.
low_stocktrue to list only items at or below reorder level.
sellabletrue/false.

Response 200

{
    "ok": true,
    "data": [
        {
            "product_id": 812,
            "name": "A2 Flyer paper",
            "unit": "pcs",
            "stock_qty": 480,
            "reorder_level": 50,
            "low_stock": false,
            "sellable": false,
            "unit_cost": 1.1,
            "stock_value": 528
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/stock?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

List stock movements

GET/stock/movements

Query parameters

page, per_pagePagination (default 25, max 100).
product_idFilter by product.
statuspending, approved or rejected.
fromOn/after date.
toOn/before date.

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 3301,
            "product_id": 812,
            "name": "A2 Flyer paper",
            "type": "add",
            "reason": null,
            "qty": 100,
            "qty_before": 380,
            "qty_after": 480,
            "status": "approved",
            "note": "Delivery from Accra Paper Mills",
            "requested_by": "API",
            "requested_at": "2026-10-02 08:15:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/stock/movements?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Adjust stock Pro

POST/stock/adjustments

Subtracting more than is on hand returns 422.

Body

product_idrequiredintegerProduct to adjust (must be a product-kind item).
directionrequiredstring"add" or "subtract".
qtyrequirednumberQuantity to add or remove (greater than 0).
reasonstringFor subtract: expired, broken or obsolete.
notestringNote for the stock history.

Response 201

{
    "ok": true,
    "data": {
        "id": 3301,
        "product_id": 812,
        "name": "A2 Flyer paper",
        "type": "add",
        "reason": null,
        "qty": 100,
        "qty_before": 380,
        "qty_after": 480,
        "status": "approved",
        "note": "Delivery from Accra Paper Mills",
        "requested_by": "API",
        "requested_at": "2026-10-02 08:15:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/stock/adjustments' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"product_id":812,"direction":"add","qty":100,"reason":null,"note":"Delivery from Accra Paper Mills"}'
Try it Changes live data

Get stock for a product

GET/stock/{id}

Includes the 20 most recent movements as "movements".

Path parameters

idrequiredStock ID.

Response 200

{
    "ok": true,
    "data": {
        "product_id": 812,
        "name": "A2 Flyer paper",
        "unit": "pcs",
        "stock_qty": 480,
        "reorder_level": 50,
        "low_stock": false,
        "sellable": false,
        "unit_cost": 1.1,
        "stock_value": 528
    }
}
curl 'https://www.blueq.app/api/v1/stock/1' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Sales & billing

Invoices /invoices

Quotes (proformas), invoices and receipts. A new invoice starts as a proforma; confirming it turns it into an invoice, and payments turn it into a receipt. Totals (taxes, discount) are recalculated on every change. Creating an invoice counts against the plan's monthly invoice limit.

The invoices object
idread-onlystringInvoice ID (used in URLs and public links).
numberread-onlystringInvoice number, generated automatically.
titlestringInvoice title / subject.
client_idintegerClient the invoice is billed to.
typeread-onlystring"proforma", "invoice" or "receipt".
statusread-onlystring"open", "completed" or "discarded".
confirmedread-onlybooleanWhether the proforma has been confirmed into an invoice.
datedateInvoice date (YYYY-MM-DD).
due_datedatePayment due / quote expiry date.
tax_group_idintegerTax group applied (see /tax-groups). Omit for the default group.
discount_typestring"amount" or "percent".
discount_valuenumberDiscount amount or percentage.
subtotalread-onlynumberSum of line items.
discountread-onlynumberDiscount applied.
tax_totalread-onlynumberLevies + VAT.
totalread-onlynumberGrand total.
paidread-onlynumberPayments received.
balance_dueread-onlynumbertotal − paid.
orders_createdread-onlybooleanProduction orders have been created from this invoice.
public_urlread-onlystringClient-facing link (works once sharing is enabled).
created_atread-onlydatetimeCreated (UTC).
itemsarray<InvoiceItem>Line items (detail view and create).
InvoiceItem object
idread-onlyintegerLine item ID.
product_idintegerCatalogue product/service, or null for a custom line.
namestringLine name. Defaults to the product name.
descriptionstringExtra detail shown under the name.
qtynumberQuantity (default 1).
unit_pricenumberUnit price. Defaults to the product price.
amountread-onlynumberqty × unit_price.
InvoicePayment object
idread-onlyintegerPayment ID.
amountnumberAmount received.
modestringcash, momo, cheque, bank, card, credit (from account balance) or a custom mode.
datedatePayment date (default today).
remarksstringReference or note.
InvoiceReminder object
idread-onlyintegerReminder ID.
remind_atdatetimeWhen to send (YYYY-MM-DD HH:MM, workspace time).
channelsarray<string>Any of email, sms, whatsapp.
messagestringOptional custom message (max 1000).
email_toarray<string>Override recipients (defaults to the client).
sms_toarray<string>Override SMS numbers.
whatsapp_toarray<string>Override WhatsApp numbers.
whatsapp_pdfbooleanAttach the PDF on WhatsApp.
recurstring"", "daily" or "monthly".
recur_untildateLast date for repeating reminders.
statusread-onlystringpending, sent or failed.

List invoices

GET/invoices

Query parameters

page, per_pagePagination (default 25, max 100).
qSearch number or title.
statusopen, completed, discarded, or all (default excludes discarded).
typeproforma, invoice or receipt.
client_idOnly this client.
fromInvoice date on/after.
toInvoice date on/before.
sortdate, total, number, -created_at (default).

Response 200

{
    "ok": true,
    "data": [
        {
            "id": "66f1c2a9e4b1d",
            "number": "0042-37-1026",
            "title": "Branded T-shirts — October order",
            "client_id": 2381,
            "type": "invoice",
            "status": "open",
            "confirmed": true,
            "date": "2026-10-08",
            "due_date": "2026-10-22",
            "tax_group_id": 1,
            "discount_type": "percent",
            "discount_value": 5,
            "subtotal": 1200,
            "discount": 60,
            "tax_total": 199.5,
            "total": 1339.5,
            "paid": 500,
            "balance_due": 839.5,
            "orders_created": false,
            "public_url": "https://blueq.app/link/invoice/66f1c2a9e4b1d",
            "created_at": "2026-10-08 09:14:00",
            "items": [
                {
                    "id": 9811,
                    "product_id": 57,
                    "name": "Round-neck T-shirt (L)",
                    "description": "Navy, 2-colour print front",
                    "qty": 40,
                    "unit_price": 30,
                    "amount": 1200
                }
            ]
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/invoices?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create an invoice Pro

POST/invoices

Pass items[] to add lines in the same call. Set "confirm": true to confirm it straight away.

Body

titlestringInvoice title / subject.
client_idintegerClient the invoice is billed to.
datedateInvoice date (YYYY-MM-DD).
due_datedatePayment due / quote expiry date.
tax_group_idintegerTax group applied (see /tax-groups). Omit for the default group.
discount_typestring"amount" or "percent".
discount_valuenumberDiscount amount or percentage.
itemsarray<InvoiceItem>Line items (detail view and create).

Response 201

{
    "ok": true,
    "data": {
        "id": "66f1c2a9e4b1d",
        "number": "0042-37-1026",
        "title": "Branded T-shirts — October order",
        "client_id": 2381,
        "type": "invoice",
        "status": "open",
        "confirmed": true,
        "date": "2026-10-08",
        "due_date": "2026-10-22",
        "tax_group_id": 1,
        "discount_type": "percent",
        "discount_value": 5,
        "subtotal": 1200,
        "discount": 60,
        "tax_total": 199.5,
        "total": 1339.5,
        "paid": 500,
        "balance_due": 839.5,
        "orders_created": false,
        "public_url": "https://blueq.app/link/invoice/66f1c2a9e4b1d",
        "created_at": "2026-10-08 09:14:00",
        "items": [
            {
                "id": 9811,
                "product_id": 57,
                "name": "Round-neck T-shirt (L)",
                "description": "Navy, 2-colour print front",
                "qty": 40,
                "unit_price": 30,
                "amount": 1200
            }
        ]
    }
}
curl -X POST 'https://www.blueq.app/api/v1/invoices' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"title":"Branded T-shirts — October order","client_id":2381,"date":"2026-10-08","due_date":"2026-10-22","tax_group_id":1,"discount_type":"percent","discount_value":5,"items":[{"product_id":57,"name":"Round-neck T-shirt (L)","description":"Navy, 2-colour print front","qty":40,"unit_price":30}]}'
Try it Changes live data

Get an invoice with items and payments

GET/invoices/{id}

Path parameters

idrequiredInvoices ID.

Response 200

{
    "ok": true,
    "data": {
        "id": "66f1c2a9e4b1d",
        "number": "0042-37-1026",
        "title": "Branded T-shirts — October order",
        "client_id": 2381,
        "type": "invoice",
        "status": "open",
        "confirmed": true,
        "date": "2026-10-08",
        "due_date": "2026-10-22",
        "tax_group_id": 1,
        "discount_type": "percent",
        "discount_value": 5,
        "subtotal": 1200,
        "discount": 60,
        "tax_total": 199.5,
        "total": 1339.5,
        "paid": 500,
        "balance_due": 839.5,
        "orders_created": false,
        "public_url": "https://blueq.app/link/invoice/66f1c2a9e4b1d",
        "created_at": "2026-10-08 09:14:00",
        "items": [
            {
                "id": 9811,
                "product_id": 57,
                "name": "Round-neck T-shirt (L)",
                "description": "Navy, 2-colour print front",
                "qty": 40,
                "unit_price": 30,
                "amount": 1200
            }
        ]
    }
}
curl 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update an invoice Pro

PATCH/invoices/{id}

Client, title, dates, tax group and discount. Line items are locked once a payment is recorded.

Path parameters

idrequiredInvoices ID.

Body

titlestringInvoice title / subject.
client_idintegerClient the invoice is billed to.
datedateInvoice date (YYYY-MM-DD).
due_datedatePayment due / quote expiry date.
tax_group_idintegerTax group applied (see /tax-groups). Omit for the default group.
discount_typestring"amount" or "percent".
discount_valuenumberDiscount amount or percentage.
itemsarray<InvoiceItem>Line items (detail view and create).

Response 200

{
    "ok": true,
    "data": {
        "id": "66f1c2a9e4b1d",
        "number": "0042-37-1026",
        "title": "Branded T-shirts — October order",
        "client_id": 2381,
        "type": "invoice",
        "status": "open",
        "confirmed": true,
        "date": "2026-10-08",
        "due_date": "2026-10-22",
        "tax_group_id": 1,
        "discount_type": "percent",
        "discount_value": 5,
        "subtotal": 1200,
        "discount": 60,
        "tax_total": 199.5,
        "total": 1339.5,
        "paid": 500,
        "balance_due": 839.5,
        "orders_created": false,
        "public_url": "https://blueq.app/link/invoice/66f1c2a9e4b1d",
        "created_at": "2026-10-08 09:14:00",
        "items": [
            {
                "id": 9811,
                "product_id": 57,
                "name": "Round-neck T-shirt (L)",
                "description": "Navy, 2-colour print front",
                "qty": 40,
                "unit_price": 30,
                "amount": 1200
            }
        ]
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"title":"Branded T-shirts — October order","client_id":2381,"date":"2026-10-08","due_date":"2026-10-22","tax_group_id":1,"discount_type":"percent","discount_value":5,"items":[{"product_id":57,"name":"Round-neck T-shirt (L)","description":"Navy, 2-colour print front","qty":40,"unit_price":30}]}'
Try it Changes live data

Discard an invoice Pro

DELETE/invoices/{id}

Invoices are never hard-deleted. Any invoice can be discarded with a note, and restored later.

Path parameters

idrequiredInvoices ID.

Body

noterequiredstringReason (required).

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"note":"Client cancelled"}'
Try it Changes live data

Restore a discarded invoice Pro

POST/invoices/{id}/restore

Path parameters

idrequiredInvoices ID.

Response 200

{
    "ok": true,
    "data": {
        "id": "66f1c2a9e4b1d",
        "number": "0042-37-1026",
        "title": "Branded T-shirts — October order",
        "client_id": 2381,
        "type": "invoice",
        "status": "open",
        "confirmed": true,
        "date": "2026-10-08",
        "due_date": "2026-10-22",
        "tax_group_id": 1,
        "discount_type": "percent",
        "discount_value": 5,
        "subtotal": 1200,
        "discount": 60,
        "tax_total": 199.5,
        "total": 1339.5,
        "paid": 500,
        "balance_due": 839.5,
        "orders_created": false,
        "public_url": "https://blueq.app/link/invoice/66f1c2a9e4b1d",
        "created_at": "2026-10-08 09:14:00",
        "items": [
            {
                "id": 9811,
                "product_id": 57,
                "name": "Round-neck T-shirt (L)",
                "description": "Navy, 2-colour print front",
                "qty": 40,
                "unit_price": 30,
                "amount": 1200
            }
        ]
    }
}
curl -X POST 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/restore' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)"
Try it Changes live data

Confirm a proforma into an invoice Pro

POST/invoices/{id}/confirm

Path parameters

idrequiredInvoices ID.

Response 200

{
    "ok": true,
    "data": {
        "id": "66f1c2a9e4b1d",
        "number": "0042-37-1026",
        "title": "Branded T-shirts — October order",
        "client_id": 2381,
        "type": "invoice",
        "status": "open",
        "confirmed": true,
        "date": "2026-10-08",
        "due_date": "2026-10-22",
        "tax_group_id": 1,
        "discount_type": "percent",
        "discount_value": 5,
        "subtotal": 1200,
        "discount": 60,
        "tax_total": 199.5,
        "total": 1339.5,
        "paid": 500,
        "balance_due": 839.5,
        "orders_created": false,
        "public_url": "https://blueq.app/link/invoice/66f1c2a9e4b1d",
        "created_at": "2026-10-08 09:14:00",
        "items": [
            {
                "id": 9811,
                "product_id": 57,
                "name": "Round-neck T-shirt (L)",
                "description": "Navy, 2-colour print front",
                "qty": 40,
                "unit_price": 30,
                "amount": 1200
            }
        ]
    }
}
curl -X POST 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/confirm' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)"
Try it Changes live data

List line items

GET/invoices/{id}/items

Path parameters

idrequiredInvoices ID.

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 9811,
            "product_id": 57,
            "name": "Round-neck T-shirt (L)",
            "description": "Navy, 2-colour print front",
            "qty": 40,
            "unit_price": 30,
            "amount": 1200
        }
    ]
}
curl 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/items' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Add a line item Pro

POST/invoices/{id}/items

Path parameters

idrequiredInvoices ID.

Body (InvoiceItem)

product_idintegerCatalogue product/service, or null for a custom line.
namestringLine name. Defaults to the product name.
descriptionstringExtra detail shown under the name.
qtynumberQuantity (default 1).
unit_pricenumberUnit price. Defaults to the product price.

Response 201

{
    "ok": true,
    "data": {
        "id": 9811,
        "product_id": 57,
        "name": "Round-neck T-shirt (L)",
        "description": "Navy, 2-colour print front",
        "qty": 40,
        "unit_price": 30,
        "amount": 1200
    }
}
curl -X POST 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/items' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"product_id":57,"name":"Round-neck T-shirt (L)","description":"Navy, 2-colour print front","qty":40,"unit_price":30}'
Try it Changes live data

Update a line item Pro

PATCH/invoices/{id}/items/{item_id}

Path parameters

idrequiredInvoices ID.
item_idrequiredItem id.

Body (InvoiceItem)

product_idintegerCatalogue product/service, or null for a custom line.
namestringLine name. Defaults to the product name.
descriptionstringExtra detail shown under the name.
qtynumberQuantity (default 1).
unit_pricenumberUnit price. Defaults to the product price.

Response 200

{
    "ok": true,
    "data": {
        "id": 9811,
        "product_id": 57,
        "name": "Round-neck T-shirt (L)",
        "description": "Navy, 2-colour print front",
        "qty": 40,
        "unit_price": 30,
        "amount": 1200
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/items/1' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"product_id":57,"name":"Round-neck T-shirt (L)","description":"Navy, 2-colour print front","qty":40,"unit_price":30}'
Try it Changes live data

Remove a line item Pro

DELETE/invoices/{id}/items/{item_id}

Path parameters

idrequiredInvoices ID.
item_idrequiredItem id.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/items/1' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

List payments on the invoice

GET/invoices/{id}/payments

Path parameters

idrequiredInvoices ID.

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 4410,
            "amount": 500,
            "mode": "momo",
            "date": "2026-10-08",
            "remarks": "MoMo ref 77812"
        }
    ]
}
curl 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/payments' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Record a payment Pro

POST/invoices/{id}/payments

Duplicate payments (same client, amount, date and mode) are refused with 409. Send an Idempotency-Key to retry safely.

Path parameters

idrequiredInvoices ID.

Body (InvoicePayment)

amountrequirednumberAmount received.
modestringcash, momo, cheque, bank, card, credit (from account balance) or a custom mode.
datedatePayment date (default today).
remarksstringReference or note.

Response 201

{
    "ok": true,
    "data": {
        "id": 4410,
        "amount": 500,
        "mode": "momo",
        "date": "2026-10-08",
        "remarks": "MoMo ref 77812"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/payments' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"amount":500,"mode":"momo","date":"2026-10-08","remarks":"MoMo ref 77812"}'
Try it Changes live data

Edit a payment Pro

PATCH/invoices/{id}/payments/{payment_id}

Path parameters

idrequiredInvoices ID.
payment_idrequiredPayment id.

Body (InvoicePayment)

amountrequirednumberAmount received.
modestringcash, momo, cheque, bank, card, credit (from account balance) or a custom mode.
datedatePayment date (default today).
remarksstringReference or note.

Response 200

{
    "ok": true,
    "data": {
        "id": 4410,
        "amount": 500,
        "mode": "momo",
        "date": "2026-10-08",
        "remarks": "MoMo ref 77812"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/payments/1' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"amount":500,"mode":"momo","date":"2026-10-08","remarks":"MoMo ref 77812"}'
Try it Changes live data

Delete a payment Pro

DELETE/invoices/{id}/payments/{payment_id}

Path parameters

idrequiredInvoices ID.
payment_idrequiredPayment id.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/payments/1' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Send the invoice to the client Pro

POST/invoices/{id}/send

Sending enables the public share link. SMS and WhatsApp use the workspace's messaging credits.

Path parameters

idrequiredInvoices ID.

Body

channelsarray<string>Any of email, sms, whatsapp (default email).
email_toarray<string>Recipients (defaults to the client).
ccstringCopy to this email.
sms_toarray<string>SMS numbers (defaults to the client).
whatsapp_toarray<string>WhatsApp numbers (defaults to the client).
messagestringMessage shown with the link.
attach_pdfbooleanInclude the PDF link.

Response 200

{
    "ok": true,
    "data": {
        "id": "66f1c2a9e4b1d",
        "number": "0042-37-1026",
        "title": "Branded T-shirts — October order",
        "client_id": 2381,
        "type": "invoice",
        "status": "open",
        "confirmed": true,
        "date": "2026-10-08",
        "due_date": "2026-10-22",
        "tax_group_id": 1,
        "discount_type": "percent",
        "discount_value": 5,
        "subtotal": 1200,
        "discount": 60,
        "tax_total": 199.5,
        "total": 1339.5,
        "paid": 500,
        "balance_due": 839.5,
        "orders_created": false,
        "public_url": "https://blueq.app/link/invoice/66f1c2a9e4b1d",
        "created_at": "2026-10-08 09:14:00",
        "items": [
            {
                "id": 9811,
                "product_id": 57,
                "name": "Round-neck T-shirt (L)",
                "description": "Navy, 2-colour print front",
                "qty": 40,
                "unit_price": 30,
                "amount": 1200
            }
        ]
    }
}
curl -X POST 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/send' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"channels":["email","whatsapp"],"email_to":["akosua@example.com"],"cc":"accounts@kenteprints.com","sms_to":[],"whatsapp_to":[],"message":"Please find your invoice below.","attach_pdf":true}'
Try it Changes live data

Get the PDF link

GET/invoices/{id}/pdf

Same as /links. The PDF is rendered by the public print page, so turn sharing on first if the client should open it.

Path parameters

idrequiredInvoices ID.

Response 200

{
    "ok": true,
    "data": {
        "id": "66f1c2a9e4b1d",
        "number": "0042-37-1026",
        "title": "Branded T-shirts — October order",
        "client_id": 2381,
        "type": "invoice",
        "status": "open",
        "confirmed": true,
        "date": "2026-10-08",
        "due_date": "2026-10-22",
        "tax_group_id": 1,
        "discount_type": "percent",
        "discount_value": 5,
        "subtotal": 1200,
        "discount": 60,
        "tax_total": 199.5,
        "total": 1339.5,
        "paid": 500,
        "balance_due": 839.5,
        "orders_created": false,
        "public_url": "https://blueq.app/link/invoice/66f1c2a9e4b1d",
        "created_at": "2026-10-08 09:14:00",
        "items": [
            {
                "id": 9811,
                "product_id": 57,
                "name": "Round-neck T-shirt (L)",
                "description": "Navy, 2-colour print front",
                "qty": 40,
                "unit_price": 30,
                "amount": 1200
            }
        ]
    }
}
curl 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/pdf' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Turn the public link on or off Pro

POST/invoices/{id}/share

Path parameters

idrequiredInvoices ID.

Body

enabledrequiredbooleanShare on/off.

Response 200

{
    "ok": true,
    "data": {
        "id": "66f1c2a9e4b1d",
        "number": "0042-37-1026",
        "title": "Branded T-shirts — October order",
        "client_id": 2381,
        "type": "invoice",
        "status": "open",
        "confirmed": true,
        "date": "2026-10-08",
        "due_date": "2026-10-22",
        "tax_group_id": 1,
        "discount_type": "percent",
        "discount_value": 5,
        "subtotal": 1200,
        "discount": 60,
        "tax_total": 199.5,
        "total": 1339.5,
        "paid": 500,
        "balance_due": 839.5,
        "orders_created": false,
        "public_url": "https://blueq.app/link/invoice/66f1c2a9e4b1d",
        "created_at": "2026-10-08 09:14:00",
        "items": [
            {
                "id": 9811,
                "product_id": 57,
                "name": "Round-neck T-shirt (L)",
                "description": "Navy, 2-colour print front",
                "qty": 40,
                "unit_price": 30,
                "amount": 1200
            }
        ]
    }
}
curl -X POST 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/share' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"enabled":true}'
Try it Changes live data

List scheduled reminders

GET/invoices/{id}/reminders

Path parameters

idrequiredInvoices ID.

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 18,
            "remind_at": "2026-10-20 09:00",
            "channels": [
                "email",
                "sms"
            ],
            "message": "Friendly reminder — balance due Friday.",
            "email_to": [],
            "sms_to": [],
            "whatsapp_to": [],
            "whatsapp_pdf": false,
            "recur": "monthly",
            "recur_until": "2026-12-31",
            "status": "pending"
        }
    ]
}
curl 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/reminders' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Schedule a reminder Pro

POST/invoices/{id}/reminders

Path parameters

idrequiredInvoices ID.

Body (InvoiceReminder)

remind_atrequireddatetimeWhen to send (YYYY-MM-DD HH:MM, workspace time).
channelsrequiredarray<string>Any of email, sms, whatsapp.
messagestringOptional custom message (max 1000).
email_toarray<string>Override recipients (defaults to the client).
sms_toarray<string>Override SMS numbers.
whatsapp_toarray<string>Override WhatsApp numbers.
whatsapp_pdfbooleanAttach the PDF on WhatsApp.
recurstring"", "daily" or "monthly".
recur_untildateLast date for repeating reminders.

Response 201

{
    "ok": true,
    "data": {
        "id": 18,
        "remind_at": "2026-10-20 09:00",
        "channels": [
            "email",
            "sms"
        ],
        "message": "Friendly reminder — balance due Friday.",
        "email_to": [],
        "sms_to": [],
        "whatsapp_to": [],
        "whatsapp_pdf": false,
        "recur": "monthly",
        "recur_until": "2026-12-31",
        "status": "pending"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/reminders' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"remind_at":"2026-10-20 09:00","channels":["email","sms"],"message":"Friendly reminder — balance due Friday.","email_to":[],"sms_to":[],"whatsapp_to":[],"whatsapp_pdf":false,"recur":"monthly","recur_until":"2026-12-31"}'
Try it Changes live data

Change a pending reminder Pro

PATCH/invoices/{id}/reminders/{reminder_id}

Path parameters

idrequiredInvoices ID.
reminder_idrequiredReminder id.

Body (InvoiceReminder)

remind_atrequireddatetimeWhen to send (YYYY-MM-DD HH:MM, workspace time).
channelsrequiredarray<string>Any of email, sms, whatsapp.
messagestringOptional custom message (max 1000).
email_toarray<string>Override recipients (defaults to the client).
sms_toarray<string>Override SMS numbers.
whatsapp_toarray<string>Override WhatsApp numbers.
whatsapp_pdfbooleanAttach the PDF on WhatsApp.
recurstring"", "daily" or "monthly".
recur_untildateLast date for repeating reminders.

Response 200

{
    "ok": true,
    "data": {
        "id": 18,
        "remind_at": "2026-10-20 09:00",
        "channels": [
            "email",
            "sms"
        ],
        "message": "Friendly reminder — balance due Friday.",
        "email_to": [],
        "sms_to": [],
        "whatsapp_to": [],
        "whatsapp_pdf": false,
        "recur": "monthly",
        "recur_until": "2026-12-31",
        "status": "pending"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/reminders/1' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"remind_at":"2026-10-20 09:00","channels":["email","sms"],"message":"Friendly reminder — balance due Friday.","email_to":[],"sms_to":[],"whatsapp_to":[],"whatsapp_pdf":false,"recur":"monthly","recur_until":"2026-12-31"}'
Try it Changes live data

Cancel a reminder Pro

DELETE/invoices/{id}/reminders/{reminder_id}

Path parameters

idrequiredInvoices ID.
reminder_idrequiredReminder id.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/invoices/66f1c2a9e4b1d/reminders/1' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Sales & billing

Payments /payments

Every payment received against invoices, across the workspace. Read-only here — record, edit or delete payments through /invoices/{id}/payments so invoice balances stay in sync.

The payments object
idread-onlyintegerPayment ID.
client_idread-onlyintegerClient who paid.
invoice_idread-onlystringInvoice the payment belongs to (null for account top-ups).
amountread-onlynumberAmount received.
moderead-onlystringcash, momo, cheque, bank, card, credit or a custom mode.
dateread-onlydatePayment date.
remarksread-onlystringReference or note.
created_atread-onlydatetimeWhen it was recorded (UTC).

List payments

GET/payments

Query parameters

page, per_pagePagination (default 25, max 100).
qSearch remarks or mode.
client_idOnly this client.
invoice_idOnly this invoice.
modeOnly this payment mode.
fromPayment date on/after.
toPayment date on/before.
sortdate, amount, -created_at (default).

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 4410,
            "client_id": 2381,
            "invoice_id": "66f1c2a9e4b1d",
            "amount": 500,
            "mode": "momo",
            "date": "2026-10-08",
            "remarks": "MoMo ref 77812",
            "created_at": "2026-10-08 11:02:40"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/payments?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Get a payment

GET/payments/{id}

Path parameters

idrequiredPayments ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 4410,
        "client_id": 2381,
        "invoice_id": "66f1c2a9e4b1d",
        "amount": 500,
        "mode": "momo",
        "date": "2026-10-08",
        "remarks": "MoMo ref 77812",
        "created_at": "2026-10-08 11:02:40"
    }
}
curl 'https://www.blueq.app/api/v1/payments/4410' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Sales & billing

Sales /sales

Counter and walk-in sales recorded outside of invoices (multi-item carts with optional payments, discount and taxes). Creating a sale counts against the plan's monthly sales limit.

The sales object
idread-onlyintegerSale ID.
datedateSale date (default today).
client_idintegerClient, or null for a walk-in.
customerstringCustomer name for walk-ins (defaults to "Walk-in").
branch_idread-onlyintegerBranch the sale was recorded at.
itemsarray<SaleItem>Cart lines (at least one).
paymentsarray<SalePayment>Payments taken. Omit for an unpaid sale.
discount_typestring"amount" or "percent".
discount_valuenumberDiscount amount or percentage.
tax_group_idintegerTax group to apply, "default", or null for no tax.
remarksstringInternal note.
subtotalread-onlynumberSum of lines.
discountread-onlynumberDiscount applied.
tax_totalread-onlynumberTaxes added.
totalread-onlynumberGrand total.
paidread-onlynumberTotal paid.
balance_dueread-onlynumbertotal − paid.
payment_statusread-onlystringpaid, partial or unpaid.
invoice_idread-onlystringInvoice generated from this sale, if any.
created_atread-onlydatetimeRecorded (UTC).
SaleItem object
product_idintegerCatalogue product/service. Price defaults to its price.
namestringLine name (required without product_id).
categorystringSales category for custom lines (default "Counter / walk-in sales").
qtynumberQuantity.
unit_pricenumberUnit price.
amountread-onlynumberqty × unit_price.
SalePayment object
amountnumberAmount.
modestringcash, momo, card, bank, cheque…
datedatePayment date (default sale date).
referencestringReference number.

List sales

GET/sales

Query parameters

page, per_pagePagination (default 25, max 100).
qSearch customer, product summary or remarks.
statuspaid, partial or unpaid.
client_idOnly this client.
fromSale date on/after.
toSale date on/before.
sortdate, total, -created_at (default).

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 812,
            "date": "2026-10-08",
            "client_id": 2381,
            "customer": "Walk-in",
            "branch_id": 1,
            "items": [
                {
                    "product_id": 57,
                    "name": "Lamination A3",
                    "category": "Counter / walk-in sales",
                    "qty": 2,
                    "unit_price": 125,
                    "amount": 250
                }
            ],
            "payments": [
                {
                    "amount": 250,
                    "mode": "cash",
                    "date": "2026-10-08",
                    "reference": ""
                }
            ],
            "discount_type": "amount",
            "discount_value": 0,
            "tax_group_id": null,
            "remarks": "Paid at the counter",
            "subtotal": 250,
            "discount": 0,
            "tax_total": 0,
            "total": 250,
            "paid": 250,
            "balance_due": 0,
            "payment_status": "paid",
            "invoice_id": null,
            "created_at": "2026-10-08 13:40:11"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/sales?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Record a sale Pro

POST/sales

Body

datedateSale date (default today).
client_idintegerClient, or null for a walk-in.
customerstringCustomer name for walk-ins (defaults to "Walk-in").
itemsrequiredarray<SaleItem>Cart lines (at least one).
paymentsarray<SalePayment>Payments taken. Omit for an unpaid sale.
discount_typestring"amount" or "percent".
discount_valuenumberDiscount amount or percentage.
tax_group_idintegerTax group to apply, "default", or null for no tax.
remarksstringInternal note.

Response 201

{
    "ok": true,
    "data": {
        "id": 812,
        "date": "2026-10-08",
        "client_id": 2381,
        "customer": "Walk-in",
        "branch_id": 1,
        "items": [
            {
                "product_id": 57,
                "name": "Lamination A3",
                "category": "Counter / walk-in sales",
                "qty": 2,
                "unit_price": 125,
                "amount": 250
            }
        ],
        "payments": [
            {
                "amount": 250,
                "mode": "cash",
                "date": "2026-10-08",
                "reference": ""
            }
        ],
        "discount_type": "amount",
        "discount_value": 0,
        "tax_group_id": null,
        "remarks": "Paid at the counter",
        "subtotal": 250,
        "discount": 0,
        "tax_total": 0,
        "total": 250,
        "paid": 250,
        "balance_due": 0,
        "payment_status": "paid",
        "invoice_id": null,
        "created_at": "2026-10-08 13:40:11"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/sales' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"date":"2026-10-08","client_id":2381,"customer":"Walk-in","items":[{"product_id":57,"name":"Lamination A3","category":"Counter / walk-in sales","qty":2,"unit_price":125}],"payments":[{"amount":250,"mode":"cash","date":"2026-10-08","reference":""}],"discount_type":"amount","discount_value":0,"tax_group_id":null,"remarks":"Paid at the counter"}'
Try it Changes live data

Get a sale with items and payments

GET/sales/{id}

Path parameters

idrequiredSales ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 812,
        "date": "2026-10-08",
        "client_id": 2381,
        "customer": "Walk-in",
        "branch_id": 1,
        "items": [
            {
                "product_id": 57,
                "name": "Lamination A3",
                "category": "Counter / walk-in sales",
                "qty": 2,
                "unit_price": 125,
                "amount": 250
            }
        ],
        "payments": [
            {
                "amount": 250,
                "mode": "cash",
                "date": "2026-10-08",
                "reference": ""
            }
        ],
        "discount_type": "amount",
        "discount_value": 0,
        "tax_group_id": null,
        "remarks": "Paid at the counter",
        "subtotal": 250,
        "discount": 0,
        "tax_total": 0,
        "total": 250,
        "paid": 250,
        "balance_due": 0,
        "payment_status": "paid",
        "invoice_id": null,
        "created_at": "2026-10-08 13:40:11"
    }
}
curl 'https://www.blueq.app/api/v1/sales/812' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a sale Pro

PATCH/sales/{id}

Fields you omit are kept. Passing items or payments replaces the whole list.

Path parameters

idrequiredSales ID.

Body

datedateSale date (default today).
client_idintegerClient, or null for a walk-in.
customerstringCustomer name for walk-ins (defaults to "Walk-in").
itemsarray<SaleItem>Cart lines (at least one).
paymentsarray<SalePayment>Payments taken. Omit for an unpaid sale.
discount_typestring"amount" or "percent".
discount_valuenumberDiscount amount or percentage.
tax_group_idintegerTax group to apply, "default", or null for no tax.
remarksstringInternal note.

Response 200

{
    "ok": true,
    "data": {
        "id": 812,
        "date": "2026-10-08",
        "client_id": 2381,
        "customer": "Walk-in",
        "branch_id": 1,
        "items": [
            {
                "product_id": 57,
                "name": "Lamination A3",
                "category": "Counter / walk-in sales",
                "qty": 2,
                "unit_price": 125,
                "amount": 250
            }
        ],
        "payments": [
            {
                "amount": 250,
                "mode": "cash",
                "date": "2026-10-08",
                "reference": ""
            }
        ],
        "discount_type": "amount",
        "discount_value": 0,
        "tax_group_id": null,
        "remarks": "Paid at the counter",
        "subtotal": 250,
        "discount": 0,
        "tax_total": 0,
        "total": 250,
        "paid": 250,
        "balance_due": 0,
        "payment_status": "paid",
        "invoice_id": null,
        "created_at": "2026-10-08 13:40:11"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/sales/812' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"date":"2026-10-08","client_id":2381,"customer":"Walk-in","items":[{"product_id":57,"name":"Lamination A3","category":"Counter / walk-in sales","qty":2,"unit_price":125}],"payments":[{"amount":250,"mode":"cash","date":"2026-10-08","reference":""}],"discount_type":"amount","discount_value":0,"tax_group_id":null,"remarks":"Paid at the counter"}'
Try it Changes live data

Delete a sale Pro

DELETE/sales/{id}

Path parameters

idrequiredSales ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/sales/812' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Add a payment to a sale Pro

POST/sales/{id}/payments

Path parameters

idrequiredSales ID.

Body (SalePayment)

amountrequirednumberAmount.
modestringcash, momo, card, bank, cheque…
datedatePayment date (default sale date).
referencestringReference number.

Response 201

{
    "ok": true,
    "data": {
        "id": 812,
        "date": "2026-10-08",
        "client_id": 2381,
        "customer": "Walk-in",
        "branch_id": 1,
        "items": [
            {
                "product_id": 57,
                "name": "Lamination A3",
                "category": "Counter / walk-in sales",
                "qty": 2,
                "unit_price": 125,
                "amount": 250
            }
        ],
        "payments": [
            {
                "amount": 250,
                "mode": "cash",
                "date": "2026-10-08",
                "reference": ""
            }
        ],
        "discount_type": "amount",
        "discount_value": 0,
        "tax_group_id": null,
        "remarks": "Paid at the counter",
        "subtotal": 250,
        "discount": 0,
        "tax_total": 0,
        "total": 250,
        "paid": 250,
        "balance_due": 0,
        "payment_status": "paid",
        "invoice_id": null,
        "created_at": "2026-10-08 13:40:11"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/sales/812/payments' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"amount":250,"mode":"cash","date":"2026-10-08","reference":""}'
Try it Changes live data

Generate an invoice from the sale Pro

POST/sales/{id}/invoice

Returns the existing invoice if one was already generated.

Path parameters

idrequiredSales ID.

Response 201

{
    "ok": true,
    "data": {
        "id": 812,
        "date": "2026-10-08",
        "client_id": 2381,
        "customer": "Walk-in",
        "branch_id": 1,
        "items": [
            {
                "product_id": 57,
                "name": "Lamination A3",
                "category": "Counter / walk-in sales",
                "qty": 2,
                "unit_price": 125,
                "amount": 250
            }
        ],
        "payments": [
            {
                "amount": 250,
                "mode": "cash",
                "date": "2026-10-08",
                "reference": ""
            }
        ],
        "discount_type": "amount",
        "discount_value": 0,
        "tax_group_id": null,
        "remarks": "Paid at the counter",
        "subtotal": 250,
        "discount": 0,
        "tax_total": 0,
        "total": 250,
        "paid": 250,
        "balance_due": 0,
        "payment_status": "paid",
        "invoice_id": null,
        "created_at": "2026-10-08 13:40:11"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/sales/812/invoice' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)"
Try it Changes live data

Sales & billing

Expenditure /expenditure

Business expenses with line items, supplier, and payments. Lines whose expense type is set to add stock (or that reference a stock product) update inventory automatically, exactly as in the app. Creating an expense counts against the plan's monthly expenditure limit.

The expenditure object
idread-onlyintegerExpense ID.
datedateExpense date (default today).
payeestringWho was paid (defaults to the supplier name).
supplier_idintegerSupplier (see /suppliers).
supplier_invoicestringSupplier's invoice / bill number.
linked_invoice_idstringCustomer invoice this cost belongs to (job costing).
categoryread-onlystringSummary of the line expense types.
itemsarray<ExpenseItem>Lines (at least one).
paymentsarray<ExpensePayment>Payments made. Omit for an unpaid bill.
totalread-onlynumberTotal of all lines.
paidread-onlynumberTotal paid.
balance_dueread-onlynumbertotal − paid.
payment_statusread-onlystringpaid, partial or unpaid.
branch_idread-onlyintegerBranch.
created_atread-onlydatetimeSaved (UTC).
ExpenseItem object
product_idintegerStock product this purchase is for (adds to stock).
namestringLine description (required without product_id).
categorystringExpense type, e.g. "Raw materials", "Utilities", "Transport".
qtynumberQuantity.
unitstringUnit of measure (pcs, box, kg, l, m…).
unit_pricenumberPrice per unit (> 0).
amountread-onlynumberqty × unit_price.
remarksstringLine note.
expiry_datedateExpiry date for perishable stock.
ExpensePayment object
amountnumberAmount paid.
modestringcash, momo, bank, cheque…
datedatePayment date (default expense date).
referencestringReference.
remarksstringNote.

List expenses

GET/expenditure

Query parameters

page, per_pagePagination (default 25, max 100).
qSearch payee, category or remarks.
statuspaid, partial or unpaid.
supplier_idOnly this supplier.
fromExpense date on/after.
toExpense date on/before.
sortdate, total, -created_at (default).

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 305,
            "date": "2026-10-07",
            "payee": "Accra Paper Mills",
            "supplier_id": 14,
            "supplier_invoice": "APM-55120",
            "linked_invoice_id": null,
            "category": "Raw materials",
            "items": [
                {
                    "product_id": 61,
                    "name": "A4 paper 80gsm (box)",
                    "category": "Raw materials",
                    "qty": 10,
                    "unit": "box",
                    "unit_price": 145,
                    "amount": 1450,
                    "remarks": "",
                    "expiry_date": null
                }
            ],
            "payments": [
                {
                    "amount": 1450,
                    "mode": "momo",
                    "date": "2026-10-07",
                    "reference": "MOMO-99812",
                    "remarks": ""
                }
            ],
            "total": 1450,
            "paid": 1450,
            "balance_due": 0,
            "payment_status": "paid",
            "branch_id": 1,
            "created_at": "2026-10-07 16:20:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/expenditure?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Record an expense Pro

POST/expenditure

Body

datedateExpense date (default today).
payeestringWho was paid (defaults to the supplier name).
supplier_idintegerSupplier (see /suppliers).
supplier_invoicestringSupplier's invoice / bill number.
linked_invoice_idstringCustomer invoice this cost belongs to (job costing).
itemsrequiredarray<ExpenseItem>Lines (at least one).
paymentsarray<ExpensePayment>Payments made. Omit for an unpaid bill.

Response 201

{
    "ok": true,
    "data": {
        "id": 305,
        "date": "2026-10-07",
        "payee": "Accra Paper Mills",
        "supplier_id": 14,
        "supplier_invoice": "APM-55120",
        "linked_invoice_id": null,
        "category": "Raw materials",
        "items": [
            {
                "product_id": 61,
                "name": "A4 paper 80gsm (box)",
                "category": "Raw materials",
                "qty": 10,
                "unit": "box",
                "unit_price": 145,
                "amount": 1450,
                "remarks": "",
                "expiry_date": null
            }
        ],
        "payments": [
            {
                "amount": 1450,
                "mode": "momo",
                "date": "2026-10-07",
                "reference": "MOMO-99812",
                "remarks": ""
            }
        ],
        "total": 1450,
        "paid": 1450,
        "balance_due": 0,
        "payment_status": "paid",
        "branch_id": 1,
        "created_at": "2026-10-07 16:20:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/expenditure' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"date":"2026-10-07","payee":"Accra Paper Mills","supplier_id":14,"supplier_invoice":"APM-55120","linked_invoice_id":null,"items":[{"product_id":61,"name":"A4 paper 80gsm (box)","category":"Raw materials","qty":10,"unit":"box","unit_price":145,"remarks":"","expiry_date":null}],"payments":[{"amount":1450,"mode":"momo","date":"2026-10-07","reference":"MOMO-99812","remarks":""}]}'
Try it Changes live data

Get an expense with lines and payments

GET/expenditure/{id}

Path parameters

idrequiredExpenditure ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 305,
        "date": "2026-10-07",
        "payee": "Accra Paper Mills",
        "supplier_id": 14,
        "supplier_invoice": "APM-55120",
        "linked_invoice_id": null,
        "category": "Raw materials",
        "items": [
            {
                "product_id": 61,
                "name": "A4 paper 80gsm (box)",
                "category": "Raw materials",
                "qty": 10,
                "unit": "box",
                "unit_price": 145,
                "amount": 1450,
                "remarks": "",
                "expiry_date": null
            }
        ],
        "payments": [
            {
                "amount": 1450,
                "mode": "momo",
                "date": "2026-10-07",
                "reference": "MOMO-99812",
                "remarks": ""
            }
        ],
        "total": 1450,
        "paid": 1450,
        "balance_due": 0,
        "payment_status": "paid",
        "branch_id": 1,
        "created_at": "2026-10-07 16:20:00"
    }
}
curl 'https://www.blueq.app/api/v1/expenditure/305' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update an expense Pro

PATCH/expenditure/{id}

Omitted fields are kept. Passing items or payments replaces the whole list; stock is re-balanced.

Path parameters

idrequiredExpenditure ID.

Body

datedateExpense date (default today).
payeestringWho was paid (defaults to the supplier name).
supplier_idintegerSupplier (see /suppliers).
supplier_invoicestringSupplier's invoice / bill number.
linked_invoice_idstringCustomer invoice this cost belongs to (job costing).
itemsarray<ExpenseItem>Lines (at least one).
paymentsarray<ExpensePayment>Payments made. Omit for an unpaid bill.

Response 200

{
    "ok": true,
    "data": {
        "id": 305,
        "date": "2026-10-07",
        "payee": "Accra Paper Mills",
        "supplier_id": 14,
        "supplier_invoice": "APM-55120",
        "linked_invoice_id": null,
        "category": "Raw materials",
        "items": [
            {
                "product_id": 61,
                "name": "A4 paper 80gsm (box)",
                "category": "Raw materials",
                "qty": 10,
                "unit": "box",
                "unit_price": 145,
                "amount": 1450,
                "remarks": "",
                "expiry_date": null
            }
        ],
        "payments": [
            {
                "amount": 1450,
                "mode": "momo",
                "date": "2026-10-07",
                "reference": "MOMO-99812",
                "remarks": ""
            }
        ],
        "total": 1450,
        "paid": 1450,
        "balance_due": 0,
        "payment_status": "paid",
        "branch_id": 1,
        "created_at": "2026-10-07 16:20:00"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/expenditure/305' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"date":"2026-10-07","payee":"Accra Paper Mills","supplier_id":14,"supplier_invoice":"APM-55120","linked_invoice_id":null,"items":[{"product_id":61,"name":"A4 paper 80gsm (box)","category":"Raw materials","qty":10,"unit":"box","unit_price":145,"remarks":"","expiry_date":null}],"payments":[{"amount":1450,"mode":"momo","date":"2026-10-07","reference":"MOMO-99812","remarks":""}]}'
Try it Changes live data

Delete an expense Pro

DELETE/expenditure/{id}

Stock added by the expense is reversed.

Path parameters

idrequiredExpenditure ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/expenditure/305' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Add a payment to an expense Pro

POST/expenditure/{id}/payments

Path parameters

idrequiredExpenditure ID.

Body (ExpensePayment)

amountrequirednumberAmount paid.
modestringcash, momo, bank, cheque…
datedatePayment date (default expense date).
referencestringReference.
remarksstringNote.

Response 201

{
    "ok": true,
    "data": {
        "id": 305,
        "date": "2026-10-07",
        "payee": "Accra Paper Mills",
        "supplier_id": 14,
        "supplier_invoice": "APM-55120",
        "linked_invoice_id": null,
        "category": "Raw materials",
        "items": [
            {
                "product_id": 61,
                "name": "A4 paper 80gsm (box)",
                "category": "Raw materials",
                "qty": 10,
                "unit": "box",
                "unit_price": 145,
                "amount": 1450,
                "remarks": "",
                "expiry_date": null
            }
        ],
        "payments": [
            {
                "amount": 1450,
                "mode": "momo",
                "date": "2026-10-07",
                "reference": "MOMO-99812",
                "remarks": ""
            }
        ],
        "total": 1450,
        "paid": 1450,
        "balance_due": 0,
        "payment_status": "paid",
        "branch_id": 1,
        "created_at": "2026-10-07 16:20:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/expenditure/305/payments' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"amount":1450,"mode":"momo","date":"2026-10-07","reference":"MOMO-99812","remarks":""}'
Try it Changes live data

Operations

Orders (production) /orders

Production orders created from invoices. Each invoice line becomes a job; each job holds one or more orders that move through pending → in_progress → completed (or cancelled). Orders can carry processes (e.g. "Design", "Print", "Laminate") that are ticked off as work progresses.

The orders (production) object
idread-onlyintegerOrder ID.
job_idintegerJob (invoice line group) this order belongs to.
invoice_idread-onlystringSource invoice.
titlestringWhat is being produced.
product_idintegerCatalogue product/service, if any.
qtynumberQuantity.
statusread-onlystringpending, in_progress, completed or cancelled.
assigned_tointegerTeam member (see /users).
start_atdatetimePlanned start (YYYY-MM-DD HH:MM).
end_atdatetimePlanned finish.
remarksstringProduction notes.
started_atread-onlydatetimeWhen work began (UTC).
completed_atread-onlydatetimeWhen completed (UTC).
cancelled_atread-onlydatetimeWhen cancelled (UTC).
processesarray<OrderProcess>Processes (detail view). On create, pass an array of names.
created_atread-onlydatetimeCreated (UTC).
OrderProcess object
idread-onlyintegerProcess ID.
nameread-onlystringProcess name.
doneread-onlybooleanCompleted.
completed_atread-onlydatetimeWhen it was ticked off (UTC).
Job object
idread-onlyintegerJob ID.
invoice_idread-onlystringInvoice.
invoice_item_idread-onlyintegerInvoice line.
titleread-onlystringLine name.
qtyread-onlynumberLine quantity.
order_countread-onlyintegerOrders under this job.
created_atread-onlydatetimeCreated (UTC).

List orders

GET/orders

Query parameters

page, per_pagePagination (default 25, max 100).
statuspending, in_progress, completed or cancelled.
invoice_idOnly orders from this invoice.
job_idOnly orders in this job.
assigned_toOnly orders assigned to this user.
qSearch title or remarks.
fromCreated on/after.
toCreated on/before.
sortstart_at, end_at, -created_at (default).

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 1204,
            "job_id": 388,
            "invoice_id": "66f1c2a9e4b1d",
            "title": "Round-neck T-shirt (L) — navy",
            "product_id": 57,
            "qty": 40,
            "status": "in_progress",
            "assigned_to": 77,
            "start_at": "2026-10-09 08:00",
            "end_at": "2026-10-10 17:00",
            "remarks": "Use Pantone 2945C",
            "started_at": "2026-10-09 08:12:00",
            "completed_at": null,
            "cancelled_at": null,
            "processes": [
                {
                    "id": 5521,
                    "name": "Screen print",
                    "done": false,
                    "completed_at": null
                }
            ],
            "created_at": "2026-10-08 15:00:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/orders?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create an order inside a job Pro

POST/orders

Body

job_idrequiredintegerJob (invoice line group) this order belongs to.
titlerequiredstringWhat is being produced.
product_idintegerCatalogue product/service, if any.
qtynumberQuantity.
assigned_tointegerTeam member (see /users).
start_atdatetimePlanned start (YYYY-MM-DD HH:MM).
end_atdatetimePlanned finish.
remarksstringProduction notes.
processesarray<OrderProcess>Processes (detail view). On create, pass an array of names.

Response 201

{
    "ok": true,
    "data": {
        "id": 1204,
        "job_id": 388,
        "invoice_id": "66f1c2a9e4b1d",
        "title": "Round-neck T-shirt (L) — navy",
        "product_id": 57,
        "qty": 40,
        "status": "in_progress",
        "assigned_to": 77,
        "start_at": "2026-10-09 08:00",
        "end_at": "2026-10-10 17:00",
        "remarks": "Use Pantone 2945C",
        "started_at": "2026-10-09 08:12:00",
        "completed_at": null,
        "cancelled_at": null,
        "processes": [
            {
                "id": 5521,
                "name": "Screen print",
                "done": false,
                "completed_at": null
            }
        ],
        "created_at": "2026-10-08 15:00:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/orders' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"job_id":388,"title":"Round-neck T-shirt (L) — navy","product_id":57,"qty":40,"assigned_to":77,"start_at":"2026-10-09 08:00","end_at":"2026-10-10 17:00","remarks":"Use Pantone 2945C","processes":[[]]}'
Try it Changes live data

List jobs (invoice line groups)

GET/orders/jobs

Query parameters

page, per_pagePagination (default 25, max 100).
invoice_idOnly jobs from this invoice.

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 388,
            "invoice_id": "66f1c2a9e4b1d",
            "invoice_item_id": 9811,
            "title": "Round-neck T-shirt (L)",
            "qty": 40,
            "order_count": 1,
            "created_at": "2026-10-08 15:00:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/orders/jobs?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create jobs and orders from an invoice Pro

POST/orders/from-invoice

One job is created per line item. Requires the invoice to be eligible for production (confirmed / paid per your settings).

Body

invoice_idrequiredstringInvoice to produce.

Response 201

{
    "ok": true,
    "data": {
        "id": 388,
        "invoice_id": "66f1c2a9e4b1d",
        "invoice_item_id": 9811,
        "title": "Round-neck T-shirt (L)",
        "qty": 40,
        "order_count": 1,
        "created_at": "2026-10-08 15:00:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/orders/from-invoice' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"invoice_id":"66f1c2a9e4b1d"}'
Try it Changes live data

Get an order with its processes

GET/orders/{id}

Path parameters

idrequiredOrders (production) ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 1204,
        "job_id": 388,
        "invoice_id": "66f1c2a9e4b1d",
        "title": "Round-neck T-shirt (L) — navy",
        "product_id": 57,
        "qty": 40,
        "status": "in_progress",
        "assigned_to": 77,
        "start_at": "2026-10-09 08:00",
        "end_at": "2026-10-10 17:00",
        "remarks": "Use Pantone 2945C",
        "started_at": "2026-10-09 08:12:00",
        "completed_at": null,
        "cancelled_at": null,
        "processes": [
            {
                "id": 5521,
                "name": "Screen print",
                "done": false,
                "completed_at": null
            }
        ],
        "created_at": "2026-10-08 15:00:00"
    }
}
curl 'https://www.blueq.app/api/v1/orders/1204' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a pending order Pro

PATCH/orders/{id}

Path parameters

idrequiredOrders (production) ID.

Body

job_idintegerJob (invoice line group) this order belongs to.
titlestringWhat is being produced.
product_idintegerCatalogue product/service, if any.
qtynumberQuantity.
assigned_tointegerTeam member (see /users).
start_atdatetimePlanned start (YYYY-MM-DD HH:MM).
end_atdatetimePlanned finish.
remarksstringProduction notes.
processesarray<OrderProcess>Processes (detail view). On create, pass an array of names.

Response 200

{
    "ok": true,
    "data": {
        "id": 1204,
        "job_id": 388,
        "invoice_id": "66f1c2a9e4b1d",
        "title": "Round-neck T-shirt (L) — navy",
        "product_id": 57,
        "qty": 40,
        "status": "in_progress",
        "assigned_to": 77,
        "start_at": "2026-10-09 08:00",
        "end_at": "2026-10-10 17:00",
        "remarks": "Use Pantone 2945C",
        "started_at": "2026-10-09 08:12:00",
        "completed_at": null,
        "cancelled_at": null,
        "processes": [
            {
                "id": 5521,
                "name": "Screen print",
                "done": false,
                "completed_at": null
            }
        ],
        "created_at": "2026-10-08 15:00:00"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/orders/1204' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"job_id":388,"title":"Round-neck T-shirt (L) — navy","product_id":57,"qty":40,"assigned_to":77,"start_at":"2026-10-09 08:00","end_at":"2026-10-10 17:00","remarks":"Use Pantone 2945C","processes":[[]]}'
Try it Changes live data

Delete a pending order Pro

DELETE/orders/{id}

Path parameters

idrequiredOrders (production) ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/orders/1204' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Start work on an order Pro

POST/orders/{id}/commence

Path parameters

idrequiredOrders (production) ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 1204,
        "job_id": 388,
        "invoice_id": "66f1c2a9e4b1d",
        "title": "Round-neck T-shirt (L) — navy",
        "product_id": 57,
        "qty": 40,
        "status": "in_progress",
        "assigned_to": 77,
        "start_at": "2026-10-09 08:00",
        "end_at": "2026-10-10 17:00",
        "remarks": "Use Pantone 2945C",
        "started_at": "2026-10-09 08:12:00",
        "completed_at": null,
        "cancelled_at": null,
        "processes": [
            {
                "id": 5521,
                "name": "Screen print",
                "done": false,
                "completed_at": null
            }
        ],
        "created_at": "2026-10-08 15:00:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/orders/1204/commence' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)"
Try it Changes live data

Complete an order Pro

POST/orders/{id}/complete

Refused with 409 while processes are open unless "force": true.

Path parameters

idrequiredOrders (production) ID.

Body

forcebooleanAlso tick off any open processes.

Response 200

{
    "ok": true,
    "data": {
        "id": 1204,
        "job_id": 388,
        "invoice_id": "66f1c2a9e4b1d",
        "title": "Round-neck T-shirt (L) — navy",
        "product_id": 57,
        "qty": 40,
        "status": "in_progress",
        "assigned_to": 77,
        "start_at": "2026-10-09 08:00",
        "end_at": "2026-10-10 17:00",
        "remarks": "Use Pantone 2945C",
        "started_at": "2026-10-09 08:12:00",
        "completed_at": null,
        "cancelled_at": null,
        "processes": [
            {
                "id": 5521,
                "name": "Screen print",
                "done": false,
                "completed_at": null
            }
        ],
        "created_at": "2026-10-08 15:00:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/orders/1204/complete' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"force":false}'
Try it Changes live data

Cancel an order Pro

POST/orders/{id}/cancel

Path parameters

idrequiredOrders (production) ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 1204,
        "job_id": 388,
        "invoice_id": "66f1c2a9e4b1d",
        "title": "Round-neck T-shirt (L) — navy",
        "product_id": 57,
        "qty": 40,
        "status": "in_progress",
        "assigned_to": 77,
        "start_at": "2026-10-09 08:00",
        "end_at": "2026-10-10 17:00",
        "remarks": "Use Pantone 2945C",
        "started_at": "2026-10-09 08:12:00",
        "completed_at": null,
        "cancelled_at": null,
        "processes": [
            {
                "id": 5521,
                "name": "Screen print",
                "done": false,
                "completed_at": null
            }
        ],
        "created_at": "2026-10-08 15:00:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/orders/1204/cancel' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)"
Try it Changes live data

Add processes Pro

POST/orders/{id}/processes

Path parameters

idrequiredOrders (production) ID.

Body

namesrequiredarray<string>Process names. Duplicates are skipped.

Response 201

{
    "ok": true,
    "data": {
        "id": 5521,
        "name": "Screen print",
        "done": false,
        "completed_at": null
    }
}
curl -X POST 'https://www.blueq.app/api/v1/orders/1204/processes' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"names":["Design","Print"]}'
Try it Changes live data

Tick off a process Pro

POST/orders/{id}/processes/{process_id}/complete

The order must be in progress.

Path parameters

idrequiredOrders (production) ID.
process_idrequiredProcess id.

Response 200

{
    "ok": true,
    "data": {
        "id": 5521,
        "name": "Screen print",
        "done": false,
        "completed_at": null
    }
}
curl -X POST 'https://www.blueq.app/api/v1/orders/1204/processes/1/complete' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)"
Try it Changes live data

Remove an open process Pro

DELETE/orders/{id}/processes/{process_id}

Path parameters

idrequiredOrders (production) ID.
process_idrequiredProcess id.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/orders/1204/processes/1' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Operations

Recipes /recipes

Recipes (bill of materials) for food and production businesses: ingredients drawn from Cost-of-Sale stock items, optional base recipes, yield, and live cost. Available when the Recipes feature is enabled for the business type. Sizes/variations and cost add-ons are managed in the app and returned read-only.

Available when the recipes feature is enabled for the workspace.

The recipes object
idread-onlyintegerRecipe ID.
namestringRecipe name.
notesstringMethod / notes.
yield_qtynumberHow much one batch makes.
yield_unitstringYield unit (portion, kg, l, pcs…).
menu_product_idread-onlyintegerLinked menu item in the catalogue, if published.
ingredientsarray<RecipeIngredient>Ingredients (at least one ingredient or base).
basesarray<RecipeBase>Base recipes used inside this recipe.
variationsread-onlyarray<object>Sizes defined in the app (read-only).
costread-onlynumberCurrent cost of one batch (ingredients + add-ons).
cost_per_unitread-onlynumbercost ÷ yield_qty.
publish_to_menubooleanCreate/refresh a sellable menu item (create only).
menu_pricenumberSelling price for the menu item (with publish_to_menu).
menu_category_idintegerCatalogue category for the menu item.
created_atread-onlydatetimeCreated (UTC).
RecipeIngredient object
product_idintegerCost-of-Sale stock item.
namestringName for a non-stock ingredient.
qtynumberQuantity per batch.
unitstringUnit (g, kg, ml, l, pcs…).
RecipeBase object
recipe_idintegerBase recipe.
variation_idintegerSize of the base recipe (0 = default).
qtynumberQuantity per batch.
unitstringUnit.

List recipes

GET/recipes

Query parameters

page, per_pagePagination (default 25, max 100).
qSearch name.
sortname (default), -created_at.

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 41,
            "name": "Jollof rice (party pack)",
            "notes": "Parboil rice 10 min before adding stew.",
            "yield_qty": 10,
            "yield_unit": "portion",
            "menu_product_id": 120,
            "ingredients": [
                {
                    "product_id": 61,
                    "name": "Water",
                    "qty": 2.5,
                    "unit": "kg"
                }
            ],
            "bases": [
                {
                    "recipe_id": 12,
                    "variation_id": 0,
                    "qty": 1,
                    "unit": "portion"
                }
            ],
            "variations": [],
            "cost": 182.5,
            "cost_per_unit": 18.25,
            "publish_to_menu": false,
            "menu_price": 35,
            "menu_category_id": 3,
            "created_at": "2026-09-30 12:00:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/recipes?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create a recipe Pro

POST/recipes

Body

namerequiredstringRecipe name.
notesstringMethod / notes.
yield_qtynumberHow much one batch makes.
yield_unitstringYield unit (portion, kg, l, pcs…).
ingredientsrequiredarray<RecipeIngredient>Ingredients (at least one ingredient or base).
basesarray<RecipeBase>Base recipes used inside this recipe.
publish_to_menubooleanCreate/refresh a sellable menu item (create only).
menu_pricenumberSelling price for the menu item (with publish_to_menu).
menu_category_idintegerCatalogue category for the menu item.

Response 201

{
    "ok": true,
    "data": {
        "id": 41,
        "name": "Jollof rice (party pack)",
        "notes": "Parboil rice 10 min before adding stew.",
        "yield_qty": 10,
        "yield_unit": "portion",
        "menu_product_id": 120,
        "ingredients": [
            {
                "product_id": 61,
                "name": "Water",
                "qty": 2.5,
                "unit": "kg"
            }
        ],
        "bases": [
            {
                "recipe_id": 12,
                "variation_id": 0,
                "qty": 1,
                "unit": "portion"
            }
        ],
        "variations": [],
        "cost": 182.5,
        "cost_per_unit": 18.25,
        "publish_to_menu": false,
        "menu_price": 35,
        "menu_category_id": 3,
        "created_at": "2026-09-30 12:00:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/recipes' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Jollof rice (party pack)","notes":"Parboil rice 10 min before adding stew.","yield_qty":10,"yield_unit":"portion","ingredients":[{"product_id":61,"name":"Water","qty":2.5,"unit":"kg"}],"bases":[{"recipe_id":12,"variation_id":0,"qty":1,"unit":"portion"}],"publish_to_menu":false,"menu_price":35,"menu_category_id":3}'
Try it Changes live data

Get a recipe with ingredients and cost

GET/recipes/{id}

Path parameters

idrequiredRecipes ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 41,
        "name": "Jollof rice (party pack)",
        "notes": "Parboil rice 10 min before adding stew.",
        "yield_qty": 10,
        "yield_unit": "portion",
        "menu_product_id": 120,
        "ingredients": [
            {
                "product_id": 61,
                "name": "Water",
                "qty": 2.5,
                "unit": "kg"
            }
        ],
        "bases": [
            {
                "recipe_id": 12,
                "variation_id": 0,
                "qty": 1,
                "unit": "portion"
            }
        ],
        "variations": [],
        "cost": 182.5,
        "cost_per_unit": 18.25,
        "publish_to_menu": false,
        "menu_price": 35,
        "menu_category_id": 3,
        "created_at": "2026-09-30 12:00:00"
    }
}
curl 'https://www.blueq.app/api/v1/recipes/41' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Get the current cost of a recipe

GET/recipes/{id}/cost

Batch cost, cost per unit and the cost of each size, priced from current stock costs.

Path parameters

idrequiredRecipes ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 41,
        "name": "Jollof rice (party pack)",
        "notes": "Parboil rice 10 min before adding stew.",
        "yield_qty": 10,
        "yield_unit": "portion",
        "menu_product_id": 120,
        "ingredients": [
            {
                "product_id": 61,
                "name": "Water",
                "qty": 2.5,
                "unit": "kg"
            }
        ],
        "bases": [
            {
                "recipe_id": 12,
                "variation_id": 0,
                "qty": 1,
                "unit": "portion"
            }
        ],
        "variations": [],
        "cost": 182.5,
        "cost_per_unit": 18.25,
        "publish_to_menu": false,
        "menu_price": 35,
        "menu_category_id": 3,
        "created_at": "2026-09-30 12:00:00"
    }
}
curl 'https://www.blueq.app/api/v1/recipes/41/cost' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a recipe Pro

PATCH/recipes/{id}

Passing ingredients or bases replaces that list. Sizes and cost add-ons are not changed.

Path parameters

idrequiredRecipes ID.

Body

namestringRecipe name.
notesstringMethod / notes.
yield_qtynumberHow much one batch makes.
yield_unitstringYield unit (portion, kg, l, pcs…).
ingredientsarray<RecipeIngredient>Ingredients (at least one ingredient or base).
basesarray<RecipeBase>Base recipes used inside this recipe.
publish_to_menubooleanCreate/refresh a sellable menu item (create only).
menu_pricenumberSelling price for the menu item (with publish_to_menu).
menu_category_idintegerCatalogue category for the menu item.

Response 200

{
    "ok": true,
    "data": {
        "id": 41,
        "name": "Jollof rice (party pack)",
        "notes": "Parboil rice 10 min before adding stew.",
        "yield_qty": 10,
        "yield_unit": "portion",
        "menu_product_id": 120,
        "ingredients": [
            {
                "product_id": 61,
                "name": "Water",
                "qty": 2.5,
                "unit": "kg"
            }
        ],
        "bases": [
            {
                "recipe_id": 12,
                "variation_id": 0,
                "qty": 1,
                "unit": "portion"
            }
        ],
        "variations": [],
        "cost": 182.5,
        "cost_per_unit": 18.25,
        "publish_to_menu": false,
        "menu_price": 35,
        "menu_category_id": 3,
        "created_at": "2026-09-30 12:00:00"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/recipes/41' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Jollof rice (party pack)","notes":"Parboil rice 10 min before adding stew.","yield_qty":10,"yield_unit":"portion","ingredients":[{"product_id":61,"name":"Water","qty":2.5,"unit":"kg"}],"bases":[{"recipe_id":12,"variation_id":0,"qty":1,"unit":"portion"}],"publish_to_menu":false,"menu_price":35,"menu_category_id":3}'
Try it Changes live data

Delete a recipe Pro

DELETE/recipes/{id}

Also removes its sizes and any menu-item variations linked to it.

Path parameters

idrequiredRecipes ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/recipes/41' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Settings

Taxes /taxes

Tax rates (levies and VAT). Levies are charged on the taxable amount; VAT is charged on the taxable amount plus levies. Group rates into tax groups to apply them to invoices and sales.

The taxes object
idread-onlyintegerTax ID.
namestringTax name.
ratenumberPercentage, 0 to 100.
typestring"levy" or "vat".
sortintegerDisplay and calculation order.
activebooleanActive taxes are added to the default tax group on create.

List taxes

GET/taxes

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 31,
            "name": "VAT",
            "rate": 15,
            "type": "vat",
            "sort": 40,
            "active": true
        }
    ]
}
curl 'https://www.blueq.app/api/v1/taxes' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create a tax Pro

POST/taxes

Body

namerequiredstringTax name.
raterequirednumberPercentage, 0 to 100.
typerequiredstring"levy" or "vat".
sortintegerDisplay and calculation order.
activebooleanActive taxes are added to the default tax group on create.

Response 201

{
    "ok": true,
    "data": {
        "id": 31,
        "name": "VAT",
        "rate": 15,
        "type": "vat",
        "sort": 40,
        "active": true
    }
}
curl -X POST 'https://www.blueq.app/api/v1/taxes' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"name":"VAT","rate":15,"type":"vat","sort":40,"active":true}'
Try it Changes live data

Get a tax

GET/taxes/{id}

Path parameters

idrequiredTaxes ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 31,
        "name": "VAT",
        "rate": 15,
        "type": "vat",
        "sort": 40,
        "active": true
    }
}
curl 'https://www.blueq.app/api/v1/taxes/31' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a tax Pro

PATCH/taxes/{id}

Invoices already confirmed keep the rates they were issued with.

Path parameters

idrequiredTaxes ID.

Body

namestringTax name.
ratenumberPercentage, 0 to 100.
typestring"levy" or "vat".
sortintegerDisplay and calculation order.
activebooleanActive taxes are added to the default tax group on create.

Response 200

{
    "ok": true,
    "data": {
        "id": 31,
        "name": "VAT",
        "rate": 15,
        "type": "vat",
        "sort": 40,
        "active": true
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/taxes/31' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"VAT","rate":15,"type":"vat","sort":40,"active":true}'
Try it Changes live data

Delete a tax Pro

DELETE/taxes/{id}

Also removes the tax from every tax group.

Path parameters

idrequiredTaxes ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/taxes/31' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Settings

Tax groups /tax-groups

Named sets of taxes (called "tax instances" in the app), e.g. "Standard VAT + levies" or "VAT exempt". One group is the default for new invoices and sales.

The tax groups object
idread-onlyintegerTax group ID.
namestringGroup name.
is_defaultbooleanDefault group for new invoices and sales.
activebooleanWhether the group can be selected.
sortintegerDisplay order.
tax_idsarrayIDs of the taxes in this group.
taxesread-onlyarrayThe active taxes in this group: [{id, name, rate, type}].

List tax groups

GET/tax-groups

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 4,
            "name": "Standard (VAT + levies)",
            "is_default": true,
            "active": true,
            "sort": 0,
            "tax_ids": [
                28,
                29,
                30,
                31
            ],
            "taxes": [
                {
                    "id": 31,
                    "name": "VAT",
                    "rate": 15,
                    "type": "vat"
                }
            ]
        }
    ]
}
curl 'https://www.blueq.app/api/v1/tax-groups' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create a tax group Pro

POST/tax-groups

Body

namerequiredstringGroup name.
is_defaultbooleanDefault group for new invoices and sales.
activebooleanWhether the group can be selected.
sortintegerDisplay order.
tax_idsarrayIDs of the taxes in this group.

Response 201

{
    "ok": true,
    "data": {
        "id": 4,
        "name": "Standard (VAT + levies)",
        "is_default": true,
        "active": true,
        "sort": 0,
        "tax_ids": [
            28,
            29,
            30,
            31
        ],
        "taxes": [
            {
                "id": 31,
                "name": "VAT",
                "rate": 15,
                "type": "vat"
            }
        ]
    }
}
curl -X POST 'https://www.blueq.app/api/v1/tax-groups' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Standard (VAT + levies)","is_default":true,"active":true,"sort":0,"tax_ids":[28,29,30,31]}'
Try it Changes live data

Get a tax group

GET/tax-groups/{id}

Path parameters

idrequiredTax groups ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 4,
        "name": "Standard (VAT + levies)",
        "is_default": true,
        "active": true,
        "sort": 0,
        "tax_ids": [
            28,
            29,
            30,
            31
        ],
        "taxes": [
            {
                "id": 31,
                "name": "VAT",
                "rate": 15,
                "type": "vat"
            }
        ]
    }
}
curl 'https://www.blueq.app/api/v1/tax-groups/4' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a tax group Pro

PATCH/tax-groups/{id}

Path parameters

idrequiredTax groups ID.

Body

namestringGroup name.
is_defaultbooleanDefault group for new invoices and sales.
activebooleanWhether the group can be selected.
sortintegerDisplay order.
tax_idsarrayIDs of the taxes in this group.

Response 200

{
    "ok": true,
    "data": {
        "id": 4,
        "name": "Standard (VAT + levies)",
        "is_default": true,
        "active": true,
        "sort": 0,
        "tax_ids": [
            28,
            29,
            30,
            31
        ],
        "taxes": [
            {
                "id": 31,
                "name": "VAT",
                "rate": 15,
                "type": "vat"
            }
        ]
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/tax-groups/4' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Standard (VAT + levies)","is_default":true,"active":true,"sort":0,"tax_ids":[28,29,30,31]}'
Try it Changes live data

Delete a tax group Pro

DELETE/tax-groups/{id}

The default group cannot be deleted (409).

Path parameters

idrequiredTax groups ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/tax-groups/4' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Settings

Branches /branches

Shop locations. Every workspace has at least one branch; one is the default. Creating a branch counts against the plan's branch limit.

The branches object
idread-onlyintegerBranch ID.
namestringBranch name.
codestringShort code (stored uppercase).
phonestringPhone (digits only).
emailstringEmail.
addressstringStreet address.
townstringTown or city.
is_defaultbooleanDefault branch for new records.
activebooleanWhether the branch is in use.
created_atread-onlydatetimeWhen it was added (UTC).

List branches

GET/branches

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 3,
            "name": "Kumasi Mall",
            "code": "KSI",
            "phone": "0322000111",
            "email": "kumasi@kenteprints.com",
            "address": "Shop 14, Kumasi City Mall",
            "town": "Kumasi",
            "is_default": false,
            "active": true,
            "created_at": "2026-05-11 12:00:00"
        }
    ]
}
curl 'https://www.blueq.app/api/v1/branches' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create a branch Pro

POST/branches

Body

namerequiredstringBranch name.
codestringShort code (stored uppercase).
phonestringPhone (digits only).
emailstringEmail.
addressstringStreet address.
townstringTown or city.
is_defaultbooleanDefault branch for new records.
activebooleanWhether the branch is in use.

Response 201

{
    "ok": true,
    "data": {
        "id": 3,
        "name": "Kumasi Mall",
        "code": "KSI",
        "phone": "0322000111",
        "email": "kumasi@kenteprints.com",
        "address": "Shop 14, Kumasi City Mall",
        "town": "Kumasi",
        "is_default": false,
        "active": true,
        "created_at": "2026-05-11 12:00:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/branches' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Kumasi Mall","code":"KSI","phone":"0322000111","email":"kumasi@kenteprints.com","address":"Shop 14, Kumasi City Mall","town":"Kumasi","is_default":false,"active":true}'
Try it Changes live data

Get a branch

GET/branches/{id}

Path parameters

idrequiredBranches ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 3,
        "name": "Kumasi Mall",
        "code": "KSI",
        "phone": "0322000111",
        "email": "kumasi@kenteprints.com",
        "address": "Shop 14, Kumasi City Mall",
        "town": "Kumasi",
        "is_default": false,
        "active": true,
        "created_at": "2026-05-11 12:00:00"
    }
}
curl 'https://www.blueq.app/api/v1/branches/3' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a branch Pro

PATCH/branches/{id}

Path parameters

idrequiredBranches ID.

Body

namestringBranch name.
codestringShort code (stored uppercase).
phonestringPhone (digits only).
emailstringEmail.
addressstringStreet address.
townstringTown or city.
is_defaultbooleanDefault branch for new records.
activebooleanWhether the branch is in use.

Response 200

{
    "ok": true,
    "data": {
        "id": 3,
        "name": "Kumasi Mall",
        "code": "KSI",
        "phone": "0322000111",
        "email": "kumasi@kenteprints.com",
        "address": "Shop 14, Kumasi City Mall",
        "town": "Kumasi",
        "is_default": false,
        "active": true,
        "created_at": "2026-05-11 12:00:00"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/branches/3' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Kumasi Mall","code":"KSI","phone":"0322000111","email":"kumasi@kenteprints.com","address":"Shop 14, Kumasi City Mall","town":"Kumasi","is_default":false,"active":true}'
Try it Changes live data

Delete a branch Pro

DELETE/branches/{id}

If the default branch is deleted another becomes the default. The last branch is recreated automatically.

Path parameters

idrequiredBranches ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/branches/3' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Settings

Users (team) /users

Team members with access to the workspace. New users are always invited by email and set their own password; passwords are never accepted or returned by the API. The workspace owner cannot be changed or removed here.

The users (team) object
idread-onlyintegerUser ID.
first_namestringFirst name.
last_namestringLast name.
emailstringEmail (the invite is sent here). Unique per workspace.
phonestringPhone. Unique per workspace.
rolestringRole key (see /roles), e.g. admin, manager, accountant, sales, production, viewer.
role_labelread-onlystringRole name.
branch_idintegerHome branch (see /branches).
activebooleanCan sign in. Invited users become active after accepting.
invite_pendingread-onlybooleanInvitation sent but not yet accepted.

List team members

GET/users

Query parameters

page, per_pagePagination (default 25, max 100).
qSearch name, email or phone.
roleOnly this role.

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 77,
            "first_name": "Esi",
            "last_name": "Mensah",
            "email": "esi@kenteprints.com",
            "phone": "0201234567",
            "role": "manager",
            "role_label": "Manager",
            "branch_id": 1,
            "active": true,
            "invite_pending": false
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/users?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Invite a team member Pro

POST/users

Sends an invitation email. "invite_sent" in the response is false if the email could not be delivered (the user is still created).

Body

first_namerequiredstringFirst name.
last_namerequiredstringLast name.
emailrequiredstringEmail (the invite is sent here). Unique per workspace.
phonestringPhone. Unique per workspace.
rolestringRole key (see /roles), e.g. admin, manager, accountant, sales, production, viewer.
branch_idintegerHome branch (see /branches).
activebooleanCan sign in. Invited users become active after accepting.

Response 201

{
    "ok": true,
    "data": {
        "id": 77,
        "first_name": "Esi",
        "last_name": "Mensah",
        "email": "esi@kenteprints.com",
        "phone": "0201234567",
        "role": "manager",
        "role_label": "Manager",
        "branch_id": 1,
        "active": true,
        "invite_pending": false
    }
}
curl -X POST 'https://www.blueq.app/api/v1/users' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"first_name":"Esi","last_name":"Mensah","email":"esi@kenteprints.com","phone":"0201234567","role":"manager","branch_id":1,"active":true}'
Try it Changes live data

Get a team member

GET/users/{id}

Path parameters

idrequiredUsers (team) ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 77,
        "first_name": "Esi",
        "last_name": "Mensah",
        "email": "esi@kenteprints.com",
        "phone": "0201234567",
        "role": "manager",
        "role_label": "Manager",
        "branch_id": 1,
        "active": true,
        "invite_pending": false
    }
}
curl 'https://www.blueq.app/api/v1/users/77' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a team member Pro

PATCH/users/{id}

Path parameters

idrequiredUsers (team) ID.

Body

first_namestringFirst name.
last_namestringLast name.
emailstringEmail (the invite is sent here). Unique per workspace.
phonestringPhone. Unique per workspace.
rolestringRole key (see /roles), e.g. admin, manager, accountant, sales, production, viewer.
branch_idintegerHome branch (see /branches).
activebooleanCan sign in. Invited users become active after accepting.

Response 200

{
    "ok": true,
    "data": {
        "id": 77,
        "first_name": "Esi",
        "last_name": "Mensah",
        "email": "esi@kenteprints.com",
        "phone": "0201234567",
        "role": "manager",
        "role_label": "Manager",
        "branch_id": 1,
        "active": true,
        "invite_pending": false
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/users/77' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"first_name":"Esi","last_name":"Mensah","email":"esi@kenteprints.com","phone":"0201234567","role":"manager","branch_id":1,"active":true}'
Try it Changes live data

Remove a team member Pro

DELETE/users/{id}

Path parameters

idrequiredUsers (team) ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/users/77' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Settings

Roles & privileges /roles

Roles decide what each team member can do. Every module has four privileges: view, add, update, delete. Built-in roles can be narrowed (except super_admin); custom roles can be created and deleted.

The roles & privileges object
keyread-onlystringRole key, used as users.role.
labelstringRole name.
summarystringShort description.
customread-onlybooleanCreated for this workspace (can be deleted).
lockedread-onlybooleanPrivileges cannot be changed (super_admin).
user_countread-onlyintegerTeam members with this role.
privilegesobjectmodule key => {view, add, update, delete}. On create/update, only modules you pass are changed.
RoleModule object
keyread-onlystringModule key, used in privileges.
labelread-onlystringModule name.
industryread-onlybooleanOnly shown for some business types.

List roles with privileges

GET/roles

Response 200

{
    "ok": true,
    "data": [
        {
            "key": "manager",
            "label": "Front desk",
            "summary": "Clients, invoices and POS only.",
            "custom": false,
            "locked": false,
            "user_count": 3,
            "privileges": {
                "invoices": {
                    "view": true,
                    "add": true,
                    "update": true,
                    "delete": false
                }
            }
        }
    ]
}
curl 'https://www.blueq.app/api/v1/roles' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create a custom role Pro

POST/roles

Body

labelrequiredstringRole name.
summarystringShort description.
privilegesobjectmodule key => {view, add, update, delete}. On create/update, only modules you pass are changed.

Response 201

{
    "ok": true,
    "data": {
        "key": "manager",
        "label": "Front desk",
        "summary": "Clients, invoices and POS only.",
        "custom": false,
        "locked": false,
        "user_count": 3,
        "privileges": {
            "invoices": {
                "view": true,
                "add": true,
                "update": true,
                "delete": false
            }
        }
    }
}
curl -X POST 'https://www.blueq.app/api/v1/roles' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"label":"Front desk","summary":"Clients, invoices and POS only.","privileges":{"invoices":{"view":true,"add":true,"update":true,"delete":false}}}'
Try it Changes live data

List modules that privileges apply to

GET/roles/modules

Response 200

{
    "ok": true,
    "data": [
        {
            "key": "invoices",
            "label": "Invoices",
            "industry": false
        }
    ]
}
curl 'https://www.blueq.app/api/v1/roles/modules' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Get a role

GET/roles/{key}

Path parameters

keyrequiredKey.

Response 200

{
    "ok": true,
    "data": {
        "key": "manager",
        "label": "Front desk",
        "summary": "Clients, invoices and POS only.",
        "custom": false,
        "locked": false,
        "user_count": 3,
        "privileges": {
            "invoices": {
                "view": true,
                "add": true,
                "update": true,
                "delete": false
            }
        }
    }
}
curl 'https://www.blueq.app/api/v1/roles/cashier' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Change a role's privileges Pro

PATCH/roles/{key}

Path parameters

keyrequiredKey.

Body

privilegesrequiredobjectmodule key => {view, add, update, delete}.

Response 200

{
    "ok": true,
    "data": {
        "key": "manager",
        "label": "Front desk",
        "summary": "Clients, invoices and POS only.",
        "custom": false,
        "locked": false,
        "user_count": 3,
        "privileges": {
            "invoices": {
                "view": true,
                "add": true,
                "update": true,
                "delete": false
            }
        }
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/roles/cashier' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"privileges":{"reports":{"view":true}}}'
Try it Changes live data

Delete a custom role Pro

DELETE/roles/{key}

Refused with 409 while users still have the role.

Path parameters

keyrequiredKey.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/roles/cashier' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Commerce

Online stores /stores

Public online stores (storefronts) and which catalogue products each one sells. Payment gateway secrets are never returned. Deleting a store takes it offline; orders are kept.

The online stores object
idread-onlyintegerStore ID.
namestringStore name (at least 5 characters).
descriptionstringShown on the storefront.
phonestringContact phone.
emailstringContact email.
slugstringLink name in the public URL (unique, at least 5 characters).
public_urlread-onlystringPublic storefront URL.
templatestringclassic, market or list.
activebooleanStore is online.
paystack_connectedread-onlybooleanPaystack keys are configured (managed in the app).
product_countread-onlyintegerActive products listed.
settingsobjectCheckout settings: checkout_payment ("paystack" or "place"), pickup_enabled, delivery_enabled, create_orders, pos_create_orders, send_receipt, reset_order_daily, tax_group_id, payment_note.
created_atread-onlydatetimeCreated (UTC).

List stores

GET/stores

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 2,
            "name": "Kente Prints Shop",
            "description": "Custom T-shirts, banners and souvenirs.",
            "phone": "0244000111",
            "email": "shop@kenteprints.com",
            "slug": "kente-prints",
            "public_url": "https://blueq.app/link/store/kente-prints",
            "template": "classic",
            "active": true,
            "paystack_connected": true,
            "product_count": 48,
            "settings": {
                "checkout_payment": "paystack",
                "pickup_enabled": true,
                "delivery_enabled": true,
                "send_receipt": true,
                "tax_group_id": null
            },
            "created_at": "2026-08-02 10:00:00"
        }
    ]
}
curl 'https://www.blueq.app/api/v1/stores' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create a store Pro

POST/stores

The first store automatically lists every sellable product.

Body

namerequiredstringStore name (at least 5 characters).
descriptionstringShown on the storefront.
phonestringContact phone.
emailstringContact email.
slugstringLink name in the public URL (unique, at least 5 characters).
templatestringclassic, market or list.
activebooleanStore is online.
settingsobjectCheckout settings: checkout_payment ("paystack" or "place"), pickup_enabled, delivery_enabled, create_orders, pos_create_orders, send_receipt, reset_order_daily, tax_group_id, payment_note.

Response 201

{
    "ok": true,
    "data": {
        "id": 2,
        "name": "Kente Prints Shop",
        "description": "Custom T-shirts, banners and souvenirs.",
        "phone": "0244000111",
        "email": "shop@kenteprints.com",
        "slug": "kente-prints",
        "public_url": "https://blueq.app/link/store/kente-prints",
        "template": "classic",
        "active": true,
        "paystack_connected": true,
        "product_count": 48,
        "settings": {
            "checkout_payment": "paystack",
            "pickup_enabled": true,
            "delivery_enabled": true,
            "send_receipt": true,
            "tax_group_id": null
        },
        "created_at": "2026-08-02 10:00:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/stores' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Kente Prints Shop","description":"Custom T-shirts, banners and souvenirs.","phone":"0244000111","email":"shop@kenteprints.com","slug":"kente-prints","template":"classic","active":true,"settings":{"checkout_payment":"paystack","pickup_enabled":true,"delivery_enabled":true,"send_receipt":true,"tax_group_id":null}}'
Try it Changes live data

Get a store

GET/stores/{id}

Path parameters

idrequiredOnline stores ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 2,
        "name": "Kente Prints Shop",
        "description": "Custom T-shirts, banners and souvenirs.",
        "phone": "0244000111",
        "email": "shop@kenteprints.com",
        "slug": "kente-prints",
        "public_url": "https://blueq.app/link/store/kente-prints",
        "template": "classic",
        "active": true,
        "paystack_connected": true,
        "product_count": 48,
        "settings": {
            "checkout_payment": "paystack",
            "pickup_enabled": true,
            "delivery_enabled": true,
            "send_receipt": true,
            "tax_group_id": null
        },
        "created_at": "2026-08-02 10:00:00"
    }
}
curl 'https://www.blueq.app/api/v1/stores/2' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a store Pro

PATCH/stores/{id}

Path parameters

idrequiredOnline stores ID.

Body

namestringStore name (at least 5 characters).
descriptionstringShown on the storefront.
phonestringContact phone.
emailstringContact email.
slugstringLink name in the public URL (unique, at least 5 characters).
templatestringclassic, market or list.
activebooleanStore is online.
settingsobjectCheckout settings: checkout_payment ("paystack" or "place"), pickup_enabled, delivery_enabled, create_orders, pos_create_orders, send_receipt, reset_order_daily, tax_group_id, payment_note.

Response 200

{
    "ok": true,
    "data": {
        "id": 2,
        "name": "Kente Prints Shop",
        "description": "Custom T-shirts, banners and souvenirs.",
        "phone": "0244000111",
        "email": "shop@kenteprints.com",
        "slug": "kente-prints",
        "public_url": "https://blueq.app/link/store/kente-prints",
        "template": "classic",
        "active": true,
        "paystack_connected": true,
        "product_count": 48,
        "settings": {
            "checkout_payment": "paystack",
            "pickup_enabled": true,
            "delivery_enabled": true,
            "send_receipt": true,
            "tax_group_id": null
        },
        "created_at": "2026-08-02 10:00:00"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/stores/2' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Kente Prints Shop","description":"Custom T-shirts, banners and souvenirs.","phone":"0244000111","email":"shop@kenteprints.com","slug":"kente-prints","template":"classic","active":true,"settings":{"checkout_payment":"paystack","pickup_enabled":true,"delivery_enabled":true,"send_receipt":true,"tax_group_id":null}}'
Try it Changes live data

Take a store offline Pro

DELETE/stores/{id}

Path parameters

idrequiredOnline stores ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/stores/2' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

List products in a store

GET/stores/{id}/products

Path parameters

idrequiredOnline stores ID.

Query parameters

page, per_pagePagination (default 25, max 100).

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 2,
            "name": "Kente Prints Shop",
            "description": "Custom T-shirts, banners and souvenirs.",
            "phone": "0244000111",
            "email": "shop@kenteprints.com",
            "slug": "kente-prints",
            "public_url": "https://blueq.app/link/store/kente-prints",
            "template": "classic",
            "active": true,
            "paystack_connected": true,
            "product_count": 48,
            "settings": {
                "checkout_payment": "paystack",
                "pickup_enabled": true,
                "delivery_enabled": true,
                "send_receipt": true,
                "tax_group_id": null
            },
            "created_at": "2026-08-02 10:00:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/stores/2/products?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Add products to a store Pro

POST/stores/{id}/products

Path parameters

idrequiredOnline stores ID.

Body

product_idsrequiredarray<integer>Sellable catalogue products.

Response 200

{
    "ok": true,
    "data": {
        "id": 2,
        "name": "Kente Prints Shop",
        "description": "Custom T-shirts, banners and souvenirs.",
        "phone": "0244000111",
        "email": "shop@kenteprints.com",
        "slug": "kente-prints",
        "public_url": "https://blueq.app/link/store/kente-prints",
        "template": "classic",
        "active": true,
        "paystack_connected": true,
        "product_count": 48,
        "settings": {
            "checkout_payment": "paystack",
            "pickup_enabled": true,
            "delivery_enabled": true,
            "send_receipt": true,
            "tax_group_id": null
        },
        "created_at": "2026-08-02 10:00:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/stores/2/products' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"product_ids":[57,58]}'
Try it Changes live data

Remove a product from a store Pro

DELETE/stores/{id}/products/{product_id}

Path parameters

idrequiredOnline stores ID.
product_idrequiredProduct id.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/stores/2/products/1' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Commerce

Store & walk-in orders /store-orders

Orders placed on online stores (channel "online") and at the POS till (channel "walkin"). Orders are created by customers at checkout or by cashiers at the till; the API reads them and marks paid orders as picked up / delivered.

The store & walk-in orders object
idread-onlyintegerOrder ID.
store_idread-onlyintegerStore.
numberread-onlystringOrder number (resets daily if configured).
receipt_numberread-onlystringReceipt number.
channelread-onlystringonline or walkin.
statusread-onlystringpending, paid or failed.
payment_moderead-onlystringPaystack, Cash, Mobile Money, Card or Place order.
customerread-onlyobjectname, phone, email, client_id.
fulfillmentread-onlystringpickup or delivery.
deliveryread-onlyobjecttown, address, zone, fee (delivery orders).
subtotalread-onlynumberItems total.
tax_totalread-onlynumberTaxes.
totalread-onlynumberGrand total.
picked_upread-onlybooleanCollected / delivered.
sale_idread-onlyintegerSale recorded for the order (see /sales).
invoice_idread-onlystringInvoice/receipt generated.
pos_session_idread-onlyintegerTill session (walk-in orders).
itemsread-onlyarray<object>product_id, name, qty, unit_price, amount (detail view).
dateread-onlydateBusiness date.
created_atread-onlydatetimePlaced (UTC).
paid_atread-onlydatetimePaid (UTC).

List orders

GET/store-orders

Query parameters

page, per_pagePagination (default 25, max 100).
store_idOnly this store.
statuspending, paid, failed or open (paid, not picked up).
channelonline or walkin.
payment_modePaystack, Cash, Mobile Money, Card, Place order.
qSearch number, receipt, customer or reference.
fromOrder date on/after.
toOrder date on/before.

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 9031,
            "store_id": 2,
            "number": "014",
            "receipt_number": "R-00412",
            "channel": "online",
            "status": "paid",
            "payment_mode": "Paystack",
            "customer": {
                "name": "Yaw Asante",
                "phone": "0244111222",
                "email": null,
                "client_id": 2399
            },
            "fulfillment": "delivery",
            "delivery": {
                "town": "East Legon",
                "address": "Lagos Ave 14",
                "zone": "Accra East",
                "fee": 25
            },
            "subtotal": 240,
            "tax_total": 0,
            "total": 265,
            "picked_up": false,
            "sale_id": 812,
            "invoice_id": null,
            "pos_session_id": null,
            "items": [],
            "date": "2026-10-08",
            "created_at": "2026-10-08 09:40:00",
            "paid_at": "2026-10-08 09:41:12"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/store-orders?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Get an order with items

GET/store-orders/{id}

Path parameters

idrequiredStore & walk-in orders ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 9031,
        "store_id": 2,
        "number": "014",
        "receipt_number": "R-00412",
        "channel": "online",
        "status": "paid",
        "payment_mode": "Paystack",
        "customer": {
            "name": "Yaw Asante",
            "phone": "0244111222",
            "email": null,
            "client_id": 2399
        },
        "fulfillment": "delivery",
        "delivery": {
            "town": "East Legon",
            "address": "Lagos Ave 14",
            "zone": "Accra East",
            "fee": 25
        },
        "subtotal": 240,
        "tax_total": 0,
        "total": 265,
        "picked_up": false,
        "sale_id": 812,
        "invoice_id": null,
        "pos_session_id": null,
        "items": [],
        "date": "2026-10-08",
        "created_at": "2026-10-08 09:40:00",
        "paid_at": "2026-10-08 09:41:12"
    }
}
curl 'https://www.blueq.app/api/v1/store-orders/9031' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Mark picked up / delivered Pro

POST/store-orders/{id}/picked-up

Only paid orders can be marked.

Path parameters

idrequiredStore & walk-in orders ID.

Body

picked_upbooleantrue to mark collected, false to undo.

Response 200

{
    "ok": true,
    "data": {
        "id": 9031,
        "store_id": 2,
        "number": "014",
        "receipt_number": "R-00412",
        "channel": "online",
        "status": "paid",
        "payment_mode": "Paystack",
        "customer": {
            "name": "Yaw Asante",
            "phone": "0244111222",
            "email": null,
            "client_id": 2399
        },
        "fulfillment": "delivery",
        "delivery": {
            "town": "East Legon",
            "address": "Lagos Ave 14",
            "zone": "Accra East",
            "fee": 25
        },
        "subtotal": 240,
        "tax_total": 0,
        "total": 265,
        "picked_up": false,
        "sale_id": 812,
        "invoice_id": null,
        "pos_session_id": null,
        "items": [],
        "date": "2026-10-08",
        "created_at": "2026-10-08 09:40:00",
        "paid_at": "2026-10-08 09:41:12"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/store-orders/9031/picked-up' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"picked_up":true}'
Try it Changes live data

Commerce

Delivery zones /delivery-zones

Delivery fees for online stores. Each zone has a price and the towns it covers; at checkout the cheapest zone covering the customer's town is used.

The delivery zones object
idread-onlyintegerZone ID.
store_idintegerStore the zone belongs to.
namestringZone name.
pricenumberDelivery fee.
townsarray<string>Towns covered (at least one).
created_atread-onlydatetimeCreated (UTC).

List delivery zones

GET/delivery-zones

Query parameters

page, per_pagePagination (default 25, max 100).
store_idOnly this store.

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 7,
            "store_id": 2,
            "name": "Accra East",
            "price": 25,
            "towns": [
                "East Legon",
                "Madina",
                "Adenta"
            ],
            "created_at": "2026-08-02 10:20:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/delivery-zones?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create a zone Pro

POST/delivery-zones

Body

store_idrequiredintegerStore the zone belongs to.
namerequiredstringZone name.
pricerequirednumberDelivery fee.
townsrequiredarray<string>Towns covered (at least one).

Response 201

{
    "ok": true,
    "data": {
        "id": 7,
        "store_id": 2,
        "name": "Accra East",
        "price": 25,
        "towns": [
            "East Legon",
            "Madina",
            "Adenta"
        ],
        "created_at": "2026-08-02 10:20:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/delivery-zones' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"store_id":2,"name":"Accra East","price":25,"towns":["East Legon","Madina","Adenta"]}'
Try it Changes live data

Get a zone

GET/delivery-zones/{id}

Path parameters

idrequiredDelivery zones ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 7,
        "store_id": 2,
        "name": "Accra East",
        "price": 25,
        "towns": [
            "East Legon",
            "Madina",
            "Adenta"
        ],
        "created_at": "2026-08-02 10:20:00"
    }
}
curl 'https://www.blueq.app/api/v1/delivery-zones/7' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a zone Pro

PATCH/delivery-zones/{id}

Passing towns replaces the list.

Path parameters

idrequiredDelivery zones ID.

Body

store_idintegerStore the zone belongs to.
namestringZone name.
pricenumberDelivery fee.
townsarray<string>Towns covered (at least one).

Response 200

{
    "ok": true,
    "data": {
        "id": 7,
        "store_id": 2,
        "name": "Accra East",
        "price": 25,
        "towns": [
            "East Legon",
            "Madina",
            "Adenta"
        ],
        "created_at": "2026-08-02 10:20:00"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/delivery-zones/7' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"store_id":2,"name":"Accra East","price":25,"towns":["East Legon","Madina","Adenta"]}'
Try it Changes live data

Delete a zone Pro

DELETE/delivery-zones/{id}

Path parameters

idrequiredDelivery zones ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/delivery-zones/7' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Commerce

Store customer accounts /store-accounts

Shopper accounts created on your online stores. Customers register themselves at checkout; the API can list, correct and remove accounts. Passwords are never exposed.

The store customer accounts object
idread-onlyintegerAccount ID.
emailread-onlystringSign-in email.
namestringFull name.
phonestringPhone.
order_countread-onlyintegerPaid orders placed with this email.
created_atread-onlydatetimeRegistered (UTC).

List customer accounts

GET/store-accounts

Query parameters

page, per_pagePagination (default 25, max 100).
qSearch email, name or phone.
fromRegistered on/after.
toRegistered on/before.

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 311,
            "email": "yaw.asante@example.com",
            "name": "Yaw Asante",
            "phone": "0244111222",
            "order_count": 4,
            "created_at": "2026-09-01 18:30:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/store-accounts?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Get a customer account

GET/store-accounts/{id}

Path parameters

idrequiredStore customer accounts ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 311,
        "email": "yaw.asante@example.com",
        "name": "Yaw Asante",
        "phone": "0244111222",
        "order_count": 4,
        "created_at": "2026-09-01 18:30:00"
    }
}
curl 'https://www.blueq.app/api/v1/store-accounts/311' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update name or phone Pro

PATCH/store-accounts/{id}

Path parameters

idrequiredStore customer accounts ID.

Body

namestringFull name.
phonestringPhone.

Response 200

{
    "ok": true,
    "data": {
        "id": 311,
        "email": "yaw.asante@example.com",
        "name": "Yaw Asante",
        "phone": "0244111222",
        "order_count": 4,
        "created_at": "2026-09-01 18:30:00"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/store-accounts/311' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Yaw Asante","phone":"0244111222"}'
Try it Changes live data

Delete a customer account Pro

DELETE/store-accounts/{id}

Past orders are kept.

Path parameters

idrequiredStore customer accounts ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/store-accounts/311' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Commerce

POS (point of sale) /pos

The walk-in till: register settings, cashier sessions ("accounts") that are opened, closed and reviewed, and walk-in sales. Sales are recorded against the cashier's open session, deduct stock, and share order numbers with the linked online store. Paystack payments need the customer present at the till and are not available through the API.

The pos (point of sale) object
idread-onlyintegerSession ID.
cashier_idintegerTeam member running the till (see /users). Defaults to the workspace owner.
cashier_nameread-onlystringCashier name.
statusread-onlystringopen, pending (closed, awaiting review), approved or rejected.
sale_countread-onlyintegerPaid sales in the session.
totalread-onlynumberSales total.
opened_atread-onlydatetimeOpened (UTC).
closed_atread-onlydatetimeClosed (UTC).
review_noteread-onlystringReviewer note.
reviewed_atread-onlydatetimeReviewed (UTC).
PosRegister object
namestringTill name shown on receipts.
phonestringReceipt phone.
emailstringReceipt email.
addressstringReceipt address.
activebooleanTill can take sales.
store_idintegerStore whose products and order numbers the till uses.
tax_group_idintegerTax group applied to sales (0 = none).
PosSale object
cashier_idintegerCashier with an open session (default: owner).
itemsarray<object>Lines: {"product_id": 57, "qty": 2}. Prices come from the catalogue.
payment_modestringCash, Mobile Money or Card.
customer_namestringDefault "Walk-in".
customer_phonestringReceipt SMS number.

Get the till register

GET/pos/register

Response 200

{
    "ok": true,
    "data": {
        "name": "Front counter",
        "phone": "0302000111",
        "email": "counter@kenteprints.com",
        "address": "Osu, Oxford St.",
        "active": true,
        "store_id": 2,
        "tax_group_id": 0
    }
}
curl 'https://www.blueq.app/api/v1/pos/register' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Create or update the till register Pro

PUT/pos/register

Body (PosRegister)

namerequiredstringTill name shown on receipts.
phonestringReceipt phone.
emailstringReceipt email.
addressstringReceipt address.
activebooleanTill can take sales.
store_idintegerStore whose products and order numbers the till uses.
tax_group_idintegerTax group applied to sales (0 = none).

Response 201

{
    "ok": true,
    "data": {
        "name": "Front counter",
        "phone": "0302000111",
        "email": "counter@kenteprints.com",
        "address": "Osu, Oxford St.",
        "active": true,
        "store_id": 2,
        "tax_group_id": 0
    }
}
curl -X PUT 'https://www.blueq.app/api/v1/pos/register' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Front counter","phone":"0302000111","email":"counter@kenteprints.com","address":"Osu, Oxford St.","active":true,"store_id":2,"tax_group_id":0}'
Try it Changes live data

List cashier sessions

GET/pos/sessions

Query parameters

page, per_pagePagination (default 25, max 100).
statusopen, pending, approved or rejected.
cashier_idOnly this cashier.

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 140,
            "cashier_id": 77,
            "cashier_name": "Esi Mensah",
            "status": "open",
            "sale_count": 23,
            "total": 1840.5,
            "opened_at": "2026-10-08 07:58:00",
            "closed_at": null,
            "review_note": null,
            "reviewed_at": null
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "total_pages": 1
    }
}
curl 'https://www.blueq.app/api/v1/pos/sessions?per_page=25' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Open a cashier session Pro

POST/pos/sessions

Body

cashier_idintegerCashier (default: owner). Returns the existing open session if there is one.

Response 201

{
    "ok": true,
    "data": {
        "id": 140,
        "cashier_id": 77,
        "cashier_name": "Esi Mensah",
        "status": "open",
        "sale_count": 23,
        "total": 1840.5,
        "opened_at": "2026-10-08 07:58:00",
        "closed_at": null,
        "review_note": null,
        "reviewed_at": null
    }
}
curl -X POST 'https://www.blueq.app/api/v1/pos/sessions' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"cashier_id":77}'
Try it Changes live data

Get a session with its sales

GET/pos/sessions/{id}

Path parameters

idrequiredPOS (point of sale) ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 140,
        "cashier_id": 77,
        "cashier_name": "Esi Mensah",
        "status": "open",
        "sale_count": 23,
        "total": 1840.5,
        "opened_at": "2026-10-08 07:58:00",
        "closed_at": null,
        "review_note": null,
        "reviewed_at": null
    }
}
curl 'https://www.blueq.app/api/v1/pos/sessions/140' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Close a session for review Pro

POST/pos/sessions/{id}/close

Path parameters

idrequiredPOS (point of sale) ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 140,
        "cashier_id": 77,
        "cashier_name": "Esi Mensah",
        "status": "open",
        "sale_count": 23,
        "total": 1840.5,
        "opened_at": "2026-10-08 07:58:00",
        "closed_at": null,
        "review_note": null,
        "reviewed_at": null
    }
}
curl -X POST 'https://www.blueq.app/api/v1/pos/sessions/140/close' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)"
Try it Changes live data

Approve or reject a closed session Pro

POST/pos/sessions/{id}/review

Path parameters

idrequiredPOS (point of sale) ID.

Body

decisionrequiredstringapproved or rejected.
notestringRequired when rejecting.

Response 200

{
    "ok": true,
    "data": {
        "id": 140,
        "cashier_id": 77,
        "cashier_name": "Esi Mensah",
        "status": "open",
        "sale_count": 23,
        "total": 1840.5,
        "opened_at": "2026-10-08 07:58:00",
        "closed_at": null,
        "review_note": null,
        "reviewed_at": null
    }
}
curl -X POST 'https://www.blueq.app/api/v1/pos/sessions/140/review' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"decision":"approved","note":""}'
Try it Changes live data

Record a walk-in sale Pro

POST/pos/sales

Fails with 422 if the cashier has no open session, the register is inactive, or stock is insufficient. Send an Idempotency-Key so a retried request never charges twice.

Body (PosSale)

cashier_idintegerCashier with an open session (default: owner).
itemsrequiredarray<object>Lines: {"product_id": 57, "qty": 2}. Prices come from the catalogue.
payment_moderequiredstringCash, Mobile Money or Card.
customer_namestringDefault "Walk-in".
customer_phonestringReceipt SMS number.

Response 201

{
    "ok": true,
    "data": {
        "id": 140,
        "cashier_id": 77,
        "cashier_name": "Esi Mensah",
        "status": "open",
        "sale_count": 23,
        "total": 1840.5,
        "opened_at": "2026-10-08 07:58:00",
        "closed_at": null,
        "review_note": null,
        "reviewed_at": null
    }
}
curl -X POST 'https://www.blueq.app/api/v1/pos/sales' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"cashier_id":77,"items":[{"product_id":57,"qty":2}],"payment_mode":"Cash","customer_name":"Walk-in","customer_phone":""}'
Try it Changes live data

Reports

Reports /reports

The same datasets as the Reports area and scheduled report e-mails: run any built-in report with filters, read its summary and chart series, and manage saved custom reports (several datasets with chosen columns). Rows come back both as raw arrays (matching columns) and as keyed objects; money columns are numbers.

The reports object
sourceread-onlystringReport key (see GET /reports).
titleread-onlystringReport title.
columnsread-onlyarray<string>Column headings.
rowsread-onlyarray<array>Rows in column order.
itemsread-onlyarray<object>Rows keyed by snake_case column name.
summaryread-onlyarray<object>Headline figures: {label, value}.
totalread-onlyintegerRows matching the filters.
truncatedread-onlybooleanMore rows match than were returned (raise limit, max 5000).
filtersread-onlyobjectFilters applied.
ReportSource object
sourceread-onlystringReport key, used in /reports/{source}.
titleread-onlystringReport title.
columnsread-onlyarray<string>Columns the report returns.
chartsread-onlyarray<string>Charts available at /reports/{source}/charts.
filtersread-onlyobjectQuery filters the report accepts: name => description.
CustomReport object
idread-onlyintegerCustom report ID.
namestringReport name.
sectionsarray<object>Datasets: {"source":"payments","columns":["Date","Amount (GHS)"],"filters":{"payment_type":"credit"},"chart":"all"}. Dates are supplied when the report is run.
updated_atread-onlydatetimeLast change (UTC).

List available reports and their filters

GET/reports

Response 200

{
    "ok": true,
    "data": [
        {
            "source": "payments",
            "title": "Payments",
            "columns": [
                "Date",
                "Client",
                "Invoice",
                "Mode",
                "Type",
                "Amount (GHS)",
                "Remarks"
            ],
            "charts": [
                "Payments by day",
                "Payments by mode"
            ],
            "filters": {
                "from": "Start date.",
                "to": "End date.",
                "client": "Client ID.",
                "payment_type": "credit or debit."
            }
        }
    ]
}
curl 'https://www.blueq.app/api/v1/reports' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

List saved custom reports

GET/reports/custom

Response 200

{
    "ok": true,
    "data": [
        {
            "id": 6,
            "name": "Monthly cash view",
            "sections": [
                {
                    "source": "payments",
                    "columns": [
                        "Date",
                        "Client",
                        "Amount (GHS)"
                    ],
                    "filters": {
                        "payment_type": "credit"
                    },
                    "chart": ""
                }
            ],
            "updated_at": "2026-10-01 10:12:00"
        }
    ]
}
curl 'https://www.blueq.app/api/v1/reports/custom' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Save a custom report Pro

POST/reports/custom

Body (CustomReport)

namerequiredstringReport name.
sectionsrequiredarray<object>Datasets: {"source":"payments","columns":["Date","Amount (GHS)"],"filters":{"payment_type":"credit"},"chart":"all"}. Dates are supplied when the report is run.

Response 201

{
    "ok": true,
    "data": {
        "id": 6,
        "name": "Monthly cash view",
        "sections": [
            {
                "source": "payments",
                "columns": [
                    "Date",
                    "Client",
                    "Amount (GHS)"
                ],
                "filters": {
                    "payment_type": "credit"
                },
                "chart": ""
            }
        ],
        "updated_at": "2026-10-01 10:12:00"
    }
}
curl -X POST 'https://www.blueq.app/api/v1/reports/custom' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Monthly cash view","sections":[{"source":"payments","columns":["Date","Client","Amount (GHS)"],"filters":{"payment_type":"credit"},"chart":""}]}'
Try it Changes live data

Get a custom report definition

GET/reports/custom/{id}

Path parameters

idrequiredReports ID.

Response 200

{
    "ok": true,
    "data": {
        "id": 6,
        "name": "Monthly cash view",
        "sections": [
            {
                "source": "payments",
                "columns": [
                    "Date",
                    "Client",
                    "Amount (GHS)"
                ],
                "filters": {
                    "payment_type": "credit"
                },
                "chart": ""
            }
        ],
        "updated_at": "2026-10-01 10:12:00"
    }
}
curl 'https://www.blueq.app/api/v1/reports/custom/1' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Update a custom report Pro

PATCH/reports/custom/{id}

Path parameters

idrequiredReports ID.

Body (CustomReport)

namerequiredstringReport name.
sectionsrequiredarray<object>Datasets: {"source":"payments","columns":["Date","Amount (GHS)"],"filters":{"payment_type":"credit"},"chart":"all"}. Dates are supplied when the report is run.

Response 200

{
    "ok": true,
    "data": {
        "id": 6,
        "name": "Monthly cash view",
        "sections": [
            {
                "source": "payments",
                "columns": [
                    "Date",
                    "Client",
                    "Amount (GHS)"
                ],
                "filters": {
                    "payment_type": "credit"
                },
                "chart": ""
            }
        ],
        "updated_at": "2026-10-01 10:12:00"
    }
}
curl -X PATCH 'https://www.blueq.app/api/v1/reports/custom/1' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Monthly cash view","sections":[{"source":"payments","columns":["Date","Client","Amount (GHS)"],"filters":{"payment_type":"credit"},"chart":""}]}'
Try it Changes live data

Delete a custom report Pro

DELETE/reports/custom/{id}

Path parameters

idrequiredReports ID.

Response 204

Empty body.

curl -X DELETE 'https://www.blueq.app/api/v1/reports/custom/1' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it Changes live data

Run a custom report

GET/reports/custom/{id}/run

Returns one result block (same shape as a built-in report) per section.

Path parameters

idrequiredReports ID.

Query parameters

fromStart date (YYYY-MM-DD).
toEnd date (YYYY-MM-DD).
limitMax rows, 1-5000 (default 1500).
(filters)Report-specific filters listed by GET /reports, e.g. ?client=2399&payment_type=credit.

Response 200

{
    "ok": true,
    "data": {
        "source": "sales",
        "title": "Sales",
        "columns": [
            "Date",
            "Category",
            "Product",
            "Customer",
            "Status",
            "Reference",
            "Amount (GHS)"
        ],
        "rows": [
            [
                "2026-10-08",
                "Products",
                "Kente stole",
                "Walk-in",
                "Paid",
                "S-0812",
                240
            ]
        ],
        "items": [
            {
                "date": "2026-10-08",
                "category": "Products",
                "product": "Kente stole",
                "customer": "Walk-in",
                "status": "Paid",
                "reference": "S-0812",
                "amount_ghs": 240
            }
        ],
        "summary": [
            {
                "label": "Rows",
                "value": "58"
            },
            {
                "label": "Amount",
                "value": "GHS 9,420.00"
            }
        ],
        "total": 58,
        "truncated": false,
        "filters": {
            "start": "2026-10-01",
            "end": "2026-10-31"
        }
    }
}
curl 'https://www.blueq.app/api/v1/reports/custom/1/run' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Run a custom report (POST)

POST/reports/custom/{id}/run

Same as the GET form, for clients that prefer POST. Allowed on read-only keys.

Path parameters

idrequiredReports ID.

Query parameters

fromStart date (YYYY-MM-DD).
toEnd date (YYYY-MM-DD).
limitMax rows, 1-5000 (default 1500).
(filters)Report-specific filters listed by GET /reports, e.g. ?client=2399&payment_type=credit.

Response 200

{
    "ok": true,
    "data": {
        "source": "sales",
        "title": "Sales",
        "columns": [
            "Date",
            "Category",
            "Product",
            "Customer",
            "Status",
            "Reference",
            "Amount (GHS)"
        ],
        "rows": [
            [
                "2026-10-08",
                "Products",
                "Kente stole",
                "Walk-in",
                "Paid",
                "S-0812",
                240
            ]
        ],
        "items": [
            {
                "date": "2026-10-08",
                "category": "Products",
                "product": "Kente stole",
                "customer": "Walk-in",
                "status": "Paid",
                "reference": "S-0812",
                "amount_ghs": 240
            }
        ],
        "summary": [
            {
                "label": "Rows",
                "value": "58"
            },
            {
                "label": "Amount",
                "value": "GHS 9,420.00"
            }
        ],
        "total": 58,
        "truncated": false,
        "filters": {
            "start": "2026-10-01",
            "end": "2026-10-31"
        }
    }
}
curl -X POST 'https://www.blueq.app/api/v1/reports/custom/1/run' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY' \
  -H 'Idempotency-Key: '"$(uuidgen)"
Try it

Run a built-in report

GET/reports/{source}

Path parameters

sourcerequiredSource.

Query parameters

fromStart date (YYYY-MM-DD).
toEnd date (YYYY-MM-DD).
limitMax rows, 1-5000 (default 1500).
(filters)Report-specific filters listed by GET /reports, e.g. ?client=2399&payment_type=credit.

Response 200

{
    "ok": true,
    "data": {
        "source": "sales",
        "title": "Sales",
        "columns": [
            "Date",
            "Category",
            "Product",
            "Customer",
            "Status",
            "Reference",
            "Amount (GHS)"
        ],
        "rows": [
            [
                "2026-10-08",
                "Products",
                "Kente stole",
                "Walk-in",
                "Paid",
                "S-0812",
                240
            ]
        ],
        "items": [
            {
                "date": "2026-10-08",
                "category": "Products",
                "product": "Kente stole",
                "customer": "Walk-in",
                "status": "Paid",
                "reference": "S-0812",
                "amount_ghs": 240
            }
        ],
        "summary": [
            {
                "label": "Rows",
                "value": "58"
            },
            {
                "label": "Amount",
                "value": "GHS 9,420.00"
            }
        ],
        "total": 58,
        "truncated": false,
        "filters": {
            "start": "2026-10-01",
            "end": "2026-10-31"
        }
    }
}
curl 'https://www.blueq.app/api/v1/reports/sales' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it

Chart series for a report

GET/reports/{source}/charts

Each chart has a title, type (bar, line, pie, doughnut) and labels/series ready for any chart library.

Path parameters

sourcerequiredSource.

Query parameters

fromStart date.
toEnd date.
(filters)Same filters as the report.

Response 200

{
    "ok": true,
    "data": {
        "source": "sales",
        "title": "Sales",
        "columns": [
            "Date",
            "Category",
            "Product",
            "Customer",
            "Status",
            "Reference",
            "Amount (GHS)"
        ],
        "rows": [
            [
                "2026-10-08",
                "Products",
                "Kente stole",
                "Walk-in",
                "Paid",
                "S-0812",
                240
            ]
        ],
        "items": [
            {
                "date": "2026-10-08",
                "category": "Products",
                "product": "Kente stole",
                "customer": "Walk-in",
                "status": "Paid",
                "reference": "S-0812",
                "amount_ghs": 240
            }
        ],
        "summary": [
            {
                "label": "Rows",
                "value": "58"
            },
            {
                "label": "Amount",
                "value": "GHS 9,420.00"
            }
        ],
        "total": 58,
        "truncated": false,
        "filters": {
            "start": "2026-10-01",
            "end": "2026-10-31"
        }
    }
}
curl 'https://www.blueq.app/api/v1/reports/sales/charts' \
  -H 'Authorization: Bearer $BLUEQ_API_KEY'
Try it