Developers

An API over the whole product

Everything the terminal and the back office do, they do over the same documented REST API you get. JWT bearer auth, idempotency where it matters, signed webhooks, and a realtime event stream — with a live sandbox you can fire real requests at.

Authentication

Aura uses JWT bearer tokens. Log in once for an access/refresh pair, then send the access token on every request. Business endpoints are namespaced under /api/v1.

POST /api/v1/auth/login
curl -s -X POST http://localhost:8080/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
        "email": "manager@aurapos.co.za",
        "password": "Aura-Pos123!",
        "tenantId": "demo-cape-town"
      }'
Response
{
  "accessToken": "eyJhbGciOi...",
  "refreshToken": "8f3c2e1a-...",
  "expiresInSeconds": 3600,
  "user": {
    "id": "...",
    "tenantId": "demo-cape-town",
    "email": "manager@aurapos.co.za",
    "role": "MANAGER",
    "locale": "en-ZA"
  }
}

Then authorise subsequent calls with Authorization: Bearer <accessToken>, and refresh when it expires. Those are the demo credentials seeded on a fresh dev boot — tenant demo-cape-town, role MANAGER.

Sign-in takes three fields, not two. One server hosts many tenants, so the tenant id is part of the credential — and every query is scoped to the caller's tenant.

Public, unauthenticated routes

A small number of routes sit outside /api/v1 because they're deliberately public:

GET/health, /readyLiveness and readiness
GET/docs, /docs/openapi.yamlThe sandbox and the spec
GET/receipts/{number}Digital receipts, signed-link access only

A few routes inside /api/v1 are public too, because the caller is a guest with no account — the guest self-order surface (/api/v1/qr/*), online booking and public click-and-collect. They resolve the venue from a location id printed on the table code and are rate-limited. The QR routes additionally answer 403 qr_not_licensed unless the venue holds the QR Ordering product, so build that branch in before you ship.


Conventions

  • Money is integer cents. No floats anywhere near a total — a POS that rounds in binary floating point eventually loses a cent, and then a bookkeeper finds it.
  • Prices are VAT-inclusive, matching how the till works. VAT is extracted at 15/115 per line, after discounts.
  • Manager-gated endpoints mirror the in-app permission model: the token's role is checked server-side regardless of what the client showed.
  • Errors use one shape with a documented code catalogue, so a client can branch on the code rather than parse prose.
  • Pagination is consistent across the list endpoints, with shared query parameters.
  • Entitlement is part of the contract. An account holds one or more of three products — Restaurant, Shop and QR Ordering — and the set rides in the access token as a claim, so a client can read it without a round trip. GET /api/v1/licence/entitlements returns the same thing for a UI that has just changed it. Endpoints that belong to a product a caller doesn't hold refuse with a specific code rather than pretending: guest ordering answers qr_not_licensed, and adding a guest order to a table's tab answers tab_not_available without the Restaurant product.

The full request and response schema for every endpoint, the error-code catalogue and the endpoint index all live in the sandbox and in the repo's docs/API.md. This page is the orientation, not the reference.


Idempotency

Payments and sale commits accept an idempotency key. This matters more than usual here because Aura is offline-first: a terminal that loses its connection mid-request will retry, and a retried payment must not charge twice. Send the same key and you get the same result rather than a second capture.

Design assumption worth knowing. Receipt numbers are sequential per device per day, so two tills ringing simultaneously — and a till that rang offline for an hour — can't collide when everything syncs.

Webhooks

Payment providers confirm asynchronously. Aura verifies the provider's signature before trusting a delivery, and capture is idempotent, so a provider retry can't double-capture a bill.

ProviderBehaviour
YocoSignature-verified. Yoco retries a failed delivery for roughly 28 hours before deactivating the subscription, so transient downtime is recoverable without manual replay.
SnapScanSignature-verified. Enables the per-order guest QR when the merchant code is also set.
ZapperSignature-verified. A blank webhook secret disables the webhook without disabling the tender.

Pay-by-link depends on this path end to end: the POS creates the checkout, shows a waiting screen, and the bill closes itself when the webhook confirms.


Realtime events

Terminals subscribe to a WebSocket stream so a change on one till appears on the others in a second or two, with a background poll as the safety net.

WS /api/v1/sync/ws
kitchen  fired · bumped · ready · rush · recalled
table    seated · cleared
payment  captured

The green "ready to run" banner every terminal pops when the kitchen plates a ticket is this stream — which is also why a browser terminal, with no live push, doesn't show it.


The sync protocol

The interesting part of the API. Terminals hold a local database and reconcile with the server rather than depending on it:

  • Push — queued changes go up every 30 seconds while online, and immediately on reconnect.
  • Retry — per item, backing off from 1 minute to 1 hour, so a flaky link doesn't hammer the server or drain a tablet.
  • Dead-letter — anything permanently rejected is parked after 10 attempts for review, rather than blocking everything behind it.
  • Pull — the device also pulls menu, prices, staff, customers, tables and reservations, so every terminal converges.

Sales rung offline generate their double-entry journals when they sync, so the ledger ends up identical either way. The protocol is documented in full in docs/API.md.


Scheduled jobs

Two endpoints are designed to be hit daily by a scheduler — Cloud Scheduler, cron, whatever you run — with a manager token:

POST/api/v1/loyalty/birthday-vouchersIssues the automatic 20% birthday vouchers, valid 30 days
POST/api/v1/checklists/summary/sendEmails the checklist completion digest

Rate limits

Per minute, per client IP, built in:

login 10 token refresh 30 guest ordering 600 guest booking 60 sync 600 webhooks 1000

Configuration

The server is configured entirely through environment variables — database, JWT secret, receipt signing secret, provider keys, WhatsApp and Mailjet credentials, web root. Nothing is required to trade in dev; the production guards refuse to boot on placeholder secrets when AURA_ENV=production.

Terminal-side, desktop builds honour AURA_API_URL, AURA_PRINTER_HOST, AURA_PRINTER_PORT and AURA_DB_PATH. The full reference is in the help centre's appendices.