Bill&Go 商店 API。

把您自己的网站、记账系统或外送合作方连接到您的店。读取菜单、订单、销售和库存,把订单发送到 POS,并把在别处付过款的订单标记为已付。

您的第一个请求
curl https://api.billandgo.app/v1/stores \
  -H "Authorization: Bearer bg_live_…"
居中的 Bill&Go 商店 API,连接着网站、手机应用、外送合作方、报表、开发者和 POS。

开始使用

  1. 使用 Starter 或更高套餐商店 API 是一种连接,和 Telegram、BasilBook 一样。如果之后您的店改用 Free,已创建的密钥仍可继续使用。
  2. 添加 API 密钥店主在管理后台打开“设置”,再进入“API 密钥”。给密钥起个名字(“网站”),选择它能做什么、能用于哪些门店。完整密钥只显示一次:请当场复制。
  3. 每个请求都带上它放在 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某一门店(此密钥可访问的)。
statusplaced、confirmed、preparing、ready、out_for_delivery、delivered、completed、rejected 或 cancelled。
updatedSince一个 UTC 时间:在此时间或之后更改的订单。
cursor上一页的 nextCursor。
limit1 到 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要发送到的门店。
orderTypetakeaway 或 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何时出现
400VALIDATION_ERROR字段或查询参数不符合要求、该门店不卖的商品、店铺不接受的付款方式,或把现金作为付款发送。details.issues 会列出相关字段。
401UNAUTHORIZED没有密钥、密钥错误,或店主已撤销该密钥。
403FORBIDDEN只读密钥发送订单或付款,或此密钥无权使用的门店。
404NOT_FOUND其他企业的门店、订单或商品,或此密钥无法使用的门店的订单。
409CONFLICT把已用其他方式付款、已退款、已取消或已拒绝的订单标记为已付。
422INVALID_MODIFIER_SELECTION选项违反了选项组的规则:漏选了必选项,或选得太多。
429TOO_MANY_REQUESTS一分钟内请求过多。请等待 Retry-After 中的秒数。

限制

  • 每个密钥每分钟 60 个请求。 再多一个就返回 429 TOO_MANY_REQUESTS,并带 Retry-After 请求头说明要等几秒。
  • 同一地址每分钟 20 次失败尝试(没有密钥、密钥错误或已撤销),之后也返回 429。
  • 这一分钟从第一个请求开始计算,并在 Bill&Go 的所有服务器上合计,所以我们更新时依然有效。

在同步订单?大约每 30 秒用您最后的 cursor 请求一次 GET /orders,远在限制之内。

要连接我们没想到的东西?

告诉我们您想把什么连接到您的店。我们会帮您设置密钥和第一批请求。

准备开始了吗? 创建您的店铺

已经在用? 登录

联系我们

只需一分钟。我们会在一个工作日内回复。

您有几家店?

我们只用这些信息来联系您。