{"openapi":"3.1.0","info":{"title":"EnruTek API","version":"1.0.0","summary":"Creá y rastreá guías en EnruTek desde cualquier sistema.","description":"> Los nombres del API (rutas, campos, parámetros, estados y códigos de\n> error) van en inglés; las explicaciones, en español.\n\nEnruTek es una plataforma de logística para operadores logísticos (en el\nAPI, *operator*: la empresa que usa EnruTek). Este API le permite a\ncualquier sistema externo (un ERP como SAP, una tienda en línea, un sistema\nde bodega) crear guías, darles seguimiento y recibir sus cambios de estado.\n\n## Autenticación\nCada solicitud lleva una llave (API key) en la cabecera `Authorization`:\n\n    Authorization: Bearer ek_live_3Fh8...\n\nLas llaves se crean en EnruTek y se muestran una sola vez. Hay dos tipos:\n\n- **Llave de cliente**: pertenece a un cliente del operador. Solo puede\n  crear y leer las guías de ese cliente. Nunca se manda `customer_id`.\n- **Llave de operador**: pertenece al operador. Puede trabajar para\n  cualquiera de los clientes del operador; al crear, `customer_id` es\n  obligatorio.\n\n## Sandbox\nPara probar sin tocar la operación real hay una empresa de pruebas,\nSandbox: misma dirección, mismo contrato y mismos límites; solo cambia la\nllave, que se pide al operador o a Alfa Tek. Sus guías nacen con el\nprefijo `SB-`, nadie las recolecta ni las entrega, y para probar los\nwebhooks está `POST /webhooks/{id}/test`.\n\n## Convenciones\n- JSON de entrada y de salida, en UTF-8. Nombres de campos en `snake_case`.\n- Fechas y horas en ISO 8601 con desfase (`2026-10-24T14:05:00-06:00`).\n- Teléfonos en E.164 (`+50688887777`).\n- Montos como `{ \"amount\": \"3000.00\", \"currency\": \"CRC\" }` (ISO 4217; el\n  monto es un texto decimal para evitar redondeos).\n- Las direcciones no dependen del país: `country` (ISO 3166-1 alfa-2) y\n  hasta tres niveles administrativos. En Costa Rica: `admin_area_1` =\n  provincia, `admin_area_2` = cantón, `admin_area_3` = distrito.\n- El precio siempre lo calcula EnruTek con la tarifa del cliente; no se\n  puede fijar por el API.\n\n## Idempotencia\nMandá una cabecera `Idempotency-Key` (cualquier texto único de hasta 100\ncaracteres) en las solicitudes `POST`. Si reintentás con la misma llave,\nrecibís el resultado original en vez de crear una segunda guía. Las llaves\nse guardan 24 horas. Además, `external_reference` es única por cliente\nentre las guías activas: una segunda guía con la misma referencia se\nrechaza con `duplicate_reference` y el número de guía que ya existe.\n\n## Errores\nLos errores traen un `code` estable, para máquinas (en inglés), y un\n`message` para personas. El mensaje viene en español por defecto; mandá\n`Accept-Language: en` para recibirlo en inglés.\n\n## Límites de uso\nCada llave tiene un máximo de solicitudes por minuto (rate limit). Cada\nrespuesta trae `RateLimit-Limit`, `RateLimit-Remaining` y\n`RateLimit-Reset`. Si se pasa del límite, el API responde `429` con\n`Retry-After`.\n\n## Uso responsable\nEl API comparte la base con la operación. Crear guías no tiene horario\n(varias juntas, en `/shipments/batch`). Para los cambios de estado usá\nwebhooks; si no podés recibirlos, consultá a lo sumo una vez por hora con\nuna sola llamada a `GET /shipments?updated_since=…`. No consultés guía\npor guía en un ciclo ni cada pocos segundos o minutos. El límite por\nllave existe para lotes y reintentos, no para consultar todo el tiempo:\nun uso que afecte la operación puede llevar a que se baje el límite de la\nllave o se revoque, con aviso previo salvo que el daño sea inmediato.\n\n## Webhooks\nEnruTek puede avisarle a tu sistema cuando una guía cambia de estado (por\nwebhook). Cada envío es un `POST` con un `WebhookEvent` en JSON y la\ncabecera `EnruTek-Signature: t=<segundos unix>,v1=<HMAC-SHA256 en hex>`,\ncalculada sobre `<t>.<cuerpo tal cual>` con el secreto de la suscripción.\nRechazá los eventos de hace más de 5 minutos. Respondé cualquier `2xx` en\nmenos de 10 segundos; si no, EnruTek reintenta con espera exponencial\ndurante hasta 24 horas. Un evento puede llegar más de una vez y en\ndesorden: usá `event.id` para descartar repetidos y `occurred_at` para\nordenarlos.\n"},"servers":[{"url":"https://api.enrutek.com/v1"}],"security":[{"apiKey":[]}],"tags":[{"name":"Shipments","x-displayName":"Guías","description":"Crear, consultar y cancelar guías."},{"name":"Catalog","x-displayName":"Catálogo","description":"Tipos de carga, sucursales y división territorial que puede usar una guía."},{"name":"Webhooks","x-displayName":"Webhooks","description":"Suscripciones para recibir los cambios de estado de las guías."}],"paths":{"/shipments":{"post":{"tags":["Shipments"],"operationId":"createShipment","summary":"Crear una guía","description":"Crea una guía. Nace en `created` (un chofer la recoge en la sucursal de\norigen). La respuesta trae el número de guía, el precio calculado con\nla tarifa y el enlace a la etiqueta 100×100.\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/AcceptLanguage"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShipmentCreate"},"example":{"external_reference":"379671","recipient":{"name":"Farmacia Plaza Flamingo","phone":"+50626545524"},"destination":{"country":"CR","admin_area_1":"Guanacaste","admin_area_2":"Santa Cruz","admin_area_3":"Tempate","postal_code":"50304","address_line":"Playa Potrero, 200 metros sur del Hotel Bahía Esmeralda"},"notes":"Entregar en horario de oficina","packages":[{"cargo_type":"seco","quantity":1}]}}}},"responses":{"200":{"description":"Misma `Idempotency-Key` que una solicitud anterior; se devuelve la guía original.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Shipment"}}}},"201":{"description":"Creada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Shipment"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/Unprocessable"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"get":{"tags":["Shipments"],"operationId":"listShipments","summary":"Listar guías","description":"Las más recientes primero, paginadas por cursor. Para sincronizar, usá\n`updated_since` con el `updated_at` de la última guía que viste.\nSin webhooks, sincronizá a lo sumo una vez por hora (ver \"Uso\nresponsable\").\n","parameters":[{"$ref":"#/components/parameters/AcceptLanguage"},{"name":"status","in":"query","description":"Uno o varios estados, separados por coma.","schema":{"type":"string","example":"out_for_delivery,delivered"}},{"name":"external_reference","in":"query","description":"Tu referencia de la guía (`external_reference`).","schema":{"type":"string","maxLength":60}},{"name":"customer_id","in":"query","description":"Solo con llaves de operador.","schema":{"type":"string","format":"uuid"}},{"name":"created_since","in":"query","description":"Solo guías creadas desde esta fecha y hora.","schema":{"type":"string","format":"date-time"}},{"name":"updated_since","in":"query","description":"Solo guías que cambiaron desde esta fecha y hora.","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","description":"Cantidad de guías por página.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","description":"El `next_cursor` de la página anterior.","schema":{"type":"string"}}],"responses":{"200":{"description":"Una página de guías (sin `events`; pedí la guía individual para ver su historial).","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Shipment"}},"next_cursor":{"type":["string","null"],"description":"Null cuando ya no hay más páginas."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/shipments/batch":{"post":{"tags":["Shipments"],"operationId":"createShipmentsBatch","summary":"Crear hasta 500 guías","description":"Cada elemento se procesa por aparte: uno inválido no detiene a los\ndemás. La respuesta trae un resultado por elemento, en el mismo orden.\nUsá una `Idempotency-Key` para todo el lote; al reintentar recibís los\nmismos resultados.\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/AcceptLanguage"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["shipments"],"properties":{"shipments":{"type":"array","minItems":1,"maxItems":500,"items":{"$ref":"#/components/schemas/ShipmentCreate"}}}}}}},"responses":{"200":{"description":"Un resultado por elemento.","content":{"application/json":{"schema":{"type":"object","required":["created","failed","results"],"properties":{"created":{"type":"integer"},"failed":{"type":"integer"},"results":{"type":"array","items":{"type":"object","required":["index","ok"],"properties":{"index":{"type":"integer","description":"Posición en la solicitud, desde 0."},"ok":{"type":"boolean"},"shipment":{"$ref":"#/components/schemas/Shipment"},"error":{"$ref":"#/components/schemas/ErrorBody"}}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"413":{"description":"Más de 500 elementos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/shipments/{tracking_number}":{"get":{"tags":["Shipments"],"operationId":"getShipment","summary":"Consultar una guía con su historial","parameters":[{"$ref":"#/components/parameters/TrackingNumber"},{"$ref":"#/components/parameters/AcceptLanguage"}],"responses":{"200":{"description":"La guía, con `events` y, una vez entregada, `proof_of_delivery` (prueba de entrega).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Shipment"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/shipments/{tracking_number}/cancel":{"post":{"tags":["Shipments"],"operationId":"cancelShipment","summary":"Cancelar una guía","description":"Solo mientras esté en `created` (nadie la ha recogido).","parameters":[{"$ref":"#/components/parameters/TrackingNumber"},{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/AcceptLanguage"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","maxLength":500,"description":"Motivo de la cancelación."}}}}}},"responses":{"200":{"description":"Cancelada (o ya estaba cancelada).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Shipment"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/shipments/{tracking_number}/label":{"get":{"tags":["Shipments"],"operationId":"getShipmentLabel","summary":"Obtener la etiqueta 100×100","description":"Una página por bulto. Por ahora en PDF; más adelante, ZPL para\nimpresoras térmicas. Todavía no está disponible: responde\n`501 not_implemented` (después de revisar que la llave puede ver la\nguía). Mientras tanto, imprimí las etiquetas desde EnruTek.\n","parameters":[{"$ref":"#/components/parameters/TrackingNumber"},{"name":"format","in":"query","description":"Formato de la etiqueta.","schema":{"type":"string","enum":["pdf"],"default":"pdf"}}],"responses":{"200":{"description":"La etiqueta.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"501":{"description":"Todavía no está disponible (`not_implemented`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/catalog":{"get":{"tags":["Catalog"],"operationId":"getCatalog","summary":"Lo que puede usar una guía","description":"Los tipos de carga con el precio del cliente y las sucursales del\ncliente (orígenes). Con llaves de operador, `customer_id` es\nobligatorio.\n","parameters":[{"name":"customer_id","in":"query","description":"Solo con llaves de operador (y ahí es obligatorio).","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/AcceptLanguage"}],"responses":{"200":{"description":"El catálogo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Catalog"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/catalog/admin-areas":{"get":{"tags":["Catalog"],"operationId":"listAdminAreas","summary":"División territorial de un país","description":"Los valores válidos de `admin_area_1..3`, en forma de árbol.","parameters":[{"name":"country","in":"query","required":true,"description":"País, en ISO 3166-1 alfa-2.","schema":{"type":"string","example":"CR"}}],"responses":{"200":{"description":"El árbol.","content":{"application/json":{"schema":{"type":"object","required":["country","levels","areas"],"properties":{"country":{"type":"string"},"levels":{"type":"array","description":"Nombre local de cada nivel.","items":{"type":"string"},"example":["provincia","cantón","distrito"]},"areas":{"type":"array","items":{"$ref":"#/components/schemas/AdminArea"}}}}}}}}}},"/webhooks":{"get":{"tags":["Webhooks"],"operationId":"listWebhooks","summary":"Listar las suscripciones de webhook de esta llave","responses":{"200":{"description":"Las suscripciones (sin el secreto).","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscription"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["Webhooks"],"operationId":"createWebhook","summary":"Suscribir una URL a los eventos de las guías","description":"El `secret` se devuelve solo en esta respuesta.\n\n**Alcance.** Una llave de cliente recibe solo los eventos de sus propias\nguías. Una llave de operador puede indicar `customer_id` para recibir\nsolo los eventos de ese cliente; sin él, recibe los eventos de todas\nlas guías del operador. `customer_id` tiene que ser un cliente del\noperador de la llave (si no, `422 customer_not_found`), y si una llave\nde cliente lo manda se rechaza (`403 customer_not_allowed`): nunca\namplía lo que una llave puede ver. El alcance no se puede cambiar\ndespués.\n\n**Reglas de la URL** (se revisan al crear y otra vez en cada envío):\nsolo `https`, sin usuario ni contraseña en la URL, un nombre de host\n(no una IP literal) que resuelva solo a direcciones públicas (se\nrechazan loopback, privadas RFC 1918, link-local 169.254.0.0/16, CGNAT\n100.64.0.0/10, loopback/ULA/link-local de IPv6 y las direcciones de\nmetadatos de la nube). No se siguen redirecciones. Una URL que no\ncumple estas reglas se rechaza con `invalid_webhook_url`.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","pattern":"^https://","maxLength":2048,"description":"Ver las reglas de la URL arriba."},"customer_id":{"type":"string","format":"uuid","description":"Solo con llaves de operador: limita la suscripción a un cliente."},"events":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/WebhookEventType"}},"description":{"type":"string","maxLength":200}}}}}},"responses":{"201":{"description":"Creada.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookSubscription"},{"type":"object","required":["secret"],"properties":{"secret":{"type":"string","description":"whsec_…, se muestra una sola vez."}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/Unprocessable"}}}},"/webhooks/{id}":{"delete":{"tags":["Webhooks"],"operationId":"deleteWebhook","summary":"Detener una suscripción","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Detenida."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/{id}/test":{"post":{"tags":["Webhooks"],"operationId":"testWebhook","summary":"Enviar un evento de prueba","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"En cola."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}}},"webhooks":{"shipmentEvent":{"post":{"summary":"Una guía cambió","description":"Lo que EnruTek le manda a la URL suscrita. Ver la sección Webhooks arriba para la firma y los reintentos.","parameters":[{"name":"EnruTek-Signature","in":"header","required":true,"description":"Firma HMAC-SHA256 del evento (`t=<segundos unix>,v1=<hex>`).","schema":{"type":"string","example":"t=1761336300,v1=5f2b…"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"2XX":{"description":"Recibido. Cualquier otra respuesta se reintenta."}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"ek_live_…","description":"Llave (API key) de cliente o de operador, en `Authorization: Bearer <llave>`."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","description":"Texto único por operación; un reintento con la misma llave devuelve el resultado original (24 horas).","schema":{"type":"string","maxLength":100}},"AcceptLanguage":{"name":"Accept-Language","in":"header","description":"Idioma de los mensajes de error y de los nombres de estado. `es` (por defecto) o `en`.","schema":{"type":"string","enum":["es","en"],"default":"es"}},"TrackingNumber":{"name":"tracking_number","in":"path","required":true,"description":"El número de guía de EnruTek, p. ej. `TF-261024ZXWPH2S`.","schema":{"type":"string","pattern":"^[A-Z]{2,4}-[0-9]{6}[0-9A-HJKMNP-TV-Z]{7}$"}}},"schemas":{"ShipmentCreate":{"type":"object","required":["recipient","destination","packages"],"additionalProperties":false,"properties":{"customer_id":{"type":"string","format":"uuid","description":"Obligatorio con llaves de operador; no se permite con llaves de cliente."},"origin_branch_id":{"type":"string","format":"uuid","description":"Sucursal del cliente donde se recoge. Por defecto, la sucursal principal."},"external_reference":{"type":"string","maxLength":60,"description":"Tu identificador de esta guía (factura, número de entrega). Única por cliente entre las guías activas. Si el cliente escanea sus propias etiquetas, es el código de barras."},"recipient":{"$ref":"#/components/schemas/Recipient"},"destination":{"$ref":"#/components/schemas/Address"},"notes":{"type":"string","maxLength":500,"description":"Indicaciones para el chofer (horario, por quién preguntar). El chofer las ve."},"packages":{"type":"array","minItems":1,"maxItems":20,"description":"Los bultos, agrupados por tipo de carga.","items":{"type":"object","required":["cargo_type","quantity"],"additionalProperties":false,"properties":{"cargo_type":{"type":"string","description":"Un `code` de `/catalog` (p. ej. `seco`, `frio`)."},"quantity":{"type":"integer","minimum":1,"maximum":999,"description":"Cantidad de bultos de este tipo de carga."}}}}}},"Recipient":{"type":"object","required":["name"],"additionalProperties":false,"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Nombre del destinatario."},"phone":{"type":"string","maxLength":20,"description":"E.164."},"email":{"type":"string","format":"email","maxLength":200,"description":"Para avisarle de la entrega al destinatario."}}},"Address":{"type":"object","required":["country","address_line"],"additionalProperties":false,"properties":{"country":{"type":"string","minLength":2,"maxLength":2,"example":"CR","description":"ISO 3166-1 alfa-2. Por ahora solo CR."},"admin_area_1":{"type":"string","maxLength":100,"description":"CR: provincia."},"admin_area_2":{"type":"string","maxLength":100,"description":"CR: cantón."},"admin_area_3":{"type":"string","maxLength":100,"description":"CR: distrito."},"postal_code":{"type":"string","maxLength":12,"description":"CR: 5 dígitos; si viene y es válido, llena los niveles administrativos."},"address_line":{"type":"string","minLength":1,"maxLength":500,"description":"Otras señas."},"lat":{"type":"number","minimum":-90,"maximum":90,"description":"Latitud del punto exacto (opcional)."},"lng":{"type":"number","minimum":-180,"maximum":180,"description":"Longitud del punto exacto (opcional)."}},"description":"Los niveles administrativos se comparan sin tildes ni mayúsculas\ncontra la lista oficial (`/catalog/admin-areas`). En Costa Rica,\n`admin_area_1` y `admin_area_2` son obligatorios salvo que\n`postal_code` los resuelva; `admin_area_3` es opcional.\n"},"Shipment":{"type":"object","required":["id","tracking_number","status","status_label","customer","origin","recipient","destination","packages","total","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid"},"tracking_number":{"type":"string","example":"TF-261024ZXWPH2S"},"tracking_url":{"type":"string","format":"uri","description":"Página pública de rastreo."},"label_url":{"type":"string","format":"uri","description":"`/shipments/{tracking_number}/label`."},"status":{"$ref":"#/components/schemas/ShipmentStatus"},"status_label":{"type":"string","description":"Nombre del estado para personas, en el idioma pedido; p. ej. \"En CEDI\"."},"external_reference":{"type":["string","null"]},"customer":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"origin":{"type":"object","properties":{"branch_id":{"type":["string","null"],"format":"uuid"},"name":{"type":"string"},"address":{"$ref":"#/components/schemas/Address"}}},"recipient":{"$ref":"#/components/schemas/Recipient"},"destination":{"$ref":"#/components/schemas/Address"},"notes":{"type":["string","null"]},"packages":{"type":"array","items":{"type":"object","required":["cargo_type","quantity","unit_price"],"properties":{"cargo_type":{"type":"string"},"cargo_type_name":{"type":"string"},"quantity":{"type":"integer"},"unit_price":{"$ref":"#/components/schemas/Money"}}}},"total":{"allOf":[{"$ref":"#/components/schemas/Money"}],"description":"Total con IVA, tal como se va a facturar."},"scheduled":{"type":["object","null"],"description":"El despacho al que está asignada, si lo hay.","properties":{"date":{"type":"string","format":"date"},"route_code":{"type":"string"}}},"proof_of_delivery":{"type":["object","null"],"description":"Prueba de entrega; solo cuando está `delivered`. Las URL van firmadas y vencen en 1 hora; volvé a pedir la guía para tener URL nuevas.","properties":{"delivered_at":{"type":"string","format":"date-time"},"received_by":{"type":"string"},"photo_url":{"type":["string","null"],"format":"uri"},"signature_url":{"type":["string","null"],"format":"uri"},"lat":{"type":["number","null"]},"lng":{"type":["number","null"]}}},"events":{"type":"array","description":"Historial, de lo más viejo a lo más nuevo. Solo en `GET /shipments/{tracking_number}` y en los webhooks.","items":{"$ref":"#/components/schemas/ShipmentEvent"}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ShipmentStatus":{"type":"string","description":"Dónde está físicamente el bulto. Finales: `delivered`, `rejected`, `cancelled`.\n","enum":["created","at_reception","picked_up","received_at_cedi","loaded","out_for_delivery","kept_on_route","back_to_cedi","delivered","rejected","cancelled"]},"ShipmentEvent":{"type":"object","required":["type","occurred_at"],"properties":{"type":{"type":"string","enum":["created","edited","picked_up","at_reception","received_at_cedi","assigned","unassigned","loaded","out_for_delivery","kept_on_route","back_to_cedi","delivered","rejected","cancelled"]},"label":{"type":"string"},"occurred_at":{"type":"string","format":"date-time"},"facility":{"type":["string","null"],"description":"Nombre del CEDI (centro de distribución), p. ej. \"GAM\"."},"comment":{"type":["string","null"]}}},"Money":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"string","pattern":"^-?[0-9]+\\.[0-9]{2}$","example":"3000.00"},"currency":{"type":"string","example":"CRC"}}},"Catalog":{"type":"object","required":["cargo_types","branches"],"properties":{"cargo_types":{"type":"array","items":{"type":"object","required":["code","name","cold_chain","unit_price"],"properties":{"code":{"type":"string"},"name":{"type":"string"},"cold_chain":{"type":"boolean"},"unit_price":{"$ref":"#/components/schemas/Money"}}}},"branches":{"type":"array","items":{"type":"object","required":["id","name","is_default"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"is_default":{"type":"boolean"},"address":{"$ref":"#/components/schemas/Address"}}}}}},"AdminArea":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"code":{"type":"string","description":"Código oficial cuando lo hay (CR: inicio del código postal)."},"children":{"type":"array","items":{"$ref":"#/components/schemas/AdminArea"}}}},"WebhookEventType":{"type":"string","enum":["shipment.created","shipment.picked_up","shipment.received_at_cedi","shipment.assigned","shipment.out_for_delivery","shipment.kept_on_route","shipment.back_to_cedi","shipment.delivered","shipment.rejected","shipment.cancelled"]},"WebhookSubscription":{"type":"object","required":["id","url","events","active","created_at","customer_id"],"properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"customer_id":{"type":["string","null"],"format":"uuid","description":"El cliente cuyos eventos recibe; null = todos los clientes del operador (solo llaves de operador). Solo lectura."},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventType"}},"description":{"type":["string","null"]},"active":{"type":"boolean","description":"Se apaga sola después de 24 horas de envíos fallidos."},"created_at":{"type":"string","format":"date-time"}}},"WebhookEvent":{"type":"object","required":["id","type","occurred_at","shipment"],"properties":{"id":{"type":"string","format":"uuid","description":"Único por evento; usalo para descartar repetidos."},"type":{"$ref":"#/components/schemas/WebhookEventType"},"occurred_at":{"type":"string","format":"date-time"},"shipment":{"allOf":[{"$ref":"#/components/schemas/Shipment"},{"type":"object","properties":{"proof_of_delivery":{"$ref":"#/components/schemas/ProofOfDeliveryWithoutLinks"}}}],"description":"La guía como estaba en el momento del evento. `proof_of_delivery` viene sin `photo_url` ni `signature_url` (los enlaces de corta vida no deben quedar en bitácoras durante los reintentos): pedí la guía para obtenerlos."}}},"ProofOfDeliveryWithoutLinks":{"type":["object","null"],"additionalProperties":false,"properties":{"delivered_at":{"type":"string","format":"date-time"},"received_by":{"type":"string"},"lat":{"type":["number","null"]},"lng":{"type":["number","null"]}}},"ErrorBody":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Estable, para máquinas.","enum":["invalid_request","invalid_field","invalid_address","unknown_cargo_type","no_rate_for_cargo_type","customer_required","customer_not_allowed","branch_not_found","customer_not_found","duplicate_reference","not_found","invalid_status","unauthorized","forbidden","module_disabled","idempotency_conflict","batch_too_large","invalid_webhook_url","rate_limited","internal_error","not_implemented"]},"message":{"type":"string","description":"Mensaje para personas, en el idioma de `Accept-Language`."},"field":{"type":["string","null"],"description":"Ruta del campo con el problema, p. ej. \"destination.admin_area_2\"."},"details":{"type":"object","description":"Datos adicionales, p. ej. `{ \"tracking_number\": \"TF-…\" }` en `duplicate_reference`."},"request_id":{"type":"string","description":"Indicalo al pedir soporte."}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}}}},"responses":{"BadRequest":{"description":"El cuerpo o un parámetro no es válido (`invalid_request`, `invalid_field`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Falta la llave, no existe o fue revocada (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"La llave no puede hacer esto (`forbidden`, `customer_not_allowed`, `module_disabled`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"No existe, o esta llave no lo puede ver (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"`duplicate_reference`, `invalid_status` o `idempotency_conflict` (misma llave, cuerpo distinto).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unprocessable":{"description":"JSON válido que EnruTek no puede usar (`invalid_address`, `unknown_cargo_type`, `no_rate_for_cargo_type`, `branch_not_found`, `invalid_webhook_url`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooManyRequests":{"description":"Se pasó del límite de uso (`rate_limited`). Ver `Retry-After`.","headers":{"Retry-After":{"description":"Segundos que hay que esperar antes de reintentar.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}