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.

SuperficiePrefijoPara qué sirve
Órdenes/api/v1/OrdersAceptar, rechazar, avanzar estado, consultar y listar.
Menú/api/v1/menuDisponibilidad, precios, edición por entidad y publicación del catálogo completo.

Base URLs por ambiente

Producción: 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

El contrato publica /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

BipBip no publica un IP allowlist fijo en ninguna dirección — tanto las llamadas salientes a BipBip como los webhooks entrantes se hacen sobre TCP/443 público con TLS. Si la política de seguridad exige allowlist estático o ruta de red privada (ej: AWS PrivateLink), el contacto es [email protected] para coordinar config custom para el tenant.

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>/accept

Protección de la API Key

No expongas la API Key en el frontend, en repositorios públicos ni en bundles del cliente. Guardala en variables de entorno del servidor y nunca la commitees.

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 type terminado en /idempotency-conflict.
  • Key nueva → request procesado normalmente.
  • Sin Idempotency-Key → HTTP 422.
  • Sin X-Bipbip-Schema-Version o valor no soportado → HTTP 400.

Cuándo reusar la key y cuándo generar una nueva

Reusá la misma key al reintentar el mismo intento lógico: timeout, 5xx, corte de red. El servidor garantiza un único side-effect. Generá una key nueva cuando arranca un intento lógico distinto — por ejemplo, después de una acción manual del operador.
{
  "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.

VentanaLímiteEstrategia
Ventana fija100 requests / minReset cada 60s
Ventana deslizante1,000 requests / horaEvaluada 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.
StatusCausaQué hacer
400Falta X-Bipbip-Schema-Version o el valor no está soportado.Agregar el header con valor 1.0 a toda mutación.
401API Key ausente, inválida o revocada.Verificar X-Bipbip-Api-Key. Si fue revocada, escribir a soporte.
404El 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.
409Transició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.
422Body inválido, falta Idempotency-Key, conflicto de idempotencia, o combo de menú no shippeado.Revisar meta.errors. No reintentar sin corregir el body.
429Cuota excedida para la API Key.Respetar Retry-After y reintentar con backoff y jitter.
502 / 503BackOffice 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

Las lecturas (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.
POST/api/v1/Orders/{orderKey}/accept

Aceptar 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

BipBip valida la llamada contra el 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 — Accepted es un paso intermedio no observable.
  • Después de /accept no llames PUT /status con preparing: devuelve 409.
  • La siguiente acción de tu POS es ready cuando el pedido esté listo.
  • En tiendas con auto-accept, BipBip corre el mismo cascade al recibir tu 200 del webhook; el expiresAt viaja null.
  • Publica el evento order.accepted_by_external_merchant.v1. El cascade interno a Preparing no emite un evento extra.

Path parameters

orderKeystringrequired
Identificador opaco de la orden entregado por BipBip en el webhook order.created (formato ord_ + 16 caracteres base62).

Headers

X-Bipbip-Api-Keystringrequired
API Key emitida por BipBip en onboarding.
X-Bipbip-Schema-Versionstringrequired
Versión del schema. Hoy: 1.0. Sin este header → 400.
Idempotency-KeyUUID v4required
Único por intento lógico. Misma key + mismo body en 24h → respuesta cacheada. Misma key + body distinto → 422.

Body

(vacío)object
v1.0 — body vacío: enviá literal {}. El remoteOrderId ya quedó vinculado en la respuesta al webhook order.created, así que no lo incluyas acá.

Returns — 202 Accepted

messagestring
Mensaje informativo. Su texto puede cambiar sin aviso: para lógica de negocio usá el status HTTP y data.
data.orderKeystring
Identificador opaco de la orden (mismo del path).
data.statusstring
Estado resultante: "Preparing".
data.acceptedAtISO 8601
Cuándo se registró la aceptación, según el reloj de BipBip.
data.remoteOrderIdstring | null
La referencia de tu POS ya vinculada. Viaja en todos los webhooks posteriores de esta orden.
POST/api/v1/Orders/{orderKey}/reject

Rechazar 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

Si enviás un valor fuera del catálogo, la API no devuelve 422 — lo normaliza aSYSTEM_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

orderKeystringrequired
Identificador opaco de la orden.

Headers

X-Bipbip-Api-Keystringrequired
API Key del comercio.
X-Bipbip-Schema-Versionstringrequired
1.0
Idempotency-KeyUUID v4required
Único por intento lógico.

Body

reasonCodeenumrequired
Motivo del rechazo. Enum cerrado: STORE_CLOSED, OUT_OF_OPERATING_HOURS, ITEM_NOT_OFFERED, ITEM_OUT_OF_STOCK, PRICE_MISMATCH, SYSTEM_ISSUE.
messagestringoptional
Detalle libre para soporte. No se le muestra al cliente final.

Returns — 202 Accepted

data.orderKeystring
Identificador opaco de la orden.
data.statusstring
Siempre "Rejected", que es terminal: la orden no admite más transiciones.
data.rejectedAtISO 8601
Cuándo se registró el rechazo, según el reloj de BipBip.
data.reasonstring | null
El reasonCode aplicado. Puede diferir del que enviaste si cayó fuera del catálogo.
PUT/api/v1/Orders/{orderKey}/status

Avanzar 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 → ready
  • ready → 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

El enum no acepta 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

orderKeystringrequired
Identificador opaco de la orden.

Headers

X-Bipbip-Api-Keystringrequired
API Key del comercio.
X-Bipbip-Schema-Versionstringrequired
1.0
Idempotency-KeyUUID v4required
Único por intento lógico.

Body

statusenumrequired
Estado destino en snake_case: preparing, 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
Cuándo ocurrió realmente el cambio en tu sistema — útil si encolás eventos y los enviás con retraso. Si se omite, BipBip usa su propio reloj. Si viene del comercio, se acota al rango [ahora−24h, ahora+5min].

Returns — 202 Accepted

data.orderKeystring
Identificador opaco de la orden.
data.statusstring
Estado resultante tras aplicar la transición.
data.changedAtISO 8601
Cuándo BipBip registró el cambio. Puede diferir del occurredAt que enviaste.
GET/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

El objeto 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

orderKeystringrequired
Identificador opaco de la orden.

Headers

X-Bipbip-Api-Keystringrequired
API Key del comercio.

Returns — 200 OK

data.orderKeystring
Identificador público y opaco de la orden. Es el único id que viaja en URLs y payloads.
data.storeIdinteger
Id interno de BipBip de la tienda. Para direccionar usá tu propio storeRemoteId.
data.brandIdinteger
Id interno de BipBip de la marca dueña de la tienda.
data.statusenum
Estado actual en PascalCase: Pending, 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 | null
Motivo del último cambio de estado, cuando quien lo originó declaró uno.
data.remoteOrderIdstring | null
Referencia de la orden en tu POS.
data.receivedAtISO 8601
Cuándo BipBip recibió la orden. Es el ancla de la ventana de aceptación.
data.acceptedAt · rejectedAt · preparingAt · readyAtISO 8601 | null
Marcas de tiempo de cada transición del comercio. preparingAt suele coincidir con acceptedAt por el cascade del accept.
data.driverAssignedAtISO 8601 | null
Cuándo se asignó repartidor. Se limpia si lo liberan y la orden queda pendiente de reasignación.
data.handedOverAtISO 8601 | null
Cuándo soltaste la comida: al repartidor si es delivery, 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 | null
Desde cuándo el cliente tiene la orden. En órdenes creadas a partir del 12 de agosto de 2026, null = 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 | null
Cuándo se canceló. Puede poblarse después de una entrega, por un reembolso o una incidencia.
data.history[]array
Traza de cambios de estado en orden cronológico, con el actor de cada uno.
fromStatusstring | null
Estado previo. null en el primer ítem, cuando la orden se creó.
toStatusstring
Estado al que pasó.
changedAtISO 8601
Cuándo ocurrió el cambio.
actorTypeenum
Quién lo originó: merchant (vos, por la API), customer, operator (operador de BipBip), driver o system (timeout de aceptación, fallo de entrega).
actorIdstring | null
Identificador del actor cuando aplica. null para system.
reasonstring | null
Motivo declarado. Texto libre: no lo parsees.
driverobject | null
Repartidor de la transición. Presente solo en los cambios de repartidor. Es acá — y no en reason — donde hay que leerlo.
eventenum
driver_assigned (hay repartidor), driver_reassigned (lo reemplazaron, hay uno nuevo) o driver_released (ya no hay repartidor, esperá una reasignación).
codestring | null
Identificador del repartidor, el mismo con el que BipBip habla de él con personas (ej: BIP-01424). Sirve para referirte a un repartidor concreto ante soporte. Cae al id interno cuando el repartidor no tiene código cargado y en las filas anteriores a que el código empezara a viajar. Tratalo como opaco: es una etiqueta, no un número.
fullNamestring | null
Nombre del repartidor.
previousCodestring | null
A quién reemplaza, con el mismo criterio. Solo en driver_reassigned.
data.orderobject | null
Contenido comercial de la orden. Mismo shape que el webhook order.created. null solo si el snapshot original no se pudo leer.
displayCodestring
Código humano-legible de la orden, para soporte.
storeRemoteIdstring
Código de la tienda en tu POS, el que configuraste en BipBip.
currencystring
Moneda ISO 4217 de todos los montos.
createdAtISO 8601
Cuándo se creó la orden en BipBip.
expiresAtISO 8601 | null
Fecha límite de aceptación. null en tiendas con auto-aceptación.
fulfillmentobject
type (delivery | pickup), isExpress, prepareBy y, según el tipo, driverPickupAt o customerPickupAt.
customerobject | null
Nombre del cliente. Solo en pickup, para llamarlo en mostrador; null en delivery por minimización de PII.
paymentobject
methods[] (soporta combinar efectivo, tarjeta y bips), amountToCollect y changeFor.
summaryobject
Totales agregados para reconciliar: subtotal, taxes, discounts, additionalCharges, grandTotal.
charges[] · discounts[]array
Cargos y descuentos desglosados, cada uno declarando quién lo cobra (billedBy) o quién lo absorbe (fundedBy).
items[]array
Productos pedidos con code, remoteCode, quantity, unitPrice, tax, lineTotal, note y sus modifierGroups[].
customerNotestring | null
Nota del cliente al pedido completo.
invoiceobject | null
taxId y businessName si el cliente pidió crédito fiscal; null en consumidor final.

Liberació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"
    }
  }
]
GET/api/v1/Orders

Listar órdenes

Lista las órdenes del cliente autenticado, ordenadas por número de orden descendente, con paginación por cursor opaco.

Headers

X-Bipbip-Api-Keystringrequired
API Key del comercio.

Query parameters

Cursorstringoptional
Cursor opaco devuelto en nextCursor de la respuesta anterior. No lo interpretes ni lo construyas a mano.
PageSizeintegeroptional
Tamaño de página. Máximo 100, default 20.
Statusstringoptional
Filtra por estado, en el mismo vocabulario que devuelve la lectura: Pending, Accepted, Preparing, Ready, HandedOver, Rejected, Cancelled. No existe un estado de repartidor por el que filtrar.
FromDateISO 8601optional
Inicio del rango de fechas.
ToDateISO 8601optional
Fin del rango de fechas.

Returns — 200 OK

data.items[]array
Página de resultados, del más reciente al más viejo.
orderKeystring
Identificador opaco. Usalo en GET /api/v1/Orders/{orderKey} para el detalle.
storeIdinteger
Id interno de BipBip de la tienda.
statusenum
Estado actual, en PascalCase.
receivedAtISO 8601
Cuándo BipBip recibió la orden. No es el criterio de orden del listado: puede diferir del orden real de creación, así que el listado ordena por número de orden y el cursor es ese mismo número.
acceptedAtISO 8601 | null
Cuándo la aceptaste. null si sigue pendiente o si la rechazaste.
remoteOrderIdstring | null
Referencia de la orden en tu POS.
driverAssignedAtISO 8601 | null
Cuándo se asignó repartidor. null si todavía no hay o si lo liberaron.
deliveredAtISO 8601 | null
Desde cuándo el cliente tiene la orden. Junto con status distingue una orden en camino de una ya entregada, sin pedir el detalle. Ver el detalle de la orden para la semántica completa.
data.nextCursorstring | null
Cursor opaco de la página siguiente. null en la última página.
data.hasMoreboolean
Condición de corte del bucle de paginación.

Máquina de estados — qué transiciona el comercio

Desde la REST API el comercio dispara estas transiciones:PendingPreparing (vía /accept, que atraviesa Accepted automáticamente) oRejected (vía /reject);PreparingReadyHandedOver (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 eventodriver_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.