api / be-pos

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 optional branch_id
  • PATCH /pos/devices/me

Schedule / bookings (court mode)

  • GET /pos/schedule?date=YYYY-MM-DD — courts + bookings for device branch only; courts sorted ascending by display_order (same as member GET /courts); includes open_time / close_time from that branch (fallback 06:0023:00); soft-deleted courts excluded
  • GET /pos/bookings/:id, GET /pos/bookings/by-code/:code
  • POST /pos/bookings/:id/check-in|check-out|pay
  • GET /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 by display_order ASC.
{ "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_id optional (omit → all tables of the branch). Sorted display_order ASC.
{ "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_order optional, default 0). 201 with the created table (same shape as list item).
  • PATCH /resto/tables/:id — body { "name"?, "capacity"?, "is_active"?, "display_order"? } (all optional). Deactivate via is_active: false; no delete. Returns the updated table.
  • GET /resto/reservations?date=YYYY-MM-DD — that day's reservations (default: today, server local time), sorted start_time ASC. 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 to Confirmed allowed 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, else 400 table does not belong to the reservation's space: table O1 (<id>). Returns the updated reservation.