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.
23 resources
148 endpoints
JSON over HTTPS
OpenAPI 3 + Postman
Base URL
https://blueq.ekklesiasys.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
In blueQ, open Settings → Developer API and click Generate sandbox key . Copy the key — it is shown only once.
Call GET /me to check the key, your plan and rate limit.
Paste the key in the box at the top of this page to use Try it on any endpoint.
When your integration works, generate a live key (Business or Pro) and swap it in. Nothing else changes.
curl https://blueq.ekklesiasys.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_…
Data A separate sandbox copy of your business. Your real clients, invoices and sales are never visible or changed. Your real business data.
Plans Every plan, including Free and Growth. Business (read-only) and Pro (read/write).
Access Full read / write. As your plan allows.
Rate limit 60 requests / minute. 60 (Business) or 300 (Pro) requests / minute.
Sample data A category, two products, a client, a draft invoice and a cash sale are created on the first request. —
SMS, email, WhatsApp Really 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
Plan Live key Live rate limit Sandbox 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.
Status Code Meaning
400 bad_requestThe request is malformed (invalid JSON, bad parameter or Idempotency-Key).
400 key_in_urlThe key was sent in the query string. Send it in the Authorization header instead, and regenerate it.
401 missing_api_keyNo API key was sent.
401 invalid_api_keyThe key is wrong or was replaced by Regenerate.
401 api_key_inactiveThe key has been deactivated in Settings → Developer API.
402 subscription_expiredThe workspace subscription has expired.
403 plan_no_apiThe workspace plan does not include live API access (Free and Growth). Sandbox keys still work.
403 plan_read_onlyA Business (read-only) key tried to create, update or delete.
403 api_disabledAPI access was switched off for this workspace by blueQ support.
403 feature_not_enabledThe module is not enabled for this business type (e.g. recipes).
403 plan_limit_reachedCreating the record would exceed a plan limit (clients, invoices, branches...).
404 not_foundThe record does not exist or belongs to another workspace.
405 method_not_allowedThe path exists but not for this HTTP method. See the Allow header.
409 conflictThe request conflicts with the record's state (duplicate, locked, in use). The code is often more specific, e.g. items_locked, invalid_status.
422 validation_failedOne or more fields are invalid. "fields" maps each field to a message.
409 idempotency_key_reusedThe Idempotency-Key was used before with a different request.
429 rate_limitedToo many requests this minute. Wait for Retry-After seconds.
500 server_errorSomething went wrong on our side. Retry later; include X-Request-Id if you contact support.
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://blueq.ekklesiasys.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-only string "live" for bq_live_ keys, "sandbox" for bq_test_ keys. workspace_idread-only integer Workspace (tenant) ID. For sandbox keys this is the sandbox workspace. live_workspace_idread-only integer The real workspace. Same as workspace_id for live keys. business_nameread-only string Business name. business_typeread-only string Industry slug. ownerread-only object Owner name, email and phone. planread-only string Current plan name. api_accessread-only string "read" (Business) or "full" (Pro). rate_limit_per_minuteread-only integer Requests allowed per minute. key_prefixread-only string First characters of the key in use. limitsread-only object Plan limits (null = unlimited). usageread-only object Current 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/me' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/me');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/me', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Customers
Clients /clients
Customers you invoice and sell to. Creating a client counts against the plan's client limit.
The clients object
idread-only integer Client ID. namestring Client or company name. contact_personstring Contact person (for companies). phonestring Phone number. Must be unique in the workspace. emailstring Email address. Must be unique in the workspace. genderstring "male" or "female". date_of_birthdate Date of birth (YYYY-MM-DD). balanceread-only number Account balance cache (credit on account). created_atread-only datetime When 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/clients?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/clients?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/clients?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create a client Pro
POST /clients
Body
namerequired string Client or company name. contact_personstring Contact person (for companies). phonerequired string Phone number. Must be unique in the workspace. emailstring Email address. Must be unique in the workspace. genderstring "male" or "female". date_of_birthdate Date 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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"}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/clients');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Akosua Boateng',
'contact_person' => 'Kwame Boateng',
'phone' => '0244123456',
'email' => 'akosua@example.com',
'gender' => 'female',
'date_of_birth' => '1992-04-18'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/clients', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"name": "Akosua Boateng",
"contact_person": "Kwame Boateng",
"phone": "0244123456",
"email": "akosua@example.com",
"gender": "female",
"date_of_birth": "1992-04-18"
}),
});
const { ok, data, error } = await res.json();
Get a client
GET /clients/{id}
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/clients/2381' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/clients/2381');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/clients/2381', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a client Pro
PATCH /clients/{id}
Path parameters
Body
namestring Client or company name. contact_personstring Contact person (for companies). phonestring Phone number. Must be unique in the workspace. emailstring Email address. Must be unique in the workspace. genderstring "male" or "female". date_of_birthdate Date 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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"}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/clients/2381');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Akosua Boateng',
'contact_person' => 'Kwame Boateng',
'phone' => '0244123456',
'email' => 'akosua@example.com',
'gender' => 'female',
'date_of_birth' => '1992-04-18'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/clients/2381', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Akosua Boateng",
"contact_person": "Kwame Boateng",
"phone": "0244123456",
"email": "akosua@example.com",
"gender": "female",
"date_of_birth": "1992-04-18"
}),
});
const { ok, data, error } = await res.json();
Delete a client Pro
DELETE /clients/{id}
Refused with 409 if the client has invoices.
Path parameters
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/clients/2381' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/clients/2381');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/clients/2381', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
List a client's invoices
GET /clients/{id}/invoices
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/clients/2381/invoices?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/clients/2381/invoices?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/clients/2381/invoices?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
List a client's payments
GET /clients/{id}/payments
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/clients/2381/payments?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/clients/2381/payments?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/clients/2381/payments?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Customers
Suppliers /suppliers
Vendors you buy stock and services from. Deleting a supplier archives it.
The suppliers object
idread-only integer Supplier ID. namestring Supplier name. phonestring Phone number. emailstring Email address. notesstring Internal notes. created_atread-only datetime When 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/suppliers?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/suppliers?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/suppliers?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create a supplier Pro
POST /suppliers
Body
namerequired string Supplier name. phonestring Phone number. emailstring Email address. notesstring Internal 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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."}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/suppliers');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Accra Paper Mills',
'phone' => '0302123456',
'email' => 'sales@accrapaper.com',
'notes' => 'Delivers on Tuesdays.'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/suppliers', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"name": "Accra Paper Mills",
"phone": "0302123456",
"email": "sales@accrapaper.com",
"notes": "Delivers on Tuesdays."
}),
});
const { ok, data, error } = await res.json();
Get a supplier
GET /suppliers/{id}
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/suppliers/57' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/suppliers/57');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/suppliers/57', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a supplier Pro
PATCH /suppliers/{id}
Path parameters
Body
namestring Supplier name. phonestring Phone number. emailstring Email address. notesstring Internal 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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."}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/suppliers/57');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Accra Paper Mills',
'phone' => '0302123456',
'email' => 'sales@accrapaper.com',
'notes' => 'Delivers on Tuesdays.'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/suppliers/57', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Accra Paper Mills",
"phone": "0302123456",
"email": "sales@accrapaper.com",
"notes": "Delivers on Tuesdays."
}),
});
const { ok, data, error } = await res.json();
Archive a supplier Pro
DELETE /suppliers/{id}
Path parameters
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/suppliers/57' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/suppliers/57');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/suppliers/57', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer Product ID. namestring Item name. descriptionstring Description shown on invoices and the store. pricenumber Selling price. category_idinteger Product category ID. kindread-only string "product", "service" or "menu" (from the category). sellableboolean true = catalogue item you sell; false = stock/inventory item. activeboolean Whether the item is active. unitstring Stock unit, e.g. pcs, kg, l, m. stock_qtynumber Quantity on hand. Set on create only; use /stock/adjustments afterwards. reorder_levelnumber Low-stock warning level (inventory items). expiry_datedate Expiry date (inventory items). supplier_idinteger Default supplier ID. supplier_costnumber Cost price from the supplier. variationsarray Sizes/options: [{id, label, price, stock_qty}]. Send on create to add variations. created_atread-only datetime When 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/products?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/products?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/products?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create a product Pro
POST /products
Body
namerequired string Item name. descriptionstring Description shown on invoices and the store. pricerequired number Selling price. category_idrequired integer Product category ID. sellableboolean true = catalogue item you sell; false = stock/inventory item. activeboolean Whether the item is active. unitstring Stock unit, e.g. pcs, kg, l, m. stock_qtynumber Quantity on hand. Set on create only; use /stock/adjustments afterwards. reorder_levelnumber Low-stock warning level (inventory items). expiry_datedate Expiry date (inventory items). supplier_idinteger Default supplier ID. supplier_costnumber Cost price from the supplier. variationsarray Sizes/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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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}]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/products');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'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
]
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/products', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"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
}
]
}),
});
const { ok, data, error } = await res.json();
Get a product
GET /products/{id}
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/products/812' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/products/812');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/products/812', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a product Pro
PATCH /products/{id}
stock_qty and variations are ignored here. Use /stock/adjustments to change quantities.
Path parameters
Body
namestring Item name. descriptionstring Description shown on invoices and the store. pricenumber Selling price. category_idinteger Product category ID. sellableboolean true = catalogue item you sell; false = stock/inventory item. activeboolean Whether the item is active. unitstring Stock unit, e.g. pcs, kg, l, m. stock_qtynumber Quantity on hand. Set on create only; use /stock/adjustments afterwards. reorder_levelnumber Low-stock warning level (inventory items). expiry_datedate Expiry date (inventory items). supplier_idinteger Default supplier ID. supplier_costnumber Cost price from the supplier. variationsarray Sizes/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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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}]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/products/812');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'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
]
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/products/812', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"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
}
]
}),
});
const { ok, data, error } = await res.json();
Delete a product Pro
DELETE /products/{id}
Deletes the item, its variations and its store links.
Path parameters
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/products/812' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/products/812');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/products/812', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Catalogue & stock
Product categories /product-categories
Groups for catalogue items. Each category has a kind: product, service or menu.
The product categories object
idread-only integer Category ID. namestring Category name. kindstring "product", "service" or "menu". store_visibleboolean Shown on the online store. product_countread-only integer Items 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/product-categories?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/product-categories?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/product-categories?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create a category Pro
POST /product-categories
Body
namerequired string Category name. kindstring "product", "service" or "menu". store_visibleboolean Shown on the online store.
Response 201
{
"ok": true,
"data": {
"id": 14,
"name": "Banners",
"kind": "product",
"store_visible": true,
"product_count": 12
}
}
cURL
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/product-categories');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Banners',
'kind' => 'product',
'store_visible' => true
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/product-categories', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"name": "Banners",
"kind": "product",
"store_visible": true
}),
});
const { ok, data, error } = await res.json();
Get a category
GET /product-categories/{id}
Path parameters
idrequired Product categories ID.
Response 200
{
"ok": true,
"data": {
"id": 14,
"name": "Banners",
"kind": "product",
"store_visible": true,
"product_count": 12
}
}
cURL
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/product-categories/14' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/product-categories/14');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/product-categories/14', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a category Pro
PATCH /product-categories/{id}
Path parameters
idrequired Product categories ID.
Body
namestring Category name. kindstring "product", "service" or "menu". store_visibleboolean Shown on the online store.
Response 200
{
"ok": true,
"data": {
"id": 14,
"name": "Banners",
"kind": "product",
"store_visible": true,
"product_count": 12
}
}
cURL
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/product-categories/14');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Banners',
'kind' => 'product',
'store_visible' => true
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/product-categories/14', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Banners",
"kind": "product",
"store_visible": true
}),
});
const { ok, data, error } = await res.json();
Delete a category Pro
DELETE /product-categories/{id}
Refused with 409 while products are still in the category.
Path parameters
idrequired Product categories ID.
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/product-categories/14' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/product-categories/14');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/product-categories/14', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer Product ID. nameread-only string Item name. unitread-only string Stock unit. stock_qtyread-only number Quantity on hand. reorder_levelread-only number Low-stock level. low_stockread-only boolean true when stock_qty is at or below reorder_level. sellableread-only boolean Catalogue item (true) or inventory item (false). unit_costread-only number Supplier cost per unit, if known. stock_valueread-only number stock_qty x unit_cost.
Stock movement object
idread-only integer Movement ID. product_idread-only integer Product ID. nameread-only string Item name at the time. typeread-only string add, discard, edit or create. reasonread-only string Discard reason: expired, broken, obsolete. qtyread-only number Quantity changed. qty_beforeread-only number Quantity before. qty_afterread-only number Quantity after (null while pending). statusread-only string pending, approved or rejected. noteread-only string Note. requested_byread-only string Who requested it. requested_atread-only datetime When (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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/stock?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/stock?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/stock?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/stock/movements?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/stock/movements?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/stock/movements?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Adjust stock Pro
POST /stock/adjustments
Subtracting more than is on hand returns 422.
Body
product_idrequired integer Product to adjust (must be a product-kind item). directionrequired string "add" or "subtract". qtyrequired number Quantity to add or remove (greater than 0). reasonstring For subtract: expired, broken or obsolete. notestring Note 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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"}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/stock/adjustments');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'product_id' => 812,
'direction' => 'add',
'qty' => 100,
'reason' => null,
'note' => 'Delivery from Accra Paper Mills'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/stock/adjustments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"product_id": 812,
"direction": "add",
"qty": 100,
"reason": null,
"note": "Delivery from Accra Paper Mills"
}),
});
const { ok, data, error } = await res.json();
Get stock for a product
GET /stock/{id}
Includes the 20 most recent movements as "movements".
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/stock/1' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/stock/1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/stock/1', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only string Invoice ID (used in URLs and public links). numberread-only string Invoice number, generated automatically. titlestring Invoice title / subject. client_idinteger Client the invoice is billed to. typeread-only string "proforma", "invoice" or "receipt". statusread-only string "open", "completed" or "discarded". confirmedread-only boolean Whether the proforma has been confirmed into an invoice. datedate Invoice date (YYYY-MM-DD). due_datedate Payment due / quote expiry date. tax_group_idinteger Tax group applied (see /tax-groups). Omit for the default group. discount_typestring "amount" or "percent". discount_valuenumber Discount amount or percentage. subtotalread-only number Sum of line items. discountread-only number Discount applied. tax_totalread-only number Levies + VAT. totalread-only number Grand total. paidread-only number Payments received. balance_dueread-only number total − paid. orders_createdread-only boolean Production orders have been created from this invoice. public_urlread-only string Client-facing link (works once sharing is enabled). created_atread-only datetime Created (UTC). itemsarray<InvoiceItem> Line items (detail view and create).
InvoiceItem object
idread-only integer Line item ID. product_idinteger Catalogue product/service, or null for a custom line. namestring Line name. Defaults to the product name. descriptionstring Extra detail shown under the name. qtynumber Quantity (default 1). unit_pricenumber Unit price. Defaults to the product price. amountread-only number qty × unit_price.
InvoicePayment object
idread-only integer Payment ID. amountnumber Amount received. modestring cash, momo, cheque, bank, card, credit (from account balance) or a custom mode. datedate Payment date (default today). remarksstring Reference or note.
InvoiceReminder object
idread-only integer Reminder ID. remind_atdatetime When to send (YYYY-MM-DD HH:MM, workspace time). channelsarray<string> Any of email, sms, whatsapp. messagestring Optional 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_pdfboolean Attach the PDF on WhatsApp. recurstring "", "daily" or "monthly". recur_untildate Last date for repeating reminders. statusread-only string pending, 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/invoices?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create an invoice Pro
POST /invoices
Pass items[] to add lines in the same call. Set "confirm": true to confirm it straight away.
Body
titlestring Invoice title / subject. client_idinteger Client the invoice is billed to. datedate Invoice date (YYYY-MM-DD). due_datedate Payment due / quote expiry date. tax_group_idinteger Tax group applied (see /tax-groups). Omit for the default group. discount_typestring "amount" or "percent". discount_valuenumber Discount 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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}]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'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
]
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"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
}
]
}),
});
const { ok, data, error } = await res.json();
Get an invoice with items and payments
GET /invoices/{id}
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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
Body
titlestring Invoice title / subject. client_idinteger Client the invoice is billed to. datedate Invoice date (YYYY-MM-DD). due_datedate Payment due / quote expiry date. tax_group_idinteger Tax group applied (see /tax-groups). Omit for the default group. discount_typestring "amount" or "percent". discount_valuenumber Discount 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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}]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'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
]
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"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
}
]
}),
});
const { ok, data, error } = await res.json();
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
Body
noterequired string Reason (required).
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"note":"Client cancelled"}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'note' => 'Client cancelled'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"note": "Client cancelled"
}),
});
const { ok, data, error } = await res.json();
Restore a discarded invoice Pro
POST /invoices/{id}/restore
Path parameters
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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/restore' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Idempotency-Key: '"$(uuidgen)"
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/restore');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/restore', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const { ok, data, error } = await res.json();
Confirm a proforma into an invoice Pro
POST /invoices/{id}/confirm
Path parameters
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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/confirm' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Idempotency-Key: '"$(uuidgen)"
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/confirm');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/confirm', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const { ok, data, error } = await res.json();
List line items
GET /invoices/{id}/items
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/items' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/items');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/items', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Add a line item Pro
POST /invoices/{id}/items
Path parameters
Body (InvoiceItem)
product_idinteger Catalogue product/service, or null for a custom line. namestring Line name. Defaults to the product name. descriptionstring Extra detail shown under the name. qtynumber Quantity (default 1). unit_pricenumber Unit 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/items');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'product_id' => 57,
'name' => 'Round-neck T-shirt (L)',
'description' => 'Navy, 2-colour print front',
'qty' => 40,
'unit_price' => 30
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/items', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"product_id": 57,
"name": "Round-neck T-shirt (L)",
"description": "Navy, 2-colour print front",
"qty": 40,
"unit_price": 30
}),
});
const { ok, data, error } = await res.json();
Update a line item Pro
PATCH /invoices/{id}/items/{item_id}
Path parameters
idrequired Invoices ID. item_idrequired Item id.
Body (InvoiceItem)
product_idinteger Catalogue product/service, or null for a custom line. namestring Line name. Defaults to the product name. descriptionstring Extra detail shown under the name. qtynumber Quantity (default 1). unit_pricenumber Unit 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/items/1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'product_id' => 57,
'name' => 'Round-neck T-shirt (L)',
'description' => 'Navy, 2-colour print front',
'qty' => 40,
'unit_price' => 30
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/items/1', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"product_id": 57,
"name": "Round-neck T-shirt (L)",
"description": "Navy, 2-colour print front",
"qty": 40,
"unit_price": 30
}),
});
const { ok, data, error } = await res.json();
Remove a line item Pro
DELETE /invoices/{id}/items/{item_id}
Path parameters
idrequired Invoices ID. item_idrequired Item id.
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/items/1' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/items/1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/items/1', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
List payments on the invoice
GET /invoices/{id}/payments
Path parameters
Response 200
{
"ok": true,
"data": [
{
"id": 4410,
"amount": 500,
"mode": "momo",
"date": "2026-10-08",
"remarks": "MoMo ref 77812"
}
]
}
cURL
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/payments' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/payments');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/payments', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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
Body (InvoicePayment)
amountrequired number Amount received. modestring cash, momo, cheque, bank, card, credit (from account balance) or a custom mode. datedate Payment date (default today). remarksstring Reference or note.
Response 201
{
"ok": true,
"data": {
"id": 4410,
"amount": 500,
"mode": "momo",
"date": "2026-10-08",
"remarks": "MoMo ref 77812"
}
}
cURL
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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"}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/payments');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 500,
'mode' => 'momo',
'date' => '2026-10-08',
'remarks' => 'MoMo ref 77812'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/payments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"amount": 500,
"mode": "momo",
"date": "2026-10-08",
"remarks": "MoMo ref 77812"
}),
});
const { ok, data, error } = await res.json();
Edit a payment Pro
PATCH /invoices/{id}/payments/{payment_id}
Path parameters
idrequired Invoices ID. payment_idrequired Payment id.
Body (InvoicePayment)
amountrequired number Amount received. modestring cash, momo, cheque, bank, card, credit (from account balance) or a custom mode. datedate Payment date (default today). remarksstring Reference or note.
Response 200
{
"ok": true,
"data": {
"id": 4410,
"amount": 500,
"mode": "momo",
"date": "2026-10-08",
"remarks": "MoMo ref 77812"
}
}
cURL
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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"}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/payments/1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 500,
'mode' => 'momo',
'date' => '2026-10-08',
'remarks' => 'MoMo ref 77812'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/payments/1', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"amount": 500,
"mode": "momo",
"date": "2026-10-08",
"remarks": "MoMo ref 77812"
}),
});
const { ok, data, error } = await res.json();
Delete a payment Pro
DELETE /invoices/{id}/payments/{payment_id}
Path parameters
idrequired Invoices ID. payment_idrequired Payment id.
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/payments/1' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/payments/1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/payments/1', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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
Body
channelsarray<string> Any of email, sms, whatsapp (default email). email_toarray<string> Recipients (defaults to the client). ccstring Copy to this email. sms_toarray<string> SMS numbers (defaults to the client). whatsapp_toarray<string> WhatsApp numbers (defaults to the client). messagestring Message shown with the link. attach_pdfboolean Include 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/send');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'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
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"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
}),
});
const { ok, data, error } = await res.json();
Public view, PDF and receipt links
GET /invoices/{id}/links
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/links' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/links');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/links', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/pdf' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/pdf');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/pdf', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Turn the public link on or off Pro
POST /invoices/{id}/share
Path parameters
Body
enabledrequired boolean Share 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/share' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Idempotency-Key: '"$(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{"enabled":true}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/share');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'enabled' => true
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/share', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"enabled": true
}),
});
const { ok, data, error } = await res.json();
List scheduled reminders
GET /invoices/{id}/reminders
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/reminders' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/reminders');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/reminders', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Schedule a reminder Pro
POST /invoices/{id}/reminders
Path parameters
Body (InvoiceReminder)
remind_atrequired datetime When to send (YYYY-MM-DD HH:MM, workspace time). channelsrequired array<string> Any of email, sms, whatsapp. messagestring Optional 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_pdfboolean Attach the PDF on WhatsApp. recurstring "", "daily" or "monthly". recur_untildate Last 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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"}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/reminders');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'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'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/reminders', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"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"
}),
});
const { ok, data, error } = await res.json();
Change a pending reminder Pro
PATCH /invoices/{id}/reminders/{reminder_id}
Path parameters
idrequired Invoices ID. reminder_idrequired Reminder id.
Body (InvoiceReminder)
remind_atrequired datetime When to send (YYYY-MM-DD HH:MM, workspace time). channelsrequired array<string> Any of email, sms, whatsapp. messagestring Optional 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_pdfboolean Attach the PDF on WhatsApp. recurstring "", "daily" or "monthly". recur_untildate Last 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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"}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/reminders/1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'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'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/reminders/1', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"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"
}),
});
const { ok, data, error } = await res.json();
Cancel a reminder Pro
DELETE /invoices/{id}/reminders/{reminder_id}
Path parameters
idrequired Invoices ID. reminder_idrequired Reminder id.
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/reminders/1' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/reminders/1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/invoices/66f1c2a9e4b1d/reminders/1', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer Payment ID. client_idread-only integer Client who paid. invoice_idread-only string Invoice the payment belongs to (null for account top-ups). amountread-only number Amount received. moderead-only string cash, momo, cheque, bank, card, credit or a custom mode. dateread-only date Payment date. remarksread-only string Reference or note. created_atread-only datetime When 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/payments?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/payments?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/payments?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Get a payment
GET /payments/{id}
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/payments/4410' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/payments/4410');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/payments/4410', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer Sale ID. datedate Sale date (default today). client_idinteger Client, or null for a walk-in. customerstring Customer name for walk-ins (defaults to "Walk-in"). branch_idread-only integer Branch 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_valuenumber Discount amount or percentage. tax_group_idinteger Tax group to apply, "default", or null for no tax. remarksstring Internal note. subtotalread-only number Sum of lines. discountread-only number Discount applied. tax_totalread-only number Taxes added. totalread-only number Grand total. paidread-only number Total paid. balance_dueread-only number total − paid. payment_statusread-only string paid, partial or unpaid. invoice_idread-only string Invoice generated from this sale, if any. created_atread-only datetime Recorded (UTC).
SaleItem object
product_idinteger Catalogue product/service. Price defaults to its price. namestring Line name (required without product_id). categorystring Sales category for custom lines (default "Counter / walk-in sales"). qtynumber Quantity. unit_pricenumber Unit price. amountread-only number qty × unit_price.
SalePayment object
amountnumber Amount. modestring cash, momo, card, bank, cheque… datedate Payment date (default sale date). referencestring Reference 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/sales?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/sales?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/sales?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Record a sale Pro
POST /sales
Body
datedate Sale date (default today). client_idinteger Client, or null for a walk-in. customerstring Customer name for walk-ins (defaults to "Walk-in"). itemsrequired array<SaleItem> Cart lines (at least one). paymentsarray<SalePayment> Payments taken. Omit for an unpaid sale. discount_typestring "amount" or "percent". discount_valuenumber Discount amount or percentage. tax_group_idinteger Tax group to apply, "default", or null for no tax. remarksstring Internal 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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"}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/sales');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'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'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/sales', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"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"
}),
});
const { ok, data, error } = await res.json();
Get a sale with items and payments
GET /sales/{id}
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/sales/812' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/sales/812');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/sales/812', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a sale Pro
PATCH /sales/{id}
Fields you omit are kept. Passing items or payments replaces the whole list.
Path parameters
Body
datedate Sale date (default today). client_idinteger Client, or null for a walk-in. customerstring Customer 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_valuenumber Discount amount or percentage. tax_group_idinteger Tax group to apply, "default", or null for no tax. remarksstring Internal 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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"}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/sales/812');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'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'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/sales/812', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"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"
}),
});
const { ok, data, error } = await res.json();
Delete a sale Pro
DELETE /sales/{id}
Path parameters
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/sales/812' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/sales/812');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/sales/812', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Add a payment to a sale Pro
POST /sales/{id}/payments
Path parameters
Body (SalePayment)
amountrequired number Amount. modestring cash, momo, card, bank, cheque… datedate Payment date (default sale date). referencestring Reference 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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":""}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/sales/812/payments');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 250,
'mode' => 'cash',
'date' => '2026-10-08',
'reference' => ''
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/sales/812/payments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"amount": 250,
"mode": "cash",
"date": "2026-10-08",
"reference": ""
}),
});
const { ok, data, error } = await res.json();
Generate an invoice from the sale Pro
POST /sales/{id}/invoice
Returns the existing invoice if one was already generated.
Path parameters
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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.app/api/v1/sales/812/invoice' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Idempotency-Key: '"$(uuidgen)"
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/sales/812/invoice');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/sales/812/invoice', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const { ok, data, error } = await res.json();
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-only integer Expense ID. datedate Expense date (default today). payeestring Who was paid (defaults to the supplier name). supplier_idinteger Supplier (see /suppliers). supplier_invoicestring Supplier's invoice / bill number. linked_invoice_idstring Customer invoice this cost belongs to (job costing). categoryread-only string Summary of the line expense types. itemsarray<ExpenseItem> Lines (at least one). paymentsarray<ExpensePayment> Payments made. Omit for an unpaid bill. totalread-only number Total of all lines. paidread-only number Total paid. balance_dueread-only number total − paid. payment_statusread-only string paid, partial or unpaid. branch_idread-only integer Branch. created_atread-only datetime Saved (UTC).
ExpenseItem object
product_idinteger Stock product this purchase is for (adds to stock). namestring Line description (required without product_id). categorystring Expense type, e.g. "Raw materials", "Utilities", "Transport". qtynumber Quantity. unitstring Unit of measure (pcs, box, kg, l, m…). unit_pricenumber Price per unit (> 0). amountread-only number qty × unit_price. remarksstring Line note. expiry_datedate Expiry date for perishable stock.
ExpensePayment object
amountnumber Amount paid. modestring cash, momo, bank, cheque… datedate Payment date (default expense date). referencestring Reference. remarksstring Note.
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/expenditure?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/expenditure?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/expenditure?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Record an expense Pro
POST /expenditure
Body
datedate Expense date (default today). payeestring Who was paid (defaults to the supplier name). supplier_idinteger Supplier (see /suppliers). supplier_invoicestring Supplier's invoice / bill number. linked_invoice_idstring Customer invoice this cost belongs to (job costing). itemsrequired array<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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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":""}]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/expenditure');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'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' => ''
]
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/expenditure', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"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": ""
}
]
}),
});
const { ok, data, error } = await res.json();
Get an expense with lines and payments
GET /expenditure/{id}
Path parameters
idrequired Expenditure 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/expenditure/305' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/expenditure/305');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/expenditure/305', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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
idrequired Expenditure ID.
Body
datedate Expense date (default today). payeestring Who was paid (defaults to the supplier name). supplier_idinteger Supplier (see /suppliers). supplier_invoicestring Supplier's invoice / bill number. linked_invoice_idstring Customer 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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":""}]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/expenditure/305');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'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' => ''
]
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/expenditure/305', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"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": ""
}
]
}),
});
const { ok, data, error } = await res.json();
Delete an expense Pro
DELETE /expenditure/{id}
Stock added by the expense is reversed.
Path parameters
idrequired Expenditure ID.
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/expenditure/305' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/expenditure/305');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/expenditure/305', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Add a payment to an expense Pro
POST /expenditure/{id}/payments
Path parameters
idrequired Expenditure ID.
Body (ExpensePayment)
amountrequired number Amount paid. modestring cash, momo, bank, cheque… datedate Payment date (default expense date). referencestring Reference. remarksstring Note.
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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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":""}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/expenditure/305/payments');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 1450,
'mode' => 'momo',
'date' => '2026-10-07',
'reference' => 'MOMO-99812',
'remarks' => ''
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/expenditure/305/payments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"amount": 1450,
"mode": "momo",
"date": "2026-10-07",
"reference": "MOMO-99812",
"remarks": ""
}),
});
const { ok, data, error } = await res.json();
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-only integer Order ID. job_idinteger Job (invoice line group) this order belongs to. invoice_idread-only string Source invoice. titlestring What is being produced. product_idinteger Catalogue product/service, if any. qtynumber Quantity. statusread-only string pending, in_progress, completed or cancelled. assigned_tointeger Team member (see /users). start_atdatetime Planned start (YYYY-MM-DD HH:MM). end_atdatetime Planned finish. remarksstring Production notes. started_atread-only datetime When work began (UTC). completed_atread-only datetime When completed (UTC). cancelled_atread-only datetime When cancelled (UTC). processesarray<OrderProcess> Processes (detail view). On create, pass an array of names. created_atread-only datetime Created (UTC).
OrderProcess object
idread-only integer Process ID. nameread-only string Process name. doneread-only boolean Completed. completed_atread-only datetime When it was ticked off (UTC).
Job object
idread-only integer Job ID. invoice_idread-only string Invoice. invoice_item_idread-only integer Invoice line. titleread-only string Line name. qtyread-only number Line quantity. order_countread-only integer Orders under this job. created_atread-only datetime Created (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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/orders?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create an order inside a job Pro
POST /orders
Body
job_idrequired integer Job (invoice line group) this order belongs to. titlerequired string What is being produced. product_idinteger Catalogue product/service, if any. qtynumber Quantity. assigned_tointeger Team member (see /users). start_atdatetime Planned start (YYYY-MM-DD HH:MM). end_atdatetime Planned finish. remarksstring Production 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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":[[]]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'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' => [
[]
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"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": [
[]
]
}),
});
const { ok, data, error } = await res.json();
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/orders/jobs?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders/jobs?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders/jobs?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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_idrequired string Invoice 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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"}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders/from-invoice');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'invoice_id' => '66f1c2a9e4b1d'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders/from-invoice', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"invoice_id": "66f1c2a9e4b1d"
}),
});
const { ok, data, error } = await res.json();
Get an order with its processes
GET /orders/{id}
Path parameters
idrequired Orders (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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/orders/1204' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders/1204');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders/1204', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a pending order Pro
PATCH /orders/{id}
Path parameters
idrequired Orders (production) ID.
Body
job_idinteger Job (invoice line group) this order belongs to. titlestring What is being produced. product_idinteger Catalogue product/service, if any. qtynumber Quantity. assigned_tointeger Team member (see /users). start_atdatetime Planned start (YYYY-MM-DD HH:MM). end_atdatetime Planned finish. remarksstring Production 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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":[[]]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders/1204');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'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' => [
[]
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders/1204', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"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": [
[]
]
}),
});
const { ok, data, error } = await res.json();
Delete a pending order Pro
DELETE /orders/{id}
Path parameters
idrequired Orders (production) ID.
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/orders/1204' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders/1204');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders/1204', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Start work on an order Pro
POST /orders/{id}/commence
Path parameters
idrequired Orders (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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.app/api/v1/orders/1204/commence' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Idempotency-Key: '"$(uuidgen)"
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders/1204/commence');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders/1204/commence', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const { ok, data, error } = await res.json();
Complete an order Pro
POST /orders/{id}/complete
Refused with 409 while processes are open unless "force": true.
Path parameters
idrequired Orders (production) ID.
Body
forceboolean Also 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.app/api/v1/orders/1204/complete' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Idempotency-Key: '"$(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{"force":false}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders/1204/complete');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'force' => false
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders/1204/complete', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"force": false
}),
});
const { ok, data, error } = await res.json();
Cancel an order Pro
POST /orders/{id}/cancel
Path parameters
idrequired Orders (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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.app/api/v1/orders/1204/cancel' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Idempotency-Key: '"$(uuidgen)"
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders/1204/cancel');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders/1204/cancel', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const { ok, data, error } = await res.json();
Add processes Pro
POST /orders/{id}/processes
Path parameters
idrequired Orders (production) ID.
Body
namesrequired array<string> Process names. Duplicates are skipped.
Response 201
{
"ok": true,
"data": {
"id": 5521,
"name": "Screen print",
"done": false,
"completed_at": null
}
}
cURL
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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"]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders/1204/processes');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'names' => [
'Design',
'Print'
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders/1204/processes', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"names": [
"Design",
"Print"
]
}),
});
const { ok, data, error } = await res.json();
Tick off a process Pro
POST /orders/{id}/processes/{process_id}/complete
The order must be in progress.
Path parameters
idrequired Orders (production) ID. process_idrequired Process id.
Response 200
{
"ok": true,
"data": {
"id": 5521,
"name": "Screen print",
"done": false,
"completed_at": null
}
}
cURL
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.app/api/v1/orders/1204/processes/1/complete' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Idempotency-Key: '"$(uuidgen)"
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders/1204/processes/1/complete');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders/1204/processes/1/complete', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const { ok, data, error } = await res.json();
Remove an open process Pro
DELETE /orders/{id}/processes/{process_id}
Path parameters
idrequired Orders (production) ID. process_idrequired Process id.
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/orders/1204/processes/1' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/orders/1204/processes/1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/orders/1204/processes/1', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer Recipe ID. namestring Recipe name. notesstring Method / notes. yield_qtynumber How much one batch makes. yield_unitstring Yield unit (portion, kg, l, pcs…). menu_product_idread-only integer Linked 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-only array<object> Sizes defined in the app (read-only). costread-only number Current cost of one batch (ingredients + add-ons). cost_per_unitread-only number cost ÷ yield_qty. publish_to_menuboolean Create/refresh a sellable menu item (create only). menu_pricenumber Selling price for the menu item (with publish_to_menu). menu_category_idinteger Catalogue category for the menu item. created_atread-only datetime Created (UTC).
RecipeIngredient object
product_idinteger Cost-of-Sale stock item. namestring Name for a non-stock ingredient. qtynumber Quantity per batch. unitstring Unit (g, kg, ml, l, pcs…).
RecipeBase object
recipe_idinteger Base recipe. variation_idinteger Size of the base recipe (0 = default). qtynumber Quantity per batch. unitstring Unit.
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/recipes?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/recipes?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/recipes?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create a recipe Pro
POST /recipes
Body
namerequired string Recipe name. notesstring Method / notes. yield_qtynumber How much one batch makes. yield_unitstring Yield unit (portion, kg, l, pcs…). ingredientsrequired array<RecipeIngredient> Ingredients (at least one ingredient or base). basesarray<RecipeBase> Base recipes used inside this recipe. publish_to_menuboolean Create/refresh a sellable menu item (create only). menu_pricenumber Selling price for the menu item (with publish_to_menu). menu_category_idinteger Catalogue 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/recipes');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'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
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/recipes', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"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
}),
});
const { ok, data, error } = await res.json();
Get a recipe with ingredients and cost
GET /recipes/{id}
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/recipes/41' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/recipes/41');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/recipes/41', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/recipes/41/cost' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/recipes/41/cost');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/recipes/41/cost', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a recipe Pro
PATCH /recipes/{id}
Passing ingredients or bases replaces that list. Sizes and cost add-ons are not changed.
Path parameters
Body
namestring Recipe name. notesstring Method / notes. yield_qtynumber How much one batch makes. yield_unitstring Yield unit (portion, kg, l, pcs…). ingredientsarray<RecipeIngredient> Ingredients (at least one ingredient or base). basesarray<RecipeBase> Base recipes used inside this recipe. publish_to_menuboolean Create/refresh a sellable menu item (create only). menu_pricenumber Selling price for the menu item (with publish_to_menu). menu_category_idinteger Catalogue 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/recipes/41');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'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
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/recipes/41', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"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
}),
});
const { ok, data, error } = await res.json();
Delete a recipe Pro
DELETE /recipes/{id}
Also removes its sizes and any menu-item variations linked to it.
Path parameters
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/recipes/41' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/recipes/41');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/recipes/41', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer Tax ID. namestring Tax name. ratenumber Percentage, 0 to 100. typestring "levy" or "vat". sortinteger Display and calculation order. activeboolean Active 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/taxes' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/taxes');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/taxes', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create a tax Pro
POST /taxes
Body
namerequired string Tax name. raterequired number Percentage, 0 to 100. typerequired string "levy" or "vat". sortinteger Display and calculation order. activeboolean Active 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/taxes');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'VAT',
'rate' => 15,
'type' => 'vat',
'sort' => 40,
'active' => true
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/taxes', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"name": "VAT",
"rate": 15,
"type": "vat",
"sort": 40,
"active": true
}),
});
const { ok, data, error } = await res.json();
Get a tax
GET /taxes/{id}
Path parameters
Response 200
{
"ok": true,
"data": {
"id": 31,
"name": "VAT",
"rate": 15,
"type": "vat",
"sort": 40,
"active": true
}
}
cURL
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/taxes/31' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/taxes/31');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/taxes/31', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a tax Pro
PATCH /taxes/{id}
Invoices already confirmed keep the rates they were issued with.
Path parameters
Body
namestring Tax name. ratenumber Percentage, 0 to 100. typestring "levy" or "vat". sortinteger Display and calculation order. activeboolean Active 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/taxes/31');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'VAT',
'rate' => 15,
'type' => 'vat',
'sort' => 40,
'active' => true
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/taxes/31', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "VAT",
"rate": 15,
"type": "vat",
"sort": 40,
"active": true
}),
});
const { ok, data, error } = await res.json();
Delete a tax Pro
DELETE /taxes/{id}
Also removes the tax from every tax group.
Path parameters
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/taxes/31' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/taxes/31');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/taxes/31', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer Tax group ID. namestring Group name. is_defaultboolean Default group for new invoices and sales. activeboolean Whether the group can be selected. sortinteger Display order. tax_idsarray IDs of the taxes in this group. taxesread-only array The 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/tax-groups' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/tax-groups');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/tax-groups', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create a tax group Pro
POST /tax-groups
Body
namerequired string Group name. is_defaultboolean Default group for new invoices and sales. activeboolean Whether the group can be selected. sortinteger Display order. tax_idsarray IDs 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/tax-groups');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Standard (VAT + levies)',
'is_default' => true,
'active' => true,
'sort' => 0,
'tax_ids' => [
28,
29,
30,
31
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/tax-groups', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"name": "Standard (VAT + levies)",
"is_default": true,
"active": true,
"sort": 0,
"tax_ids": [
28,
29,
30,
31
]
}),
});
const { ok, data, error } = await res.json();
Get a tax group
GET /tax-groups/{id}
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/tax-groups/4' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/tax-groups/4');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/tax-groups/4', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a tax group Pro
PATCH /tax-groups/{id}
Path parameters
Body
namestring Group name. is_defaultboolean Default group for new invoices and sales. activeboolean Whether the group can be selected. sortinteger Display order. tax_idsarray IDs 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/tax-groups/4');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Standard (VAT + levies)',
'is_default' => true,
'active' => true,
'sort' => 0,
'tax_ids' => [
28,
29,
30,
31
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/tax-groups/4', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Standard (VAT + levies)",
"is_default": true,
"active": true,
"sort": 0,
"tax_ids": [
28,
29,
30,
31
]
}),
});
const { ok, data, error } = await res.json();
Delete a tax group Pro
DELETE /tax-groups/{id}
The default group cannot be deleted (409).
Path parameters
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/tax-groups/4' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/tax-groups/4');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/tax-groups/4', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer Branch ID. namestring Branch name. codestring Short code (stored uppercase). phonestring Phone (digits only). emailstring Email. addressstring Street address. townstring Town or city. is_defaultboolean Default branch for new records. activeboolean Whether the branch is in use. created_atread-only datetime When 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/branches' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/branches');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/branches', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create a branch Pro
POST /branches
Body
namerequired string Branch name. codestring Short code (stored uppercase). phonestring Phone (digits only). emailstring Email. addressstring Street address. townstring Town or city. is_defaultboolean Default branch for new records. activeboolean Whether 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/branches');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Kumasi Mall',
'code' => 'KSI',
'phone' => '0322000111',
'email' => 'kumasi@kenteprints.com',
'address' => 'Shop 14, Kumasi City Mall',
'town' => 'Kumasi',
'is_default' => false,
'active' => true
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/branches', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"name": "Kumasi Mall",
"code": "KSI",
"phone": "0322000111",
"email": "kumasi@kenteprints.com",
"address": "Shop 14, Kumasi City Mall",
"town": "Kumasi",
"is_default": false,
"active": true
}),
});
const { ok, data, error } = await res.json();
Get a branch
GET /branches/{id}
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/branches/3' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/branches/3');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/branches/3', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a branch Pro
PATCH /branches/{id}
Path parameters
Body
namestring Branch name. codestring Short code (stored uppercase). phonestring Phone (digits only). emailstring Email. addressstring Street address. townstring Town or city. is_defaultboolean Default branch for new records. activeboolean Whether 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/branches/3');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Kumasi Mall',
'code' => 'KSI',
'phone' => '0322000111',
'email' => 'kumasi@kenteprints.com',
'address' => 'Shop 14, Kumasi City Mall',
'town' => 'Kumasi',
'is_default' => false,
'active' => true
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/branches/3', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Kumasi Mall",
"code": "KSI",
"phone": "0322000111",
"email": "kumasi@kenteprints.com",
"address": "Shop 14, Kumasi City Mall",
"town": "Kumasi",
"is_default": false,
"active": true
}),
});
const { ok, data, error } = await res.json();
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
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/branches/3' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/branches/3');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/branches/3', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer User ID. first_namestring First name. last_namestring Last name. emailstring Email (the invite is sent here). Unique per workspace. phonestring Phone. Unique per workspace. rolestring Role key (see /roles), e.g. admin, manager, accountant, sales, production, viewer. role_labelread-only string Role name. branch_idinteger Home branch (see /branches). activeboolean Can sign in. Invited users become active after accepting. invite_pendingread-only boolean Invitation 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/users?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/users?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/users?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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_namerequired string First name. last_namerequired string Last name. emailrequired string Email (the invite is sent here). Unique per workspace. phonestring Phone. Unique per workspace. rolestring Role key (see /roles), e.g. admin, manager, accountant, sales, production, viewer. branch_idinteger Home branch (see /branches). activeboolean Can 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/users');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'first_name' => 'Esi',
'last_name' => 'Mensah',
'email' => 'esi@kenteprints.com',
'phone' => '0201234567',
'role' => 'manager',
'branch_id' => 1,
'active' => true
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/users', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"first_name": "Esi",
"last_name": "Mensah",
"email": "esi@kenteprints.com",
"phone": "0201234567",
"role": "manager",
"branch_id": 1,
"active": true
}),
});
const { ok, data, error } = await res.json();
Get a team member
GET /users/{id}
Path parameters
idrequired Users (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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/users/77' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/users/77');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/users/77', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a team member Pro
PATCH /users/{id}
Path parameters
idrequired Users (team) ID.
Body
first_namestring First name. last_namestring Last name. emailstring Email (the invite is sent here). Unique per workspace. phonestring Phone. Unique per workspace. rolestring Role key (see /roles), e.g. admin, manager, accountant, sales, production, viewer. branch_idinteger Home branch (see /branches). activeboolean Can 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/users/77');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'first_name' => 'Esi',
'last_name' => 'Mensah',
'email' => 'esi@kenteprints.com',
'phone' => '0201234567',
'role' => 'manager',
'branch_id' => 1,
'active' => true
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/users/77', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"first_name": "Esi",
"last_name": "Mensah",
"email": "esi@kenteprints.com",
"phone": "0201234567",
"role": "manager",
"branch_id": 1,
"active": true
}),
});
const { ok, data, error } = await res.json();
Remove a team member Pro
DELETE /users/{id}
Path parameters
idrequired Users (team) ID.
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/users/77' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/users/77');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/users/77', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only string Role key, used as users.role. labelstring Role name. summarystring Short description. customread-only boolean Created for this workspace (can be deleted). lockedread-only boolean Privileges cannot be changed (super_admin). user_countread-only integer Team members with this role. privilegesobject module key => {view, add, update, delete}. On create/update, only modules you pass are changed.
RoleModule object
keyread-only string Module key, used in privileges. labelread-only string Module name. industryread-only boolean Only 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/roles' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/roles');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/roles', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create a custom role Pro
POST /roles
Body
labelrequired string Role name. summarystring Short description. privilegesobject module 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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}}}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/roles');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'label' => 'Front desk',
'summary' => 'Clients, invoices and POS only.',
'privileges' => [
'invoices' => [
'view' => true,
'add' => true,
'update' => true,
'delete' => false
]
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/roles', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"label": "Front desk",
"summary": "Clients, invoices and POS only.",
"privileges": {
"invoices": {
"view": true,
"add": true,
"update": true,
"delete": false
}
}
}),
});
const { ok, data, error } = await res.json();
List modules that privileges apply to
GET /roles/modules
Response 200
{
"ok": true,
"data": [
{
"key": "invoices",
"label": "Invoices",
"industry": false
}
]
}
cURL
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/roles/modules' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/roles/modules');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/roles/modules', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Get a role
GET /roles/{key}
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/roles/cashier' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/roles/cashier');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/roles/cashier', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Change a role's privileges Pro
PATCH /roles/{key}
Path parameters
Body
privilegesrequired object module 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.app/api/v1/roles/cashier' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"privileges":{"reports":{"view":true}}}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/roles/cashier');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'privileges' => [
'reports' => [
'view' => true
]
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/roles/cashier', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"privileges": {
"reports": {
"view": true
}
}
}),
});
const { ok, data, error } = await res.json();
Delete a custom role Pro
DELETE /roles/{key}
Refused with 409 while users still have the role.
Path parameters
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/roles/cashier' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/roles/cashier');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/roles/cashier', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer Store ID. namestring Store name (at least 5 characters). descriptionstring Shown on the storefront. phonestring Contact phone. emailstring Contact email. slugstring Link name in the public URL (unique, at least 5 characters). public_urlread-only string Public storefront URL. templatestring classic, market or list. activeboolean Store is online. paystack_connectedread-only boolean Paystack keys are configured (managed in the app). product_countread-only integer Active products listed. settingsobject Checkout 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-only datetime Created (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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/stores' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/stores');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/stores', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create a store Pro
POST /stores
The first store automatically lists every sellable product.
Body
namerequired string Store name (at least 5 characters). descriptionstring Shown on the storefront. phonestring Contact phone. emailstring Contact email. slugstring Link name in the public URL (unique, at least 5 characters). templatestring classic, market or list. activeboolean Store is online. settingsobject Checkout 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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}}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/stores');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'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
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/stores', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"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
}
}),
});
const { ok, data, error } = await res.json();
Get a store
GET /stores/{id}
Path parameters
idrequired Online 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/stores/2' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/stores/2');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/stores/2', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a store Pro
PATCH /stores/{id}
Path parameters
idrequired Online stores ID.
Body
namestring Store name (at least 5 characters). descriptionstring Shown on the storefront. phonestring Contact phone. emailstring Contact email. slugstring Link name in the public URL (unique, at least 5 characters). templatestring classic, market or list. activeboolean Store is online. settingsobject Checkout 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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}}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/stores/2');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'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
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/stores/2', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"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
}
}),
});
const { ok, data, error } = await res.json();
Take a store offline Pro
DELETE /stores/{id}
Path parameters
idrequired Online stores ID.
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/stores/2' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/stores/2');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/stores/2', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
List products in a store
GET /stores/{id}/products
Path parameters
idrequired Online 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/stores/2/products?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/stores/2/products?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/stores/2/products?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Add products to a store Pro
POST /stores/{id}/products
Path parameters
idrequired Online stores ID.
Body
product_idsrequired array<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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/stores/2/products');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'product_ids' => [
57,
58
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/stores/2/products', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"product_ids": [
57,
58
]
}),
});
const { ok, data, error } = await res.json();
Remove a product from a store Pro
DELETE /stores/{id}/products/{product_id}
Path parameters
idrequired Online stores ID. product_idrequired Product id.
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/stores/2/products/1' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/stores/2/products/1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/stores/2/products/1', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer Order ID. store_idread-only integer Store. numberread-only string Order number (resets daily if configured). receipt_numberread-only string Receipt number. channelread-only string online or walkin. statusread-only string pending, paid or failed. payment_moderead-only string Paystack, Cash, Mobile Money, Card or Place order. customerread-only object name, phone, email, client_id. fulfillmentread-only string pickup or delivery. deliveryread-only object town, address, zone, fee (delivery orders). subtotalread-only number Items total. tax_totalread-only number Taxes. totalread-only number Grand total. picked_upread-only boolean Collected / delivered. sale_idread-only integer Sale recorded for the order (see /sales). invoice_idread-only string Invoice/receipt generated. pos_session_idread-only integer Till session (walk-in orders). itemsread-only array<object> product_id, name, qty, unit_price, amount (detail view). dateread-only date Business date. created_atread-only datetime Placed (UTC). paid_atread-only datetime Paid (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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/store-orders?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/store-orders?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/store-orders?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Get an order with items
GET /store-orders/{id}
Path parameters
idrequired Store & 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/store-orders/9031' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/store-orders/9031');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/store-orders/9031', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Mark picked up / delivered Pro
POST /store-orders/{id}/picked-up
Only paid orders can be marked.
Path parameters
idrequired Store & walk-in orders ID.
Body
picked_upboolean true 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/store-orders/9031/picked-up');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'picked_up' => true
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/store-orders/9031/picked-up', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"picked_up": true
}),
});
const { ok, data, error } = await res.json();
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-only integer Zone ID. store_idinteger Store the zone belongs to. namestring Zone name. pricenumber Delivery fee. townsarray<string> Towns covered (at least one). created_atread-only datetime Created (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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/delivery-zones?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/delivery-zones?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/delivery-zones?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create a zone Pro
POST /delivery-zones
Body
store_idrequired integer Store the zone belongs to. namerequired string Zone name. pricerequired number Delivery fee. townsrequired array<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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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"]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/delivery-zones');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'store_id' => 2,
'name' => 'Accra East',
'price' => 25,
'towns' => [
'East Legon',
'Madina',
'Adenta'
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/delivery-zones', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"store_id": 2,
"name": "Accra East",
"price": 25,
"towns": [
"East Legon",
"Madina",
"Adenta"
]
}),
});
const { ok, data, error } = await res.json();
Get a zone
GET /delivery-zones/{id}
Path parameters
idrequired Delivery 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/delivery-zones/7' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/delivery-zones/7');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/delivery-zones/7', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a zone Pro
PATCH /delivery-zones/{id}
Passing towns replaces the list.
Path parameters
idrequired Delivery zones ID.
Body
store_idinteger Store the zone belongs to. namestring Zone name. pricenumber Delivery 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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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"]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/delivery-zones/7');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'store_id' => 2,
'name' => 'Accra East',
'price' => 25,
'towns' => [
'East Legon',
'Madina',
'Adenta'
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/delivery-zones/7', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"store_id": 2,
"name": "Accra East",
"price": 25,
"towns": [
"East Legon",
"Madina",
"Adenta"
]
}),
});
const { ok, data, error } = await res.json();
Delete a zone Pro
DELETE /delivery-zones/{id}
Path parameters
idrequired Delivery zones ID.
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/delivery-zones/7' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/delivery-zones/7');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/delivery-zones/7', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer Account ID. emailread-only string Sign-in email. namestring Full name. phonestring Phone. order_countread-only integer Paid orders placed with this email. created_atread-only datetime Registered (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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/store-accounts?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/store-accounts?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/store-accounts?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Get a customer account
GET /store-accounts/{id}
Path parameters
idrequired Store 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/store-accounts/311' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/store-accounts/311');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/store-accounts/311', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update name or phone Pro
PATCH /store-accounts/{id}
Path parameters
idrequired Store customer accounts ID.
Body
namestring Full name. phonestring Phone.
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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.app/api/v1/store-accounts/311' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"name":"Yaw Asante","phone":"0244111222"}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/store-accounts/311');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Yaw Asante',
'phone' => '0244111222'
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/store-accounts/311', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Yaw Asante",
"phone": "0244111222"
}),
});
const { ok, data, error } = await res.json();
Delete a customer account Pro
DELETE /store-accounts/{id}
Past orders are kept.
Path parameters
idrequired Store customer accounts ID.
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/store-accounts/311' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/store-accounts/311');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/store-accounts/311', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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-only integer Session ID. cashier_idinteger Team member running the till (see /users). Defaults to the workspace owner. cashier_nameread-only string Cashier name. statusread-only string open, pending (closed, awaiting review), approved or rejected. sale_countread-only integer Paid sales in the session. totalread-only number Sales total. opened_atread-only datetime Opened (UTC). closed_atread-only datetime Closed (UTC). review_noteread-only string Reviewer note. reviewed_atread-only datetime Reviewed (UTC).
PosRegister object
namestring Till name shown on receipts. phonestring Receipt phone. emailstring Receipt email. addressstring Receipt address. activeboolean Till can take sales. store_idinteger Store whose products and order numbers the till uses. tax_group_idinteger Tax group applied to sales (0 = none).
PosSale object
cashier_idinteger Cashier with an open session (default: owner). itemsarray<object> Lines: {"product_id": 57, "qty": 2}. Prices come from the catalogue. payment_modestring Cash, Mobile Money or Card. customer_namestring Default "Walk-in". customer_phonestring Receipt 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/pos/register' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/pos/register');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/pos/register', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Create or update the till register Pro
PUT /pos/register
Body (PosRegister)
namerequired string Till name shown on receipts. phonestring Receipt phone. emailstring Receipt email. addressstring Receipt address. activeboolean Till can take sales. store_idinteger Store whose products and order numbers the till uses. tax_group_idinteger Tax 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
PHP
JavaScript
Copy
curl -X PUT 'https://blueq.ekklesiasys.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}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/pos/register');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Front counter',
'phone' => '0302000111',
'email' => 'counter@kenteprints.com',
'address' => 'Osu, Oxford St.',
'active' => true,
'store_id' => 2,
'tax_group_id' => 0
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/pos/register', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Front counter",
"phone": "0302000111",
"email": "counter@kenteprints.com",
"address": "Osu, Oxford St.",
"active": true,
"store_id": 2,
"tax_group_id": 0
}),
});
const { ok, data, error } = await res.json();
Try it
Changes live data
Send PUT request
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/pos/sessions?per_page=25' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/pos/sessions?per_page=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/pos/sessions?per_page=25', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Open a cashier session Pro
POST /pos/sessions
Body
cashier_idinteger Cashier (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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.app/api/v1/pos/sessions' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Idempotency-Key: '"$(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{"cashier_id":77}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/pos/sessions');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'cashier_id' => 77
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/pos/sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"cashier_id": 77
}),
});
const { ok, data, error } = await res.json();
Get a session with its sales
GET /pos/sessions/{id}
Path parameters
idrequired POS (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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/pos/sessions/140' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/pos/sessions/140');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/pos/sessions/140', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Close a session for review Pro
POST /pos/sessions/{id}/close
Path parameters
idrequired POS (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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.app/api/v1/pos/sessions/140/close' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Idempotency-Key: '"$(uuidgen)"
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/pos/sessions/140/close');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/pos/sessions/140/close', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const { ok, data, error } = await res.json();
Approve or reject a closed session Pro
POST /pos/sessions/{id}/review
Path parameters
idrequired POS (point of sale) ID.
Body
decisionrequired string approved or rejected. notestring Required 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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":""}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/pos/sessions/140/review');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'decision' => 'approved',
'note' => ''
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/pos/sessions/140/review', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"decision": "approved",
"note": ""
}),
});
const { ok, data, error } = await res.json();
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_idinteger Cashier with an open session (default: owner). itemsrequired array<object> Lines: {"product_id": 57, "qty": 2}. Prices come from the catalogue. payment_moderequired string Cash, Mobile Money or Card. customer_namestring Default "Walk-in". customer_phonestring Receipt 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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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":""}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/pos/sales');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'cashier_id' => 77,
'items' => [
[
'product_id' => 57,
'qty' => 2
]
],
'payment_mode' => 'Cash',
'customer_name' => 'Walk-in',
'customer_phone' => ''
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/pos/sales', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"cashier_id": 77,
"items": [
{
"product_id": 57,
"qty": 2
}
],
"payment_mode": "Cash",
"customer_name": "Walk-in",
"customer_phone": ""
}),
});
const { ok, data, error } = await res.json();
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-only string Report key (see GET /reports). titleread-only string Report title. columnsread-only array<string> Column headings. rowsread-only array<array> Rows in column order. itemsread-only array<object> Rows keyed by snake_case column name. summaryread-only array<object> Headline figures: {label, value}. totalread-only integer Rows matching the filters. truncatedread-only boolean More rows match than were returned (raise limit, max 5000). filtersread-only object Filters applied.
ReportSource object
sourceread-only string Report key, used in /reports/{source}. titleread-only string Report title. columnsread-only array<string> Columns the report returns. chartsread-only array<string> Charts available at /reports/{source}/charts. filtersread-only object Query filters the report accepts: name => description.
CustomReport object
idread-only integer Custom report ID. namestring Report 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-only datetime Last 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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/reports' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/reports');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/reports', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/reports/custom' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/reports/custom');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/reports/custom', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Save a custom report Pro
POST /reports/custom
Body (CustomReport)
namerequired string Report name. sectionsrequired array<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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.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":""}]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/reports/custom');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Monthly cash view',
'sections' => [
[
'source' => 'payments',
'columns' => [
'Date',
'Client',
'Amount (GHS)'
],
'filters' => [
'payment_type' => 'credit'
],
'chart' => ''
]
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/reports/custom', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"name": "Monthly cash view",
"sections": [
{
"source": "payments",
"columns": [
"Date",
"Client",
"Amount (GHS)"
],
"filters": {
"payment_type": "credit"
},
"chart": ""
}
]
}),
});
const { ok, data, error } = await res.json();
Get a custom report definition
GET /reports/custom/{id}
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/reports/custom/1' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/reports/custom/1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/reports/custom/1', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Update a custom report Pro
PATCH /reports/custom/{id}
Path parameters
Body (CustomReport)
namerequired string Report name. sectionsrequired array<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
PHP
JavaScript
Copy
curl -X PATCH 'https://blueq.ekklesiasys.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":""}]}'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/reports/custom/1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Monthly cash view',
'sections' => [
[
'source' => 'payments',
'columns' => [
'Date',
'Client',
'Amount (GHS)'
],
'filters' => [
'payment_type' => 'credit'
],
'chart' => ''
]
]
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/reports/custom/1', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Monthly cash view",
"sections": [
{
"source": "payments",
"columns": [
"Date",
"Client",
"Amount (GHS)"
],
"filters": {
"payment_type": "credit"
},
"chart": ""
}
]
}),
});
const { ok, data, error } = await res.json();
Delete a custom report Pro
DELETE /reports/custom/{id}
Path parameters
Response 204
Empty body.
cURL
PHP
JavaScript
Copy
curl -X DELETE 'https://blueq.ekklesiasys.app/api/v1/reports/custom/1' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/reports/custom/1');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/reports/custom/1', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
Run a custom report
GET /reports/custom/{id}/run
Returns one result block (same shape as a built-in report) per section.
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/reports/custom/1/run' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/reports/custom/1/run');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/reports/custom/1/run', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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
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
PHP
JavaScript
Copy
curl -X POST 'https://blueq.ekklesiasys.app/api/v1/reports/custom/1/run' \
-H 'Authorization: Bearer $BLUEQ_API_KEY' \
-H 'Idempotency-Key: '"$(uuidgen)"
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/reports/custom/1/run');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/reports/custom/1/run', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
},
});
const { ok, data, error } = await res.json();
Run a built-in report
GET /reports/{source}
Path parameters
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/reports/sales' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/reports/sales');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/reports/sales', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();
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
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
PHP
JavaScript
Copy
curl 'https://blueq.ekklesiasys.app/api/v1/reports/sales/charts' \
-H 'Authorization: Bearer $BLUEQ_API_KEY'
<?php
$ch = curl_init('https://blueq.ekklesiasys.app/api/v1/reports/sales/charts');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BLUEQ_API_KEY'),
],
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
const res = await fetch('https://blueq.ekklesiasys.app/api/v1/reports/sales/charts', {
headers: {
Authorization: `Bearer ${process.env.BLUEQ_API_KEY}`,
},
});
const { ok, data, error } = await res.json();