01 — Auth, conventions, errors, health
For Android / iOS clients integrating invoice-services.
Base URL
Mount point for all versioned APIs:
{INVOICE_SERVICE_BASE_URL}/v1/...
Examples (environment-specific — use your deployment config):
https://<invoice-host>/v1/invoices/sync
https://<invoice-host>/v1/kds-orders
Gateway may already rewrite legacy /api/v2/... paths to these; prefer calling the invoice service paths listed in this pack.
Body size limit: JSON body up to 25 MB (express.json limit).
Authentication (all business endpoints)
Authorization: Bearer <jwt>
Content-Type: application/json
Required JWT claims
| Claim | Also accepted | Required | Notes |
|---|---|---|---|
customer_id |
customerId |
yes | Tenant customer |
location_id |
locationId |
yes | Tenant location |
If either is missing → HTTP 400 with message:
Authentication must include customer_id and location_id
Missing / invalid / expired JWT → HTTP 401.
All list/get/cancel queries are scoped to the token’s customer_id + location_id. Sending another tenant id in the body for sync must align with your business rules; history/KDS/dine/cancel ignore query-string tenant overrides and use the token only.
Useful optional claims (cancel audit)
| Claim | Use |
|---|---|
username or user |
Appended to cancel note (**canceled by:{username}) |
id or user_id |
Default close_by_user_id when cancelling an open invoice |
Common conventions
Enums (exact strings)
| Field | Values |
|---|---|
Invoice status |
"open", "cancelled", "completed" |
Line-item discount_type |
"percentage", "amount" |
Boolean / flag fields
Send and expect numbers:
| Value | Meaning |
|---|---|
0 |
false / off |
1 |
true / on |
| omitted | usually defaults to 0 (server Joi) |
Exceptions:
| Field | Notes |
|---|---|
is_syspos |
default 1 |
is_combo, is_internal |
may be 0 / 1 / null (is_combo also allows 2 on write via level2BooleanFlag) |
is_accepted (reads / KDS) |
0 pending, 1 accepted, 2 rejected |
Timestamps
| Context | Format |
|---|---|
| Sync request / history / KDS / dine invoice fields | Unix seconds (number) |
Ably status event updated_at, done_at |
ISO-8601 strings |
Notes (iOS / legacy TRACK)
| Client key | DB column | Read key |
|---|---|---|
notes (array of strings) preferred |
order_notes |
notes |
order_notes (alias) |
same | — |
If both notes and order_notes are sent on sync, notes wins.
Payments (preferred shape)
[
{
"payment_method": "cash",
"amount": 105,
"tip_amount": 0
}
]
Avoid legacy payment_method object maps for new code. History/KDS/dine always return payments as above (or null).
Line items: addons & modifiers
| Concept | How to send / receive |
|---|---|
| Addons | Nested under parent items[].addons[]; server flattens to DB rows with is_addon = 1 |
| Modifiers | Flat array on the line: items[].modifiers[] (not nested items groups) |
Parent linked_uuid |
Required for correct addon re-nesting on history/KDS/dine |
Extra JSON keys
Invoice and line-item objects allow unknown keys (.unknown(true)). Prefer documented fields; unknown keys may be ignored or persisted depending on refactor/DB mapping.
App metadata (recommended on sync)
| Field | Example |
|---|---|
os_type |
"android" / "ios" |
app_version |
"1.6.0" |
build_number |
number |
app_os_version |
OS version string |
KDS / dine-orders also accept app_version + build_number as query params to control whether items[].qty is returned as int vs float (legacy iOS compatibility around app 1.5.5).
Response envelopes (know the differences)
| Endpoint | Success shape |
|---|---|
POST /sync |
{ status: true, code: 200, message, data: [...], trace_id } |
GET /history |
{ status: true, message, data: { current_page, records_per_page, total_records, orders } } |
PUT /cancel |
{ status: true, message, data: { uuid, status, … } } |
GET /kds-orders |
raw array of invoices |
PUT /kds-orders/:uuid |
{ status: true, message } |
GET /dine-orders |
raw array of table objects |
GET /invoices/:uuid |
raw invoice row (no sync mapper — see 03) |
Do not assume every endpoint uses the same wrapper.
Error envelope
Global handler returns roughly:
{
"success": false,
"code": 400,
"error_code": "DUPLICATE_REQUEST",
"service_name": "",
"message": "…"
}
| Field | Notes |
|---|---|
success |
Always false on errors |
code |
HTTP status |
error_code |
Optional; sync duplicates may use DUPLICATE_REQUEST on the invoice object in data, not always in this envelope |
message |
Human-readable; validation joins Joi messages with ", " |
stack |
Only in development |
Common HTTP codes
| Code | When |
|---|---|
400 |
Validation, missing JWT claims, missing uuid / request_id on sync |
401 |
Auth failure |
404 |
Invoice not found for tenant (cancel / get-by-uuid) |
500 |
Unexpected server error |
Idempotency headers (optional logging)
On sync, the service may log:
- Header
x-request-id(normalized lowercase) — correlation, separate from bodyrequest_id - Response
trace_idon sync success
Body field request_id (per invoice) is the business idempotency key — required. See 02-INVOICE-SYNC.md.
Realtime (KDS)
Subscribe with Ably:
| Channel | order:{invoiceUuid} |
| Event | status |
Payload:
{
"status": "pending" | "preparing" | "ready" | "cancelled",
"is_accepted": 0 | 1 | 2,
"done_at": null | "2026-06-16T12:34:56.789Z",
"updated_at": "2026-06-16T12:34:56.789Z"
}
Derived status:
| Condition | status |
|---|---|
is_accepted === 2 |
cancelled |
done_at set |
ready |
is_accepted === 1 |
preparing |
| else | pending |
Published after successful KDS PUT when ABLY_PUBLISH_KEY is configured. Details: 04-KDS.md.
Health checks (no auth)
GET /health
→ { "message": "Invoice service is running" }
GET /v1/health
GET /v1/health/detailed
Use for connectivity / load-balancer probes only.
Platform checklist
Android
- OkHttp/Retrofit base URL ends before
/v1or includes it consistently - Gson/Moshi: map flags as
Int/Integer(0/1), notBoolean - Timestamps as
Longseconds - Sync body =
List<Invoice>(JSON array root, not{ "invoices": [...] }) - Keep local UUID +
request_idstable across retries
iOS
- Decode flags as
Int(0/1) - Prefer
notes: [String]on sync (maps to DBorder_notes, returns asnotes) - Sync body must be a top-level JSON array
- Do not treat sync
200as “row fully committed”; refresh from history/KDS if needed - Pass
app_version/build_numberon KDS/dine if qty typing matters for your build
Scope reminder
This service targets master catalogues: modifiers + addons. Non-master extras/materials flows are out of scope.