开始使用
- 使用 Starter 或更高套餐商店 API 是一种连接,和 Telegram、BasilBook 一样。如果之后您的店改用 Free,已创建的密钥仍可继续使用。
- 添加 API 密钥店主在管理后台打开“设置”,再进入“API 密钥”。给密钥起个名字(“网站”),选择它能做什么、能用于哪些门店。完整密钥只显示一次:请当场复制。
- 每个请求都带上它放在
Authorization请求头中,如下所示。本页所有地址都以https://api.billandgo.app/v1开头。
Authorization: Bearer bg_live_…把密钥保存在您的服务器上,绝不要放在网页或手机应用里:拿到它的人就能读取您店里的数据。如果泄露,请在管理后台撤销并新建一个。
密钥和权限
| 在管理后台 | 密钥可以 |
|---|---|
| 读取 | 本页所有 GET。 |
| 读取并下单 | 另外还有 POST /orders 和 POST /orders/:id/payment。 |
- 门店。 未选择门店的密钥可访问所有门店,包括以后新开的门店。选择门店后,密钥只能查看这些门店并在这些门店下单。
- 撤销。 撤销后,密钥在下一个请求时即失效。被撤销的密钥永远不能再用。
- 每家企业最多 10 个有效密钥。 每个系统各用一个,这样撤销一个不会影响其他系统。
- 仅限此 API。 密钥只能用于
/v1下的地址,管理后台的登录在那里无效。
响应格式
- 输入和输出都是 JSON。API 不认识的字段会被拒绝,所以拼写错误不会被忽略。
- 时间 使用 UTC,例如
2026-10-23T03:04:05.123Z。带时间的响应还会附上店铺的timezone;按天统计以该时区为准。 - 金额 是最小单位的整数,旁边附有
currency:20900配KHR是 20 900 瑞尔,450配USD是 $4.50。 - 列表 格式为
{ "data": [ … ] }。 - 错误 格式为
{ "error": { "code", "message", "details" } }。见错误。
接口
GET/stores
任何密钥。
此密钥可访问的门店。
{
"data": [
{
"id": "0b6c…", "name": "Riverside", "address": "St. 108, Phnom Penh",
"lat": 11.57, "lng": 104.92, "phone": "+855 12 345 678",
"openingHours": { "mon": [{ "open": "07:00", "close": "21:00" }] },
"active": true, "taxRate": 10, "deliveryFee": 2000, "deliveryRadiusM": 5000,
"currency": "KHR", "timezone": "Asia/Phnom_Penh"
}
],
"timezone": "Asia/Phnom_Penh"
}GET/orders
任何密钥。
订单按最早更改的排在最前,分页返回:用来让另一个系统保持同步。
| 字段 | 说明 |
|---|---|
storeId | 某一门店(此密钥可访问的)。 |
status | placed、confirmed、preparing、ready、out_for_delivery、delivered、completed、rejected 或 cancelled。 |
updatedSince | 一个 UTC 时间:在此时间或之后更改的订单。 |
cursor | 上一页的 nextCursor。 |
limit | 1 到 100。省略时为 50。 |
{
"data": [ { "id": "9a1e…", "…": "an order, as below" } ],
"nextCursor": "WyIyMDI2LTEwLTIzVDAzOjA0OjA1LjEyMzQ1NloiLCI3ZjFjLi4uIl0",
"hasMore": false,
"timezone": "Asia/Phnom_Penh"
}保持同步。 当 hasMore 为 true 时继续跟随 nextCursor。即使 hasMore 为 false,也请保留最后一个 nextCursor,稍后再用它请求:之后更改过的订单会再次返回,每次更改一次。完全不依赖您自己的时钟。
GET /orders/:id
单个订单。商品行和价格是售出时的样子,而不是今天菜单上的。source 为 app、api、counter 或 phone。
有多家分店的企业,在第一家分店未及时接单时,可能把订单转给另一家分店。storeId 和 storeName 是订单现在所在的门店,callNumber 是该分店的叫号;单号和价格保持下单时不变,且订单只会转到您的密钥可以下单的门店。estimatedReadyAt 是预计完成时间:根据该分店最近制作一张订单所需的时间,计算这一单及排在它前面的每一单。订单完成后以及在列表中,它为 null。
{
"id": "9a1e…", "storeId": "0b6c…", "storeName": "Riverside",
"estimatedReadyAt": "2026-10-23T03:16:05.000Z", "reference": "20261023-0042", "callNumber": 42,
"source": "api", "orderType": "takeaway", "status": "placed",
"paymentStatus": "unpaid", "paymentMethod": null,
"items": [
{
"productId": "p1…", "name": "Iced Latte", "nameKm": "ឡាតេទឹកកក", "qty": 2, "pack": null,
"options": [{ "group": "Sugar", "name": "Extra sweet", "nameKm": null, "priceDelta": 500 }],
"basePrice": 9000, "unitPrice": 9500, "lineTotal": 19000, "note": null
}
],
"subtotal": 19000, "discount": 0, "discounts": [], "deliveryFee": 0, "tax": 1900, "total": 20900,
"currency": "KHR",
"customerName": "Dara", "customerPhone": null, "deliveryAddress": null,
"deliveryLat": null, "deliveryLng": null, "notes": null, "cancelReason": null,
"placedAt": "2026-10-23T03:04:05.123Z", "paidAt": null, "cancelledAt": null, "refundedAt": null,
"createdAt": "2026-10-23T03:04:05.123Z", "updatedAt": "2026-10-23T03:04:05.123Z",
"timezone": "Asia/Phnom_Penh"
}GET/sales/daily?from=YYYY-MM-DD&to=YYYY-MM-DD
任何密钥。
按营业日、门店和币种统计的营业额,最多 92 天。加上 storeId 可只看一家门店。
{
"from": "2026-10-23", "to": "2026-10-23", "timezone": "Asia/Phnom_Penh",
"data": [
{
"day": "2026-10-23", "storeId": "0b6c…", "currency": "KHR",
"orders": 2, "sales": 15000, "discount": 0, "tax": 909, "deliveryFees": 0,
"refunds": 1, "refunded": 5000, "net": 10000
}
]
}orders、sales、discount、tax、deliveryFees:当天付款的订单,按收到款项的时间计,包括之后退款的。refunds、refunded:当天退还的金额,不论何时付的款。net是sales减去refunded。- 没有数据的日子会被省略。切勿把不同币种的金额相加。
GET/stock?storeId=…
任何密钥。
店铺计数的每件商品在架上的数量,以单件计。省略 storeId 时为密钥可访问的所有门店。数量可能小于零:销售从不因库存被拒绝。
{
"data": [
{
"storeId": "0b6c…", "productId": "p9…", "name": "Coca-Cola", "nameKm": null, "unitName": "Can",
"onHand": 77, "lowStockAt": 24, "packs": [{ "id": "u1…", "name": "Box", "contains": 24 }]
}
],
"timezone": "Asia/Phnom_Penh"
}POST/orders
需要可以下单的密钥。
把订单发送到 POS。必须带 Idempotency-Key 请求头(见安全地重新发送)。
curl -X POST https://api.billandgo.app/v1/orders \
-H "Authorization: Bearer bg_live_…" \
-H "Idempotency-Key: web-order-58213" \
-H "Content-Type: application/json" \
-d '{
"storeId": "0b6c…",
"orderType": "takeaway",
"items": [
{ "productId": "p1…", "qty": 2, "modifierIds": ["o2…"], "note": "less ice" },
{ "productId": "p9…", "qty": 1, "unitId": "u1…" }
],
"customerName": "Dara",
"customerPhone": "+855 12 345 678",
"notes": "Pick up at 7"
}'| 字段 | 说明 |
|---|---|
storeId | 要发送到的门店。 |
orderType | takeaway 或 delivery。外送需要 customerPhone 和 deliveryAddress,可附带定位(deliveryLat 和 deliveryLng),超出门店配送距离会被拒绝。 |
items[] | productId、qty(1 到 99)、modifierIds(菜单中的选项 id,所有组放在一起)、unitId(包装)、grams(称重商品的重量,用于有 perGrams 的商品)、note(给厨房的备注)。 |
customerName, customerPhone, notes | 选填。 |
无需发送价格:Bill&Go 会像 POS 一样为每张订单计价,包括选项规则、门店菜单、正在进行的优惠、税和配送费。响应为 201,附带订单。
它以线上订单的形式到达:状态为 placed,未付款。员工会收到通知,柜台接受或拒绝。订单被接受时打印厨房小票,或按店铺的打印规则立即打印。用 GET /orders 跟进。
POST/orders/:id/payment
需要可以下单的密钥。
把在别处付过款的订单标记为已付:外送合作方或您的网站已经向顾客收款。之后 POS 交付订单时不会再收钱。
curl -X POST https://api.billandgo.app/v1/orders/9a1e…/payment \
-H "Authorization: Bearer bg_live_…" \
-H "Idempotency-Key: grab-GF-58213" \
-H "Content-Type: application/json" \
-d '{ "method": "grab", "reference": "GF-58213" }'| 字段 | 说明 |
|---|---|
method | 店铺的某个付款方式代码,且在此门店可用(设置 → 付款方式;例如 card、khqr,或店铺添加的 grab)。不能是现金方式:现金在 POS 的钱箱中清点。 |
reference | 选填。您自己的付款 id,最多 100 个字符,与付款一同保存。 |
Idempotency-Key | 选填请求头。再次使用同一个键会被视为重复请求。 |
响应为 200,附带订单,此时为 "paymentStatus": "paid"。订单状态不变:仍在等待的订单仍在等柜台接受。小票在店里打印。
{
"id": "9a1e…", "status": "placed",
"paymentStatus": "paid", "paymentMethod": "grab",
"total": 20900, "currency": "KHR",
"paidAt": "2026-10-23T03:09:41.002Z",
"…": "the rest of the order"
}- 使用相同的
Idempotency-Key,或相同的付款方式和参考号再次发送:返回 200 和订单,并带Idempotent-Replayed: true。不会重复付款。 - 已用其他方式付款(在 POS 上,或用其他参考号)、已退款、已取消或已拒绝:409
CONFLICT。 - 此密钥无法使用的门店的订单:404。
安全地重新发送
网络会中断。要重新发送请求而不产生第二张订单或第二次付款,请给它一个 Idempotency-Key 请求头:任意自定义文本,1 到 255 个不含空格的可打印字符,每张订单或每次付款一个,重试时始终使用同一个。
- 再次使用同一个键会返回 200,内容是第一次请求的结果,并带
Idempotent-Replayed: true,即使两个请求同时到达也是如此。 - 键属于您的 API 密钥:两个系统使用相同的文本也不会冲突。
- 重复请求按第一次请求作答,无论它带了什么内容:切勿把同一个键用于两张不同的订单。
- 失败的请求(400、422…)没有产生任何效果,所以重新发送就是真正的重试。
错误
{
"error": {
"code": "VALIDATION_ERROR",
"message": "This shop does not take that payment method",
"details": { "method": "bitcoin" }
}
}请根据 code 和 details 判断。message 是写给人看的,可能会变。
| 状态码 | code | 何时出现 |
|---|---|---|
| 400 | VALIDATION_ERROR | 字段或查询参数不符合要求、该门店不卖的商品、店铺不接受的付款方式,或把现金作为付款发送。details.issues 会列出相关字段。 |
| 401 | UNAUTHORIZED | 没有密钥、密钥错误,或店主已撤销该密钥。 |
| 403 | FORBIDDEN | 只读密钥发送订单或付款,或此密钥无权使用的门店。 |
| 404 | NOT_FOUND | 其他企业的门店、订单或商品,或此密钥无法使用的门店的订单。 |
| 409 | CONFLICT | 把已用其他方式付款、已退款、已取消或已拒绝的订单标记为已付。 |
| 422 | INVALID_MODIFIER_SELECTION | 选项违反了选项组的规则:漏选了必选项,或选得太多。 |
| 429 | TOO_MANY_REQUESTS | 一分钟内请求过多。请等待 Retry-After 中的秒数。 |
限制
- 每个密钥每分钟 60 个请求。 再多一个就返回 429
TOO_MANY_REQUESTS,并带Retry-After请求头说明要等几秒。 - 同一地址每分钟 20 次失败尝试(没有密钥、密钥错误或已撤销),之后也返回 429。
- 这一分钟从第一个请求开始计算,并在 Bill&Go 的所有服务器上合计,所以我们更新时依然有效。
在同步订单?大约每 30 秒用您最后的 cursor 请求一次 GET /orders,远在限制之内。
