Esta documentación cubre los endpoints de la API de órdenes y autenticación.
Renueva un access token usando un refresh token válido. Solo requiere el refresh token en el body — no es necesario el header Authorization. Devuelve un nuevo access token y un nuevo refresh token. El refresh token se obtiene del endpoint de login y tiene una duración de 7 días. El refresh token solo puede usarse en este endpoint; intentar usarlo en otros endpoints devuelve 401.
| refreshToken required | string |
{- "refreshToken": "string"
}Refresh exitoso
{ "success": true, "data": { "access_token": "eyJhbGciOiJIUzI1NiJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiJ9...", "token_type": "Bearer", "expires_in": 86400, "user_id": 12345, "email": "usuario@tucan.la", "user_type": "ADMIN" }, "message": "Token refreshed successfully", "code": "OK" }
Autentica un usuario con email y contraseña contra el servicio centralizado de usuarios, devolviendo un access token JWT y un refresh token para renovación automática. El access token debe incluirse en el header Authorization como 'Bearer {token}' para las siguientes requests. El refresh token se usa para renovar el access token cuando esté próximo a expirar.
| email required | string Email del usuario para autenticación |
| password required | string [ 6 .. 2147483647 ] characters Contraseña del usuario (mínimo 6 caracteres) |
{- "email": "usuario@tucan.la",
- "password": "miPassword123"
}Login exitoso
{ "success": true, "data": { "access_token": "eyJhbGciOiJIUzI1NiJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiJ9...", "token_type": "Bearer", "expires_in": 86400, "user_id": 12345, "email": "usuario@tucan.la", "user_type": "ADMIN" }, "message": "Authentication successful", "code": "OK" }
Retorna la estructura completa del menú: categorías, ítems con sus tamaños y grupos de extras. Cada ítem, extra y tamaño incluye el campo externalReferenceId con el ID del sistema POS externo.
| menuId required | integer <int64> ID del menú |
Menú con categorías e ítems
{ "success": true, "data": { "id": 492, "name": "Menu Integración", "categories": [ { "id": 2816, "name": "Entradas", "order": 16, "visibleInMenu": true, "withImage": false, "showName": "Entradas", "items": [ { "id": 19605, "externalReferenceId": "425", "name": "Provoleta de la casa a la leña", "description": "Provolone asado a la leña, cherrys confitados y rúcula.", "order": 0, "isVisibleInMenu": true, "withImage": true, "showName": "Provoleta de la casa a la leña", "maxQuantity": null, "isCombo": false, "discountedPrice": null, "imageUrl": "https://sandbox.images.tucan.la/media/78/images/plates/19605", "sizes": [ { "id": 20199, "externalReferenceId": "425", "name": "Único", "price": 17650, "discountedPrice": null, "isVisibleInMenu": true } ], "extraGroups": [ { "id": 1021, "name": "Acompañamientos", "minAmount": 0, "maxAmount": 2, "mandatory": false, "multipleSelection": true, "order": 1, "extras": [ { "id": 4412, "externalReferenceId": "101", "name": "Papas fritas", "price": 2500, "description": null, "visible": true }, { "id": 4413, "externalReferenceId": "102", "name": "Ensalada mixta", "price": 1800, "description": null, "visible": true } ] } ] } ] } ] }, "message": "Menu retrieved successfully", "code": "OK" }
Sincroniza la estructura del menú indicado: categorías, ítems, tamaños y grupos de extras. El campo storeId corresponde al ID del BranchContact (no del Branch). Los ítems raíz son tratados como productos (PRODUCT). Los toppings van en el campo extras del ítem padre y requieren maxLimit.
Procesamiento asincrónico: la respuesta es inmediata (HTTP 202 Accepted) y devuelve un trackingId; el menú se aplica en segundo plano (recomendado para menús grandes, evita timeouts). La validación de estructura del request sí es inmediata (400 si es inválida). Para conocer el resultado final de la sincronización, consultá GET /api/v1/menu/sync/{trackingId}.
| menuId required | integer <int64> ID del menú a actualizar |
| Authorization required | string |
| storeId | string |
required | Array of objects (V1MenuItemDTO) |
| menuId | string |
| storeIdOrMenuIdProvided | boolean |
Menú con producto y toppings
{- "storeId": "254",
- "items": [
- {
- "name": "Provoleta de la casa a la leña",
- "description": "Provolone asado a la leña, cherrys confitados y rúcula.",
- "externalReferenceId": "425",
- "price": 17650,
- "order": 0,
- "isCombo": false,
- "category": {
- "id": "entradas",
- "name": "Entradas",
- "minQty": 0,
- "maxQty": 0,
- "sortingPosition": 16
}, - "extras": [
- {
- "name": "Papas fritas",
- "externalReferenceId": "101",
- "price": 2500,
- "order": 0,
- "maxLimit": 2,
- "category": {
- "id": "acompañamientos",
- "name": "Acompañamientos",
- "minQty": 0,
- "maxQty": 2,
- "sortingPosition": 1
}
}, - {
- "name": "Ensalada mixta",
- "externalReferenceId": "102",
- "price": 1800,
- "order": 1,
- "maxLimit": 2,
- "category": {
- "id": "acompañamientos",
- "name": "Acompañamientos",
- "minQty": 0,
- "maxQty": 2,
- "sortingPosition": 1
}
}
]
}, - {
- "name": "Buñuelos de espinaca",
- "description": "Con crema ácida",
- "externalReferenceId": "414",
- "price": 12400,
- "order": 1,
- "combo": false,
- "category": {
- "id": "entradas",
- "name": "Entradas",
- "minQty": 0,
- "maxQty": 0,
- "sortingPosition": 16
}
}
]
}{ "success": true, "data": { "status": "success", "message": "Menú recibido y en proceso de sincronización", "trackingId": "3b1d2fd7-52ba-4714-ae56-3cd5adf2d397" }, "message": "Menú recibido y en proceso de sincronización", "code": "OK" }
Retorna los menús activos del branch asociado al usuario autenticado.
| branchId | integer <int64> |
| page | integer <int32> Default: 0 |
| size | integer <int32> Default: 50 |
Lista de menús
{ "success": true, "data": [ { "id": 492, "name": "Menu Integración", "branchId": 78 } ], "message": "Menus retrieved successfully", "code": "OK" }
Devuelve el estado del sync async iniciado por PUT /api/v1/menu/{menuId}, identificado por el trackingId que devolvió esa llamada. Estados posibles: PENDING (encolado), PROCESSING (aplicándose), DONE (aplicado OK), FAILED (falló — ver el mensaje de error).
| trackingId required | string trackingId devuelto por PUT /api/v1/menu/{menuId} |
{ "success": true, "data": { "syncId": "3b1d2fd7-52ba-4714-ae56-3cd5adf2d397", "status": "DONE", "menuId": "492", "attempts": 1, "errorMessage": null, "updatedAt": "2026-06-14T16:21:24Z" }, "message": "Sync status retrieved", "code": "OK" }
Obtiene todas las órdenes con paginación y filtros opcionales por estado, cliente y tienda. Los resultados se filtran automáticamente por la compañía del usuario autenticado. El parámetro size debe estar entre 1 y 100. El parámetro status acepta únicamente los valores indicados — si se envía un valor inválido se retorna 400.
| page | integer <int32> >= 0 Default: 0 Número de página (0-based) |
| size | integer <int32> [ 1 .. 100 ] Default: 20 Example: size=20 Tamaño de la página (entre 1 y 100) |
| status | string Enum: "UNCONFIRMED" "GENERATED" "ACCEPTED" "READY" "DELIVERED" "CANCELED" Example: status=ACCEPTED Filtrar por estado de la orden. Case-insensitive. |
| customerId | string Example: customerId=12345 ID del cliente para filtrar |
| store_id | integer <int64> Example: store_id=254 ID de la tienda |
Respuesta exitosa
{ "success": true, "data": [ { "order_detail": { "order_id": "15672", "code": "VZS3E", "status": "ACCEPTED", "delivery_method": "TAKEAWAY", "cooking_time": 25, "created_at": "2024-01-15T10:30:00.000Z", "payment_method": "EF", "items": [ { "sku": "425", "id": "425", "external_id": "19605", "name": "Provoleta de la casa a la leña", "price": 17650, "quantity": 1, "subitems": [] } ], "totals": { "total_products": 17650, "total_discounts": 0, "total_order": 17650, "charges": {} }, "discounts": [], "delivery_discount": { "total_percentage_discount": 0, "total_value_discount": 0 } }, "customer": { "first_name": "Juan", "phone_number": "+5491112345678", "email": "juan@example.com", "document_number": "" }, "store": { "internal_id": "254", "external_id": "sucursal-centro", "name": "Sucursal Centro" } } ], "message": "Orders retrieved successfully", "code": "OK", "pagination": { "page": 0, "size": 20, "total_elements": 150, "total_pages": 8, "has_next": true, "has_previous": false } }
Crea una nueva orden en el sistema. El body se procesa sin transformaciones y la respuesta se devuelve tal cual (mismo status code y body). Para órdenes DELIVERY, destination es obligatorio (con lat y lng requeridos), al igual que customerName y phone. lastName es opcional. Los campos name y price de ítems y extras son opcionales: si no se envían, se toman del catálogo según referenceId y sizeId.
| type required | string Enum: "TAKEAWAY" "DELIVERY" "IN_DINING" Tipo de orden |
| paymentType required | string Enum: "CASH" "CARD" "NOT_SPECIFIED" Método de pago |
required | object (CreateOrderBranchContactDTO) Local donde se crea la orden |
| price | number <double> Precio total de la orden. Si no se envía, se calcula sumando price * quantity de cada ítem. |
required | Array of objects (CreateOrderItemDTO) Lista de ítems de la orden |
| customerName | string Nombre del cliente. Obligatorio para órdenes DELIVERY. |
| lastName | string Apellido del cliente. |
| phone | string Teléfono del cliente. Obligatorio para órdenes DELIVERY. |
| customerEmail | string Email del cliente |
| notes | string Aclaraciones generales de la orden |
| partnerName | string Nombre del partner que origina la orden |
| itemsPrice | number <double> Precio total de ítems sin cargos adicionales |
object (CreateOrderDestinationDTO) Dirección de entrega. Solo requerido para órdenes DELIVERY, ignorado en otros tipos. |
Mínimo requerido
{- "type": "TAKEAWAY",
- "paymentType": "CASH",
- "branchContact": {
- "id": 254
}, - "price": 17650,
- "items": [
- {
- "referenceId": 19605,
- "sizeId": 20199,
- "quantity": 1
}
]
}Orden creada
{ "id": "15672", "code": "TVSKS", "type": "TAKEAWAY", "price": 4000, "paymentType": "CASH", "source": "API", "status": "GENERATED", "customerName": "Juan", "phone": "+5491112345678", "notes": "sin sal", "items": [ { "quantity": 1, "name": "Empanada carne", "price": 1000, "referenceId": 19605, "extras": [] }, { "quantity": 1, "name": "Triple empanada", "price": 3000, "notes": "bien cocida", "referenceId": 19606, "extras": [ { "groupId": "6202", "groupName": "gustos", "groupItems": [ { "quantity": 1, "name": "queso", "price": 0, "referenceId": 5475, "extras": [] } ] } ] } ] }
Obtiene una orden específica por su código alfanumérico de 5 caracteres (ej: VZS3E). Este código es el campo code devuelto al crear la orden. Es distinto del order_id numérico que aparece en el body de respuesta. Solo devuelve órdenes que pertenezcan al branch del usuario autenticado.
| orderId required | string^[A-Z0-9]{5}$ Example: VZS3E Código alfanumérico de la orden (5 caracteres). Es el campo |
Orden encontrada
{ "success": true, "data": { "order_detail": { "order_id": "15672", "code": "VZS3E", "status": "ACCEPTED", "delivery_method": "TAKEAWAY", "cooking_time": 25, "created_at": "2024-01-15T10:30:00.000Z", "payment_method": "EF", "items": [ { "sku": "425", "id": "425", "external_id": "19605", "name": "Provoleta de la casa a la leña", "price": 17650, "quantity": 1, "subitems": [] } ], "totals": { "total_products": 17650, "total_discounts": 0, "total_order": 17650, "charges": {} }, "discounts": [], "delivery_discount": { "total_percentage_discount": 0, "total_value_discount": 0 } }, "customer": { "first_name": "Juan", "phone_number": "+5491112345678", "email": "juan@example.com", "document_number": "" }, "store": { "internal_id": "254", "external_id": "sucursal-centro", "name": "Sucursal Centro" } }, "message": "Order retrieved successfully", "code": "OK" }
Actualiza el estado de una orden específica. Solo se permiten transiciones de estado válidas según el workflow configurado.
| orderId required | string^[A-Z0-9]{5}$ Example: VZS3E Código alfanumérico de la orden (5 caracteres). Es el campo |
| storeId required | string Example: storeId=254 ID de la tienda |
| state required | string Enum: "GENERATED" "ACCEPTED" "READY" "DELIVERED" "CANCELED" Example: state=ACCEPTED Nuevo estado de la orden |
| Authorization required | string |
{ "success": false, "data": null, "message": "Invalid state. Valid states are: GENERATED, ACCEPTED, READY, DELIVERED, CANCELED", "code": "INVALID_STATE" }
Obtiene los locales/tiendas de una marca específica con soporte para filtros, búsqueda y paginación. Cada local expone dos niveles de disponibilidad: 'available_for_order' indica si el local está abierto y aceptando pedidos en este momento (considera horarios, configuración de delivery y estado general); 'order_modes[].order_enabled' indica si esa modalidad específica (Delivery, Takeaway, Salón, etc.) está configurada para recibir pedidos — un local puede estar abierto pero tener ciertas modalidades deshabilitadas. Solo se retornan las modalidades visibles (active=true).
| id required | integer <int64> Example: 78 ID único de la marca |
| limit | integer <int32> Default: 20 Example: limit=20 Límite de resultados por página (1-200) |
| offset | integer <int32> Default: 0 Desplazamiento para paginación (≥0) |
| sort | string Example: sort=name Campo de ordenamiento. Usar '-' para descendente (ej: -name) |
| q | string Example: q=tienda Término de búsqueda en name o alias (case-insensitive) |
Locales encontrados
{ "success": true, "data": [ { "id": 254, "name": "Sucursal Centro", "alias": "sucursalcentro", "available_for_order": true, "order_modes": [ { "id": 3, "show_name": "Delivery", "order_enabled": true, "menu_id": 492 }, { "id": 7, "show_name": "Takeaway", "order_enabled": false, "menu_id": 493 } ] }, { "id": 255, "name": "Sucursal Norte", "alias": "sucursalnorte", "available_for_order": false, "order_modes": [] } ], "message": "Branch contacts retrieved successfully", "code": "OK", "pagination": { "page": 0, "size": 20, "total_elements": 2, "total_pages": 1, "has_next": false, "has_previous": false } }
Tucan envía un POST a la URL que hayas configurado como suscriptor cada vez que un pedido cambia de estado.
order_detail.status)| Estado | Descripción |
|---|---|
GENERATED |
Pedido recibido, pendiente de confirmación |
ACCEPTED |
Pedido aceptado por el local |
READY |
Pedido listo para retirar / despachar |
DELIVERED |
Pedido entregado al cliente |
CANCELED |
Pedido cancelado |
POSTX-Webhook-Secret: mi-secreto)Tu servidor debe responder con HTTP 2xx. Si no responde o responde con error, Tucan reintentará el envío.
Payload del evento enviado por Tucan a tu servidor
Pedido ACCEPTED (con descuento)
{- "order_detail": {
- "order_id": 4044528,
- "order_code": "TEN0Z",
- "status": "ACCEPTED",
- "delivery_operation_type": "DELIVERY",
- "cooking_time": 37,
- "min_cooking_time": 30,
- "max_cooking_time": 45,
- "created_at": "2026-03-10T17:24:49Z",
- "delivery_method": "DELIVERY",
- "payment_method": "MERCADO_PAGO",
- "items": [
- {
- "sku": "568269",
- "id": 10743659,
- "internal_id": "568269",
- "external_id": "PLATE",
- "name": "Sándwich Lomito de Praga 2.0",
- "price": 13500,
- "quantity": 1,
- "subitems": [ ],
- "discounts": [ ]
}, - {
- "sku": "553400",
- "id": 10743660,
- "internal_id": "553400",
- "external_id": "PLATE",
- "name": "Agua de Limón, Manzana, Menta y Cedrón",
- "price": 3500,
- "quantity": 1,
- "subitems": [ ],
- "discounts": [ ]
}
], - "totals": {
- "total_products": 17000,
- "total_order": 13600,
- "total_to_pay": 13600,
- "charges": 0
}, - "discounts": [
- {
- "description": "Cupón de Descuento - GEN1-1RA",
- "amount": 3400
}
]
}, - "customer": {
- "customer_id": 100001,
- "external_customer_id": "DEMO001XYZ",
- "first_name": "María",
- "last_name": "González",
- "phone_number": "5491155550001",
- "email": "maria.gonzalez@example.com"
}, - "store": {
- "internal_id": "254",
- "external_id": "sucursalcentro",
- "name": "Sucursal Centro"
}
}Obtiene las estimaciones de costo y tiempo de los proveedores de delivery junto con el tiempo de espera configurado para el local.
| Authorization required | string Bearer token de autenticación |
Datos de la solicitud de estimación
| vehicleType | string Tipo de vehículo: CAR o BIKE |
| storeId | integer <int32> ID del local |
object (DropOffDTO) Datos del punto de entrega | |
| isCash | boolean Indica si el pago es en efectivo |
| cashAmount | number <double> Monto en efectivo (requerido si isCash es true) |
| roundTrip | boolean Indica si el envío es de ida y vuelta |
object (PackageDTO) Información del paquete a enviar | |
| longDistance | boolean Indica si es un envío de larga distancia |
Estimación con BIKE
{- "vehicleType": "BIKE",
- "storeId": 1,
- "dropOff": {
- "customer": {
- "phone": "+542911111111",
- "email": "mail@mail.com",
- "name": "password"
}, - "address": {
- "fullAddress": "Gral. José Gervasio Artigas 700, C1406ABL Buenos Aires",
- "street": "General José Gervasio Artigas",
- "number": 7777,
- "city": "Buenos Aires",
- "country": "Argentina",
- "floor": 7,
- "department": "a",
- "details": "reja negra",
- "coordinates": {
- "lat": -34.62221596491731,
- "lng": -58.467245671163965
}
}
}, - "isCash": false
}Estimación exitosa
{ "success": true, "data": { "estimations": [ { "success": true, "provider": "CABIFY", "price": 5.59, "currency": "ARS" }, { "success": true, "provider": "RAPPI", "price": 6615, "currency": "ARS" }, { "success": false, "provider": "OWN_FLEET", "error": "Could not get rate for provider: OWN_FLEET" } ], "minDelayInMinutes": 20, "maxDelayInMinutes": 40 }, "message": "Estimations retrieved successfully", "code": "OK" }
Recibe la notificación de un partner externo sobre el resultado de un pago y actualiza el estado de la orden.
Flujo:
from en el bodyCampos del body:
from — identificador del medio de pago (ej: "MORFY"). Requerido para routing correcto.type — tipo de entidad. Opcional; ORDER es el valor por defecto para la mayoría de los providers. Usar CURRENT_ACCOUNT, CAMPAIGN u otros según el caso.orderId, status para MORFY)Estados soportados (campo status, varía por provider):
PAID → confirma el pago, la orden pasa de UNCONFIRMED a GENERATED (o ACCEPTED)REFUNDED → registra el reembolso (solo si la orden ya estaba pagada)CANCELLED → registra la cancelación (solo si la orden ya estaba pagada)PROCESSING → informativo, no genera cambio de estadoBackwards compat: from y type también se pueden enviar como query params.
Pago confirmado
{- "from": "MORFY",
- "orderId": "12345",
- "status": "PAID"
}