Orders API
Crear pedido
Este endpoint cubre el flujo público inicial para integraciones dispatcher. Permite crear un pedido para un restaurante existente o para un restaurante externo enviado en el payload. Opcionalmente puede registrar un webhook inmutable de cambios de estado para ese pedido.
Requisitos para restaurante existente
- Enviar restaurantId como UUID de restaurante OperioHub.
- 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.
- 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 dispatcherId; OperioHub lo asigna solo después de adjudicar una propuesta.
- 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. |
externalRestaurant | object | Restaurante externo | Datos del restaurante cuando aún no existe en OperioHub, incluyendo foto o logo públicos si están disponibles. |
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>. |
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",
"restaurantId": "0f4ef34f-1d2d-4e5f-bf54-09b3483e1f65",
"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 restaurantId.
ORDER_CREATE_DISPATCHER_ID_REQUIRED_FOR_RESTAURANT Falta dispatcher
Restaurante crea pedido y no se puede resolver dispatcherId.
ORDER_CREATE_RESTAURANT_AND_EXTERNAL_MUTUALLY_EXCLUSIVE Restaurante duplicado
Se envían restaurantId 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 restaurantId.
ORDER_CREATE_DISPATCHER_RESTAURANT_RELATION_REQUIRED Relación no permitida
Dispatcher no vinculado con el restaurante registrado.
ORDER_CREATE_RESTAURANT_NOT_FOUND Restaurante no encontrado
restaurantId no existe.
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"
}
}