Commencer
- Être en Starter ou au-dessusLa Shop API est une connexion, comme Telegram et BasilBook. Si votre boutique passe ensuite au forfait gratuit, les clés déjà créées continuent de fonctionner.
- Ajouter une clé d’APILe propriétaire ouvre Paramètres, puis Clés d’API, dans le tableau de bord. Donnez un nom à la clé (« Site web »), choisissez ce qu’elle peut faire, et pour quelles succursales. La clé entière n’est affichée qu’une fois : copiez-la à ce moment-là.
- L’envoyer à chaque requêteDans l’en-tête
Authorization, comme ci-dessous. Chaque adresse de cette page commence parhttps://api.billandgo.app/v1.
Authorization: Bearer bg_live_…Gardez la clé sur votre serveur, jamais dans une page web ou une appli mobile : quiconque la possède peut lire votre boutique. Si elle fuit, révoquez-la dans le tableau de bord et créez-en une nouvelle.
Clés et accès
| Dans le tableau de bord | La clé peut |
|---|---|
| Lecture | Chaque GET de cette page. |
| Lecture et passage de commandes | En plus POST /orders et POST /orders/:id/payment. |
- Succursales. Une clé sans succursale choisie atteint toutes les succursales, y compris celles que vous ouvrirez plus tard. Choisissez des succursales et la clé ne voit qu’elles, et ne commande que là.
- Révoquer. La révocation arrête la clé dès sa requête suivante. Une clé révoquée ne fonctionne plus jamais.
- Jusqu’à 10 clés actives par entreprise. Créez-en une par système, pour pouvoir en révoquer une sans arrêter les autres.
- Uniquement pour cette API. Une clé ne fonctionne que sur les adresses sous
/v1, et une connexion au tableau de bord n’y fonctionne pas.
Format des réponses
- JSON en entrée et en sortie. Un champ que l’API ne connaît pas est refusé : une faute de frappe ne passe jamais inaperçue.
- Les heures sont en UTC, comme
2026-10-23T03:04:05.123Z. Les réponses avec des heures portent aussi letimezonede la boutique ; les jours sont comptés sur cette horloge. - L’argent est un nombre entier dans la plus petite unité, à côté d’une
currency:20900avecKHRvaut 20 900 riels,450avecUSDvaut 4,50 $. - Les listes arrivent sous la forme
{ "data": [ … ] }. - Les erreurs arrivent sous la forme
{ "error": { "code", "message", "details" } }. Voir Erreurs.
Endpoints
GET/stores
Toute clé.
Les succursales que cette clé atteint.
{
"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
Toute clé.
Les commandes, de la plus anciennement modifiée à la plus récente, une page à la fois : de quoi tenir un autre système à jour.
| Champ | Ce que c’est |
|---|---|
storeId | Une succursale (que cette clé atteint). |
status | placed, confirmed, preparing, ready, out_for_delivery, delivered, completed, rejected ou cancelled. |
updatedSince | Une heure UTC : les commandes modifiées à cette heure ou après. |
cursor | Le nextCursor de la page précédente. |
limit | De 1 à 100. 50 s’il est omis. |
{
"data": [ { "id": "9a1e…", "…": "an order, as below" } ],
"nextCursor": "WyIyMDI2LTEwLTIzVDAzOjA0OjA1LjEyMzQ1NloiLCI3ZjFjLi4uIl0",
"hasMore": false,
"timezone": "Asia/Phnom_Penh"
}Rester à jour. Suivez nextCursor tant que hasMore vaut true. Gardez le dernier nextCursor même quand hasMore vaut false, et redemandez avec lui plus tard : une commande modifiée entre-temps revient, une fois par modification. Votre propre horloge n’entre jamais en jeu.
GET /orders/:id
Une commande. Les lignes et les prix sont ceux de la vente, pas ceux du menu d’aujourd’hui. source vaut app, api, counter ou phone.
Une entreprise à plusieurs succursales peut passer une commande à une autre succursale quand la première ne l’accepte pas à temps. storeId et storeName disent où elle est maintenant, et callNumber est celui de cette succursale ; la référence et le prix restent ceux de la commande passée, et elle ne va que vers une succursale où votre clé peut commander. estimatedReadyAt est une estimation de l’heure à laquelle elle sera prête : le temps récent de la succursale pour préparer une commande, pour celle-ci et chacune avant elle. Il vaut null une fois la commande prête, et dans les listes.
{
"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
Toute clé.
Les recettes par jour de la boutique, par succursale et par monnaie, sur 92 jours au plus. Ajoutez storeId pour une seule succursale.
{
"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: les commandes payées ce jour-là, selon le moment où l’argent est arrivé, y compris celles remboursées plus tard.refunds,refunded: l’argent rendu ce jour-là, quelle que soit la date du paiement.netvautsalesmoinsrefunded.- Les jours sans rien sont omis. N’additionnez jamais des montants de monnaies différentes.
GET/stock?storeId=…
Toute clé.
Ce qu’il y a en rayon, à l’unité, pour chaque produit que la boutique compte. Toutes les succursales que la clé atteint quand storeId est omis. Il peut passer sous zéro : une vente n’est jamais refusée faute de stock.
{
"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
Demande une clé qui peut passer des commandes.
Envoyer une commande au POS. L’en-tête Idempotency-Key est obligatoire (voir Renvoyer sans risque).
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"
}'| Champ | Ce que c’est |
|---|---|
storeId | La succursale à qui l’envoyer. |
orderType | takeaway ou delivery. Une livraison demande customerPhone et deliveryAddress, et peut porter un repère (deliveryLat et deliveryLng), refusé au-delà de la distance de livraison de la succursale. |
items[] | productId, qty (de 1 à 99), modifierIds (ids d’options du menu, tous groupes confondus), unitId (un conditionnement), grams (le poids pesé, pour un produit avec perGrams), note (pour la cuisine). |
customerName, customerPhone, notes | Facultatifs. |
Il n’y a pas de prix à envoyer : Bill&Go calcule le prix de chaque commande comme le POS, avec les règles des options, le menu de la succursale, ses offres en cours, la taxe et les frais de livraison. La réponse est 201 avec la commande.
Elle arrive comme une commande en ligne : statut placed, non payée. Le personnel est prévenu, et le comptoir l’accepte ou la refuse. Son ticket cuisine s’imprime à l’acceptation, ou tout de suite si les règles d’impression de la boutique le prévoient. Suivez-la avec GET /orders.
POST/orders/:id/payment
Demande une clé qui peut passer des commandes.
Marquer comme payée une commande réglée ailleurs : un partenaire de livraison ou votre site a déjà encaissé le client. Le POS remet alors la commande sans demander d’argent.
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" }'| Champ | Ce que c’est |
|---|---|
method | Le code d’un des moyens de paiement de la boutique, accepté dans cette succursale (Paramètres, Moyens de paiement ; par exemple card, khqr, ou un moyen ajouté par la boutique comme grab). Pas un moyen en espèces : les espèces se comptent dans le tiroir, au POS. |
reference | Facultatif. Votre propre identifiant du paiement, jusqu’à 100 caractères, conservé avec lui. |
Idempotency-Key | En-tête facultatif. La même clé renvoyée répond comme une répétition. |
La réponse est 200 avec la commande, désormais "paymentStatus": "paid". Son statut ne change pas : une commande en attente attend toujours que le comptoir l’accepte. Le reçu s’imprime à la boutique.
{
"id": "9a1e…", "status": "placed",
"paymentStatus": "paid", "paymentMethod": "grab",
"total": 20900, "currency": "KHR",
"paidAt": "2026-10-23T03:09:41.002Z",
"…": "the rest of the order"
}- Renvoyée avec la même
Idempotency-Key, ou avec le même moyen et la même référence : 200 avec la commande etIdempotent-Replayed: true. Rien n’est payé deux fois. - Déjà payée autrement (au POS, ou avec une autre référence), remboursée, annulée ou refusée : 409
CONFLICT. - Une commande dans une succursale que cette clé ne peut pas utiliser : 404.
Renvoyer sans risque
Le réseau coupe. Pour renvoyer une requête sans créer une deuxième commande ni un deuxième paiement, donnez-lui un en-tête Idempotency-Key : n’importe quel texte de votre choix, de 1 à 255 caractères imprimables sans espace, un par commande ou paiement voulu, et le même à chaque nouvel essai.
- La même clé renvoyée répond 200 avec ce que la première requête a produit, et
Idempotent-Replayed: true, même quand les deux arrivent en même temps. - Une clé appartient à votre clé d’API : deux systèmes qui utilisent le même texte ne se croisent jamais.
- Une répétition reçoit la réponse de la première requête, quel que soit son contenu : n’utilisez jamais une clé pour deux commandes différentes.
- Une requête qui a échoué (400, 422…) n’a rien fait : la renvoyer réessaie vraiment.
Erreurs
{
"error": {
"code": "VALIDATION_ERROR",
"message": "This shop does not take that payment method",
"details": { "method": "bitcoin" }
}
}Décidez selon code et details. Le message est écrit pour les personnes et peut changer.
| Statut | code | Quand |
|---|---|---|
| 400 | VALIDATION_ERROR | Un champ ou une requête qui ne convient pas, un produit que cette succursale ne vend pas, un moyen de paiement que la boutique n’accepte pas, ou des espèces envoyées comme paiement. details.issues nomme les champs. |
| 401 | UNAUTHORIZED | Pas de clé, une mauvaise clé, ou une clé révoquée par le propriétaire. |
| 403 | FORBIDDEN | Une clé en lecture seule qui envoie une commande ou un paiement, ou une succursale que cette clé n’a pas le droit d’utiliser. |
| 404 | NOT_FOUND | La succursale, la commande ou le produit d’une autre entreprise, ou une commande dans une succursale que cette clé ne peut pas utiliser. |
| 409 | CONFLICT | Marquer payée une commande déjà payée autrement, remboursée, annulée ou refusée. |
| 422 | INVALID_MODIFIER_SELECTION | Des options qui enfreignent les règles d’un groupe : un choix obligatoire oublié, ou trop de choix. |
| 429 | TOO_MANY_REQUESTS | Trop de requêtes en une minute. Attendez le nombre de secondes indiqué dans Retry-After. |
Limites
- 60 requêtes par minute et par clé. Une de plus et la réponse est 429
TOO_MANY_REQUESTS, avec un en-têteRetry-Afterqui dit combien de secondes attendre. - 20 essais ratés par minute depuis une même adresse (pas de clé, une mauvaise ou une révoquée), puis 429 aussi.
- La minute commence à la première requête et se compte sur tous les serveurs de Bill&Go : elle tient même pendant nos mises à jour.
Vous synchronisez des commandes ? Appelez GET /orders avec votre dernier curseur toutes les 30 secondes environ ; cela reste largement sous la limite.
