EnruTek API v1

EnruTek API 1.0.0

Creá y rastreá guías en EnruTek desde cualquier sistema.

Dirección base: https://api.enrutek.com/v1. Contrato: https://api.enrutek.com/openapi.json y https://api.enrutek.com/openapi.yaml.

Empezá en 5 pasos

Todo va a https://api.enrutek.com/v1, con JSON y con tu llave en la cabecera Authorization.

1. Pedí tu llave. La crea tu operador logístico en EnruTek y se muestra una sola vez. Guardala en el servidor de tu sistema, nunca en una página web, una app ni un repositorio.

2. Mirá qué podés usar. /catalog devuelve tus tipos de carga con su precio y tus sucursales. El code de cada tipo de carga es lo que va en cargo_type.

curl https://api.enrutek.com/v1/catalog \
  -H "Authorization: Bearer $ENRUTEK_KEY"

3. Creá una guía. Responde 201 con el número de guía y el total calculado con tu tarifa.

curl -X POST https://api.enrutek.com/v1/shipments \
  -H "Authorization: Bearer $ENRUTEK_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: factura-379671" \
  -d '{
    "external_reference": "379671",
    "recipient": { "name": "Farmacia Plaza Flamingo", "phone": "+50626545524" },
    "destination": {
      "country": "CR",
      "admin_area_1": "Guanacaste",
      "admin_area_2": "Santa Cruz",
      "address_line": "Playa Potrero, 200 metros sur del Hotel Bahía Esmeralda"
    },
    "packages": [{ "cargo_type": "seco", "quantity": 2 }]
  }'

4. Consultala. Trae el estado, el historial y, una vez entregada, la prueba de entrega. Es para cuando alguien la necesita ver, no para preguntar cada rato si cambió: para eso está el paso 5.

curl https://api.enrutek.com/v1/shipments/TF-261024ZXWPH2S \
  -H "Authorization: Bearer $ENRUTEK_KEY"

5. Enterate de los cambios. Suscribí una URL de tu sistema con POST /webhooks para no tener que estar preguntando.

Probar en Sandbox

Antes de conectar tu sistema a la operación real, probá en Sandbox, una empresa de pruebas dentro de EnruTek. Lo que creés ahí no le llega a ningún operador ni sale a la calle.

Rigen las mismas reglas de uso responsable que en producción.

Uso responsable

El API comparte la base de datos con la operación de bodega y de los choferes. Por eso:

El límite técnico por llave (RateLimit-Limit) existe para absorber lotes y reintentos; no es una invitación a consultar todo el tiempo. EnruTek revisa el registro de llamadas de cada llave: un uso que afecte la operación puede llevar a que se baje el límite de la llave o se revoque, con aviso previo salvo que el daño sea inmediato.

Crear varias guías de una vez

Si un botón o un proceso de tu sistema manda varias guías juntas, usá POST /shipments/batch: una sola llamada, hasta 500 guías y una sola Idempotency-Key para todo el lote.

curl -X POST https://api.enrutek.com/v1/shipments/batch \
  -H "Authorization: Bearer $ENRUTEK_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: despacho-2026-11-10-tarde" \
  -d '{ "shipments": [ { "external_reference": "379671", "...": "..." },
                       { "external_reference": "379672", "...": "..." } ] }'

Cada guía se procesa por aparte: una inválida no detiene a las demás.

El lote responde 200 aunque alguna guía haya fallado. Revisá siempre failed y recorré results, que viene en el mismo orden en que mandaste las guías:

{
  "created": 1,
  "failed": 1,
  "results": [
    { "index": 0, "ok": true, "shipment": { "tracking_number": "TF-261024ZXWPH2S" } },
    { "index": 1, "ok": false, "error": { "code": "unknown_cargo_type", "message": "…", "field": "packages.0.cargo_type" } }
  ]
}

Si failed es mayor que cero, corregí esas guías y mandá solamente esas en un lote nuevo, con otra Idempotency-Key.

Reintentar sin duplicar

Las redes fallan. Hay dos protecciones y conviene usar las dos:

Situación Qué mandar Qué pasa
Se cortó la conexión y no sabés si llegó Lo mismo, con la misma Idempotency-Key Recibís el resultado original y no se crea nada nuevo
Alguien vuelve a tocar el botón más tarde Otra Idempotency-Key, la misma external_reference 409 duplicate_reference, con el número de la guía que ya existe en details
Misma Idempotency-Key, cuerpo distinto No lo hagás 409 idempotency_conflict

Las Idempotency-Key se guardan 24 horas. La external_reference es única por cliente entre las guías activas: al cancelar una guía, su referencia se puede volver a usar.

Recibir webhooks

EnruTek le manda un POST a tu URL cada vez que una guía cambia. Para probar tu receptor sin mover ninguna guía, usá POST /webhooks/{id}/test: el evento llega con "test": true.

Tu receptor tiene que:

  1. Validar la firma con el cuerpo crudo, tal como llegó, antes de convertirlo a JSON.
  2. Rechazar eventos con más de 5 minutos.
  3. Responder cualquier 2xx en menos de 10 segundos y hacer el trabajo pesado después.
  4. Descartar repetidos con event.id: un evento puede llegar más de una vez.
  5. Ordenar con occurred_at, porque pueden llegar en desorden.

Si tu sistema no responde 2xx, EnruTek reintenta con espera creciente hasta por 24 horas. Una suscripción que lleva 24 horas fallando se apaga sola.

La cabecera EnruTek-Signature trae t=<segundos unix>,v1=<firma>. La firma es HMAC-SHA256 en hexadecimal de <t>.<cuerpo crudo>, con el secreto whsec_… que recibiste al crear la suscripción.

Node.js

import { createHmac, timingSafeEqual } from "node:crypto"

export function firmaValida(cuerpoCrudo, cabecera, secreto) {
  const partes = Object.fromEntries(cabecera.split(",").map((p) => p.split("=")))
  if (!partes.t || !partes.v1) return false
  if (Math.abs(Date.now() / 1000 - Number(partes.t)) > 300) return false
  const esperada = createHmac("sha256", secreto).update(`${partes.t}.${cuerpoCrudo}`).digest()
  const recibida = Buffer.from(partes.v1, "hex")
  return recibida.length === esperada.length && timingSafeEqual(recibida, esperada)
}

Python

import hashlib, hmac, time

def firma_valida(cuerpo_crudo: bytes, cabecera: str, secreto: str) -> bool:
    partes = dict(p.split("=", 1) for p in cabecera.split(","))
    if "t" not in partes or "v1" not in partes:
        return False
    if abs(time.time() - int(partes["t"])) > 300:
        return False
    mensaje = partes["t"].encode() + b"." + cuerpo_crudo
    esperada = hmac.new(secreto.encode(), mensaje, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperada, partes["v1"])

PHP

function firma_valida(string $cuerpoCrudo, string $cabecera, string $secreto): bool {
    parse_str(str_replace(',', '&', $cabecera), $partes);
    if (empty($partes['t']) || empty($partes['v1'])) return false;
    if (abs(time() - (int) $partes['t']) > 300) return false;
    $esperada = hash_hmac('sha256', $partes['t'] . '.' . $cuerpoCrudo, $secreto);
    return hash_equals($esperada, $partes['v1']);
}

Qué hacer con cada error

Todos los errores traen error.code, error.message, error.field y error.details. Programá contra code, que no cambia; message es para mostrárselo a una persona.

code Qué significa Qué hacer
invalid_request El pedido no se pudo procesar Revisá el JSON y la ruta
invalid_field Un campo no es válido Corregí el campo que indica field
invalid_address La dirección no coincide con la división oficial Usá los nombres de /catalog/admin-areas o un postal_code válido
unknown_cargo_type Ese tipo de carga no existe para este cliente Usá un code de /catalog
no_rate_for_cargo_type Ese tipo de carga no tiene tarifa Pedile al operador que la configure
customer_required Una llave de operador tiene que indicar el cliente Mandá customer_id
customer_not_allowed Una llave de cliente no puede mandar customer_id Quitá ese campo
customer_not_found Ese cliente no es del operador Revisá el customer_id
branch_not_found Esa sucursal no es del cliente Usá un id de las sucursales de /catalog
duplicate_reference Ya hay una guía activa con esa referencia No la vuelvas a crear; el número está en details.tracking_number
not_found No existe o tu llave no la puede ver Revisá el número de guía y la llave
invalid_status El estado de la guía no permite esa operación Consultá la guía; por ejemplo, solo se cancela en created
unauthorized Falta la llave, no existe o fue revocada Revisá la cabecera Authorization
forbidden Esa llave no puede hacer esto Hablá con tu operador
module_disabled El API no está activo para este operador Hablá con tu operador
idempotency_conflict Esa Idempotency-Key se usó con otro cuerpo, o ese pedido sigue en curso Usá una llave nueva si es otra operación; si es la misma, esperá y reintentá
batch_too_large Más de 500 guías, o más de 2 MB Partí el lote
invalid_webhook_url La URL no cumple las reglas Usá https y un nombre de host público
rate_limited Pasaste el límite de la llave Esperá los segundos de Retry-After
not_implemented Todavía no está disponible Hoy aplica a la etiqueta: imprimila desde EnruTek
internal_error Falló de nuestro lado Reintentá con la misma Idempotency-Key; si sigue, indicá el request_id

Si sos una IA ayudando a integrar

Estas reglas evitan los errores más comunes:

Reglas generales

Los nombres del API (rutas, campos, parámetros, estados y códigos de error) van en inglés; las explicaciones, en español.

EnruTek es una plataforma de logística para operadores logísticos (en el API, operator: la empresa que usa EnruTek). Este API le permite a cualquier sistema externo (un ERP como SAP, una tienda en línea, un sistema de bodega) crear guías, darles seguimiento y recibir sus cambios de estado.

Autenticación

Cada solicitud lleva una llave (API key) en la cabecera Authorization:

Authorization: Bearer ek_live_3Fh8...

Las llaves se crean en EnruTek y se muestran una sola vez. Hay dos tipos:

Sandbox

Para probar sin tocar la operación real hay una empresa de pruebas, Sandbox: misma dirección, mismo contrato y mismos límites; solo cambia la llave, que se pide al operador o a Alfa Tek. Sus guías nacen con el prefijo SB-, nadie las recolecta ni las entrega, y para probar los webhooks está POST /webhooks/{id}/test.

Convenciones

Idempotencia

Mandá una cabecera Idempotency-Key (cualquier texto único de hasta 100 caracteres) en las solicitudes POST. Si reintentás con la misma llave, recibís el resultado original en vez de crear una segunda guía. Las llaves se guardan 24 horas. Además, external_reference es única por cliente entre las guías activas: una segunda guía con la misma referencia se rechaza con duplicate_reference y el número de guía que ya existe.

Errores

Los errores traen un code estable, para máquinas (en inglés), y un message para personas. El mensaje viene en español por defecto; mandá Accept-Language: en para recibirlo en inglés.

Límites de uso

Cada llave tiene un máximo de solicitudes por minuto (rate limit). Cada respuesta trae RateLimit-Limit, RateLimit-Remaining y RateLimit-Reset. Si se pasa del límite, el API responde 429 con Retry-After.

Uso responsable

El API comparte la base con la operación. Crear guías no tiene horario (varias juntas, en /shipments/batch). Para los cambios de estado usá webhooks; si no podés recibirlos, consultá a lo sumo una vez por hora con una sola llamada a GET /shipments?updated_since=…. No consultés guía por guía en un ciclo ni cada pocos segundos o minutos. El límite por llave existe para lotes y reintentos, no para consultar todo el tiempo: un uso que afecte la operación puede llevar a que se baje el límite de la llave o se revoque, con aviso previo salvo que el daño sea inmediato.

Webhooks

EnruTek puede avisarle a tu sistema cuando una guía cambia de estado (por webhook). Cada envío es un POST con un WebhookEvent en JSON y la cabecera EnruTek-Signature: t=<segundos unix>,v1=<HMAC-SHA256 en hex>, calculada sobre <t>.<cuerpo tal cual> con el secreto de la suscripción. Rechazá los eventos de hace más de 5 minutos. Respondé cualquier 2xx en menos de 10 segundos; si no, EnruTek reintenta con espera exponencial durante hasta 24 horas. Un evento puede llegar más de una vez y en desorden: usá event.id para descartar repetidos y occurred_at para ordenarlos.

Referencia de rutas

Guías

Crear, consultar y cancelar guías.

GET /shipments

Listar guías.

Las más recientes primero, paginadas por cursor. Para sincronizar, usá updated_since con el updated_at de la última guía que viste. Sin webhooks, sincronizá a lo sumo una vez por hora (ver "Uso responsable").

Parámetros:

Nombre Dónde Tipo Obligatorio Descripción
Accept-Language header es, en no Idioma de los mensajes de error y de los nombres de estado. es (por defecto) o en.
status query string no Uno o varios estados, separados por coma.
external_reference query string no Tu referencia de la guía (external_reference).
customer_id query string (uuid) no Solo con llaves de operador.
created_since query string (date-time) no Solo guías creadas desde esta fecha y hora.
updated_since query string (date-time) no Solo guías que cambiaron desde esta fecha y hora.
limit query integer no Cantidad de guías por página.
cursor query string no El next_cursor de la página anterior.

Respuestas:

POST /shipments

Crear una guía.

Crea una guía. Nace en created (un chofer la recoge en la sucursal de origen). La respuesta trae el número de guía, el precio calculado con la tarifa y el enlace a la etiqueta 100×100.

Parámetros:

Nombre Dónde Tipo Obligatorio Descripción
Idempotency-Key header string no Texto único por operación; un reintento con la misma llave devuelve el resultado original (24 horas).
Accept-Language header es, en no Idioma de los mensajes de error y de los nombres de estado. es (por defecto) o en.

Cuerpo (JSON):

Campo Tipo Obligatorio Descripción
customer_id string (uuid) no Obligatorio con llaves de operador; no se permite con llaves de cliente.
origin_branch_id string (uuid) no Sucursal del cliente donde se recoge. Por defecto, la sucursal principal.
external_reference string no 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 object sí
recipient.name string sí Nombre del destinatario.
recipient.phone string no E.164.
recipient.email string (email) no Para avisarle de la entrega al destinatario.
destination object sí Los niveles administrativos se comparan sin tildes ni mayúsculas contra la lista oficial (/catalog/admin-areas). En Costa Rica, admin_area_1 y admin_area_2 son obligatorios salvo que postal_code los resuelva; admin_area_3 es opcional.
destination.country string sí ISO 3166-1 alfa-2. Por ahora solo CR.
destination.admin_area_1 string no CR: provincia.
destination.admin_area_2 string no CR: cantón.
destination.admin_area_3 string no CR: distrito.
destination.postal_code string no CR: 5 dígitos; si viene y es válido, llena los niveles administrativos.
destination.address_line string sí Otras señas.
destination.lat number no Latitud del punto exacto (opcional).
destination.lng number no Longitud del punto exacto (opcional).
notes string no Indicaciones para el chofer (horario, por quién preguntar). El chofer las ve.
packages lista de object sí Los bultos, agrupados por tipo de carga.
packages[].cargo_type string sí Un code de /catalog (p. ej. seco, frio).
packages[].quantity integer sí Cantidad de bultos de este tipo de carga.

Ejemplo:

{
  "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
    }
  ]
}

Respuestas:

POST /shipments/batch

Crear hasta 500 guías.

Cada elemento se procesa por aparte: uno inválido no detiene a los demás. La respuesta trae un resultado por elemento, en el mismo orden. Usá una Idempotency-Key para todo el lote; al reintentar recibís los mismos resultados.

Parámetros:

Nombre Dónde Tipo Obligatorio Descripción
Idempotency-Key header string no Texto único por operación; un reintento con la misma llave devuelve el resultado original (24 horas).
Accept-Language header es, en no Idioma de los mensajes de error y de los nombres de estado. es (por defecto) o en.

Cuerpo (JSON):

Campo Tipo Obligatorio Descripción
shipments lista de object sí
shipments[].customer_id string (uuid) no Obligatorio con llaves de operador; no se permite con llaves de cliente.
shipments[].origin_branch_id string (uuid) no Sucursal del cliente donde se recoge. Por defecto, la sucursal principal.
shipments[].external_reference string no 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.
shipments[].recipient object sí
shipments[].recipient.name string sí Nombre del destinatario.
shipments[].recipient.phone string no E.164.
shipments[].recipient.email string (email) no Para avisarle de la entrega al destinatario.
shipments[].destination object sí Los niveles administrativos se comparan sin tildes ni mayúsculas contra la lista oficial (/catalog/admin-areas). En Costa Rica, admin_area_1 y admin_area_2 son obligatorios salvo que postal_code los resuelva; admin_area_3 es opcional.
shipments[].destination.country string sí ISO 3166-1 alfa-2. Por ahora solo CR.
shipments[].destination.admin_area_1 string no CR: provincia.
shipments[].destination.admin_area_2 string no CR: cantón.
shipments[].destination.admin_area_3 string no CR: distrito.
shipments[].destination.postal_code string no CR: 5 dígitos; si viene y es válido, llena los niveles administrativos.
shipments[].destination.address_line string sí Otras señas.
shipments[].destination.lat number no Latitud del punto exacto (opcional).
shipments[].destination.lng number no Longitud del punto exacto (opcional).
shipments[].notes string no Indicaciones para el chofer (horario, por quién preguntar). El chofer las ve.
shipments[].packages lista de object sí Los bultos, agrupados por tipo de carga.
shipments[].packages[].cargo_type string sí Un code de /catalog (p. ej. seco, frio).
shipments[].packages[].quantity integer sí Cantidad de bultos de este tipo de carga.

Respuestas:

GET /shipments/{tracking_number}

Consultar una guía con su historial.

Parámetros:

Nombre Dónde Tipo Obligatorio Descripción
tracking_number path string sí El número de guía de EnruTek, p. ej. TF-261024ZXWPH2S.
Accept-Language header es, en no Idioma de los mensajes de error y de los nombres de estado. es (por defecto) o en.

Respuestas:

POST /shipments/{tracking_number}/cancel

Cancelar una guía.

Solo mientras esté en created (nadie la ha recogido).

Parámetros:

Nombre Dónde Tipo Obligatorio Descripción
tracking_number path string sí El número de guía de EnruTek, p. ej. TF-261024ZXWPH2S.
Idempotency-Key header string no Texto único por operación; un reintento con la misma llave devuelve el resultado original (24 horas).
Accept-Language header es, en no Idioma de los mensajes de error y de los nombres de estado. es (por defecto) o en.

Cuerpo (JSON):

Campo Tipo Obligatorio Descripción
reason string no Motivo de la cancelación.

Respuestas:

GET /shipments/{tracking_number}/label

Obtener la etiqueta 100×100.

Una página por bulto. Por ahora en PDF; más adelante, ZPL para impresoras térmicas. Todavía no está disponible: responde 501 not_implemented (después de revisar que la llave puede ver la guía). Mientras tanto, imprimí las etiquetas desde EnruTek.

Parámetros:

Nombre Dónde Tipo Obligatorio Descripción
tracking_number path string sí El número de guía de EnruTek, p. ej. TF-261024ZXWPH2S.
format query pdf no Formato de la etiqueta.

Respuestas:

Catálogo

Tipos de carga, sucursales y división territorial que puede usar una guía.

GET /catalog

Lo que puede usar una guía.

Los tipos de carga con el precio del cliente y las sucursales del cliente (orígenes). Con llaves de operador, customer_id es obligatorio.

Parámetros:

Nombre Dónde Tipo Obligatorio Descripción
customer_id query string (uuid) no Solo con llaves de operador (y ahí es obligatorio).
Accept-Language header es, en no Idioma de los mensajes de error y de los nombres de estado. es (por defecto) o en.

Respuestas:

GET /catalog/admin-areas

División territorial de un país.

Los valores válidos de admin_area_1..3, en forma de árbol.

Parámetros:

Nombre Dónde Tipo Obligatorio Descripción
country query string sí País, en ISO 3166-1 alfa-2.

Respuestas:

Webhooks

Suscripciones para recibir los cambios de estado de las guías.

GET /webhooks

Listar las suscripciones de webhook de esta llave.

Respuestas:

POST /webhooks

Suscribir una URL a los eventos de las guías.

El secret se devuelve solo en esta respuesta.

Alcance. Una llave de cliente recibe solo los eventos de sus propias guías. Una llave de operador puede indicar customer_id para recibir solo los eventos de ese cliente; sin él, recibe los eventos de todas las guías del operador. customer_id tiene que ser un cliente del operador de la llave (si no, 422 customer_not_found), y si una llave de cliente lo manda se rechaza (403 customer_not_allowed): nunca amplía lo que una llave puede ver. El alcance no se puede cambiar después.

Reglas de la URL (se revisan al crear y otra vez en cada envío): solo https, sin usuario ni contraseña en la URL, un nombre de host (no una IP literal) que resuelva solo a direcciones públicas (se rechazan loopback, privadas RFC 1918, link-local 169.254.0.0/16, CGNAT 100.64.0.0/10, loopback/ULA/link-local de IPv6 y las direcciones de metadatos de la nube). No se siguen redirecciones. Una URL que no cumple estas reglas se rechaza con invalid_webhook_url.

Cuerpo (JSON):

Campo Tipo Obligatorio Descripción
url string (uri) sí Ver las reglas de la URL arriba.
customer_id string (uuid) no Solo con llaves de operador: limita la suscripción a un cliente.
events lista de 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 sí
description string no

Respuestas:

DELETE /webhooks/{id}

Detener una suscripción.

Parámetros:

Nombre Dónde Tipo Obligatorio Descripción
id path string (uuid) sí

Respuestas:

POST /webhooks/{id}/test

Enviar un evento de prueba.

Parámetros:

Nombre Dónde Tipo Obligatorio Descripción
id path string (uuid) sí

Respuestas:

Evento de webhook

Lo que EnruTek le manda a la URL suscrita.

Campo Tipo Obligatorio Descripción
id string (uuid) sí Único por evento; usalo para descartar repetidos.
type 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 sí
occurred_at string (date-time) sí
shipment object sí 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.
shipment.id string (uuid) sí
shipment.tracking_number string sí
shipment.tracking_url string (uri) no Página pública de rastreo.
shipment.label_url string (uri) no /shipments/{tracking_number}/label.
shipment.status created, at_reception, picked_up, received_at_cedi, loaded, out_for_delivery, kept_on_route, back_to_cedi, delivered, rejected, cancelled sí Dónde está físicamente el bulto. Finales: delivered, rejected, cancelled.
shipment.status_label string sí Nombre del estado para personas, en el idioma pedido; p. ej. "En CEDI".
shipment.external_reference string no
shipment.customer object sí
shipment.customer.id string (uuid) sí
shipment.customer.name string sí
shipment.origin object sí
shipment.origin.branch_id string (uuid) no
shipment.origin.name string no
shipment.origin.address object no Los niveles administrativos se comparan sin tildes ni mayúsculas contra la lista oficial (/catalog/admin-areas). En Costa Rica, admin_area_1 y admin_area_2 son obligatorios salvo que postal_code los resuelva; admin_area_3 es opcional.
shipment.origin.address.country string sí ISO 3166-1 alfa-2. Por ahora solo CR.
shipment.origin.address.admin_area_1 string no CR: provincia.
shipment.origin.address.admin_area_2 string no CR: cantón.
shipment.origin.address.admin_area_3 string no CR: distrito.
shipment.origin.address.postal_code string no CR: 5 dígitos; si viene y es válido, llena los niveles administrativos.
shipment.origin.address.address_line string sí Otras señas.
shipment.origin.address.lat number no Latitud del punto exacto (opcional).
shipment.origin.address.lng number no Longitud del punto exacto (opcional).
shipment.recipient object sí
shipment.recipient.name string sí Nombre del destinatario.
shipment.recipient.phone string no E.164.
shipment.recipient.email string (email) no Para avisarle de la entrega al destinatario.
shipment.destination object sí Los niveles administrativos se comparan sin tildes ni mayúsculas contra la lista oficial (/catalog/admin-areas). En Costa Rica, admin_area_1 y admin_area_2 son obligatorios salvo que postal_code los resuelva; admin_area_3 es opcional.
shipment.destination.country string sí ISO 3166-1 alfa-2. Por ahora solo CR.
shipment.destination.admin_area_1 string no CR: provincia.
shipment.destination.admin_area_2 string no CR: cantón.
shipment.destination.admin_area_3 string no CR: distrito.
shipment.destination.postal_code string no CR: 5 dígitos; si viene y es válido, llena los niveles administrativos.
shipment.destination.address_line string sí Otras señas.
shipment.destination.lat number no Latitud del punto exacto (opcional).
shipment.destination.lng number no Longitud del punto exacto (opcional).
shipment.notes string no
shipment.packages lista de object sí
shipment.packages[].cargo_type string sí
shipment.packages[].cargo_type_name string no
shipment.packages[].quantity integer sí
shipment.packages[].unit_price object sí
shipment.packages[].unit_price.amount string sí
shipment.packages[].unit_price.currency string sí
shipment.total object sí Total con IVA, tal como se va a facturar.
shipment.total.amount string sí
shipment.total.currency string sí
shipment.scheduled object no El despacho al que está asignada, si lo hay.
shipment.scheduled.date string (date) no
shipment.scheduled.route_code string no
shipment.proof_of_delivery object no
shipment.proof_of_delivery.delivered_at string (date-time) no
shipment.proof_of_delivery.received_by string no
shipment.proof_of_delivery.lat number no
shipment.proof_of_delivery.lng number no
shipment.events lista de object no Historial, de lo más viejo a lo más nuevo. Solo en GET /shipments/{tracking_number} y en los webhooks.
shipment.events[].type created, edited, picked_up, at_reception, received_at_cedi, assigned, unassigned, loaded, out_for_delivery, kept_on_route, back_to_cedi, delivered, rejected, cancelled sí
shipment.events[].label string no
shipment.events[].occurred_at string (date-time) sí
shipment.events[].facility string no Nombre del CEDI (centro de distribución), p. ej. "GAM".
shipment.events[].comment string no
shipment.created_at string (date-time) sí
shipment.updated_at string (date-time) sí