Inbound · Llamada desde el POS
REST API Reference
Tu POS llama a BipBip. La API tiene dos superficies: Órdenes, para gestionar el ciclo de vida de lo que BipBip te entrega por webhook, y Menú, para mantener sincronizado tu catálogo — disponibilidad, precios y el menú completo de la marca. Es la contraparte delWebhook Spec.
| Superficie | Prefijo | Para qué sirve |
|---|---|---|
| Órdenes | /api/v1/Orders | Aceptar, rechazar, avanzar estado, consultar y listar. |
| Menú | /api/v1/menu | Disponibilidad, precios, edición por entidad y publicación del catálogo completo. |
Base URLs por ambiente
https://merchant-api.bipbip.hn. Staging: https://merchant-api.bipbip.dev. Las rutas llevan prefijo /api/v1/ con versionado en el path.Casing de los paths
/api/v1/Orderscon O mayúscula y /api/v1/menuen minúsculas. Usá el casing exacto documentado en cada endpoint de esta página.Sin IP allowlist público
Autenticación
Incluí el headerX-Bipbip-Api-Keyen cada request, con el valor que el equipo BipBip te entrega durante el onboarding. Cada comercio integrado recibe su propia API Key, que identifica al cliente y acota todo lo que la API te devuelve — órdenes, tiendas y marca. Las mutaciones (POST/PUT/PATCH) requieren ademásIdempotency-Key yX-Bipbip-Schema-Version(hoy 1.0). Para obtener tus API Keys, escribí al equipo BipBip a [email protected].
# Lectura — solo X-Bipbip-Api-Key
curl -H "X-Bipbip-Api-Key: <api-key>" \
https://merchant-api.bipbip.hn/api/v1/Orders/<orderKey>
# Mutación — agregar Idempotency-Key y 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>/acceptProtección de la API Key
Idempotency-Key
Todas las mutaciones — las de Órdenes y las de Menú — requieren el headerIdempotency-Key(UUID v4, único por intento lógico) junto conX-Bipbip-Schema-Version: 1.0. Las respuestas se cachean en el servidor por 24 horas.
- Misma key + mismo body dentro de 24h → BipBip devuelve la respuesta original sin reejecutar.
- Misma key + body diferente → HTTP 422 con
typeterminado en/idempotency-conflict. - Key nueva → request procesado normalmente.
- Sin
Idempotency-Key→ HTTP 422. - Sin
X-Bipbip-Schema-Versiono valor no soportado → HTTP 400.
Cuándo reusar la key y cuándo generar una nueva
{
"type": "https://bipbip.app/probs/idempotency-conflict",
"title": "Idempotency conflict",
"detail": "Idempotency-Key reutilizada con un body distinto.",
"status": 422,
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"meta": null
}Rate limits
Los límites se aplican por API Key y están activos en todos los endpoints. Al superarlos, BipBip devuelve HTTP 429 con el headerRetry-After — la solución es exponential backoff con jitter en el cliente.
| Ventana | Límite | Estrategia |
|---|---|---|
| Ventana fija | 100 requests / min | Reset cada 60s |
| Ventana deslizante | 1,000 requests / hora | Evaluada continuamente |
Errores
Toda respuesta de error sigue RFC 7807 Problem Details, con los campostype,title,detail,status,traceId ymeta. El contenido de meta depende del error: los 409 de transición traencurrentStatus y allowedTransitions[]; los 422 de validación traen errors, un mapa de ruta de campo a lista de reglas incumplidas.
Discriminá por el sufijo de type, no por detail
detail es texto para humanos y puede cambiar entre versiones. El sufijo detype es estable. Compará contype.endsWith('/invalid-state-transition') para no acoplarte al host, que varía entre ambientes.| Status | Causa | Qué hacer |
|---|---|---|
| 400 | Falta X-Bipbip-Schema-Version o el valor no está soportado. | Agregar el header con valor 1.0 a toda mutación. |
| 401 | API Key ausente, inválida o revocada. | Verificar X-Bipbip-Api-Key. Si fue revocada, escribir a soporte. |
| 404 | El recurso no existe o no pertenece al cliente autenticado (orden, tienda, código de menú). | Confirmar el identificador exacto recibido por webhook o por lectura. |
| 409 | Transición prohibida por la máquina de estados, plazo de aceptación vencido, o desactivación masiva bloqueada en PUT /menu. | Leer meta.allowedTransitions[] y pivotar. Lista vacía = estado terminal, no reintentar. |
| 422 | Body inválido, falta Idempotency-Key, conflicto de idempotencia, o combo de menú no shippeado. | Revisar meta.errors. No reintentar sin corregir el body. |
| 429 | Cuota excedida para la API Key. | Respetar Retry-After y reintentar con backoff y jitter. |
| 502 / 503 | BackOffice no respondió o no está disponible. Solo en los endpoints de menú que delegan en él. | Reintentar con la misma Idempotency-Key y backoff. |
Error genérico
{
"type": "https://bipbip.app/probs/not-found",
"title": "Order not found",
"detail": "La orden ord_xxx no existe o no pertenece al cliente autenticado.",
"status": 404,
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"meta": null
}Transición inválida (409)
{
"type": "https://bipbip.app/probs/invalid-state-transition",
"title": "Invalid state transition",
"detail": "No se permite la transición de 'Preparing' a 'HandedOver'.",
"status": 409,
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"meta": {
"currentStatus": "Preparing",
"allowedTransitions": [
"Ready"
]
}
}Error de validación (422)
{
"type": "https://bipbip.app/probs/validation",
"title": "Validation failed",
"detail": "Uno o más campos del request son inválidos.",
"status": 422,
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"meta": {
"errors": {
"items[0].targetCode": [
"El campo targetCode es obligatorio."
]
}
}
}Órdenes
Ciclo de vida de las órdenes que BipBip te entrega por webhook. Las mutaciones requierenX-Bipbip-Api-Key,X-Bipbip-Schema-Version eIdempotency-Key.
Los estados se leen en PascalCase y se escriben en snake_case
GET y el campo status de las respuestas) devuelven Pending, Accepted,Preparing, Ready,HandedOver,Rejected, Cancelled. El body de PUT /status, en cambio, aceptapreparing, ready yhanded_over. No son el mismo vocabulario: no compares elstatus que leés contra el que escribís sin normalizar./api/v1/Orders/{orderKey}/acceptAceptar una orden
Ejecuta el cascade atómico Pending → Accepted → Preparing en una sola operación. La orden nunca queda observable en Accepted para tu POS: el status de la respuesta es Preparing.
El plazo de aceptación vence, aunque la orden siga en Pending
expiresAt que publicó en el webhook de creación. Pasado ese instante, /accept devuelve409 con type terminado en/acceptance-timeout — incluso si GETtodavía te muestra la orden como Pending. Leéorder.expiresAt y no aceptes fuera de ventana.Comportamiento clave
- El response trae
status: "Preparing"directo —Acceptedes un paso intermedio no observable. - Después de
/acceptno llamesPUT /statusconpreparing: devuelve 409. - La siguiente acción de tu POS es
readycuando el pedido esté listo. - En tiendas con auto-accept, BipBip corre el mismo cascade al recibir tu 200 del webhook; el
expiresAtviajanull. - Publica el evento
order.accepted_by_external_merchant.v1. El cascade interno aPreparingno emite un evento extra.
Path parameters
orderKeystringrequiredorder.created (formato ord_ + 16 caracteres base62).Headers
X-Bipbip-Api-KeystringrequiredX-Bipbip-Schema-Versionstringrequired1.0. Sin este header → 400.Idempotency-KeyUUID v4requiredBody
(vacío)object{}. El remoteOrderId ya quedó vinculado en la respuesta al webhook order.created, así que no lo incluyas acá.Returns — 202 Accepted
messagestringdata.data.orderKeystringdata.statusstring"Preparing".data.acceptedAtISO 8601data.remoteOrderIdstring | null/api/v1/Orders/{orderKey}/rejectRechazar una orden
Transiciona la orden de Pending a Rejected (estado terminal), persiste la razón estructurada y cancela el job de timeout de aceptación.
Un reasonCode desconocido no falla: se normaliza
SYSTEM_ISSUE y lo devuelve así endata.reason. Un typo en el código no se manifiesta como error, se manifiesta como una métrica de rechazo mal clasificada. Comparalo contra lo que enviaste.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", que es terminal: la orden no admite más transiciones.data.rejectedAtISO 8601data.reasonstring | nullreasonCode aplicado. Puede diferir del que enviaste si cayó fuera del catálogo./api/v1/Orders/{orderKey}/statusAvanzar estado
Avanza al siguiente estado válido de una orden ya aceptada: preparing → ready → handed_over. Solo transiciones forward.
Flujo de transiciones esperado
Después de /accept la orden ya está en Preparing. Desde ahí:
preparing → readyready → handed_over
preparing sigue en el enum por compatibilidad, pero invocarlo después de/accept devuelve 409.
Las cancelaciones post-aceptación no se exponen en v1.0
cancelled. Las cancelaciones posteriores a la aceptación se originan en BipBip — cliente, operador, timeout o webhook fallido — y llegan a tu POS por el eventocancelled del webhook PUT /events. Para cancelar una orden ya aceptada, escribí a[email protected].Path parameters
orderKeystringrequiredHeaders
X-Bipbip-Api-KeystringrequiredX-Bipbip-Schema-Versionstringrequired1.0Idempotency-KeyUUID v4requiredBody
statusenumrequiredpreparing, ready o handed_over. Una transición no permitida devuelve 409 con los destinos válidos en meta.allowedTransitions[].ready no depende solo de vos: el repartidor también puede marcarla lista desde su app, y ese registro avanza la orden. Un 409 con meta.currentStatus: "Ready" no es un error — es un estado ya alcanzado. Seguí con handed_over.occurredAtISO 8601optional[ahora−24h, ahora+5min].Returns — 202 Accepted
data.orderKeystringdata.statusstringdata.changedAtISO 8601occurredAt que enviaste./api/v1/Orders/{orderKey}Consultar una orden
Devuelve el estado actual, las marcas de tiempo de cada transición, el historial de cambios y el contenido comercial completo. Read-only: no requiere Idempotency-Key.
Sirve para recuperar un webhook que no pudiste procesar
data.order tiene el mismo shape que el payload del webhook order.created: items,payment, summary,charges, discounts einvoice. El mismo parser sirve para los dos canales — si un webhook se te cayó, lo reconstruís desde acá sin código nuevo.Path parameters
orderKeystringrequiredHeaders
X-Bipbip-Api-KeystringrequiredReturns — 200 OK
data.orderKeystringdata.storeIdintegerstoreRemoteId.data.brandIdintegerdata.statusenumPending, Accepted, Preparing, Ready, HandedOver, Rejected o Cancelled.El repartidor no es un estado. Si hay uno asignado te lo dice
driverAssignedAt, no status.data.merchantStatusReasonstring | nulldata.remoteOrderIdstring | nulldata.receivedAtISO 8601data.acceptedAt · rejectedAt · preparingAt · readyAtISO 8601 | nullpreparingAt suele coincidir con acceptedAt por el cascade del accept.data.driverAssignedAtISO 8601 | nulldata.handedOverAtISO 8601 | nulldelivery, al cliente si es pickup. Ahí termina tu avance.Puede venir
null con la orden ya en HandedOver: pasa si nunca reportaste el handover y BipBip cerró la orden al confirmarse la entrega. En ese caso nadie sabe en qué momento la soltaste. No uses este campo para detectar que la orden salió — para eso está status.data.deliveredAtISO 8601 | nullnull = todavía no hay entrega confirmada. Las anteriores devuelven null de forma permanente, aunque hayan sido entregadas: el campo se incorporó sin completar los registros previos. Si conciliás un período que cruza esa fecha, no leas ese null como «no se entregó».Es el campo que distingue «se la llevó el repartidor, va en camino» de «ya llegó»: las dos situaciones se ven
HandedOver, porque tu avance como comercio termina cuando soltás la comida. En delivery lo confirma el repartidor y es posterior a handedOverAt; en pickup coincide con él, porque se la entregaste vos mismo.data.cancelledAtISO 8601 | nullLiberación de repartidor — cómo se ve en history
El repartidor no mueve el estado del comercio, así que asignar, reasignar y liberar producen filas confromStatus == toStatus — la orden se queda donde estaba. Sin mirar driver.event, una liberación parece una asignación y creerías que la orden sigue con repartidor.
[
{
"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/OrdersListar órdenes
Lista las órdenes del cliente autenticado, ordenadas por número de orden descendente, con paginación por cursor opaco.
Headers
X-Bipbip-Api-KeystringrequiredQuery parameters
CursorstringoptionalnextCursor de la respuesta anterior. No lo interpretes ni lo construyas a mano.PageSizeintegeroptionalStatusstringoptionalPending, Accepted, Preparing, Ready, HandedOver, Rejected, Cancelled. No existe un estado de repartidor por el que filtrar.FromDateISO 8601optionalToDateISO 8601optionalReturns — 200 OK
data.nextCursorstring | nullnull en la última página.data.hasMorebooleanMáquina de estados — qué transiciona el comercio
Pending →Preparing (vía /accept, que atraviesa Accepted automáticamente) oRejected (vía /reject);Preparing →Ready →HandedOver (vía PUT /status).El estado
Cancelled lo setea BipBip y llega al POS por webhook — el comercio NO lo transiciona desde la REST API.La asignación de repartidor no es un estado. BipBip te la notifica con el evento
driver_assigned del webhookPUT /events y la registra endriverAssignedAt, pero elstatus de la orden no se mueve: se queda donde estaba hasta que vos lo avances. El ciclo del repartidor corre en paralelo, así que ese evento puede llegar incluso mientras la orden está enPreparing, antes de que reportesready.