MPC Backend POS — API Contract
Base: /api/v1. Device header X-Pos-Key + staff Authorization: Bearer where noted.
Branch scoping
Schedule and court booking ops use device outlet → branch_id. No client branch_id required. If outlet has no branch yet, filters are skipped (legacy).
Devices
POST /pos/devices/register— body{ outlet_code, name, court_enabled?, fnb_enabled? }GET /pos/devices/me— includes optionalbranch_idPATCH /pos/devices/me
Schedule / bookings (court mode)
GET /pos/schedule?date=YYYY-MM-DD— courts + bookings for device branch only; courts sorted ascending bydisplay_order(same as memberGET /courts); includesopen_time/close_timefrom that branch (fallback06:00–23:00); soft-deleted courts excludedGET /pos/bookings/:id,GET /pos/bookings/by-code/:codePOST /pos/bookings/:id/check-in|check-out|payGET /pos/bookings/:id/review-qr
Cross-branch booking IDs return 404.
Resto (spaces, tables, reservations)
All routes require device X-Pos-Key + staff Authorization: Bearer. Spaces are scoped by device outlet → branch_id (same as courts); cross-branch IDs return 404. Reservations are created by members (mpc-be) or admin — POS only reads, updates status, and assigns tables.
GET /resto/spaces— active spaces, sorted bydisplay_orderASC.
{ "success": true, "message": "Spaces retrieved", "data": [
{ "id": "uuid", "name": "Indoor Dining", "code": "SPC-INDOOR", "space_type": "TableArea",
"capacity_pax": 50, "minimum_spend": null, "display_order": 1 }
] }
GET /resto/tables?space_id=<uuid>— physical tables (active + inactive);space_idoptional (omit → all tables of the branch). Sorteddisplay_orderASC.
{ "success": true, "message": "Tables retrieved", "data": [
{ "id": "uuid", "space_id": "uuid", "name": "T1", "capacity": 4, "is_active": true, "display_order": 1 }
] }
POST /resto/tables— body{ "space_id": "uuid", "name": "T7", "capacity": 8, "display_order": 7 }(display_orderoptional, default 0).201with the created table (same shape as list item).PATCH /resto/tables/:id— body{ "name"?, "capacity"?, "is_active"?, "display_order"? }(all optional). Deactivate viais_active: false; no delete. Returns the updated table.GET /resto/reservations?date=YYYY-MM-DD— that day's reservations (default: today, server local time), sortedstart_timeASC.minimum_spend= snapshot at booking time.
{ "success": true, "message": "Reservations retrieved", "data": {
"date": "2026-08-13",
"reservations": [
{ "id": "uuid", "code": "RSV-...",
"space": { "id": "uuid", "name": "Indoor Dining", "space_type": "TableArea" },
"customer_name": "User Test", "guest_name": null, "pax": 6,
"start_time": "2026-08-13T12:00:00Z", "end_time": "2026-08-13T13:30:00Z",
"status": "Confirmed", "source": "Online", "notes": null,
"minimum_spend": null,
"tables": [ { "id": "uuid", "name": "T1" } ] }
] } }
PATCH /resto/reservations/:id/status— body{ "status": "Seated" }. Allowed:Confirmed | Seated | Completed | NoShow | Cancelled(back toConfirmedallowed as correction). Invalid value →400 invalid status. Returns the updated reservation (same shape as list item).PUT /resto/reservations/:id/tables— body{ "table_ids": ["uuid", ...] }. Replaces the whole assignment set (transactional delete + insert); empty array clears it. All tables must belong to the reservation's space, else400table does not belong to the reservation's space: table O1 (<id>). Returns the updated reservation.