API Reference

REST API

Base URL: /api · 所有端點回傳 JSON。價格以港幣分為單位(如 3800 = HK$38.00)。

概述

Zest 提供完整的 REST API,涵蓋菜品、訂單、桌位、訂位、設定、變體和時段。所有端點回傳 JSON 格式。

認證

受保護的端點需要 API Key。可透過以下方式傳遞:

Header
Authorization: Bearer ***
Alternative Header
x-api-key: ***
Query Param
?api_key=sk-...

角色

super_admin完整權限:管理用戶、所有菜品操作

admin管理菜品、管理自己的 API Key

staff唯讀:查看菜品、管理自己的 API Key

錯誤格式

Error Response
{"error": {"code": "missing_api_key", "message": "..."}}
StatusCode含義
401missing_api_key未提供 API Key
401invalid_api_keyKey 無效、格式錯誤或已過期
403insufficient_role角色權限不足
404not_found資源不存在
409conflict重複或衝突
422validation_error缺少或無效的請求體
429rate_limited請求過多

認證端點

POST/api/auth/register

註冊首位用戶(super_admin)。

請求體:

{"username": "owner", "password": "password123"}

回應:

{"api_key": "sk-...", "user": {"id": "...", "username": "owner", "role": "super_admin"}}

錯誤: 422, 409, 403

POST/api/auth/login

使用用戶名/密碼認證。

請求體:

{"username": "owner", "password": "password123"}

回應:

{"api_key": "sk-...", "user": {"id": "...", "username": "owner", "role": "..."}}

錯誤: 422, 401

GET/api/auth/keys

列出用戶的 API Key。

回應:

Array of {id, key_prefix, name, last_used_at, created_at, expires_at}
POST/api/auth/keys

產生新的 API Key。

請求體:

{"name": "office laptop", "expires_at": "2026-12-31T23:59:59Z"}

回應:

{"api_key": "sk-...", "key": {"id": "...", "key_prefix": "sk-a1b2...", "name": "office laptop"}}

錯誤: 422

DELETE/api/auth/keys/:idAny (own) / super_admin (any)

撤銷 API Key。

回應:

{"deleted": true}

錯誤: 404, 403

管理端點

GET/api/admin/userssuper_admin

列出所有用戶。

回應:

Array of {id, username, role, created_at}
POST/api/admin/userssuper_admin

建立新用戶。

請求體:

{"username": "staff1", "password": "pass123", "role": "staff"}

錯誤: 422, 409

PUT/api/admin/users/:idsuper_admin

更新用戶。

錯誤: 404, 422, 409

DELETE/api/admin/users/:idsuper_admin

刪除用戶。

錯誤: 404, 422

POST/api/admin/ai-fill-dishadmin+

AI 生成菜品名稱。

請求體:

{"hint": "salmon teriyaki"}

回應:

{name_ja, name_en, name_zh, description_ja/en/zh, category}
POST/api/admin/upload-dish-imageadmin+

上傳菜品圖片。

回應:

{"image_url": "/dishes/123456-abc.jpg"}

菜品端點

GET/api/dishesAny

列出菜品。按 sort_order 排序。

請求體:

?category=sashimi (opt) · ?time_slot_id=1 (opt)

回應:

Array of {id, name_ja, name_en, name_zh, category, price_hkd, image_url, variant_groups, ...}
GET/api/dishes/topAny

按銷量取得暢銷菜品。

請求體:

?limit=4 (opt, max 50)

回應:

Array of dishes with extra total_sold and revenue_hkd
GET/api/dishes/:idAny

取得單道菜品。

錯誤: 404

POST/api/dishesadmin+

建立菜品。

請求體:

{"name_ja": "...", "name_en": "...", "name_zh": "...", "category": "sashimi", "price_hkd": 8800}

錯誤: 422

PUT/api/dishes/:idadmin+

更新菜品。

錯誤: 404, 422

DELETE/api/dishes/:idadmin+

刪除菜品。

回應:

{"deleted": true}

錯誤: 404

分類

Slug日本語English中文
appetizer前菜Appetizer前菜
sashimi刺身Sashimi刺身
grilled焼物Grilled烤物
fried揚物Fried炸物
rice_bowl丼物Rice Bowl盖饭
noodles麺類Noodles面类
dessert甘味Dessert甜品
drink飲物Drink饮品

桌位端點

GET/api/tablesAny

列出活躍桌位。

請求體:

?qr_code=... (opt)

回應:

Array of {id, label, qr_code, capacity, section, is_active, has_active_order}
POST/api/tablesadmin+

建立桌位。

請求體:

{"label": "Table 1", "capacity": 4, "section": "main"}

錯誤: 422, 409

PUT/api/tables/:idadmin+

更新桌位。

錯誤: 404

DELETE/api/tables/:idadmin+

刪除桌位。

錯誤: 404

訂單端點

GET/api/ordersAny

列出訂單及明細。

請求體:

?status=... &table_id=... &offset=0 &limit=50

回應:

Array of orders with items, table info, service charge, totals
GET/api/orders/:idAny

取得單筆訂單。

錯誤: 404

POST/api/orders

從 QR 掃描下單(公開端點)。

請求體:

{"table_qr_code": "...", "items": [{"dish_id": 1, "quantity": 2}], "party_size": 4}

錯誤: 422, 404, 409, 429

POST/api/orders/:id/items

追加一輪菜品。

請求體:

{"table_qr_code": "...", "items": [{"dish_id": 3, "quantity": 1}]}

錯誤: 403, 404, 409, 429

PUT/api/orders/:idstaff+

更新訂單狀態。

請求體:

{"status": "preparing|served|paid|cancelled"}

錯誤: 404, 409, 422

DELETE/api/orders/:id/items/:itemIdstaff+

取消菜品(軟刪除)。

錯誤: 400, 404, 409

GET/api/orders/streamstaff / table_qr_code

SSE 端點,用於即時訂單更新。

回應:

Events: connected, initial_order, order_created, order_updated, items_added, item_removed, keepalive

訂位端點

GET/api/bookingsAny

列出訂位。

請求體:

?date=2026-05-10 (opt)

回應:

Array of {id, customer_name, party_size, booking_date, booking_time, status, table_id, ...}
POST/api/bookings

建立訂位(公開端點)。

請求體:

{"customer_name": "John", "booking_date": "2026-05-10", "booking_time": "19:00", "party_size": 4}

錯誤: 422

PUT/api/bookings/:idstaff+

更新訂位。

請求體:

{"status": "confirmed|arrived|cancelled|no_show", "table_id": 1}

錯誤: 404, 422

DELETE/api/bookings/:idadmin+

刪除訂位。

錯誤: 404

設定端點

GET/api/settings

取得餐廳設定。

回應:

{theme, dark_mode, service_charge_enabled, service_charge_percentage, basic_fee_enabled, basic_fee_per_person, restaurant_address, restaurant_phone}
PUT/api/settingsadmin+

更新設定。

請求體:

{"service_charge_percentage": 10}

錯誤: 400

時段端點

GET/api/time-slotsAny

列出所有時段。

回應:

Array of {id, name, display_name_ja/en/zh, start_time, end_time, is_active, sort_order}
GET/api/time-slots/currentAny

取得當前活躍時段。

錯誤: 404

POST/api/time-slotsadmin+

建立時段。

請求體:

{"name": "dinner", "start_time": "18:00", "end_time": "22:00"}

錯誤: 422

PUT/api/time-slots/:idadmin+

更新時段。

錯誤: 404

DELETE/api/time-slots/:idadmin+

刪除時段。

錯誤: 404, 409

變體端點

GET/api/admin/variant-groupsAny

列出變體群組及其選項。

回應:

Array of {id, name, sort_order, options: [{id, name, price_adjustment, is_default}]}
POST/api/admin/variant-groupsadmin+

建立變體群組。

請求體:

{"name": "Temperature", "sort_order": 1}

錯誤: 422

POST/api/admin/variant-groups/:id/optionsadmin+

向變體群組新增選項。

請求體:

{"name": "Medium Rare", "price_adjustment": 0}
GET/api/admin/dishes/:id/variantsAny

取得菜品已分配的變體群組。

POST/api/admin/dishes/:id/variantsadmin+

將變體群組分配給菜品。

請求體:

{"variant_group_id": 1, "is_required": true}

錯誤: 422, 409

curl Examples

Register (first user only)
curl -X POST http://localhost:3898/api/auth/register \\  -H "Content-Type: application/json" \\  -d '{"username":"owner","password":"password123"}'
Login
curl -X POST http://localhost:3898/api/auth/login \\  -H "Content-Type: application/json" \\  -d '{"username":"owner","password":"password123"}'
List all dishes
curl http://localhost:3898/api/dishes \\  -H "Authorization: Bearer ***"
Place an order (public)
curl -X POST http://localhost:3898/api/orders \\  -H "Content-Type: application/json" \\  -d '{"table_qr_code":"a1b2c3d4-...","items":[{"dish_id":1,"quantity":2}]}'