Inbound · Called from your POS
REST API Reference
Your POS calls BipBip. The API has two surfaces: Orders, to manage the lifecycle of what BipBip delivers to you via webhook, and Menu, to keep your catalog in sync — availability, prices and the brand's full menu. It is the counterpart of theWebhook Spec.
| Surface | Prefix | What it does |
|---|---|---|
| Orders | /api/v1/Orders | Accept, reject, advance status, get and list. |
| Menu | /api/v1/menu | Availability, prices, per-entity edits and full catalog publishing. |
Base URLs per environment
https://merchant-api.bipbip.hn. Staging: https://merchant-api.bipbip.dev. Routes are prefixed with /api/v1/, versioned in the path.Path casing
/api/v1/Orderswith a capital O and /api/v1/menuin lowercase. Use the exact casing documented on each endpoint below.No public IP allowlist
Authentication
Send theX-Bipbip-Api-Keyheader on every request, with the value the BipBip team gives you during onboarding. Each integrated merchant gets its own API Key, which identifies the client and scopes everything the API returns — orders, stores and brand.Mutations (POST/PUT/PATCH) additionally requireIdempotency-Key andX-Bipbip-Schema-Version(currently 1.0). To get your API Keys, email [email protected].
# Read — X-Bipbip-Api-Key only
curl -H "X-Bipbip-Api-Key: <api-key>" \
https://merchant-api.bipbip.hn/api/v1/Orders/<orderKey>
# Mutation — add Idempotency-Key and X-Bipbip-Schema-Version
curl -X POST \
-H "X-Bipbip-Api-Key: <api-key>" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "X-Bipbip-Schema-Version: 1.0" \
-H "Content-Type: application/json" \
-d '{}' \
https://merchant-api.bipbip.hn/api/v1/Orders/<orderKey>/acceptProtecting the API Key
Idempotency-Key
Every mutation — Orders and Menu alike — requires theIdempotency-Keyheader (UUID v4, unique per logical attempt) along withX-Bipbip-Schema-Version: 1.0. Responses are cached server-side for 24 hours.
- Same key + same body within 24h → BipBip returns the original response without re-executing.
- Same key + different body → HTTP 422 with a
typeending in/idempotency-conflict. - New key → request processed normally.
- Missing
Idempotency-Key→ HTTP 422. - Missing or unsupported
X-Bipbip-Schema-Version→ HTTP 400.
When to reuse the key and when to mint a new one
{
"type": "https://bipbip.app/probs/idempotency-conflict",
"title": "Idempotency conflict",
"detail": "Idempotency-Key reused with a different request body.",
"status": 422,
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"meta": null
}Rate limits
Limits apply per API Key and are active on every endpoint. When you exceed them, BipBip returns HTTP 429 with aRetry-Afterheader — the fix is exponential backoff with jitter on the client.
| Window | Limit | Strategy |
|---|---|---|
| Fixed window | 100 requests / min | Resets every 60s |
| Sliding window | 1,000 requests / hour | Evaluated continuously |
Errors
Every error response follows RFC 7807 Problem Details, with the fieldstype,title,detail,status,traceId andmeta. What meta carries depends on the error: transition 409s bringcurrentStatus and allowedTransitions[]; validation 422s bring errors, a map from field path to the list of rules it broke.
Discriminate on the type suffix, not on detail
detail is human-facing text and may change between versions. Thetype suffix is stable. Match withtype.endsWith('/invalid-state-transition') so you don't couple to the host, which varies across environments.| Status | Cause | What to do |
|---|---|---|
| 400 | Missing X-Bipbip-Schema-Version, or the value is not supported. | Add the header with value 1.0 to every mutation. |
| 401 | API Key missing, invalid or revoked. | Check X-Bipbip-Api-Key. If it was revoked, contact support. |
| 404 | The resource does not exist or does not belong to the authenticated client (order, store, menu code). | Confirm the exact identifier received via webhook or read. |
| 409 | Transition forbidden by the state machine, acceptance window expired, or bulk deactivation blocked on PUT /menu. | Read meta.allowedTransitions[] and pivot. Empty list = terminal state, do not retry. |
| 422 | Invalid body, missing Idempotency-Key, idempotency conflict, or a menu combo that is not shipped. | Inspect meta.errors. Do not retry without fixing the body. |
| 429 | Quota exceeded for the API Key. | Respect Retry-After and retry with backoff and jitter. |
| 502 / 503 | BackOffice did not respond or is unavailable. Only on the menu endpoints that delegate to it. | Retry with the same Idempotency-Key and backoff. |
Generic error
{
"type": "https://bipbip.app/probs/not-found",
"title": "Order not found",
"detail": "Order ord_xxx does not exist or does not belong to the authenticated client.",
"status": 404,
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"meta": null
}Invalid transition (409)
{
"type": "https://bipbip.app/probs/invalid-state-transition",
"title": "Invalid state transition",
"detail": "Cannot transition from 'Preparing' to 'HandedOver'.",
"status": 409,
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"meta": {
"currentStatus": "Preparing",
"allowedTransitions": [
"Ready"
]
}
}Validation error (422)
{
"type": "https://bipbip.app/probs/validation",
"title": "Validation failed",
"detail": "One or more request fields are invalid.",
"status": 422,
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"meta": {
"errors": {
"items[0].targetCode": [
"The targetCode field is required."
]
}
}
}Orders
Lifecycle of the orders BipBip delivers to you via webhook. Mutations requireX-Bipbip-Api-Key,X-Bipbip-Schema-Version andIdempotency-Key.
Statuses are read in PascalCase and written in snake_case
GET and the status field of every response) return Pending, Accepted,Preparing, Ready,HandedOver,Rejected, Cancelled. The PUT /status body, in contrast, acceptspreparing, ready andhanded_over. They are not the same vocabulary: do not compare thestatus you read against the one you write without normalizing./api/v1/Orders/{orderKey}/acceptAccept an order
Runs the atomic cascade Pending → Accepted → Preparing in a single operation. The order is never observable in Accepted from your POS: the response status is Preparing.
The acceptance window expires even while the order still reads Pending
expiresAt it published in the creation webhook. Past that instant, /accept returns409 with a type ending in/acceptance-timeout — even if GETstill shows the order as Pending. Readorder.expiresAt and don't accept out of window.Key behavior
- The response carries
status: "Preparing"directly —Acceptedis a non-observable intermediate step. - After
/accept, do not callPUT /statuswithpreparing: it returns 409. - Your POS's next action is
readywhen the order is done. - For auto-accept stores, BipBip runs the same cascade when it receives your webhook 200;
expiresAtarrives asnull. - Publishes the
order.accepted_by_external_merchant.v1event. The internal cascade toPreparingemits no extra event.
Path parameters
orderKeystringrequiredorder.created webhook (format ord_ + 16 base62 characters).Headers
X-Bipbip-Api-KeystringrequiredX-Bipbip-Schema-Versionstringrequired1.0. Without it → 400.Idempotency-KeyUUID v4requiredBody
(empty)object{}. The remoteOrderId was already bound when you replied to the order.created webhook, so do not include it here.Returns — 202 Accepted
messagestringdata.data.orderKeystringdata.statusstring"Preparing".data.acceptedAtISO 8601data.remoteOrderIdstring | null/api/v1/Orders/{orderKey}/rejectReject an order
Moves the order from Pending to Rejected (terminal state), persists the structured reason and cancels the acceptance timeout job.
An unknown reasonCode does not fail — it is normalized
SYSTEM_ISSUE and returns that indata.reason. A typo in the code does not surface as an error, it surfaces as a misclassified rejection metric. Compare it against what you sent.Path parameters
orderKeystringrequiredHeaders
X-Bipbip-Api-KeystringrequiredX-Bipbip-Schema-Versionstringrequired1.0Idempotency-KeyUUID v4requiredBody
reasonCodeenumrequiredSTORE_CLOSED, OUT_OF_OPERATING_HOURS, ITEM_NOT_OFFERED, ITEM_OUT_OF_STOCK, PRICE_MISMATCH, SYSTEM_ISSUE.messagestringoptionalReturns — 202 Accepted
data.orderKeystringdata.statusstring"Rejected", which is terminal: the order accepts no further transitions.data.rejectedAtISO 8601data.reasonstring | nullreasonCode that was applied. It may differ from what you sent if it fell outside the catalog./api/v1/Orders/{orderKey}/statusAdvance status
Advances an already-accepted order to the next valid state: preparing → ready → handed_over. Forward transitions only.
Expected transition flow
After /accept the order is already in Preparing. From there:
preparing → readyready → handed_over
preparing remains in the enum for compatibility, but calling it after/accept returns 409.
Post-acceptance cancellations are not exposed in v1.0
cancelled. Cancellations after acceptance originate inside BipBip — customer, operator, timeout or failed webhook — and reach your POS through thecancelled event of the PUT /events webhook. To cancel an already-accepted order, email[email protected].Path parameters
orderKeystringrequiredHeaders
X-Bipbip-Api-KeystringrequiredX-Bipbip-Schema-Versionstringrequired1.0Idempotency-KeyUUID v4requiredBody
statusenumrequiredpreparing, ready or handed_over. A transition that is not allowed returns 409 with the valid targets in meta.allowedTransitions[].ready does not depend on you alone: the driver can also mark it ready from their app, and that record advances the order. A 409 with meta.currentStatus: "Ready" is not an error — it is a state already reached. Continue with handed_over.occurredAtISO 8601optional[now−24h, now+5min].Returns — 202 Accepted
data.orderKeystringdata.statusstringdata.changedAtISO 8601occurredAt you sent./api/v1/Orders/{orderKey}Get an order
Returns the current status, the timestamp of every transition, the change history and the full commercial content. Read-only: no Idempotency-Key required.
Use it to recover a webhook you could not process
data.order object has the same shape as theorder.created webhook payload: items,payment, summary,charges, discounts andinvoice. The same parser serves both channels — if a webhook was dropped, you rebuild it from here with no new code.Path parameters
orderKeystringrequiredHeaders
X-Bipbip-Api-KeystringrequiredReturns — 200 OK
data.orderKeystringdata.storeIdintegerstoreRemoteId.data.brandIdintegerdata.statusenumPending, Accepted, Preparing, Ready, HandedOver, Rejected or Cancelled.The driver is not a status. Whether one is assigned is told by
driverAssignedAt, not by status.data.merchantStatusReasonstring | nulldata.remoteOrderIdstring | nulldata.receivedAtISO 8601data.acceptedAt · rejectedAt · preparingAt · readyAtISO 8601 | nullpreparingAt usually matches acceptedAt because of the accept cascade.data.driverAssignedAtISO 8601 | nulldata.handedOverAtISO 8601 | nulldelivery, to the customer on pickup. That is where your progress ends.It can be
null with the order already in HandedOver: that happens when you never reported the handover and BipBip closed the order once delivery was confirmed. In that case nobody knows when you released it. Do not use this field to detect that the order left — use status for that.data.deliveredAtISO 8601 | nullnull = no confirmed delivery yet. Earlier ones return null permanently, even if they were delivered: the field shipped without backfilling the earlier rows. If you reconcile a period spanning that date, do not read that null as “not delivered”.This is the field that tells “the driver picked it up, it is on its way” apart from “it arrived”: both look like
HandedOver, because your progress as a merchant ends when you release the food. On delivery the driver confirms it and it is later than handedOverAt; on pickup it matches it, because you handed the order to the customer yourself.data.cancelledAtISO 8601 | nullDriver release — how it looks in history
The driver does not move the merchant's status, so assign, reassign and release all produce rows wherefromStatus == toStatus — the order stays where it was. Without looking at driver.event, a release looks like an assignment and you would believe the order still has a driver.
[
{
"fromStatus": "Ready",
"toStatus": "Ready",
"changedAt": "2026-08-07T17:20:10Z",
"actorType": "operator",
"actorId": "Daniel",
"reason": "Released: driver BIP-01424 (manual_release)",
"driver": {
"event": "driver_released",
"code": "BIP-01424",
"fullName": "Juan Pérez"
}
},
{
"fromStatus": "Ready",
"toStatus": "Ready",
"changedAt": "2026-08-07T17:18:02Z",
"actorType": "operator",
"actorId": "Daniel",
"reason": "Driver BIP-01424 asignado manualmente por operador Daniel",
"driver": {
"event": "driver_assigned",
"code": "BIP-01424",
"fullName": "Juan Pérez"
}
}
]/api/v1/OrdersList orders
Lists the authenticated client's orders, sorted by order number descending, with opaque cursor pagination.
Headers
X-Bipbip-Api-KeystringrequiredQuery parameters
CursorstringoptionalnextCursor by the previous response. Do not interpret or hand-build it.PageSizeintegeroptionalStatusstringoptionalPending, Accepted, Preparing, Ready, HandedOver, Rejected, Cancelled. There is no driver status to filter on.FromDateISO 8601optionalToDateISO 8601optionalReturns — 200 OK
data.nextCursorstring | nullnull on the last page.data.hasMorebooleanState machine — what the merchant transitions
Pending →Preparing (via /accept, which passes through Accepted automatically) orRejected (via /reject);Preparing →Ready →HandedOver (via PUT /status).The
Cancelled state is set by BipBip and reaches your POS via webhook — the merchant does NOT transition it from the REST API.Driver assignment is not a status. BipBip notifies it with the
driver_assigned event of thePUT /events webhook and records it indriverAssignedAt, but the order'sstatus does not move: it stays where it was until you advance it. The driver cycle runs in parallel, so that event can arrive even while the order is inPreparing, before you reportready.