Orders API
Crear pedido
Este endpoint cubre el flujo público inicial para integraciones dispatcher y restaurantes. Para una cuenta dispatcher, la selección del restaurante existente se hace con restaurantToken y la flota queda determinada por el token de autenticación. Para una cuenta restaurante, el restaurante se obtiene del token de autenticación y dispatcherToken es opcional: permite seleccionar una flota concreta y, si se omite, el pedido queda pendiente sin asignación directa salvo que la autoasignación configurada resuelva una única flota vinculada. restaurantId se mantiene como alias legacy retrocompatible cuando contiene un token; el backend lo normaliza internamente a restaurantToken. Los clientes antiguos que envían un UUID en restaurantId también siguen siendo compatibles. Permite crear un pedido para un restaurante existente o para un restaurante externo enviado en la petición. Opcionalmente puede registrar un webhook inmutable de cambios de estado para ese pedido.
Requisitos para restaurante existente
- Si la cuenta autenticada es dispatcher, enviar restaurantToken como token del restaurante destino de OperioHub; no es el bearer de autenticación y la cabecera Authorization sigue usando el token de acceso.
- Por compatibilidad, también se acepta restaurantId como alias de restaurantToken cuando contiene el token; si se envían ambos, restaurantToken tiene prioridad.
- Si la cuenta autenticada es restaurante, el restaurante se obtiene de Authorization y no hace falta repetir restaurantToken.
- Para asignar desde una cuenta restaurante a una flota concreta, enviar dispatcherToken; si se omite, el pedido se crea en pending_approval sin flota asignada salvo que la autoasignación resuelva una única flota vinculada. dispatcherId no forma parte de este POST.
- Enviar destino con deliveryAddress, deliveryLat y deliveryLng.
- Los datos de recogida pueden omitirse si el restaurante ya tiene ubicación configurada.
Requisitos para restaurante externo
- Enviar externalRestaurant con source, externalId, displayName y location.
- Opcionalmente enviar requireRiderAssignment: true para exigir la asignación inmediata de un rider elegible.
- requireRiderAssignment solo funciona con pedidos externalRestaurant en modo direct; no se admite en marketplace.
- No enviar restaurantId; la referencia externa del restaurante va en externalRestaurant.externalId.
- Opcionalmente enviar photoUrl y logoUrl como URLs públicas del restaurante.
- Enviar recogida completa: pickupAddress, pickupLat y pickupLng.
- Enviar destino con deliveryAddress, deliveryLat y deliveryLng.
Requisitos para marketplace de reparto
- Enviar dispatchMode con valor marketplace y el objeto marketplace completo.
- No enviar dispatcherToken; OperioHub lo asigna solo después de adjudicar una propuesta.
- Enviar restaurantToken como token del restaurante destino de OperioHub; no es el bearer de autenticación y la cabecera Authorization sigue usando el token de acceso.
- Por compatibilidad, también se acepta restaurantId como alias de restaurantToken cuando contiene el token; si se envían ambos, restaurantToken tiene prioridad.
- Enviar recogida y entrega con dirección y coordenadas.
- La ventana actual de recogida de propuestas es de 60 segundos; si no hay propuesta válida, el pedido queda sin adjudicar y puede reintentarse.
Campos principales
Resumen de los campos relevantes para este endpoint.
| Campo | Tipo | Uso | Descripción |
|---|---|---|---|
dispatchOrderId | string | Opcional | Referencia de tu sistema para localizar el pedido en listados, logs o soporte. |
restaurantToken | string | Obligatorio para dispatcher; opcional para restaurante | Token de selección del restaurante destino de OperioHub cuando la cuenta autenticada es dispatcher. No es la cabecera de autenticación; Authorization sigue llevando el token de acceso. |
dispatcherToken | string | Opcional; solo restaurante | Token de la flota a la que se asignará el pedido. Solo se acepta cuando la cuenta autenticada es un restaurante y existe una relación activa; no se puede sustituir por dispatcherId. |
externalRestaurant | object | Restaurante externo | Datos del restaurante cuando aún no existe en OperioHub, incluyendo foto o logo públicos si están disponibles. |
requireRiderAssignment | boolean | Opcional; solo restaurante externo | Si es true, OperioHub debe asignar inmediatamente un rider elegible al pedido. Solo se admite con externalRestaurant y dispatchMode direct; el rider debe estar online, tener ubicación reciente, tener la autoasignación activa y no contar con una ruta activa. |
externalRestaurant.source | string | Obligatorio para restaurante externo | Origen de la referencia externa, por ejemplo el marketplace, POS o sistema integrador que identifica el restaurante. |
externalRestaurant.externalId | string | Obligatorio para restaurante externo | Identificador del restaurante en tu sistema. Debe ser una referencia estable para que OperioHub pueda crear o reutilizar el mismo restaurante externo. |
externalRestaurant.displayName | string | Obligatorio para restaurante externo | Nombre visible del restaurante externo en la operación. |
externalRestaurant.location | string | Obligatorio para restaurante externo | Dirección o descripción del punto de recogida del restaurante externo. |
externalRestaurant.phone | string | Opcional | Teléfono operativo del restaurante externo si está disponible. |
externalRestaurant.contactEmail | string | null | Opcional | Email de contacto del restaurante externo si está disponible. |
externalRestaurant.photoUrl | string | Opcional | URL pública de una foto del restaurante externo. |
externalRestaurant.logoUrl | string | Opcional | URL pública del logo del restaurante externo. |
customerInfo | object | Opcional | Datos del cliente final: nombre, teléfono, email y notas. |
deliveryInfo | object | Obligatorio | Dirección y coordenadas de entrega. En restaurantes externos también incluye datos de recogida. Puede incluir deliveryPhoneCode para el contacto seguro con el cliente. |
deliveryInfo.deliveryPhoneCode | string | null | Opcional | Código de verificación que el rider puede usar al contactar con el cliente. Se recorta y admite entre 1 y 100 caracteres; mantenlo separado de deliveryNotes. |
paymentInfo | object | Opcional | Método de pago, estado del cobro, importe total, moneda e importe esperado en efectivo. |
webhooks | object | Opcional | Objeto extensible para endpoints de notificación asociados al pedido. Solo se acepta durante la creación del pedido. |
webhooks.onOrderStatusWebhook | object | Opcional | Webhook inmutable que recibe un evento por cada cambio de estado del pedido. El envío es asíncrono y no bloquea la creación ni las transiciones. |
webhooks.onOrderStatusWebhook.url | string | Opcional | URL HTTPS pública del integrador. No se aceptan credenciales embebidas, localhost, dominios .local ni rangos IP privados o reservados. OperioHub no sigue redirecciones al enviar el webhook. |
webhooks.onOrderStatusWebhook.auth | object | Opcional | Autenticación del webhook. Actualmente soporta { type: "bearer", token: "..." }; OperioHub enviará Authorization: Bearer <token>. |
Asignación inmediata de rider
Usa requireRiderAssignment cuando un pedido de restaurante externo no pueda quedar pendiente de asignación.
- Envía requireRiderAssignment: true junto con externalRestaurant. El valor omitido o false mantiene el flujo normal de creación.
- La opción solo está disponible para pedidos directos de restaurante externo; no puede combinarse con dispatchMode: marketplace.
- Si se envía con un restaurante registrado o con dispatchMode: marketplace, la API responde 400 con ORDER_CREATE_INVALID_PAYLOAD.
- OperioHub busca un rider del dispatcher con autoasignación activa, presencia online y ubicación reciente, que no tenga una ruta activa.
- Cuando hay un rider elegible, la respuesta 201 incluye el pedido y la route creada para esa asignación.
- Si no hay ningún rider elegible, la API responde 409 con ORDER_CREATE_REQUIRED_RIDER_ASSIGNMENT_UNAVAILABLE y no deja el pedido pendiente de asignación.
| Campo | Tipo | Uso | Descripción |
|---|---|---|---|
requireRiderAssignment | boolean | Opcional | Exige una asignación inmediata a un rider elegible para pedidos externalRestaurant directos. |
Código de verificación de entrega
Usa este campo cuando el contacto con el cliente requiera un código adicional de seguridad.
- Envía deliveryInfo.deliveryPhoneCode como texto opcional; no lo mezcles con deliveryInfo.deliveryNotes.
- El valor se recorta y admite entre 1 y 100 caracteres.
- El rider lo verá como código de verificación durante la entrega; si no existe, la app mostrará Sin código de verificación.
Webhooks
El bloque webhooks agrupa las notificaciones salientes asociadas al pedido. Es opcional y extensible: hoy solo está disponible onOrderStatusWebhook, pero el contrato queda preparado para añadir más eventos en el futuro.
- Los webhooks solo se registran durante la creación del pedido.
- La configuración es inmutable: no se puede modificar mediante actualización del pedido.
- Cada webhook se envía de forma asíncrona y no bloquea la creación ni los cambios de estado del pedido.
- El endpoint debe ser HTTPS público. OperioHub no sigue redirecciones y rechaza localhost, dominios .local, credenciales embebidas y rangos IP privados o reservados.
- Si se configura auth bearer, OperioHub enviará el token en el header Authorization.
| Campo | Tipo | Uso | Descripción |
|---|---|---|---|
webhooks.onOrderStatusWebhook | object | Opcional | Webhook de cambio de estado del pedido. |
webhooks.onOrderStatusWebhook.url | string | Obligatorio si se configura | Endpoint HTTPS público que recibirá los eventos order.status_changed. |
webhooks.onOrderStatusWebhook.auth.type | string | Opcional | Actualmente solo se soporta bearer. |
webhooks.onOrderStatusWebhook.auth.token | string | Opcional | Token que OperioHub enviará como Authorization: Bearer <token>. |
Webhook order.status_changed
OperioHub envía un evento por cada cambio de estado registrado en el historial del pedido. El body no incluye datos de cliente, entrega ni pago.
- Método: POST.
- Headers: Content-Type: application/json y, si se configuró token, Authorization: Bearer <token>.
- Intentos: 1 intento total, sin reintentos automáticos en esta versión.
- El campo status.meta se limita a metadata operativa controlada. En incidencias reportadas por el rider incluye issueType, issuePhase, reasonCode, reasonLabel, customReason y releaseFromRiderRoute.
- Para actualizar el estado desde tu integración, usa POST /api/orders/:orderId/status; los webhooks son solo notificaciones salientes.
| Campo | Tipo | Uso | Descripción |
|---|---|---|---|
pending_approval | OrderStatus | Posible estado | Pedido creado y pendiente de aceptación operativa. |
dispatcher_accepted | OrderStatus | Posible estado | Pedido aceptado por el dispatcher. |
going_to_pickup | OrderStatus | Posible estado | Rider asignado en camino al punto de recogida. |
arrived_at_pickup | OrderStatus | Posible estado | Rider ha llegado al punto de recogida. |
in_route | OrderStatus | Posible estado | Pedido recogido y en ruta hacia el destino. |
arrived_at_dropoff | OrderStatus | Posible estado | Rider ha llegado al punto de entrega. |
delivered | OrderStatus | Posible estado | Pedido entregado. |
failed | OrderStatus | Posible estado | Pedido no completado por incidencia. |
cancelled | OrderStatus | Posible estado | Pedido cancelado. |
Payload JSON recibido
{
"eventType": "order.status_changed",
"eventId": "018f1f21-7c2a-7e8b-9c0d-123456789abc",
"occurredAt": "2026-06-19T10:30:00.000Z",
"order": {
"orderId": "ord_7G9K2Q",
"dispatchOrderId": "EXT-1001",
"currentStatus": "failed"
},
"status": {
"historyId": 123,
"value": "failed",
"changedAt": "2026-06-19T10:30:00.000Z",
"meta": {
"source": "rider_app",
"previousStatus": "arrived_at_dropoff",
"issueType": "delivery_failed",
"issuePhase": "delivery",
"reasonCode": "other",
"reasonLabel": "Otro",
"customReason": "Cliente no responde en el punto de entrega",
"releaseFromRiderRoute": false
}
}
} Request entrante en tu endpoint
POST /webhooks/order-status HTTP/1.1
Host: integrador.example.com
Content-Type: application/json
Authorization: Bearer YOUR_WEBHOOK_TOKEN
{
"eventType": "order.status_changed",
"eventId": "018f1f21-7c2a-7e8b-9c0d-123456789abc",
"occurredAt": "2026-06-19T10:30:00.000Z",
"order": {
"orderId": "ord_7G9K2Q",
"dispatchOrderId": "EXT-1001",
"currentStatus": "failed"
},
"status": {
"historyId": 123,
"value": "failed",
"changedAt": "2026-06-19T10:30:00.000Z",
"meta": {
"source": "rider_app",
"previousStatus": "arrived_at_dropoff",
"issueType": "delivery_failed",
"issuePhase": "delivery",
"reasonCode": "other",
"reasonLabel": "Otro",
"customReason": "Cliente no responde en el punto de entrega",
"releaseFromRiderRoute": false
}
}
} Request JSON
{
"dispatchOrderId": "POS-93442",
"restaurantToken": "rest_tok_0f4ef34f1d2d4e5fbf5409b3483e1f65",
"customerInfo": {
"customerName": "Ana Perez",
"customerPhone": "+34 600 000 000",
"customerEmail": "ana\u0040example.com",
"customerNotes": "Llamar si no responde"
},
"deliveryInfo": {
"deliveryAddress": "C. Cisne, 21-17, 03006 Alicante",
"deliveryLat": 38.346158,
"deliveryLng": -0.510089,
"deliveryNotes": "Piso 7, puerta 13"
},
"paymentInfo": {
"method": "cash",
"status": "pending",
"totalAmount": "18.50",
"currency": "EUR",
"cashExpectedAmount": "20.00"
},
"webhooks": {
"onOrderStatusWebhook": {
"url": "https://integrador.example.com/webhooks/order-status",
"auth": {
"type": "bearer",
"token": "YOUR_WEBHOOK_TOKEN"
}
}
}
} Respuesta 201
{
"data": {
"orderId": "ord_7G9K2Q",
"dispatchOrderId": "EXT-1001",
"currentStatus": "dispatcher_accepted",
"createdAt": "2026-06-16T10:30:00.000Z",
"updatedAt": "2026-06-16T10:30:00.000Z",
"customerInfo": {
"customerName": "Ana Perez",
"customerPhone": "+34 600 000 000",
"customerEmail": null,
"customerNotes": null
},
"deliveryInfo": {
"pickupAddress": "Av. de Orihuela, 27, 03007 Alicante",
"pickupLat": 38.344498,
"pickupLng": -0.509259,
"deliveryAddress": "C. Cisne, 21-17, 03006 Alicante",
"deliveryLat": 38.346158,
"deliveryLng": -0.510089,
"deliveryNotes": "Piso 7, puerta 13"
},
"paymentInfo": {
"method": "cash",
"status": "pending",
"totalAmount": "18.50",
"currency": "EUR",
"cashExpectedAmount": "20.00"
}
}
} Campo deliveryPhoneCode
{
"deliveryInfo": {
"deliveryPhoneCode": "A7-42"
}
} Consola de prueba
Envía una petición de creación
Ajusta la URL base, pega un token válido y modifica el payload para enviar una petición de prueba desde esta página. También puedes copiar el ejemplo como cURL o JavaScript.
Errores esperados
Respuestas habituales que debe contemplar la integración.
ORDER_CREATE_INVALID_PAYLOAD Payload inválido
Body inválido no cubierto por una regla más específica.
ORDER_CREATE_INVALID_WEBHOOKS Webhooks inválidos
El objeto webhooks contiene una URL no permitida, una autenticación no soportada o un token bearer inválido.
ORDER_CREATE_RESTAURANT_ID_REQUIRED_FOR_DISPATCHER Falta restaurante
Dispatcher crea pedido para restaurante registrado sin restaurantToken.
ORDER_CREATE_RESTAURANT_AND_EXTERNAL_MUTUALLY_EXCLUSIVE Restaurante duplicado
Se envían restaurantToken y externalRestaurant a la vez.
ORDER_CREATE_DELIVERY_INFO_REQUIRED Falta deliveryInfo
La petición no incluye el bloque deliveryInfo.
ORDER_CREATE_DELIVERY_COORDINATES_REQUIRED Destino incompleto
Faltan deliveryAddress, deliveryLat o deliveryLng.
ORDER_CREATE_EXTERNAL_RESTAURANT_IDENTITY_REQUIRED Restaurante externo incompleto
externalRestaurant no trae source, externalId o displayName.
ORDER_CREATE_EXTERNAL_RESTAURANT_PICKUP_REQUIRED Recogida incompleta
Pedido externo sin pickupAddress, pickupLat o pickupLng.
ORDER_CREATE_PICKUP_COORDINATES_REQUIRED Recogida no resoluble
Pedido registrado sin pickup completo y restaurante sin ubicación completa configurada.
ORDER_CREATE_EXTERNAL_RESTAURANT_REQUIRES_DISPATCHER Perfil incorrecto
Token no dispatcher intenta crear externalRestaurant.
ORDER_CREATE_RESTAURANT_MEMBERSHIP_REQUIRED Membresía requerida
Usuario restaurante sin membresía válida.
ORDER_CREATE_RESTAURANT_ID_MISMATCH Restaurante no coincide
Restaurante autenticado intenta usar otro restaurantToken.
ORDER_CREATE_DISPATCHER_RESTAURANT_RELATION_REQUIRED Relación no permitida
Dispatcher no vinculado con el restaurante registrado.
ORDER_CREATE_DISPATCHER_TOKEN_MISMATCH Dispatcher no coincide
El dispatcherToken no coincide con la cuenta dispatcher autenticada.
ORDER_CREATE_RESTAURANT_NOT_FOUND Restaurante no encontrado
restaurantToken no existe.
ORDER_CREATE_REQUIRED_RIDER_ASSIGNMENT_UNAVAILABLE Rider obligatorio no disponible
Se solicitó requireRiderAssignment, pero no hay un rider elegible y disponible para recibir el pedido en ese momento.
ORDER_CREATE_ATOMIC_MISSING_ORDER_ID Creación incompleta
La operación de creación no devuelve orderId.
ORDER_CREATE_ATOMIC_FAILED Creación fallida
Error no clasificado durante la creación atómica del pedido.
Ejemplo de error
{
"error": {
"code": "ORDER_CREATE_EXTERNAL_RESTAURANT_PICKUP_REQUIRED",
"message": "pickupAddress, pickupLat and pickupLng are required for external restaurant orders"
}
}