4.2 KiB
API Documentation
Base URL: http://localhost:3001/api/v1
Swagger UI: http://localhost:3001/api/docs
Authentication
Dashboard Users
- Login to receive JWT access token (24h) + refresh token (30 days)
- Include access token in
Authorization: Bearer <token>header
Devices
- Register to receive device bearer token (90 days)
- Include device token in
Authorization: Bearer <token>header - Device ID must match URL parameter
Endpoints
Health
GET /health - Server health check (503 when the database is unreachable)
Auth
POST /auth/login - Login with email/password
POST /auth/refresh - Refresh access token
POST /auth/logout - Logout and revoke refresh token
Venues
GET /venues - List venues
GET /venues/:venueId - Get venue details
GET /venues/:venueId/devices - List devices in venue
Devices
POST /devices/register - Register a new device
POST /devices/:deviceId/sync - Sync device state (device auth)
POST /devices/:deviceId/commands/:commandId/ack - Acknowledge command (device auth)
GET /devices/generation-code - Generate registration code (admin)
Sync semantics:
-
openTabsmust be the device's complete list of open tabs. Tabs it no longer reports (e.g. deleted on the terminal) are removed from the dashboard. Settling a tab on the terminal keeps the tab open under the same name with a zero balance, so the name stays listed. -
Every sync returns all pending commands for the device;
cursoris still returned but no longer filters. Devices dedupe by command id and re-send the ack for commands they already processed. -
The first ack for a command wins; later acks are accepted as no-ops.
-
plannedTabNamesonly contains planned customers the device has not opened a tab for yet. Once a sync reports an open tab with that name (ignoring case and extra spaces), the customer is marked delivered and no longer sent. The customer (and their email, used for reminders) is kept. -
events(optional) reports what happened on tabs since the last sync, for sales statistics. Each event has a terminal-generated UUIDid, atype,tabId,customerNameandoccurredAt(ISO-8601, UTC):line_added/line_removed:productName,unitPriceInCents,quantity(always positive)settled:amountInCents,paymentMethod(cash,payconiq,payment_pendingorother), optionalcommandId(the remote close that caused it) anditems(the settled lines)
The response lists
acknowledgedEventIds(stored, including events already stored by an earlier sync) andrejectedEvents([{ id, reason }], invalid and never stored). The terminal removes both from its outbox. An invalid event never fails the sync. At most 1000 events per sync.
Tabs
GET /venues/:venueId/tabs - List open tabs
Planned Tabs (customer list)
GET /venues/:venueId/planned-tabs - List planned customers
POST /venues/:venueId/planned-tabs - Add a customer (admin)
POST /venues/:venueId/planned-tabs/import - Bulk import { entries: [{ customerName, email? }], mode: 'merge' | 'replace' } (admin).
Names match case-insensitively and keep their existing spelling; replace also removes customers missing from the import.
PUT /venues/:venueId/planned-tabs/:id - Rename a customer / set email (admin)
DELETE /venues/:venueId/planned-tabs/:id - Remove a customer (admin)
Reminders
GET /venues/:venueId/reminders/candidates - Customers with an unpaid balance and email, not reminded in the last 7 days
POST /venues/:venueId/reminders/send - Send payment reminders
GET /venues/:venueId/reminders - Reminder history
POST /venues/:venueId/reminders/test/:plannedTabId - Send a test email (admin)
Commands
POST /venues/:venueId/commands - Create a command
GET /venues/:venueId/commands - List command history
POST /venues/:venueId/commands/devices/:deviceId/revoke - Revoke device (admin)
All /venues/:venueId/... routes return 403 venue_forbidden unless the user belongs to that venue.
Error Format
{
"error": {
"code": "error_code",
"message": "Human-readable message"
}
}
HTTP Status Codes
- 200 Success
- 201 Created
- 400 Bad Request
- 401 Unauthorized
- 403 Forbidden
- 404 Not Found
- 409 Conflict
- 429 Too Many Requests