Kết nối hệ thống bên ngoài (API)


Đây là gì và để làm gì

Hệ thống bên ngoài — máy tính tiền, phần mềm kế toán hoặc khách sạn — có thể lấy thực đơn, đọc đơn và hóa đơn của quán, và gửi cho chúng tôi đơn mà chính nó đã nhận. Có một API riêng cho việc này: nó hoạt động nhân danh quán và không cần tài khoản nhân viên.

Địa chỉ là địa chỉ ứng dụng Qreaty cộng với /api/external/v1. Trên bản chính thức đó là https://qreaty.com/api/external/v1; địa chỉ có sẵn được hiển thị trong mục «Truy cập qua API» (cách mở) — hãy sao chép từ đó. Mọi yêu cầu bên dưới đều tính từ địa chỉ này: GET /menu/items nghĩa là GET https://qreaty.com/api/external/v1/menu/items.

Phản hồi là JSON theo chuẩn JSON:API (data, attributes), nội dung yêu cầu là JSON. Token gửi trong header Authorization: Bearer <token>; nên luôn kèm Accept: application/json.

Ngôn ngữ. Tên và mô tả trả về bằng ngôn ngữ bạn yêu cầu: tham số ?locale=vi (mã ngôn ngữ: ru, en, vi…) hoặc header Accept-Language: vi. Có những ngôn ngữ nào, API tự cho biết: mỗi phản hồi của GET /menu/items và GET /menu/modifiers có meta.menus — với mỗi thực đơn là danh sách locales (đã dịch sang những ngôn ngữ nào) và source_locale — ngôn ngữ bản gốc, ngôn ngữ chủ quán đã nhập thực đơn. Nếu chưa có bản dịch cho ngôn ngữ được yêu cầu (hoặc không yêu cầu), trả về bản gốc.

{primary} Token thuộc về quán, không thuộc về một người: nhân viên đã tạo token nghỉ việc thì token vẫn hoạt động cho đến khi bạn thu hồi. Mỗi token chỉ thấy quán của nó.

API làm được gì:

Việc Yêu cầu Quyền của token
Tất cả món GET /menu/items menu:read
Nhóm tùy chọn và các tùy chọn GET /menu/modifiers menu:read
Bàn (id, number, name, zone) GET /tables orders:read
Đơn GET /orders, GET /orders/{id} orders:read
Hóa đơn GET /bills, GET /bills/{id} orders:read
Nhận một đơn POST /orders orders:write

Token truy cập

Chủ quán hoặc người có quyền thay đổi cài đặt của quán tạo token.

  1. Mở Cài đặt → Phần mềm bán hàng (POS) và tìm mục «Truy cập qua API».
  2. Nhấn «Tạo token», đặt tên theo hệ thống («Máy tính tiền, lễ tân») và chọn những gì được phép: đọc thực đơn, đọc đơn và hóa đơn, tạo đơn. Chỉ cấp đúng những gì cần: hệ thống chỉ đối chiếu giá thì không nên tạo được đơn.
  3. Nếu muốn, đặt thời hạn.
  4. Sao chép token ngay. Token chỉ hiện một lần; sau đó danh sách chỉ cho thấy tên, quyền và thời điểm dùng gần nhất.

{warning} Token giống như mật khẩu: ai có nó đều hành động nhân danh quán trong phạm vi quyền đã cấp. Đừng gửi nó trong chat hay lưu trong kho mã. Nếu token có thể đã bị lộ, hãy thu hồi ở cùng mục và tạo token mới — token đã thu hồi ngừng hoạt động ngay.

Một token cho một hệ thống được kết nối. Nhờ vậy biết ai gọi và khi nào, và thu hồi một hệ thống không làm hỏng các hệ thống khác.

SKU: mã của món và tùy chọn

Hệ thống bên ngoài gọi các mục bằng SKU — mã nằm trong danh mục của nó. Mỗi món và mỗi tùy chọn đều có SKU, không bao giờ để trống:

  • Mục mới được gán mã QR-D<số> (món) hoặc QR-M<số> (tùy chọn) — duy nhất và không đổi.
  • Để khớp với danh mục của đối tác, nhập mã của họ: mở món (hoặc tùy chọn trong nhóm) và đổi trường «SKU». Cho phép chữ, số và các ký tự . _ - /.
  • Trong một thực đơn, SKU không được trùng — ứng dụng sẽ không lưu bản trùng. Cùng một món ở hai thực đơn của quán có thể dùng chung một mã.
  • Bản sao của món có mã mới riêng, còn bản sao cả thực đơn giữ nguyên các mã.

Đơn ghi nhớ SKU cùng với tên: nếu ngày mai bạn đổi mã món, hóa đơn hôm qua vẫn giữ mã mà món đã được bán.

{primary} Nếu hai món trong một thực đơn trùng mã (có thể xảy ra với dữ liệu cũ liên kết thủ công), API sẽ báo: GET /menu/items và /menu/modifiers có meta.duplicate_skus. Cho đến khi sửa bản trùng, không thể đặt SKU đó — yêu cầu sẽ nhận lỗi.

Thực đơn: món và tùy chọn

GET /menu/items trả về các món của thực đơn đang hoạt động — thực đơn mà khách và quầy dùng để gọi món — 50 món mỗi trang (?per_page= tối đa 200, ?page=). Có thể yêu cầu thực đơn khác một cách rõ ràng: filter[menu]=<id>; id thực đơn và ngôn ngữ của chúng nằm trong meta.menus.

Khóa của món là SKU: nó cũng nằm ở id và được gửi lại trong đơn. Một món có: sku, name, description, price_type (fixed, from, range, variable — theo yêu cầu) cùng price_value / price_min / price_max / price_unit, currency, kind (food, drink, alcohol, other), section, available — có gọi được ngay bây giờ không (không bị ẩn, không hết hàng, danh mục đang bật), sold_out — món có nằm trong danh sách hết hàng không, và modifier_group_ids — các nhóm tùy chọn gắn với món.

Bộ lọc: filter[available]=1, filter[section], filter[sku], filter[menu], filter[updated_since]=2026-10-14T00:00:00Z — tiện cho việc đối chiếu định kỳ: chỉ lấy phần đã thay đổi.

GET /menu/modifiers — các nhóm tùy chọn của thực đơn đang hoạt động cùng các tùy chọn. Mỗi tùy chọn gọi bằng SKU riêng (options[].sku) và có name, price, is_default, max_qty cùng prices — bảng giá theo kích cỡ (driver_option_sku là tùy chọn kích cỡ, price là giá khi chọn nó). Nhóm có name, pricing_mode (replace — giá tùy chọn thay giá món, như kích cỡ; add — cộng thêm, như topping), required, selection_min / selection_max — được chọn bao nhiêu, item_skus — gắn với những món nào, và parent_option_sku — khi nhóm mở ra dưới một tùy chọn của nhóm khác. id của nhóm là tham chiếu của nó trong modifier_group_ids của món.

curl -s -g "https://qreaty.com/api/external/v1/menu/items?filter[available]=1&locale=vi" \
  -H "Authorization: Bearer $TOKEN" -H "Accept-Language: vi"

Yêu cầu mẫu: thực đơn

{primary} Tất cả yêu cầu bên dưới đã được gom thành bộ sưu tập Postman làm sẵn: tải về. Nhập tệp, điền token vào biến token của bộ sưu tập — là gửi được; SKU và số bàn tự điền.

Địa chỉ có bộ lọc được ghép từ các phần: đường-dẫn?filter[tên]=giá-trị&filter[tên2]=giá-trị2. Dưới đây là các dòng làm sẵn, dùng nguyên văn. Với curl thêm -g để dấu ngoặc vuông không bị hiểu là mẫu; trong Postman gõ đúng như ở đây. Dấu + trong giờ viết là %2B (+07:00 → %2B07:00), nếu không nó sẽ thành khoảng trắng.

Cần gì Yêu cầu
Tất cả món bằng tiếng Việt, 200 món mỗi trang GET /menu/items?locale=vi&per_page=200
Chỉ các món hiện có thể gọi GET /menu/items?filter[available]=1
Món theo SKU GET /menu/items?filter[sku]=PHO-1
Món của một thực đơn (id thực đơn nằm trong meta.menus) GET /menu/items?filter[menu]=12
Thực đơn đã đổi gì kể từ một thời điểm GET /menu/items?filter[updated_since]=2026-10-14T00:00:00Z
Nhóm tùy chọn của một thực đơn GET /menu/modifiers?filter[menu]=12

Đơn và hóa đơn

GET /orders — các đơn (lượt gọi của hóa đơn), mới nhất ở trên. Mỗi đơn kèm các dòng, tùy chọn đã chọn, trạng thái, bàn, số và thời gian. Dòng gọi món bằng SKU (lines[].sku), còn tùy chọn lồng nhau bằng lines[].modifiers[].sku.

Bộ lọc Tác dụng
filter[status] pending, confirmed, completed, rejected, cancelled; nhiều giá trị — cách nhau bằng dấu phẩy
filter[source] guest (khách qua QR), staff (nhân viên), external (nhận qua API)
filter[table] số bàn
filter[bill] id hóa đơn
filter[external_id] id đơn trong hệ thống của bạn
filter[placed_from], filter[placed_to] khoảng thời gian nhận đơn (từ — gồm, đến — không gồm)
filter[updated_since] mọi thứ thay đổi từ thời điểm đã cho

GET /bills — hóa đơn của bàn: số tiền, thuế, hình thức thanh toán (tender), số phòng, thời điểm mở và đóng, và danh sách id đơn. Với ?embed=orders các đơn được trả đầy đủ. Bộ lọc: filter[status]=open|closed, filter[tender] (cash, transfer, card, ewallet, room_charge, voucher, none — chưa ghi thanh toán), filter[table], filter[number], filter[room_number], filter[opened_from|opened_to], filter[closed_from|closed_to], filter[updated_since]. Sắp xếp: ?sort=-closed_at.

Thời gian trong yêu cầu theo ISO 8601 (2026-10-14T09:00:00+07:00); phản hồi cũng vậy.

{primary} Để không bỏ sót thay đổi, hãy lưu thời điểm lần lấy thành công gần nhất và lần sau yêu cầu filter[updated_since] lùi lại một chút — bản ghi lặp vẫn nhận ra qua id.

Yêu cầu mẫu: đơn và hóa đơn

Cùng quy tắc (-g trong curl, %2B thay cho +). Giờ theo múi giờ Việt Nam (+07:00).

Cần gì Yêu cầu
Đơn đang chờ xác nhận GET /orders?filter[status]=pending
Đã xác nhận và đã hoàn tất GET /orders?filter[status]=confirmed,completed
Đơn của ngày 14 tháng 10 GET /orders?filter[placed_from]=2026-10-14T00:00:00%2B07:00&filter[placed_to]=2026-10-15T00:00:00%2B07:00
Chỉ đơn đến qua API GET /orders?filter[source]=external
Đơn đã xác nhận của bàn 5 GET /orders?filter[table]=6512&filter[status]=confirmed
Mọi thứ thay đổi kể từ lần đối chiếu gần nhất, cũ trước GET /orders?filter[updated_since]=2026-10-14T09:00:00Z&sort=updated_at
Đơn của chúng tôi theo số trong hệ thống của bạn GET /orders?filter[external_id]=POS-100234
Hóa đơn đang mở GET /bills?filter[status]=open
Hóa đơn đã đóng ngày 14 tháng 10, kèm đơn và các dòng GET /bills?filter[status]=closed&filter[closed_from]=2026-10-14T00:00:00%2B07:00&filter[closed_to]=2026-10-15T00:00:00%2B07:00&embed=orders
Hóa đơn ghi vào phòng khách sạn GET /bills?filter[status]=closed&filter[tender]=room_charge
Hóa đơn của một phòng GET /bills?filter[room_number]=412
Hóa đơn đã đóng chưa ghi thanh toán GET /bills?filter[status]=closed&filter[tender]=none
Trang tiếp theo thêm &page=2 vào bất kỳ yêu cầu nào

Nhận đơn từ hệ thống bên ngoài

POST /orders — hệ thống bên ngoài chuyển đơn mà nó đã nhận từ khách. Đơn vào quán như một đơn đã xác nhận bình thường: đi xuống bếp và in phiếu, trừ kho, vào hóa đơn của bàn và báo cáo. Giá do chúng tôi tính theo thực đơn — bạn không cần gửi giá.

{
  "external_id": "POS-100234",
  "table": 6512,
  "room_number": "412",
  "note": "Không ngò",
  "items": [
    {
      "sku": "PHO-1",
      "quantity": 2,
      "note": "Ớt để riêng",
      "modifiers": [
        { "sku": "SIZE-L" },
        { "sku": "EGG", "quantity": 1 }
      ]
    }
  ]
}
Trường Ý nghĩa
external_id Số đơn trong hệ thống của bạn. Bắt buộc: yêu cầu lặp được nhận ra qua nó
table id của bàn đúng như GET /tables trả về (một số như 6512; số ghi trên bàn là trường number, chỉ dùng để đối chiếu bàn của bạn với bàn của chúng tôi)
room_number Số phòng, để ghi đơn vào phòng khách sạn
served true — đơn đã được phục vụ: ghi nhận là hoàn tất, không đi xuống bếp
items[].sku, quantity Món và số lượng (1–99)
items[].modifiers[] Tùy chọn đã chọn theo SKU; tùy chọn lồng nhau nằm trong modifiers của chúng

Phản hồi 201 chứa đơn đã tạo — với id, số và giá của chúng tôi.

Gửi lại là an toàn. Nếu kết nối bị đứt và bạn gửi lại cùng external_id với cùng nội dung, bạn nhận 200 và đơn đã tạo — không có đơn thứ hai. Cùng external_id với nội dung khác là lỗi IDEMPOTENCY_KEY_REUSED: hoặc là lỗi, hoặc là đơn mới mang số cũ.

Đơn chỉ được nhận cho các món của thực đơn đang hoạt động mà hiện có thể gọi (không bị ẩn, không nằm trong danh sách hết hàng).

Thử trong Postman

Bộ sưu tập Postman làm sẵn chứa mọi yêu cầu của trang này, với bộ lọc bật/tắt được và các bài kiểm tra. Tải về: qreaty-external-api.postman_collection.json.

  1. Trong Postman nhấn Import (góc trên bên trái) và kéo tệp đã tải vào cửa sổ — hoặc dán liên kết tệp (https://qreaty.com/downloads/qreaty-external-api.postman_collection.json) rồi nhấn Enter. Bộ sưu tập «Qreaty — External API» sẽ xuất hiện.
  2. Mở bộ sưu tập và thẻ Variables. Dán token từ mục «Truy cập qua API» vào token (cách tạo). base_url đã trỏ tới https://qreaty.com/api/external/v1; đổi nó cho môi trường khác (không có dấu gạch chéo ở cuối). Nhấn Save.
  3. Gửi Menu → Menu items, Menu modifiers và Tables → List tables: các bài kiểm tra tự lưu sku, option_sku và số bàn vào các biến làm việc của bộ sưu tập (chúng xuất hiện trong danh sách sau lần chạy đầu tiên).
  4. Gửi Orders → Place order. Phản hồi 201 nghĩa là đơn đã được tạo; gửi lại với cùng external_id (tạo biến bộ sưu tập keep_external_id với giá trị bất kỳ) sẽ trả về 200 và đơn cũ.
  5. Bộ lọc trong «List orders» và «List bills» đang tắt bằng ô đánh dấu — bật những cái cần và điền giá trị của bạn.

{primary} Xác thực là Bearer token cho cả bộ sưu tập, nên không cần đặt trong từng yêu cầu. Nếu phản hồi là 401, kiểm tra token dán không có khoảng trắng và chưa bị thu hồi; 403 nghĩa là token thiếu quyền cho yêu cầu đó.

Lỗi và giới hạn

Mã Khi nào Cần làm gì
401 không có token, token đã thu hồi, hết hạn, hoặc không phải token của quán tạo token mới
403 token thiếu quyền cho yêu cầu này cấp quyền hoặc tạo token khác
404 không có đơn hoặc hóa đơn với id này trong quán kiểm tra id
422 nội dung sai: SKU hoặc bàn không tồn tại, món hiện không có, chưa chọn tùy chọn bắt buộc đọc errors — mỗi lỗi nêu đường dẫn, ví dụ items.0.sku
422 + IDEMPOTENCY_KEY_REUSED external_id đã dùng với nội dung khác đổi số hoặc gửi lại nội dung gốc
429 quá nhanh thử lại sau Retry-After giây

Giới hạn là 120 yêu cầu mỗi phút cho mỗi token. Với việc đối chiếu thực đơn định kỳ thì thừa sức: dùng filter[updated_since] và trang 200 mục.

Có thể yêu cầu bộ phận hỗ trợ cung cấp đặc tả máy đọc được (OpenAPI) cho lập trình viên của bên tích hợp.