Features - Payment reminders via Resend (candidates, send, history, test email) - Bulk import of the customer list from Excel/CSV in the dashboard - Dashboard i18n (en/nl) with language selector Security - Enforce venue isolation on all /venues/:venueId routes - Require admin for planned-tab changes and pairing codes - Rate-limit login and device registration; opt-in TRUST_PROXY - Require JWT_SECRET in production; stop leaking internal errors - Escape customer names in reminder emails Sync and commands - Remove tabs the terminal no longer reports; validate nested sync payload - Return all pending commands on every sync (fixes missed-command race) - First command ack wins; atomic registration code claim - Reject commands for revoked devices, stale versions or duplicates - Remind only unpaid balances, with a 7-day cooldown Dashboard - Share a single token refresh across concurrent 401s (random logouts) - Keep the session alive for the refresh token lifetime Deployment - Replace `db push --accept-data-loss` with migrations (baselines legacy DBs) - Set NODE_ENV=production; pass Resend and TRUST_PROXY env in prod compose
3.1 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.
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