Enviadores
GuíaReferencia

Referencia de la API

Base https://api.enviadores.com.mx/api/v1 · autentica con Authorization: Bearer ek_test_… (sandbox) o ek_live_…. Errores, reintentos, webhooks y modo de prueba están en la guía.

Versión 1.2.0 · OpenAPI 3.1.0 · openapi.json

rates

Cotización multi-paquetería

Cotizar en todas las paqueterías

post/rates

Cotiza con todas las paqueterías activas y devuelve tarifas con id (rate_id) listas para POST /shipments.

›Ver reglas

Requiere scope rates:read. Solo rutas domésticas MX (country ≠ MX → 422). Bultos: UN paquete (package) o VARIOS en un mismo envío (packages, 2–10 bultos; mandar ambos → 422). Con packages solo se devuelven ofertas reservables como envío multi-bulto (los servicios que no lo soportan no aparecen) y el peso facturable es la SUMA de los bultos. Códigos postales: deben existir en el catálogo SEPOMEX — un CP inexistente (p. ej. 99999) responde 422 nombrando from.postal_code/to.postal_code ANTES de consultar a las paqueterías (antes se cotizaba por zona a ciegas). Las tarifas de referencia de mostrador (market) NO se incluyen: todo rate_id devuelto es enviable. Seguro: insurance.insured_value (alias obsoleto declared_value) contrata cobertura para el envío: la póliza de la plataforma (prima incluida y firmada dentro de pricing.total_price) o, si esa póliza no está disponible, la cobertura de la propia paquetería — el precio mostrado ya la incluye y cada tarifa lo indica con insurance_included / insurance_not_supported. *Insurance: insured_value buys coverage; when the platform policy is unavailable the carrier's own coverage is used and the price shown already includes it.* Vista agrupada: ?view=grouped devuelve cada servicio distinto UNA sola vez (paquetería + nivel de servicio) con su rango de precios y sus opciones reservables (ver RatesGroupedResponse); la forma plana por defecto no cambia. La respuesta es la MISMA para toda llave (los costos de proveedor nunca se exponen). Plazo: la cotización responde en un máximo de ~15 s con las tarifas que llegaron a tiempo; si alguna cotización no llegó a tiempo, incomplete es true — las tarifas listadas siguen siendo reservables y una nueva cotización puede mostrar más. *Deadline: the quote answers within ~15 s with the rates that arrived; incomplete: true flags that more may exist.*

Parámetros

  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

  • view"grouped"· query
    grouped: colapsa services[] en un elemento por servicio distinto (RatesGroupedResponse, con view: "grouped"). Omitido: la forma plana (RatesResponse).
›Campos del cuerpo (7)
  • Mostrar 5 campos
    • postal_codestringrequerido

      CP de 5 dígitos (MX).

    • country"MX"

      Opcional. Solo MX (sin distinción de mayúsculas/minúsculas; vacío = MX) — cualquier otro valor responde 422 (v1 es doméstico MX).

    • statestring
    • citystring
    • neighborhoodstring
  • from_idstring

    Id de un remitente del directorio (GET /senders). Mutuamente excluyente con from.

  • Mostrar 5 campos
    • postal_codestringrequerido

      CP de 5 dígitos (MX).

    • country"MX"

      Opcional. Solo MX (sin distinción de mayúsculas/minúsculas; vacío = MX) — cualquier otro valor responde 422 (v1 es doméstico MX).

    • statestring
    • citystring
    • neighborhoodstring
  • to_idstring

    Id de un destinatario del directorio (GET /recipients). Mutuamente excluyente con to.

  • packageobject
    Mostrar 4 campos
    • weight_kgnumberrequerido

      Peso real en kg. El facturable es max(real, volumétrico L*W*H/5000); máximo 70 kg.

    • length_cmnumber

      Largo exterior de la caja ya cerrada (cm). Manda las tres medidas: sin ellas la cotización se calcula para una caja de 10×10×10 cm, llega con size_estimated: true y POST /shipments la rechaza (422 PACKAGE_DIMENSIONS_REQUIRED). *Outer size of the closed box; without all three the price is only an estimate and create is refused.*

    • width_cmnumber

      Ancho exterior de la caja ya cerrada (cm).

    • height_cmnumber

      Alto exterior de la caja ya cerrada (cm).

  • packagesPackagesList
    Mostrar 4 campos
    • weight_kgnumberrequerido

      Peso real del bulto (kg).

    • length_cmnumber

      Largo exterior del bulto cerrado (cm). Obligatorio para POST /shipments; en POST /rates, sin las tres medidas el precio es estimado (size_estimated).

    • width_cmnumber

      Ancho exterior del bulto cerrado (cm).

    • height_cmnumber

      Alto exterior del bulto cerrado (cm).

  • Mostrar 2 campos
    • insured_valuenumber

      Valor a asegurar (MXN). La prima queda incluida en pricing.total_price de cada tarifa.

    • declared_valuenumber

      OBSOLETO — alias de insured_value (compatibilidad con integradores previos). Si mandas ambos deben coincidir.

Respuestas

HTTPCódigoQué significa
200Cotización exitosa (forma autenticada multi-paquetería).
400INVALID_JSONCuerpo vacío/no-JSON
MISSING_ROUTE
MISSING_PACKAGE
WEIGHT_EXCEEDEDPeso facturable > 70 kg
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDRuta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
422VALIDATION_ERRORDetails.fields lista los campos — incluye country ≠ MX, package y packages juntos, packages fuera de 2–10, un CP inexistente en SEPOMEX (from.postal_code: el CP 99999 no existe en el catálogo SEPOMEX) y campos desconocidos a nivel raíz
INSURANCE_VALUE_TOO_HIGHValor asegurado sobre el máximo asegurable
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500RATES_ERRORLa cotización falló; reintenta.
›Campos de la respuesta 200
Petición
curl -X POST "https://api.enviadores.com.mx/api/v1/rates" \  -H "Authorization: Bearer $ENVIADORES_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "from": {    "postal_code": "01000"  },  "to": {    "postal_code": "64000"  },  "package": {    "weight_kg": 1,    "length_cm": 20,    "width_cm": 15,    "height_cm": 10  }}'
Respuesta 200
{
  "success": true,
  "data": {
    "services": [
      {
        "id": "e2etest_E2ETestCarrier_E2EStandard_ab12cd34",
        "carrier": "Estafeta",
        "service_name": "Terrestre",
        "service_type": "standard_economy",
        "tier": "standard_economy",
        "delivery_window": "ground",
        "pickup_included": false,
        "address_delivery": true,
        "pricing": {
          "total_price": 145.5,
          "currency": "MXN",
          "iva_included": true,
          "insurance_premium": 0,
          "insured_value": null
        },
        "delivery": {
          "estimated_days": "3-5",
          "estimated_date": null,
          "min_days": 3,
          "max_days": 5
        },
        "insurance_included": false,
        "insurance_not_supported": false,
        "zona_extendida": false
      },
      {
        "id": "e2etest_E2ETestCarrier_E2EExpress_7f0a16b2",
        "carrier": "FedEx",
        "service_name": "Express Nacional",
        "service_type": "express",
        "tier": "express",
        "delivery_window": "next_day",
        "pickup_included": true,
        "address_delivery": true,
        "pricing": {
          "total_price": 289,
          "currency": "MXN",
          "iva_included": true,
          "insurance_premium": 0,
          "insured_value": null
        },
        "delivery": {
          "estimated_days": "1-2",
          "estimated_date": null,
          "min_days": 1,
          "max_days": 2
        },
        "insurance_included": false,
        "insurance_not_supported": false,
        "zona_extendida": false
      }
    ],
    "fetched_at": "2026-07-13T18:42:05Z",
    "stale_after": "2026-07-13T18:47:05Z",
    "meta": {
      "billable_weight": 1,
      "volumetric_weight": 0.6,
      "zone": 5
    },
    "rate_id_expires_in_seconds": 1800,
    "incomplete": false
  },
  "requestId": "req_a1b2c3d4e5"
}

shipments

Creación y consulta de envíos

Listar envíos (paginado)

get/shipments

Por defecto: solo envíos creados por el usuario de la llave, para TODOS los roles — una llave admin ve únicamente sus propios envíos de cuenta de servicio.

›Ver reglas

Alcance por PDV (opt-in). Una llave admin que envía X-PDV-ID y tiene el scope shipments:read:pdv lista en cambio los envíos de ese punto de venta, sin importar qué operador los creó — el mismo fondo que ya responden /senders, /recipients y /balance bajo ese header. Sin el scope, el header no amplía nada y sigues viendo solo lo tuyo (los integradores que ya lo envían no cambian de resultados).

Cambio de contrato (2026-07): X-PDV-ID ahora se valida en esta ruta aunque no amplíe nada. Antes se ignoraba por completo, así que un PDV inexistente o fuera de la allowlist de la llave respondía 200 con lista vacía mientras /senders respondía 403; ahora responde 403 PDV_NOT_ALLOWED como todas las rutas hermanas.

Orden: más recientes primero. Requiere scope shipments:read (+ shipments:read:pdv para el alcance por PDV).

Parámetros

  • pageinteger· query
  • limitinteger· query
  • guiastring· query
    Búsqueda exacta por número de guía. más

    Es la forma de llegar a un envío cuando solo tienes el número que ve el cliente (el id interno no es adivinable). Devuelve una página de 0 o 1 elementos, sujeta a la misma visibilidad que el resto del listado.

  • status"created" | "collected" | "in_transit" | "out_for_delivery" | "delivered" | "exception" | "returned" | "cancelled" | "unknown"· query
    Filtra por estatus canónico — exactamente el mismo valor que devuelve el campo status de cada envío. Un valor fuera del enum es 422 VALIDATION_ERROR, nunca una lista vacía silenciosa.
  • fromstring· query
    Día calendario INCLUSIVO (zona horaria de negocio America/Mexico_City) desde el cual listar, por fecha de creación. Solo YYYY-MM-DD.
  • tostring· query
    Día calendario INCLUSIVO (America/Mexico_City) hasta el cual listar — el día completo, hasta las 23:59:59 locales. from posterior a to es 422.
  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

Respuestas

HTTPCódigoQué significa
200Página de envíos.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDRuta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
422VALIDATION_ERRORFiltro inválido (details.fields).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • shipmentsShipment[]requerido
    Mostrar 17 campos
    • idstringrequerido
    • guiastring | nullrequerido
    • carrierstring | nullrequerido

      Nombre canónico de la paquetería física (SSOT de la plataforma).

    • servicestring | nullrequerido
    • status"created" | "collected" | "in_transit" | "out_for_delivery" | "delivered" | "exception" | "returned" | "cancelled" | "unknown"requerido
    • status_labelstringrequerido

      Etiqueta en español del estatus canónico.

    • totalnumber | nullrequerido
    • currency"MXN"requerido
    • created_atstring | nullrequerido

      ISO-8601 UTC (Z).

    • tracking_pendingbooleanrequerido
    • label_urlstringrequerido
    • packagesobject[]requerido

      Lo que se registró al crear el envío, en el mismo vocabulario que usa POST /shipments (weight_kg, length_cm, …). Para envíos multi-paquete es la lista completa por bulto, no el agregado. Lista vacía si el envío no tiene medidas registradas.

      Envíos anteriores al 2026-07-28: las dimensiones no se persistían (solo se usaban para calcular el peso volumétrico y se descartaban), así que length_cm/width_cm/height_cm llegan en null en todo envío creado antes de esa fecha. weight_kg sí está disponible en todo el histórico. Un null significa "no registrado", nunca "cero": las dimensiones no declaradas tampoco se rellenan con un valor por defecto.

      Mostrar 4 campos
      • weight_kgnumber | nullrequerido
      • length_cmnumber | nullrequerido
      • width_cmnumber | nullrequerido
      • height_cmnumber | nullrequerido
    • billable_weight_kgnumber | nullrequerido

      Peso facturable con el que se cotizó — max(peso real, peso volumétrico). Es el número contra el que hay que comparar cuando la paquetería aplica un sobrepeso; el peso real por sí solo no explica el cargo. El repeso de la paquetería NO vive en esta API (está en su reporte de facturación).

    • declared_valuenumber | nullrequerido

      Valor declarado (MXN).

    • contentstring | nullrequerido

      Descripción del contenido capturada al crear el envío.

    • senderobjectrequerido

      Remitente, con la misma forma mínima que recipient: solo nombre + ciudad/estado/CP — nunca la dirección completa ni teléfonos.

      Mostrar 4 campos
      • namestring | nullrequerido
      • citystring | nullrequerido
      • statestring | nullrequerido
      • postal_codestring | nullrequerido
    • recipientobjectrequerido

      Solo nombre + ciudad/estado/CP — nunca la dirección completa ni teléfonos.

      Mostrar 4 campos
      • namestring | nullrequerido
      • citystring | nullrequerido
      • statestring | nullrequerido
      • postal_codestring | nullrequerido
  • paginationPaginationrequerido
    Mostrar 3 campos
    • pageintegerrequerido
    • limitintegerrequerido
    • totalintegerrequerido
Petición
curl "https://api.enviadores.com.mx/api/v1/shipments" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "shipments": [
      {
        "id": "20260713-000123",
        "guia": "1234567890",
        "carrier": "Estafeta",
        "service": "Terrestre",
        "status": "in_transit",
        "status_label": "En tránsito",
        "total": 145.5,
        "currency": "MXN",
        "created_at": "2026-07-13T18:45:12Z",
        "tracking_pending": false,
        "label_url": "/api/v1/labels/20260713-000123",
        "packages": [
          {
            "weight_kg": 1,
            "length_cm": 20,
            "width_cm": 15,
            "height_cm": 10
          }
        ],
        "billable_weight_kg": 1,
        "declared_value": null,
        "content": "Ropa",
        "sender": {
          "name": "Juan Pérez",
          "city": "Ciudad de México",
          "state": "CDMX",
          "postal_code": "01000"
        },
        "recipient": {
          "name": "María López",
          "city": "Monterrey",
          "state": "Nuevo León",
          "postal_code": "64000"
        }
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 1
    }
  },
  "requestId": "req_e5f6a7b8c9"
}

Crear un envío (genera la guía y cobra créditos)

post/shipments

Crea el envío contra un rate_id vigente (≤ 30 min) cotizado con la MISMA llave (y mismo PDV, si aplica).

›Ver reglas

Solo domésticos MX (customs/ocurre, country ≠ MX o campos desconocidos → 422). Bultos: los MISMOS que se cotizaron — package (uno) o packages (2–10 en un solo envío; ambos → 422); un conjunto distinto al cotizado responde 409 RATE_PACKAGE_MISMATCH. CP: origen/destino inline deben existir en SEPOMEX (422 nombrando from.postal_code/to.postal_code, antes de retener saldo). Seguro vs. valor declarado (no son lo mismo): insurance.insured_value es la póliza de plataforma que YA se cotizó — debe coincidir con la cotización (422 INSURANCE_MISMATCH si difiere o falta; el total firmado nunca se recalcula al crear); declared_value es el valor de la mercancía que se declara a la paquetería (MXN, informativo; algunos servicios lo exigen — DECLARED_VALUE_REQUIRED). Remitente/destinatario van inline (la plataforma crea o reutiliza los registros del directorio con dedupe exacto normalizado) o por referencia (from_id/to_id del directorio — la fila guardada se usa tal cual). El cargo sigue el ciclo autorizar→capturar/anular — un envío rechazado nunca deja fondos retenidos. Usa `Idempotency-Key` en todo intento.

Parámetros

  • Idempotency-Keystring· header
    UUID fresco por operación lógica (1–128 caracteres ASCII imprimibles; más

    presente pero inválida → 400 IDEMPOTENCY_KEY_INVALID). Un reintento con la misma llave Y el mismo cuerpo repite la respuesta 2xx original (Idempotent-Replay: true) sin doble cargo, durante 15 minutos. La llave queda ligada al cuerpo exacto de la primera solicitud y al X-PDV-ID: cuerpo distinto → 409 IDEMPOTENCY_KEY_PAYLOAD_MISMATCH; pasados los 15 min → 409 IDEMPOTENCY_KEY_EXPIRED. Cambio 2026-08: ya NO se comparte espacio de nombres con el carril web del mismo usuario (antes una llave se repetía entre /v1/shipments y la app web; ahora eso responde 409 IDEMPOTENCY_KEY_REUSED). Fuertemente recomendado en POST /shipments.

  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

›Campos del cuerpo (12)
  • rate_idstringrequerido

    El id de una tarifa de POST /rates cotizada con esta misma llave (y mismo X-PDV-ID, si se usó), con menos de 30 minutos. Trátalo como OPACO: cópialo tal cual; su formato no es parte del contrato (difiere entre el carril de prueba y el live) y no debe interpretarse ni validarse.

  • Mostrar 14 campos
    • namestringrequerido
    • apellido_paternostring

      DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

    • apellido_maternostring

      DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

    • companystring

      Razón social (solo remitente; ignorado en destinatario).

    • phonestringrequerido
    • emailstring
    • streetstringrequerido
    • numberstring

      Número exterior.

    • coloniastringrequerido
    • citystringrequerido
    • statestringrequerido
    • postal_codestringrequerido
    • country"MX"

      Opcional. Solo MX (sin distinción de mayúsculas/minúsculas; vacío = MX) — cualquier otro valor responde 422 (v1 es doméstico MX).

    • rfcstring

      Solo remitente; usado por algunas paqueterías.

  • from_idstring

    Id de un remitente del directorio. Mutuamente excluyente con from.

  • Mostrar 14 campos
    • namestringrequerido
    • apellido_paternostring

      DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

    • apellido_maternostring

      DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

    • companystring

      Razón social (solo remitente; ignorado en destinatario).

    • phonestringrequerido
    • emailstring
    • streetstringrequerido
    • numberstring

      Número exterior.

    • coloniastringrequerido
    • citystringrequerido
    • statestringrequerido
    • postal_codestringrequerido
    • country"MX"

      Opcional. Solo MX (sin distinción de mayúsculas/minúsculas; vacío = MX) — cualquier otro valor responde 422 (v1 es doméstico MX).

    • rfcstring

      Solo remitente; usado por algunas paqueterías.

  • to_idstring

    Id de un destinatario del directorio (debe pertenecer al from_id enviado). Mutuamente excluyente con to; requiere from_id.

  • packageobject

    La MISMA caja que se cotizó, con sus tres medidas: sin largo, ancho y alto → 422 PACKAGE_DIMENSIONS_REQUIRED (no se cobra nada).

    Mostrar 5 campos
    • weight_kgnumberrequerido
    • length_cmnumberrequerido
    • width_cmnumberrequerido
    • height_cmnumberrequerido
    • type"paquete"

      Opcional. Solo paquete en v1 (sin distinción de mayúsculas; vacío/null = paquete). Cualquier otro valor — p. ej. sobre — responde 422 tanto en /rates como en /shipments (envelope aún no soportado).

  • packagesPackagesList
    Mostrar 4 campos
    • weight_kgnumberrequerido

      Peso real del bulto (kg).

    • length_cmnumber

      Largo exterior del bulto cerrado (cm). Obligatorio para POST /shipments; en POST /rates, sin las tres medidas el precio es estimado (size_estimated).

    • width_cmnumber

      Ancho exterior del bulto cerrado (cm).

    • height_cmnumber

      Alto exterior del bulto cerrado (cm).

  • insuranceobject

    La cobertura que YA se cotizó (insured_value buys coverage; when the platform policy is unavailable the carrier's own coverage is used and the price shown already includes it). Omitir insurance acepta la cobertura cotizada; mandar un insured_value DISTINTO → 422 INSURANCE_MISMATCH (el total firmado nunca se recalcula al crear). Una tarifa cuya paquetería no ofrece cobertura (insurance_not_supported: true) → 422 INSURANCE_NOT_AVAILABLE.

    Mostrar 1 campo
    • insured_valuenumberrequerido

      Valor asegurado (MXN) — el mismo de la cotización.

  • declared_valuenumber

    Valor de la mercancía DECLARADO A LA PAQUETERÍA (MXN). NO es seguro, pero la paquetería puede cobrarlo: debe ser EXACTAMENTE el valor con el que se cotizó (el asegurado) — un valor que la cotización no llevaba responde 422 DECLARED_VALUE_MISMATCH, salvo en servicios que lo tratan como informativo (la recuperación de DECLARED_VALUE_REQUIRED). Si se omite se declara el valor cotizado; si la cotización no llevaba ninguno, no se inventa. *declared_value must equal the value the rate was quoted with; a carrier may price it.*

  • contenidostring

    Descripción del contenido (aparece en la guía cuando la paquetería lo soporta).

  • referencestring

    RESERVADO — aceptado pero no persistido en v1.

  • metadataobject

    RESERVADO en el carril live (aceptado, no persistido). En el carril SANDBOX, metadata.test_scenario fuerza una falla para rehearsal (ver x-test-mode).

    Mostrar 1 campo
    • test_scenario"insufficient_funds" | "rate_expired" | "vendor_error"

      Solo llaves ek_test_, y solo sobre ofertas SIMULADAS del catálogo: sobre una tarifa del carril FedEx de prueba (que va al entorno real de FedEx) responde 422 TEST_SCENARIO_NOT_AVAILABLE. insufficient_funds → 402 CREDIT_ERROR (details {required, available, shortfall}) sin importar el saldo; rate_expired → 409 RATE_EXPIRED; vendor_error → 422 VENDOR_ERROR (retryable=false).

Respuestas

HTTPCódigoQué significa
200Envío creado (o repetido idempotentemente — header Idempotent-Replay: true; o deduplicado contra una guía existente — duplicate: true, en cuyo caso carrier/service pueden ser null).
400INVALID_JSON
VALIDATION_FAILEDRechazo del núcleo, details.validationErrors
INVALID_VENDOR
OCURRE_CARRIER_MISMATCH
IDEMPOTENCY_KEY_INVALIDHeader presente pero vacío, >128 chars o con caracteres no imprimibles; omitirlo es válido, mandarlo mal no
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
402CREDIT_ERRORCréditos insuficientes. details: {required, available, shortfall}. Recarga y reintenta con el MISMO Idempotency-Key.
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDRuta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
409RATE_NOT_FOUNDCotiza de nuevo
RATE_EXPIRED>30 min
RATE_TAMPERED
RATE_PDV_MISMATCHSe cotizó bajo otro PDV
RATE_PACKAGE_MISMATCHEl package/packages enviado difiere del cotizado — medidas, peso o valor declarado — reenvía el MISMO conjunto que cotizaste, o cotiza de nuevo
GUIA_COLLISION
IDEMPOTENCY_KEY_REUSEDMisma llave en otro endpoint — desde 2026-08 el carril web cuenta como otro endpoint
IDEMPOTENCY_KEY_PAYLOAD_MISMATCHMisma llave, cuerpo o X-PDV-ID distintos: manda una llave nueva
IDEMPOTENCY_KEY_EXPIREDLa llave ya se completó hace más de 15 min: consulta GET /shipments o manda una llave nueva
RATE_ADDRESS_MISMATCHLa cotización corresponde a OTRA ruta: los CP de origen/destino con los que se está creando el envío no son los que se cotizaron — cotiza de nuevo con las direcciones definitivas; desde 2026-08 esto rechaza en lugar de solo registrarse
REQUOTE_REQUIREDEl precio del envío no coincide con el de la cotización; no se espera en este carril — el total se toma de la MISMA fila firmada que el ejecutor vuelve a verificar — y se documenta por completitud: cotiza de nuevo
422VALIDATION_ERRORContrato público, details.fields — incluye CP inexistente en SEPOMEX
PACKAGE_DIMENSIONS_REQUIRED
Falta largo, ancho o alto de alguna caja — details.fields las nombra, p. ej. package.height_cm o packages[1].length_cm; más

no se cobró: pide las medidas de la caja cerrada y cotiza de nuevo, porque el precio depende de ellas

INSURANCE_NOT_AVAILABLESe mandó insurance sobre una tarifa cuya paquetería no ofrece cobertura — venía con insurance_not_supported: true; cotiza de nuevo y elige una con insurance_included, o crea sin insurance
INSURANCE_MISMATCHinsurance.insured_value explícito distinto de la cobertura cotizada — details {quoted_insured_value, requested_insured_value}; omitirlo acepta la cobertura cotizada
DECLARED_VALUE_MISMATCHdeclared_value distinto del que se cotizó, o presente cuando la cotización no llevaba ninguno y la paquetería lo cobra — details {quoted_declared_value, requested_declared_value}; cotiza de nuevo
DECLARED_VALUE_REQUIREDEl servicio exige declared_value; no se cobró
PRICING_CONFIG_ERRORMargen mal configurado; contacta soporte
MISSING_PHONERemitente y/o destinatario sin teléfono válido de 10 dígitos; details.missing lista cuál
429RISK_LIMITTope de guías/gasto diario de la cuenta, o tope de gasto de la LLAVE en ventana móvil de 24 h — este último con details {daily_cap_mxn, spent_24h_mxn, attempted_mxn}
RATE_LIMITEDLímite por llave; ver Retry-After
500SHIPMENT_ERRORFalla pre-commit; el cargo se revirtió — details.refunded
ADDRESS_ERRORNo se pudo registrar remitente/destinatario; reintenta
CREDIT_ERRORFalla interna de créditos
POST_COMMIT_ERROREL ENVÍO ES REAL y el cargo NO se revierte (details.committed: true). NO reintentes con llave nueva: consulta GET /shipments.
502VENDOR_ERROR
La paquetería rechazó/falló ANTES del commit; el cargo se revirtió (details.refunded). más

details.vendorError: true; el status HTTP refleja el de la paquetería (4xx/5xx). Reintenta solo si retryable aplica (falla de red). Toda falla de paquetería trae `details.nextStep.action` — fix_data (corrige los campos de details.nextStep.fields: {party, field, label, problem, min?, max?}; código SHIPMENT_DATA_INVALID, 422), choose_other_option (otra opción de la misma cotización; SERVICE_UNAVAILABLE / CARRIER_ACCOUNT_BALANCE), check_shipments (la guía podría existir: consulta GET /shipments antes de reintentar; LABEL_MAY_EXIST), retry (reintenta una vez con el MISMO Idempotency-Key), add_declared_value, adjust_package.

›Campos de la respuesta 200
  • shipmentobjectrequerido
    Mostrar 10 campos
    • idstringrequerido
    • guiastring | null

      Null cuando la paquetería publica la guía con retraso (tracking_pending: true); consúltala después vía GET /shipments/{id}.

    • carrierstring | null

      Null posible en respuestas duplicate: true.

    • servicestring | null
    • statusstringrequerido

      Estatus canónico (created | collected | in_transit | out_for_delivery | ready_for_pickup | delivered | exception | returned | cancelled | unknown).

    • totalnumber | null

      Monto cobrado (MXN).

    • currency"MXN"requerido
    • label_urlstring | nullrequerido

      Ruta de NUESTRO endpoint autenticado de etiquetas (/api/v1/labels/{id}) — nunca la URL cruda del proveedor.

    • tracking_pendingbooleanrequerido
    • created_atstring | null

      ISO-8601 UTC (Z).

  • duplicateboolean

    Presente (true) cuando la paquetería devolvió una guía ya registrada y la plataforma deduplicó sin doble cargo.

  • recoveredboolean

    Presente (true) cuando la etiqueta se recuperó tras un error transitorio del proveedor.

Petición
curl -X POST "https://api.enviadores.com.mx/api/v1/shipments" \  -H "Authorization: Bearer $ENVIADORES_API_KEY" \  -H "Idempotency-Key: $(uuidgen)" \  -H "Content-Type: application/json" \  -d '{  "rate_id": "e2etest_E2ETestCarrier_E2EStandard_ab12cd34",  "from": {    "name": "Juan Pérez",    "phone": "5512345678",    "street": "Av. Insurgentes Sur",    "number": "600",    "colonia": "Del Valle",    "city": "Ciudad de México",    "state": "CDMX",    "postal_code": "01000"  },  "to": {    "name": "María López",    "phone": "8187654321",    "street": "Av. Constitución",    "number": "400",    "colonia": "Centro",    "city": "Monterrey",    "state": "Nuevo León",    "postal_code": "64000"  },  "package": {    "weight_kg": 1,    "length_cm": 20,    "width_cm": 15,    "height_cm": 10  },  "contenido": "Ropa"}'
Respuesta 200
{
  "success": true,
  "data": {
    "shipment": {
      "id": "20260713-000123",
      "guia": "1234567890",
      "carrier": "Estafeta",
      "service": "Terrestre",
      "status": "created",
      "total": 145.5,
      "currency": "MXN",
      "label_url": "/api/v1/labels/20260713-000123",
      "tracking_pending": false,
      "created_at": "2026-07-13T18:45:12Z"
    }
  },
  "requestId": "req_b2c3d4e5f6"
}

Consultar un envío

get/shipments/{id}

Misma visibilidad que el listado: propios por defecto, o los del PDV con X-PDV-ID + scope shipments:read:pdv.

›Ver reglas

Devuelve exactamente los mismos campos que cada elemento de GET /shipments (incluyendo packages, pesos y remitente) — el detalle no es más rico que la lista, por diseño. Si solo tienes el número de guía, usa GET /shipments?guia=….

Parámetros

  • idstring· pathrequerido
    Id del envío (p. ej. 20260712-000123).
  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

Respuestas

HTTPCódigoQué significa
200El envío.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDNo existe o no es visible para la llave (respuesta idéntica en ambos casos).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • shipmentShipmentrequerido
    Mostrar 17 campos
    • idstringrequerido
    • guiastring | nullrequerido
    • carrierstring | nullrequerido

      Nombre canónico de la paquetería física (SSOT de la plataforma).

    • servicestring | nullrequerido
    • status"created" | "collected" | "in_transit" | "out_for_delivery" | "delivered" | "exception" | "returned" | "cancelled" | "unknown"requerido
    • status_labelstringrequerido

      Etiqueta en español del estatus canónico.

    • totalnumber | nullrequerido
    • currency"MXN"requerido
    • created_atstring | nullrequerido

      ISO-8601 UTC (Z).

    • tracking_pendingbooleanrequerido
    • label_urlstringrequerido
    • packagesobject[]requerido

      Lo que se registró al crear el envío, en el mismo vocabulario que usa POST /shipments (weight_kg, length_cm, …). Para envíos multi-paquete es la lista completa por bulto, no el agregado. Lista vacía si el envío no tiene medidas registradas.

      Envíos anteriores al 2026-07-28: las dimensiones no se persistían (solo se usaban para calcular el peso volumétrico y se descartaban), así que length_cm/width_cm/height_cm llegan en null en todo envío creado antes de esa fecha. weight_kg sí está disponible en todo el histórico. Un null significa "no registrado", nunca "cero": las dimensiones no declaradas tampoco se rellenan con un valor por defecto.

      Mostrar 4 campos
      • weight_kgnumber | nullrequerido
      • length_cmnumber | nullrequerido
      • width_cmnumber | nullrequerido
      • height_cmnumber | nullrequerido
    • billable_weight_kgnumber | nullrequerido

      Peso facturable con el que se cotizó — max(peso real, peso volumétrico). Es el número contra el que hay que comparar cuando la paquetería aplica un sobrepeso; el peso real por sí solo no explica el cargo. El repeso de la paquetería NO vive en esta API (está en su reporte de facturación).

    • declared_valuenumber | nullrequerido

      Valor declarado (MXN).

    • contentstring | nullrequerido

      Descripción del contenido capturada al crear el envío.

    • senderobjectrequerido

      Remitente, con la misma forma mínima que recipient: solo nombre + ciudad/estado/CP — nunca la dirección completa ni teléfonos.

      Mostrar 4 campos
      • namestring | nullrequerido
      • citystring | nullrequerido
      • statestring | nullrequerido
      • postal_codestring | nullrequerido
    • recipientobjectrequerido

      Solo nombre + ciudad/estado/CP — nunca la dirección completa ni teléfonos.

      Mostrar 4 campos
      • namestring | nullrequerido
      • citystring | nullrequerido
      • statestring | nullrequerido
      • postal_codestring | nullrequerido
Petición
curl "https://api.enviadores.com.mx/api/v1/shipments/ID" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "shipment": {
      "id": "20260713-000123",
      "guia": "1234567890",
      "carrier": "Estafeta",
      "service": "Terrestre",
      "status": "in_transit",
      "status_label": "En tránsito",
      "total": 145.5,
      "currency": "MXN",
      "created_at": "2026-07-13T18:45:12Z",
      "tracking_pending": false,
      "label_url": "/api/v1/labels/20260713-000123",
      "packages": [
        {
          "weight_kg": 1,
          "length_cm": 20,
          "width_cm": 15,
          "height_cm": 10
        }
      ],
      "billable_weight_kg": 1,
      "declared_value": null,
      "content": "Ropa",
      "sender": {
        "name": "Juan Pérez",
        "city": "Ciudad de México",
        "state": "CDMX",
        "postal_code": "01000"
      },
      "recipient": {
        "name": "María López",
        "city": "Monterrey",
        "state": "Nuevo León",
        "postal_code": "64000"
      }
    }
  },
  "requestId": "req_f6a7b8c9d0"
}

tracking

Rastreo público

Rastrear cualquier guía

get/tracking/{guia}

Datos públicos de rastreo (mismo payload PII-mínimo de la página pública: estatus canónico, ciudad/estado de origen y destino, línea de tiempo con nombres de receptor depurados).

›Ver reglas

No requiere que la guía sea propia. Agnóstico al modo de la llave (funciona igual con ek_test_). Requiere scope tracking:read.

Parámetros

  • guiastring· pathrequerido
    Número de guía (coincidencia EXACTA).

Respuestas

HTTPCódigoQué significa
200Estado de rastreo.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDGenérico y de forma estable para toda guía desconocida/ inválida (sin oráculo de existencia).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

›Campos de la respuesta 200
  • guiastringrequerido
  • carrierstring | nullrequerido
  • statusstringrequerido
  • status_labelstringrequerido
  • originobjectrequerido
    Mostrar 2 campos
    • citystring | null
    • statestring | null
  • destinationobjectrequerido
    Mostrar 2 campos
    • citystring | null
    • statestring | null
  • created_atstring | nullrequerido
  • eventsobject[]requerido
    Mostrar 3 campos
    • timestampstring | null
    • descriptionstring | null

      Nombres de receptor (POD) depurados.

    • locationstring | null
Petición
curl "https://api.enviadores.com.mx/api/v1/tracking/GUIA" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "guia": "1234567890",
    "carrier": "Estafeta",
    "status": "in_transit",
    "status_label": "En tránsito",
    "origin": {
      "city": "Ciudad de México",
      "state": "CDMX"
    },
    "destination": {
      "city": "Monterrey",
      "state": "Nuevo León"
    },
    "created_at": "2026-07-13T18:45:12Z",
    "events": [
      {
        "timestamp": "2026-07-13T20:10:00Z",
        "description": "Recolectado",
        "location": "Ciudad de México, CDMX"
      },
      {
        "timestamp": "2026-07-14T09:30:00Z",
        "description": "En tránsito al destino",
        "location": "Querétaro, QRO"
      }
    ]
  },
  "requestId": "req_c3d4e5f6a7"
}

cancellations

Solicitudes de cancelación

Solicitar la cancelación de un envío propio

post/cancellations

Crea una solicitud de cancelación (estado inicial pending).

›Ver reglas

El avance del estado es operado por el staff/los procesos de la plataforma — consulta GET /cancellations/{id}. El reembolso esperado es lo que se cobró por el envío. Requiere scope cancellations:write.

›Campos del cuerpo (3)
  • shipment_idstringrequerido

    Id del envío propio a cancelar.

  • reason_code"wrong_address" | "duplicate" | "customer_changed_mind" | "damaged" | "never_shipped" | "not_picked_up" | "other"
  • reason_textstring

Respuestas

HTTPCódigoQué significa
201Solicitud creada.
400INVALID_JSON
REQUEST_FAILEDRechazo del servicio
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403FORBIDDENRechazo de permiso del servicio
INSUFFICIENT_SCOPE
PDV_NOT_ALLOWED.
404NOT_FOUNDEl envío no existe o no pertenece al usuario de la llave (respuesta idéntica).
409CONFLICTEl envío ya está cancelado o ya existe una solicitud abierta (details.existing_request_id).
422VALIDATION_ERRORDetails.fields
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500REQUEST_ERROR.
›Campos de la respuesta 201
  • idintegerrequerido
  • display_idstringrequerido

    Live: formato CR%08d. Sandbox: SBX-CR-<id>.

  • status"pending" | "cancelled"requerido

    Live devuelve pending (la solicitud entra a la cola de staff). Sandbox devuelve cancelled (cancelación síncrona y terminal — ver x-test-mode).

  • refund_status"not_applicable" | "refunded"requerido

    Live: not_applicable (reembolso lo resuelve el staff). Sandbox: refunded (reembolso inmediato).

  • refund_amount_expectednumber | null
  • cancellation_deadlinestring | null

    ISO-8601 UTC (Z). Siempre null en sandbox.

Petición
curl -X POST "https://api.enviadores.com.mx/api/v1/cancellations" \  -H "Authorization: Bearer $ENVIADORES_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "shipment_id": "20260713-000123",  "reason_code": "customer_changed_mind"}'
Respuesta 201
{
  "success": true,
  "data": {
    "id": 42,
    "display_id": "CR00000042",
    "status": "pending",
    "refund_status": "not_applicable",
    "refund_amount_expected": 145.5,
    "cancellation_deadline": "2026-07-14T18:45:12Z"
  },
  "requestId": "req_a7b8c9d0e1"
}

Consultar una solicitud de cancelación

get/cancellations/{id}

Visibilidad idéntica a la del envío que cancela (propios por defecto;

›Ver reglas

los del PDV con X-PDV-ID + scope shipments:read:pdv) — deliberadamente en paralelo, para que un envío que puedes leer nunca tenga una cancelación que no.

Máquina de estados: pending → approved → in_progress → cancelled | rejected_by_carrier; otros estados: not_cancellable (la guía no admite cancelación vía API — terminal), reported_to_carrier (reporte manual ante la paquetería, sigue abierta), rejected, failed, expired. Reembolso: not_applicable → refund_in_progress → refunded | refund_denied (los reembolsos son pass-through de la paquetería; refunded cubre también el reembolso manual por override administrativo). Requiere scope cancellations:read.

Parámetros

  • idinteger· pathrequerido
  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

Respuestas

HTTPCódigoQué significa
200La solicitud.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDNo existe o el envío subyacente no pertenece al usuario de la llave (respuesta idéntica).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500GET_ERROR.
›Campos de la respuesta 200
  • cancellationCancellationrequerido
    Mostrar 13 campos
    • idintegerrequerido
    • display_idstringrequerido
    • shipment_idstring | nullrequerido
    • status"pending" | "approved" | "in_progress" | "cancelled" | "rejected_by_carrier" | "not_cancellable" | "reported_to_carrier" | "rejected" | "failed" | "expired"requerido
    • refund_status"not_applicable" | "refund_in_progress" | "refunded" | "refund_denied"requerido
    • reason_codestring | nullrequerido
    • reason_textstring | nullrequerido
    • refund_amount_expectednumber | nullrequerido
    • refund_amount_actualnumber | nullrequerido
    • cancellation_deadlinestring | nullrequerido
    • created_atstring | nullrequerido
    • updated_atstring | nullrequerido
    • resolved_atstring | nullrequerido
Petición
curl "https://api.enviadores.com.mx/api/v1/cancellations/ID" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "cancellation": {
      "id": 42,
      "display_id": "CR00000042",
      "shipment_id": "20260713-000123",
      "status": "cancelled",
      "refund_status": "refunded",
      "reason_code": "customer_changed_mind",
      "reason_text": null,
      "refund_amount_expected": 145.5,
      "refund_amount_actual": 145.5,
      "cancellation_deadline": "2026-07-14T18:45:12Z",
      "created_at": "2026-07-13T19:02:33Z",
      "updated_at": "2026-07-15T10:12:05Z",
      "resolved_at": "2026-07-15T10:12:05Z"
    }
  },
  "requestId": "req_b8c9d0e1f2"
}

pickups

Recolecciones a domicilio (solicitudes; las confirma nuestro equipo)

Listar tus solicitudes de recolección

get/pickups

Las solicitudes de recolección de la cuenta, de la más reciente a la más antigua — qué pediste, para cuándo, y si nuestro equipo ya la gestionó.

›Ver reglas

Visibilidad idéntica a la del listado de envíos: por omisión las recolecciones de los envíos que creó el usuario de la llave; con X-PDV-ID validado y el scope ampliado shipments:read:pdv, las de ese punto de venta (las dos ramas son EXCLUYENTES). Mientras la programación de recolecciones no esté habilitada en la cuenta, este listado responde 200 con una página vacía — un listado de algo que no puedes usar no tiene nada que reportar. Requiere scope pickups:read.

Parámetros

  • pageinteger· query
  • limitinteger· query
  • status"pending" | "scheduled" | "awaiting_confirmation" | "failed" | "cancelled"· query
    Filtra por estatus público de la recolección — el mismo valor que devuelve el campo status. más

    awaiting_confirmation = la registramos y nuestro equipo aún la está gestionando; scheduled = ya quedó con la paquetería. Un valor fuera del enum es 422 VALIDATION_ERROR.

  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

Respuestas

HTTPCódigoQué significa
200Página de solicitudes de recolección.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDRuta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
422VALIDATION_ERRORstatus fuera del enum o paginación inválida (campos en details.fields).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500GET_ERROR.
›Campos de la respuesta 200
  • pickupsPickup[]requerido
    Mostrar 12 campos
    • idintegerrequerido

      Id numérico de la recolección.

    • display_idstringrequerido

      Live: formato PR%08d. Sandbox: SBX-PU-<id>.

    • shipment_idstring | nullrequerido
    • status"pending" | "scheduled" | "awaiting_confirmation" | "failed" | "cancelled"requerido

      Una solicitud SIEMPRE nace en awaiting_confirmation (se está gestionando; NO la presentes como confirmada). Pasa a scheduled cuando el equipo la confirma con la paquetería — ahí llegan confirmed_window_* y confirmation_number, y se envía el correo al solicitante. failed = no se pudo agendar (entregar en sucursal). cancelled = la canceló quien la pidió. pending no se usa. El sandbox devuelve awaiting_confirmation, igual que producción.

    • carrierstring | null

      Paquetería física que hará la recolección.

    • requested_window_startstring | null

      ISO-8601 UTC (Z). Inicio de la ventana solicitada.

    • requested_window_endstring | null

      ISO-8601 UTC (Z). Fin de la ventana solicitada.

    • confirmed_window_startstring | null

      ISO-8601 UTC (Z). Ventana confirmada por la paquetería, cuando la devuelve.

    • confirmed_window_endstring | null

      ISO-8601 UTC (Z).

    • confirmation_numberstring | null

      Folio/referencia de recolección de la paquetería.

    • created_atstring | null
    • updated_atstring | null
  • paginationPaginationrequerido
    Mostrar 3 campos
    • pageintegerrequerido
    • limitintegerrequerido
    • totalintegerrequerido
Petición
curl "https://api.enviadores.com.mx/api/v1/pickups" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "pickups": [
      {
        "id": 42,
        "display_id": "PR00000042",
        "shipment_id": "20260830-000123",
        "status": "awaiting_confirmation",
        "carrier": "Estafeta",
        "requested_window_start": "2026-08-31T16:00:00Z",
        "requested_window_end": "2026-08-31T23:00:00Z",
        "confirmed_window_start": null,
        "confirmed_window_end": null,
        "confirmation_number": null,
        "created_at": "2026-08-30T10:00:00Z",
        "updated_at": "2026-08-30T10:00:02Z"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 1
    }
  },
  "requestId": "req_a7b8c9d0e1"
}

Solicitar la recolección de un envío propio

post/pickups

Solicita que la paquetería pase por el paquete al domicilio del remitente, en lugar de que el remitente lo lleve a sucursal.

›Ver reglas

Esto es una solicitud, no una reserva. No agendamos con la paquetería de forma automática: el equipo de Enviadores la gestiona y avisa por correo al solicitante cuando queda. Por eso la respuesta siempre regresa status: awaiting_confirmation — nunca reportes al usuario que ya hay mensajero confirmado. Consulta GET /pickups/{id} para el estado final, o espera el correo.

Por qué. Un 200 de una paquetería no es prueba de que el mensajero vaya a llegar, y el peor error posible aquí es decirle a alguien que su recolección está confirmada y dejarlo esperando. Preferimos ser lentos y ciertos.

Sólo se puede pedir para Estafeta, FedEx, DHL, UPS y Paquetexpress (422 PICKUP_CARRIER_NOT_ELIGIBLE para el resto; con cualquier otra, el remitente lleva el paquete a un punto de esa empresa). Una sola recolección abierta por envío.

pickup_date, ready_time y close_time son hora local de México (America/Mexico_City); la respuesta devuelve las ventanas en UTC (Z).

La recolección no cobra saldo; cualquier cargo de la paquetería llega después por la vía de sobrecargos, igual que en el carril web.

La dirección de recolección es la del propio envío — este endpoint no la cambia. Requiere scope pickups:write.

›Campos del cuerpo (5)
  • shipment_idstringrequerido

    Id de un envío propio con guía, no entregado ni cancelado.

  • pickup_datestringrequerido

    Fecha local (America/Mexico_City). Hoy o después.

  • ready_timestringrequerido

    HH:MM 24h local — desde cuándo está listo el paquete.

  • close_timestringrequerido

    HH:MM 24h local — hasta cuándo puede pasar el mensajero. Debe ser posterior a ready_time.

  • notesstring

    Indicación para el mensajero.

Respuestas

HTTPCódigoQué significa
201Solicitud registrada — status siempre es awaiting_confirmation. Un 201 significa que recibimos la solicitud, NO que haya mensajero confirmado.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403PICKUP_UNAVAILABLELa programación de recolecciones aún no está habilitada en esta cuenta (el paquete puede entregarse en sucursal; escríbenos para agendarla manualmente).
PICKUP_UNLOCK_REQUIREDLa cuenta todavía sólo puede usar iMile Terrestre y Estafeta Terrestre y Día Siguiente sin recolección (estas se dejan en una sucursal Estafeta); se desbloquea al verificar identidad (INE) en Mi cuenta o al acumular $2,500 MXN en recargas.
FORBIDDEN
INSUFFICIENT_SCOPE
PDV_NOT_ALLOWED.
404NOT_FOUNDEl envío no existe o no es de esta llave (respuesta idéntica, sin oráculo de existencia).
409CONFLICTYa existe una recolección abierta para este envío (details.existing_request_id).
422VALIDATION_ERRORCampos en details.fields
PICKUP_CARRIER_NOT_ELIGIBLELa paquetería del envío no está en la lista (details.eligible_carriers); el paquete puede entregarse en sucursal
REQUEST_FAILEDEl envío no admite recolección (sin guía, entregado, cancelado, o fecha/hora imposible).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500REQUEST_ERROR.
›Campos de la respuesta 201
  • pickupPickuprequerido
    Mostrar 12 campos
    • idintegerrequerido

      Id numérico de la recolección.

    • display_idstringrequerido

      Live: formato PR%08d. Sandbox: SBX-PU-<id>.

    • shipment_idstring | nullrequerido
    • status"pending" | "scheduled" | "awaiting_confirmation" | "failed" | "cancelled"requerido

      Una solicitud SIEMPRE nace en awaiting_confirmation (se está gestionando; NO la presentes como confirmada). Pasa a scheduled cuando el equipo la confirma con la paquetería — ahí llegan confirmed_window_* y confirmation_number, y se envía el correo al solicitante. failed = no se pudo agendar (entregar en sucursal). cancelled = la canceló quien la pidió. pending no se usa. El sandbox devuelve awaiting_confirmation, igual que producción.

    • carrierstring | null

      Paquetería física que hará la recolección.

    • requested_window_startstring | null

      ISO-8601 UTC (Z). Inicio de la ventana solicitada.

    • requested_window_endstring | null

      ISO-8601 UTC (Z). Fin de la ventana solicitada.

    • confirmed_window_startstring | null

      ISO-8601 UTC (Z). Ventana confirmada por la paquetería, cuando la devuelve.

    • confirmed_window_endstring | null

      ISO-8601 UTC (Z).

    • confirmation_numberstring | null

      Folio/referencia de recolección de la paquetería.

    • created_atstring | null
    • updated_atstring | null
Petición
curl -X POST "https://api.enviadores.com.mx/api/v1/pickups" \  -H "Authorization: Bearer $ENVIADORES_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "shipment_id": "20260830-000123",  "pickup_date": "2026-08-31",  "ready_time": "10:00",  "close_time": "17:00",  "notes": "Timbre azul, preguntar por Ana"}'
Respuesta 201
{
  "success": true,
  "data": {
    "pickup": {
      "id": 42,
      "display_id": "PR00000042",
      "shipment_id": "20260830-000123",
      "status": "awaiting_confirmation",
      "carrier": "Estafeta",
      "requested_window_start": "2026-08-31T16:00:00Z",
      "requested_window_end": "2026-08-31T23:00:00Z",
      "confirmed_window_start": null,
      "confirmed_window_end": null,
      "confirmation_number": null,
      "created_at": "2026-08-30T10:00:00Z",
      "updated_at": "2026-08-30T10:00:02Z"
    }
  },
  "requestId": "req_a7b8c9d0e1"
}

Consultar una recolección

get/pickups/{id}

Estado de una solicitud de recolección propia — úsalo para dar seguimiento mientras está en awaiting_confirmation (el solicitante también recibe un correo cuando se resuelve). Visibilidad idéntica a la del envío al que pertenece, en paralelo deliberado con GET /cancellations/{id}. Requiere scope pickups:read.

Parámetros

  • idinteger· pathrequerido
  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

Respuestas

HTTPCódigoQué significa
200La recolección.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDNo existe o el envío subyacente no pertenece al usuario de la llave (respuesta idéntica).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500GET_ERROR.
›Campos de la respuesta 200
  • pickupPickuprequerido
    Mostrar 12 campos
    • idintegerrequerido

      Id numérico de la recolección.

    • display_idstringrequerido

      Live: formato PR%08d. Sandbox: SBX-PU-<id>.

    • shipment_idstring | nullrequerido
    • status"pending" | "scheduled" | "awaiting_confirmation" | "failed" | "cancelled"requerido

      Una solicitud SIEMPRE nace en awaiting_confirmation (se está gestionando; NO la presentes como confirmada). Pasa a scheduled cuando el equipo la confirma con la paquetería — ahí llegan confirmed_window_* y confirmation_number, y se envía el correo al solicitante. failed = no se pudo agendar (entregar en sucursal). cancelled = la canceló quien la pidió. pending no se usa. El sandbox devuelve awaiting_confirmation, igual que producción.

    • carrierstring | null

      Paquetería física que hará la recolección.

    • requested_window_startstring | null

      ISO-8601 UTC (Z). Inicio de la ventana solicitada.

    • requested_window_endstring | null

      ISO-8601 UTC (Z). Fin de la ventana solicitada.

    • confirmed_window_startstring | null

      ISO-8601 UTC (Z). Ventana confirmada por la paquetería, cuando la devuelve.

    • confirmed_window_endstring | null

      ISO-8601 UTC (Z).

    • confirmation_numberstring | null

      Folio/referencia de recolección de la paquetería.

    • created_atstring | null
    • updated_atstring | null
Petición
curl "https://api.enviadores.com.mx/api/v1/pickups/ID" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "pickup": {
      "id": 42,
      "display_id": "PR00000042",
      "shipment_id": "20260830-000123",
      "status": "awaiting_confirmation",
      "carrier": "Estafeta",
      "requested_window_start": "2026-08-31T16:00:00Z",
      "requested_window_end": "2026-08-31T23:00:00Z",
      "confirmed_window_start": null,
      "confirmed_window_end": null,
      "confirmation_number": null,
      "created_at": "2026-08-30T10:00:00Z",
      "updated_at": "2026-08-30T10:00:02Z"
    }
  },
  "requestId": "req_a7b8c9d0e1"
}

balance

Saldo de créditos

Consultar el saldo que cargarían tus envíos

get/balance

Sin X-PDV-ID: el saldo del usuario de la llave.

›Ver reglas

Con X-PDV-ID (llaves admin, misma allowlist que POST /shipments): el fondo del punto de venta — exactamente el principal que un envío bajo ese header cargaría. available es lo gastable; held son autorizaciones pendientes; balance = available + held. Requiere scope balance:read.

Parámetros

  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

Respuestas

HTTPCódigoQué significa
200Saldo del principal.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDRuta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • balancenumberrequerido

    available + held (total del principal en el libro mayor).

  • heldnumberrequerido

    Autorizaciones pendientes (holds).

  • availablenumberrequerido

    Lo gastable por un envío nuevo.

  • currency"MXN"requerido
  • scopeobjectrequerido
    Mostrar 2 campos
    • type"user" | "pdv"requerido
    • idstringrequerido
Petición
curl "https://api.enviadores.com.mx/api/v1/balance" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "balance": 4820.5,
    "held": 145.5,
    "available": 4675,
    "currency": "MXN",
    "scope": {
      "type": "user",
      "id": "U0000001"
    }
  },
  "requestId": "req_d4e5f6a7b8"
}

labels

Etiquetas (guías)

Descargar la etiqueta de un envío propio

get/labels/{envio_id}

Transmite la copia de la etiqueta que guarda la plataforma (PDF/ZPL);

›Ver reglas

si aún no existe, la obtiene de la paquetería, la guarda y la transmite. Nunca redirige a la URL de la paquetería. Con ?format=json responde JSON (LabelLink) en lugar de bytes. Solo envíos creados por el usuario de la llave (todos los roles). Requiere scope labels:read.

Parámetros

  • envio_idstring· pathrequerido
  • format"json"· query
    json = en vez de transmitir bytes responde JSON con un enlace firmado y con vencimiento a la copia de Enviadores (LabelLink). más

    Es la vía para agentes (la herramienta MCP get_label): un agente no puede consumir el PDF transmitido, así que un enlace que se abre sin llave es la única respuesta útil.

Respuestas

HTTPCódigoQué significa
200Bytes de la etiqueta (copia de la plataforma). Con ?format=json: application/json con LabelLink.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDNo existe, no es del usuario de la llave, o no hay etiqueta disponible (respuesta idéntica en todos los casos).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

›Campos de la respuesta 200
  • shipment_idstringrequerido
  • guiastring

    Solo en sandbox (la vía en vivo omite el campo).

  • label_urlstring | nullrequerido

    En vivo: enlace firmado a la copia de Enviadores (https://api.enviadores.com.mx/api/v1/label-files/{envio_id}/{exp}/{firma}.pdf). Se abre sin llave, vence en expires_at (30 días; pide uno nuevo con esta misma llamada) y nunca es la URL de la paquetería. En sandbox: enlace a la etiqueta de prueba. null cuando la etiqueta aún no está disponible; note lo explica.

  • expires_atstring

    Solo en vivo: cuándo deja de funcionar label_url (UTC).

  • format"pdf"requerido
  • sandboxbooleanrequerido

    true con llaves ek_test_: etiqueta de prueba sin validez.

  • notestring

    Presente solo cuando hay algo que aclarar (etiqueta sandbox, o sin URL directa).

Petición
curl "https://api.enviadores.com.mx/api/v1/labels/ENVIO_ID" \  -H "Authorization: Bearer $ENVIADORES_API_KEY" \  -o guia.pdf

addresses

Directorio: remitentes y destinatarios

Listar remitentes del directorio

get/senders

Remitentes visibles para la llave.

›Ver reglas

Visibilidad: llaves de cliente y llaves admin SIN X-PDV-ID ven solo los registros creados por el usuario de la llave; una llave admin con X-PDV-ID validado ve exactamente el directorio de ese punto de venta (el mismo que usa el personal de mostrador). Nunca hay lectura global en este carril. Requiere scope addresses:read. Este endpoint SÍ devuelve dirección completa y teléfono — el guardián es la visibilidad, no el recorte de campos.

Parámetros

  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

  • pageinteger· query
  • limitinteger· query
  • postal_codestring· query
    Filtro exacto por CP (5 dígitos; otro formato → 422).
  • qstring· query
    Búsqueda de texto sobre nombre, razón social, RFC, teléfono, email y dirección (calle/colonia/municipio/estado/CP). más

    Cada palabra debe aparecer en ALGÚN campo, no necesariamente en el mismo: arturo meijueiro encuentra un registro cuyo nombre es ARTURO y cuyo apellido solo aparece en el email. Insensible a acentos y mayúsculas; se consideran las primeras 5 palabras.

Respuestas

HTTPCódigoQué significa
200Página de remitentes.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

422VALIDATION_ERRORFiltro inválido (details.fields).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • sendersSender[]requerido
    Mostrar 15 campos
    • idstringrequerido
    • namestring | nullrequerido
    • apellido_paternonullrequerido

      Siempre null: el nombre completo está en name.

    • apellido_maternonullrequerido

      Siempre null: el nombre completo está en name.

    • companystring | nullrequerido
    • rfcstring | nullrequerido
    • phonestring | nullrequerido
    • emailstring | nullrequerido
    • streetstring | nullrequerido
    • numberstring | nullrequerido
    • coloniastring | nullrequerido
    • citystring | nullrequerido
    • statestring | nullrequerido
    • postal_codestring | nullrequerido
    • created_atstring | nullrequerido

      ISO-8601 UTC (Z).

  • paginationPaginationrequerido
    Mostrar 3 campos
    • pageintegerrequerido
    • limitintegerrequerido
    • totalintegerrequerido
Petición
curl "https://api.enviadores.com.mx/api/v1/senders" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "senders": [
      {
        "id": "C0012345",
        "name": "Juan Pérez",
        "apellido_paterno": null,
        "apellido_materno": null,
        "company": null,
        "rfc": null,
        "phone": "5512345678",
        "email": "[email protected]",
        "street": "Av. Insurgentes Sur",
        "number": "600",
        "colonia": "Del Valle",
        "city": "Ciudad de México",
        "state": "CDMX",
        "postal_code": "01000",
        "created_at": "2026-07-13T18:40:00Z"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 1
    }
  },
  "requestId": "req_c9d0e1f2a3"
}

Crear (o reutilizar) un remitente

post/senders

Create-or-reuse con el MISMO dedupe exacto normalizado que usa POST /shipments al registrar direcciones inline, evaluado sobre las filas visibles para la llave: si ya existe un remitente idéntico visible, se devuelve ese (created: false, 200);

›Ver reglas

si no, se crea (created: true, 201). El postal_code debe tener 5 dígitos y EXISTIR en el catálogo postal (SEPOMEX); si city/state vienen, deben ser coherentes con el CP (un desajuste responde 422 nombrando el valor esperado) y si se omiten se AUTOCOMPLETAN del catálogo — la misma validación de exactitud que POST /contacts/import. Requiere scope addresses:write. Con X-PDV-ID (llave admin), la fila creada pertenece al directorio de ese PDV y queda visible para su personal.

Parámetros

  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

›Campos del cuerpo (14)
  • namestringrequerido

    Nombre de pila (nombre).

  • apellido_paternostring

    DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

  • apellido_maternostring

    DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

  • companystring

    Razón social.

  • rfcstring
  • phonestringrequerido
  • emailstring
  • streetstringrequerido
  • numberstring

    Número exterior.

  • coloniastringrequerido
  • citystring

    Opcional: si se omite se autocompleta del catálogo SEPOMEX según el CP; si viene, debe coincidir con el CP (si no → 422 nombrando la ciudad esperada).

  • statestring

    Opcional: si se omite se autocompleta del catálogo SEPOMEX según el CP; si viene, debe coincidir con el CP (si no → 422 nombrando el estado esperado).

  • postal_codestringrequerido

    5 dígitos; debe EXISTIR en el catálogo postal (SEPOMEX).

  • country"MX"

    Opcional. Solo MX — cualquier otro valor responde 422.

Respuestas

HTTPCódigoQué significa
200Remitente idéntico ya existente reutilizado (created: false).
201Remitente creado (created: true).
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

422VALIDATION_ERRORCampos faltantes/inválidos o desconocidos (details.fields).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500ADDRESS_ERRORNo se pudo registrar; reintenta.
›Campos de la respuesta 200
  • senderSenderrequerido
    Mostrar 15 campos
    • idstringrequerido
    • namestring | nullrequerido
    • apellido_paternonullrequerido

      Siempre null: el nombre completo está en name.

    • apellido_maternonullrequerido

      Siempre null: el nombre completo está en name.

    • companystring | nullrequerido
    • rfcstring | nullrequerido
    • phonestring | nullrequerido
    • emailstring | nullrequerido
    • streetstring | nullrequerido
    • numberstring | nullrequerido
    • coloniastring | nullrequerido
    • citystring | nullrequerido
    • statestring | nullrequerido
    • postal_codestring | nullrequerido
    • created_atstring | nullrequerido

      ISO-8601 UTC (Z).

  • createdbooleanrequerido
Petición
curl -X POST "https://api.enviadores.com.mx/api/v1/senders" \  -H "Authorization: Bearer $ENVIADORES_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "name": "Juan Pérez García",  "phone": "5512345678",  "email": "[email protected]",  "street": "Av. Insurgentes Sur",  "number": "600",  "colonia": "Del Valle",  "city": "Ciudad de México",  "state": "CDMX",  "postal_code": "01000"}'
Respuesta 200
{
  "success": true,
  "data": {
    "sender": {
      "id": "C0012345",
      "name": "Juan Pérez",
      "apellido_paterno": null,
      "apellido_materno": null,
      "company": null,
      "rfc": null,
      "phone": "5512345678",
      "email": "[email protected]",
      "street": "Av. Insurgentes Sur",
      "number": "600",
      "colonia": "Del Valle",
      "city": "Ciudad de México",
      "state": "CDMX",
      "postal_code": "01000",
      "created_at": "2026-07-13T18:40:00Z"
    },
    "created": false
  },
  "requestId": "req_e1f2a3b4c5"
}
Respuesta 201
{
  "success": true,
  "data": {
    "sender": {
      "id": "C0012345",
      "name": "Juan Pérez",
      "apellido_paterno": null,
      "apellido_materno": null,
      "company": null,
      "rfc": null,
      "phone": "5512345678",
      "email": "[email protected]",
      "street": "Av. Insurgentes Sur",
      "number": "600",
      "colonia": "Del Valle",
      "city": "Ciudad de México",
      "state": "CDMX",
      "postal_code": "01000",
      "created_at": "2026-07-13T18:40:00Z"
    },
    "created": true
  },
  "requestId": "req_d0e1f2a3b4"
}

Consultar un remitente

get/senders/{id}

Requiere scope addresses:read. 404 idéntico para inexistente y no-visible (sin oráculo).

Parámetros

  • idstring· pathrequerido
  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

Respuestas

HTTPCódigoQué significa
200El remitente.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDRuta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • senderSenderrequerido
    Mostrar 15 campos
    • idstringrequerido
    • namestring | nullrequerido
    • apellido_paternonullrequerido

      Siempre null: el nombre completo está en name.

    • apellido_maternonullrequerido

      Siempre null: el nombre completo está en name.

    • companystring | nullrequerido
    • rfcstring | nullrequerido
    • phonestring | nullrequerido
    • emailstring | nullrequerido
    • streetstring | nullrequerido
    • numberstring | nullrequerido
    • coloniastring | nullrequerido
    • citystring | nullrequerido
    • statestring | nullrequerido
    • postal_codestring | nullrequerido
    • created_atstring | nullrequerido

      ISO-8601 UTC (Z).

Petición
curl "https://api.enviadores.com.mx/api/v1/senders/ID" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "sender": {
      "id": "C0012345",
      "name": "Juan Pérez",
      "apellido_paterno": null,
      "apellido_materno": null,
      "company": null,
      "rfc": null,
      "phone": "5512345678",
      "email": "[email protected]",
      "street": "Av. Insurgentes Sur",
      "number": "600",
      "colonia": "Del Valle",
      "city": "Ciudad de México",
      "state": "CDMX",
      "postal_code": "01000",
      "created_at": "2026-07-13T18:40:00Z"
    }
  },
  "requestId": "req_f2a3b4c5d6"
}

Editar un remitente

patch/senders/{id}

Actualización PARCIAL de un remitente visible.

›Ver reglas

Requiere scope addresses:write. Solo los campos presentes cambian; se debe enviar al menos uno. Los requeridos pueden cambiarse pero no vaciarse; los opcionales aceptan null para limpiarse. Los apellidos NO forman parte del dedupe, así que PATCH es la forma de completarlos en una fila creada sin ellos. 404 idéntico para inexistente y no-visible (sin oráculo).

Parámetros

  • idstring· pathrequerido
  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

›Campos del cuerpo (14)
  • namestring
  • apellido_paternostring | null

    DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

  • apellido_maternostring | null

    DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

  • companystring | null
  • rfcstring | null
  • phonestring
  • emailstring | null
  • streetstring
  • numberstring | null
  • coloniastring
  • citystring
  • statestring
  • postal_codestring
  • country"MX"

Respuestas

HTTPCódigoQué significa
200El remitente actualizado.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDRuta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
409DUPLICATE_ADDRESSLa edición dejaría este remitente idéntico a OTRO ya visible para la llave. La fila no se modifica.
422VALIDATION_ERROR(details.fields) — campo desconocido, requerido vaciado, country ≠ MX, apellido (deprecated) > 50, o cuerpo sin campos editables.
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • senderSenderrequerido
    Mostrar 15 campos
    • idstringrequerido
    • namestring | nullrequerido
    • apellido_paternonullrequerido

      Siempre null: el nombre completo está en name.

    • apellido_maternonullrequerido

      Siempre null: el nombre completo está en name.

    • companystring | nullrequerido
    • rfcstring | nullrequerido
    • phonestring | nullrequerido
    • emailstring | nullrequerido
    • streetstring | nullrequerido
    • numberstring | nullrequerido
    • coloniastring | nullrequerido
    • citystring | nullrequerido
    • statestring | nullrequerido
    • postal_codestring | nullrequerido
    • created_atstring | nullrequerido

      ISO-8601 UTC (Z).

Petición
curl -X PATCH "https://api.enviadores.com.mx/api/v1/senders/ID" \  -H "Authorization: Bearer $ENVIADORES_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "name": "Juan Pérez García",  "email": null}'
Respuesta 200
{
  "success": true,
  "data": {
    "sender": {
      "id": "C0012345",
      "name": "Juan Pérez García",
      "apellido_paterno": null,
      "apellido_materno": null,
      "company": null,
      "rfc": null,
      "phone": "5512345678",
      "email": "[email protected]",
      "street": "Av. Insurgentes Sur",
      "number": "600",
      "colonia": "Del Valle",
      "city": "Ciudad de México",
      "state": "CDMX",
      "postal_code": "01000",
      "created_at": "2026-07-13T18:40:00Z"
    }
  },
  "requestId": "req_a3b4c5d6e7"
}

Listar destinatarios del directorio

get/recipients

Misma visibilidad que GET /senders. Un destinatario siempre pertenece a un remitente (sender_id). Requiere scope addresses:read.

Parámetros

  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

  • pageinteger· query
  • limitinteger· query
  • sender_idstring· query
    Solo destinatarios de este remitente.
  • postal_codestring· query
  • qstring· query
    Búsqueda de texto sobre nombre, alias, teléfono, email y dirección (calle/colonia/ciudad/estado/CP). más

    Cada palabra debe aparecer en ALGÚN campo, no necesariamente en el mismo. Insensible a acentos y mayúsculas; se consideran las primeras 5 palabras.

Respuestas

HTTPCódigoQué significa
200Página de destinatarios.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

422VALIDATION_ERRORFiltro inválido (details.fields).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • recipientsRecipient[]requerido
    Mostrar 15 campos
    • idstringrequerido
    • sender_idstringrequerido
    • aliasstring | nullrequerido
    • namestring | nullrequerido
    • phonestring | nullrequerido
    • emailstring | nullrequerido
    • streetstring | nullrequerido
    • numberstring | nullrequerido
    • coloniastring | nullrequerido
    • citystring | nullrequerido
    • statestring | nullrequerido
    • postal_codestring | nullrequerido
    • referenciastring | nullrequerido
    • delivery_instructionsstring | nullrequerido
    • created_atstring | nullrequerido

      ISO-8601 UTC (Z).

  • paginationPaginationrequerido
    Mostrar 3 campos
    • pageintegerrequerido
    • limitintegerrequerido
    • totalintegerrequerido
Petición
curl "https://api.enviadores.com.mx/api/v1/recipients" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "recipients": [
      {
        "id": "D0067890",
        "sender_id": "C0012345",
        "alias": "Oficina",
        "name": "María López",
        "phone": "8187654321",
        "email": null,
        "street": "Av. Constitución",
        "number": "400",
        "colonia": "Centro",
        "city": "Monterrey",
        "state": "Nuevo León",
        "postal_code": "64000",
        "referencia": "Edificio azul, junto a la farmacia",
        "delivery_instructions": "Entregar en recepción",
        "created_at": "2026-07-13T18:41:00Z"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 1
    }
  },
  "requestId": "req_b4c5d6e7f8"
}

Crear (o reutilizar) un destinatario

post/recipients

Create-or-reuse bajo el remitente sender_id (debe ser visible para la llave;

›Ver reglas

si no → 404 ciego). Mismo dedupe normalizado que el carril de envíos; en una reutilización, alias/referencia/delivery_instructions NO se aplican (no forman parte de la identidad — la fila existente se devuelve intacta). El postal_code debe tener 5 dígitos y EXISTIR en el catálogo postal (SEPOMEX); si city/state vienen, deben ser coherentes con el CP (un desajuste responde 422 nombrando el valor esperado) y si se omiten se AUTOCOMPLETAN del catálogo — la misma validación de exactitud que POST /contacts/import. Requiere scope addresses:write.

Parámetros

  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

›Campos del cuerpo (14)
  • sender_idstringrequerido

    Remitente dueño (visible para la llave; si no → 404 ciego).

  • aliasstring

    Nombre descriptivo ("Casa", "Oficina").

  • namestringrequerido
  • phonestringrequerido
  • emailstring
  • streetstringrequerido
  • numberstring
  • coloniastringrequerido
  • citystring

    Opcional: si se omite se autocompleta del catálogo SEPOMEX según el CP; si viene, debe coincidir con el CP (si no → 422 nombrando la ciudad esperada).

  • statestring

    Opcional: si se omite se autocompleta del catálogo SEPOMEX según el CP; si viene, debe coincidir con el CP (si no → 422 nombrando el estado esperado).

  • postal_codestringrequerido

    5 dígitos; debe EXISTIR en el catálogo postal (SEPOMEX).

  • country"MX"
  • referenciastring

    Referencias del domicilio.

  • delivery_instructionsstring

Respuestas

HTTPCódigoQué significa
200Destinatario idéntico ya existente reutilizado (created: false).
201Destinatario creado (created: true).
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDsender_id inexistente o no visible (respuesta idéntica).
422VALIDATION_ERRORCampos faltantes/inválidos o desconocidos (details.fields).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500ADDRESS_ERRORNo se pudo registrar; reintenta.
›Campos de la respuesta 200
  • recipientRecipientrequerido
    Mostrar 15 campos
    • idstringrequerido
    • sender_idstringrequerido
    • aliasstring | nullrequerido
    • namestring | nullrequerido
    • phonestring | nullrequerido
    • emailstring | nullrequerido
    • streetstring | nullrequerido
    • numberstring | nullrequerido
    • coloniastring | nullrequerido
    • citystring | nullrequerido
    • statestring | nullrequerido
    • postal_codestring | nullrequerido
    • referenciastring | nullrequerido
    • delivery_instructionsstring | nullrequerido
    • created_atstring | nullrequerido

      ISO-8601 UTC (Z).

  • createdbooleanrequerido
Petición
curl -X POST "https://api.enviadores.com.mx/api/v1/recipients" \  -H "Authorization: Bearer $ENVIADORES_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "sender_id": "C0012345",  "alias": "Oficina",  "name": "María López",  "phone": "8187654321",  "street": "Av. Constitución",  "number": "400",  "colonia": "Centro",  "city": "Monterrey",  "state": "Nuevo León",  "postal_code": "64000",  "referencia": "Edificio azul, junto a la farmacia",  "delivery_instructions": "Entregar en recepción"}'
Respuesta 200
{
  "success": true,
  "data": {
    "recipient": {
      "id": "D0067890",
      "sender_id": "C0012345",
      "alias": "Oficina",
      "name": "María López",
      "phone": "8187654321",
      "email": null,
      "street": "Av. Constitución",
      "number": "400",
      "colonia": "Centro",
      "city": "Monterrey",
      "state": "Nuevo León",
      "postal_code": "64000",
      "referencia": "Edificio azul, junto a la farmacia",
      "delivery_instructions": "Entregar en recepción",
      "created_at": "2026-07-13T18:41:00Z"
    },
    "created": false
  },
  "requestId": "req_d6e7f8a9b0"
}
Respuesta 201
{
  "success": true,
  "data": {
    "recipient": {
      "id": "D0067890",
      "sender_id": "C0012345",
      "alias": "Oficina",
      "name": "María López",
      "phone": "8187654321",
      "email": null,
      "street": "Av. Constitución",
      "number": "400",
      "colonia": "Centro",
      "city": "Monterrey",
      "state": "Nuevo León",
      "postal_code": "64000",
      "referencia": "Edificio azul, junto a la farmacia",
      "delivery_instructions": "Entregar en recepción",
      "created_at": "2026-07-13T18:41:00Z"
    },
    "created": true
  },
  "requestId": "req_c5d6e7f8a9"
}

Consultar un destinatario

get/recipients/{id}

Requiere scope addresses:read. 404 idéntico para inexistente y no-visible (sin oráculo).

Parámetros

  • idstring· pathrequerido
  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

Respuestas

HTTPCódigoQué significa
200El destinatario.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDRuta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • recipientRecipientrequerido
    Mostrar 15 campos
    • idstringrequerido
    • sender_idstringrequerido
    • aliasstring | nullrequerido
    • namestring | nullrequerido
    • phonestring | nullrequerido
    • emailstring | nullrequerido
    • streetstring | nullrequerido
    • numberstring | nullrequerido
    • coloniastring | nullrequerido
    • citystring | nullrequerido
    • statestring | nullrequerido
    • postal_codestring | nullrequerido
    • referenciastring | nullrequerido
    • delivery_instructionsstring | nullrequerido
    • created_atstring | nullrequerido

      ISO-8601 UTC (Z).

Petición
curl "https://api.enviadores.com.mx/api/v1/recipients/ID" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "recipient": {
      "id": "D0067890",
      "sender_id": "C0012345",
      "alias": "Oficina",
      "name": "María López",
      "phone": "8187654321",
      "email": null,
      "street": "Av. Constitución",
      "number": "400",
      "colonia": "Centro",
      "city": "Monterrey",
      "state": "Nuevo León",
      "postal_code": "64000",
      "referencia": "Edificio azul, junto a la farmacia",
      "delivery_instructions": "Entregar en recepción",
      "created_at": "2026-07-13T18:41:00Z"
    }
  },
  "requestId": "req_e7f8a9b0c1"
}

Editar un destinatario

patch/recipients/{id}

Actualización PARCIAL de un destinatario visible.

›Ver reglas

Requiere scope addresses:write. sender_id es inmutable (no se re-asigna de remitente). Solo los campos presentes cambian; al menos uno. Los requeridos pueden cambiarse pero no vaciarse; los opcionales aceptan null. 404 idéntico para inexistente y no-visible.

Parámetros

  • idstring· pathrequerido
  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

›Campos del cuerpo (13)
  • aliasstring | null
  • namestring
  • phonestring
  • emailstring | null
  • streetstring
  • numberstring | null
  • coloniastring
  • citystring
  • statestring
  • postal_codestring
  • country"MX"
  • referenciastring | null
  • delivery_instructionsstring | null

Respuestas

HTTPCódigoQué significa
200El destinatario actualizado.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDRuta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
409DUPLICATE_ADDRESSLa edición dejaría este destinatario idéntico a OTRO del MISMO remitente. La fila no se modifica.
422VALIDATION_ERROR(details.fields) — campo desconocido, sender_id presente (inmutable), requerido vaciado, country ≠ MX, o cuerpo sin campos editables.
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • recipientRecipientrequerido
    Mostrar 15 campos
    • idstringrequerido
    • sender_idstringrequerido
    • aliasstring | nullrequerido
    • namestring | nullrequerido
    • phonestring | nullrequerido
    • emailstring | nullrequerido
    • streetstring | nullrequerido
    • numberstring | nullrequerido
    • coloniastring | nullrequerido
    • citystring | nullrequerido
    • statestring | nullrequerido
    • postal_codestring | nullrequerido
    • referenciastring | nullrequerido
    • delivery_instructionsstring | nullrequerido
    • created_atstring | nullrequerido

      ISO-8601 UTC (Z).

Petición
curl -X PATCH "https://api.enviadores.com.mx/api/v1/recipients/ID" \  -H "Authorization: Bearer $ENVIADORES_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "phone": "8187654322",  "delivery_instructions": null}'
Respuesta 200
{
  "success": true,
  "data": {
    "recipient": {
      "id": "D0067890",
      "sender_id": "C0012345",
      "alias": "Oficina",
      "name": "María López",
      "phone": "8187654322",
      "email": null,
      "street": "Av. Constitución",
      "number": "400",
      "colonia": "Centro",
      "city": "Monterrey",
      "state": "Nuevo León",
      "postal_code": "64000",
      "referencia": "Edificio azul, junto a la farmacia",
      "delivery_instructions": "Entregar en recepción",
      "created_at": "2026-07-13T18:41:00Z"
    }
  },
  "requestId": "req_f8a9b0c1d2"
}

Validar un código postal y listar sus colonias

get/postal-codes/{postal_code}

Resuelve un código postal mexicano (catálogo SEPOMEX) en su estado, municipio y el conjunto de colonias/asentamientos — justo lo que se necesita para llenar una dirección antes de POST /senders o POST /shipments.

›Ver reglas

Requiere scope addresses:read. Datos de referencia públicos y de solo lectura: responde IDÉNTICo con llaves ek_live_ y ek_test_ (no hay nada que simular). 422 si el CP no son 5 dígitos; 404 si no existe en el catálogo; 200 con los datos si es válido.

Parámetros

  • postal_codestring· pathrequerido
    Código postal de 5 dígitos.

Respuestas

HTTPCódigoQué significa
200El código postal existe.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUND
El código postal (bien formado) no está en el catálogo SEPOMEX. más

details.suggestions lista los CP reales a un dígito de distancia (un dígito cambiado o dos dígitos contiguos invertidos), cada uno con postal_code, state, state_code, municipality y city, ordenados por CP; vacío si no hay ninguno. Elige el que coincida con el municipio de la dirección y valídalo para ver sus colonias; no adivines un CP.

422VALIDATION_ERROREl código postal debe ser exactamente 5 dígitos.
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • postal_codestringrequerido

    El CP consultado (5 dígitos).

  • statestring | nullrequerido

    Estado (d_estado). Úsalo como state.

  • state_codestring | nullrequerido

    Código de estado normalizado (mismo esquema que state_code del directorio; p. ej. DF, JAL, NL).

  • municipalitystring | nullrequerido

    Municipio/alcaldía (d_mnpio). Suele ser el mejor valor para city.

  • citystring | nullrequerido

    Ciudad (d_ciudad) cuando SEPOMEX la distingue del municipio; null en la mayoría de los CP.

  • coloniasColonia[]requerido

    Asentamientos únicos del CP, ordenados por nombre. Un CP mapea a un estado/municipio pero a muchas colonias.

    Mostrar 2 campos
    • namestringrequerido

      Nombre del asentamiento (d_asenta de SEPOMEX). Úsalo como colonia/neighborhood.

    • settlement_typestring | nullrequerido

      Tipo de asentamiento SEPOMEX: Colonia, Fraccionamiento, Pueblo, Barrio, etc. Gran usuario = CP propio de una institución (ver gran_usuario).

  • gran_usuarioobject | nullrequerido

    Presente cuando el CP es de «gran usuario»: un código que Correos de México asignó a una sola institución (p. ej. 80129, Palacio de Gobierno del Estado de Sinaloa) y que ya no publica en su catálogo, aunque la institución lo sigue usando. Es válido, pero varias paqueterías no lo atienden y cotizarlo trae menos tarifas; cotiza y envía con parent_postal_code y parent_settlement como colonia. null en un CP ordinario.

    Mostrar 3 campos
    • institutionstringrequerido

      La institución dueña del CP.

    • parent_postal_codestring | nullrequerido

      CP oficial SEPOMEX de la colonia donde está la institución (80129 → 80000). Úsalo en lugar del CP especial. null si no se conoce.

    • parent_settlementstring | nullrequerido

      La colonia de parent_postal_code donde está la institución (p. ej. Centro Sinaloa). null si no se conoce.

Petición
curl "https://api.enviadores.com.mx/api/v1/postal-codes/POSTAL_CODE" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "postal_code": "64000",
    "state": "Nuevo León",
    "state_code": "NL",
    "municipality": "Monterrey",
    "city": "Monterrey",
    "colonias": [
      {
        "name": "Centro",
        "settlement_type": "Colonia"
      }
    ],
    "gran_usuario": null
  },
  "requestId": "req_a9b0c1d2e3"
}
Respuesta 404
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Postal code not found",
    "details": {
      "suggestions": [
        {
          "postal_code": "72776",
          "state": "Puebla",
          "state_code": "PU",
          "municipality": "San Pedro Cholula",
          "city": "San Pedro Cholula"
        }
      ]
    }
  },
  "requestId": "req_c1d2e3f4a5"
}

Importar contactos al directorio en bloque (remitentes y destinatarios)

post/contacts/import

Importación masiva del directorio — pensada para migrar contactos desde una hoja de cálculo, otra plataforma o una lista fotografiada, típicamente a través de un asistente de IA. Requiere scope addresses:write (es el mismo alcance de POST /senders / POST /recipients: una importación son esos dos creates en bloque).

›Ver reglas

Dos carriles de entrada, mutuamente excluyentes: - contacts: arreglo de filas {type: 'sender'|'recipient', name, phone, street, number?, colonia, city?, state?, postal_code, email?, company?, rfc?} — el mismo vocabulario de campos que POST /senders / POST /recipients (los destinatarios además aceptan sender_id?, alias?, referencia?, delivery_instructions?). El cliente (p. ej. el modelo) debe mapear los datos del usuario TAL CUAL, nunca inventar valores. - raw_csv: el contenido del CSV del usuario, VERBATIM (UTF-8, BOM opcional, máx. 256KB). El servidor lo interpreta de forma determinista: delimitador coma/punto y coma/tabulador (detectado), fila de encabezado obligatoria con las columnas de la plantilla tipo,nombre,telefono,calle,numero,colonia,ciudad,estado,codigo_postal,email,empresa,rfc (coincidencia insensible a mayúsculas y acentos, cualquier orden; columnas desconocidas → 422, nunca adivinadas). tipo acepta remitente/destinatario (o sender/recipient).

Dos fases sin estado: dry_run: true (el DEFAULT — el modo seguro) valida todo y devuelve la vista previa normalizada sin escribir nada; dry_run: false re-valida e inserta. La seguridad ante reintentos viene del DEDUPE POR CONTENIDO, no de claims: una fila idéntica (tupla normalizada nombre+teléfono+dirección+CP — el MISMO dedupe de POST /senders) a un contacto existente visible se reporta duplicate y se omite, así que repetir un commit jamás duplica el directorio.

Validación por fila (nunca aborta el lote completo): codigo_postal de 5 dígitos y existente en el catálogo SEPOMEX; si ciudad/estado vienen, deben ser coherentes con el CP (un desajuste nombra el valor esperado) y si faltan se AUTOCOMPLETAN del catálogo; telefono se normaliza a 10 dígitos (se toleran espacios, guiones y el prefijo +52/521); email con formato válido cuando venga. Cada fila responde status: ok|duplicate|invalid con issues[] en español nombrando campo y motivo.

Padre de los destinatarios: un destino siempre pertenece a un remitente. Orden de resolución: sender_id explícito de la fila → el PRIMER remitente válido del mismo lote → el remitente más reciente del directorio. Sin candidato, la fila es invalid con instrucciones.

Topes: máximo 200 filas por llamada (más → 422 pidiendo trocear el lote) y 256KB de raw_csv.

Conteos (invariantes): received = valid + invalid, valid = filas ok + duplicates; en commit además created y skipped con received = created + skipped + invalid — imposible perder filas en silencio.

Con una llave ek_test_ la importación escribe en el directorio del sandbox (mismas reglas, mismos formatos de respuesta). La visibilidad/destino de las filas es la misma de POST /senders: llaves de cliente y llaves admin sin X-PDV-ID escriben como el usuario de la llave; una llave admin con X-PDV-ID validado escribe en el directorio de ese punto de venta.

Parámetros

  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

›Campos del cuerpo (3)
  • contactsobject[]

    Filas mapeadas tal cual de los datos del usuario. Mutuamente excluyente con raw_csv.

    Mostrar 19 campos
    • type"sender" | "recipient"requerido
    • namestringrequerido
    • phonestringrequerido

      10 dígitos MX; se normalizan espacios/guiones/+52.

    • emailstring
    • companystring

      Solo remitentes (razón social).

    • rfcstring

      Solo remitentes.

    • apellido_paternostring

      DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

    • apellido_maternostring

      DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

    • streetstringrequerido
    • numberstring
    • coloniastringrequerido
    • citystring

      Opcional — se autocompleta desde el CP; si viene, se valida contra él.

    • statestring

      Opcional — se autocompleta desde el CP; si viene, se valida contra él.

    • postal_codestringrequerido
    • countrystring

      Opcional; solo MX.

    • sender_idstring

      Solo destinatarios: remitente dueño; default = primer remitente del lote o el más reciente del directorio.

    • aliasstring

      Solo destinatarios.

    • referenciastring

      Solo destinatarios.

    • delivery_instructionsstring

      Solo destinatarios.

  • raw_csvstring

    CSV del usuario, verbatim (UTF-8, máx. 256KB). Mutuamente excluyente con contacts.

  • dry_runboolean

    true (default): valida y previsualiza sin escribir. false: re-valida e inserta.

Respuestas

HTTPCódigoQué significa
200Resultado por fila + resumen. En dry_run nada se escribió; en commit, created/skipped reportan lo insertado/omitido.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

422VALIDATION_ERRORError de contrato (ambos carriles a la vez, ninguno, campo desconocido, más de 200 filas → trocear el lote, CSV >256KB) o de forma del CSV (encabezado con columnas desconocidas/faltantes, no-UTF-8), con los motivos en details.fields. Los problemas POR FILA nunca son 422: viajan como status: invalid dentro de rows.
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • dry_runbooleanrequerido
  • rowsobject[]requerido
    Mostrar 8 campos
    • indexintegerrequerido

      Posición 0-based de la fila en la solicitud.

    • lineinteger | null

      Línea del archivo en el carril raw_csv (encabezado = línea 1); null en el carril contacts.

    • type"sender" | "recipient" | nullrequerido
    • namestring | nullrequerido
    • status"ok" | "duplicate" | "invalid"requerido
    • issuesstring[]requerido

      Motivos en español, nombrando el campo.

    • contactobject | null

      La fila normalizada (teléfono a 10 dígitos, ciudad/estado autocompletados) cuando status=ok.

    • idstring | null

      Id creado (commit) o del contacto existente (duplicate).

  • summaryobjectrequerido
    Mostrar 6 campos
    • receivedintegerrequerido
    • validintegerrequerido

      Filas ok + duplicates (received = valid + invalid).

    • invalidintegerrequerido
    • duplicatesintegerrequerido
    • createdinteger

      Solo en commit. received = created + skipped + invalid.

    • skippedinteger

      Solo en commit; = duplicates.

Petición
curl -X POST "https://api.enviadores.com.mx/api/v1/contacts/import" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "dry_run": false,
    "rows": [
      {
        "index": 0,
        "line": 2,
        "type": "sender",
        "name": "Juan Pérez",
        "status": "ok",
        "issues": [],
        "contact": {
          "type": "sender",
          "name": "Juan Pérez",
          "phone": "5512345678",
          "street": "Av. Insurgentes Sur",
          "number": "600",
          "colonia": "San Ángel",
          "city": "Ciudad de México",
          "state": "Ciudad de México",
          "postal_code": "01000"
        },
        "id": "C0012399"
      },
      {
        "index": 1,
        "line": 3,
        "type": "recipient",
        "name": "Ana López",
        "status": "duplicate",
        "issues": [
          "ya existe un contacto idéntico en tu directorio (se omite)"
        ],
        "contact": null,
        "id": "D0045012"
      },
      {
        "index": 2,
        "line": 4,
        "type": "sender",
        "name": "Mal Teléfono",
        "status": "invalid",
        "issues": [
          "telefono: debe tener 10 dígitos después de quitar espacios, guiones y el prefijo +52 (recibido «123»)"
        ],
        "contact": null,
        "id": null
      }
    ],
    "summary": {
      "received": 3,
      "valid": 2,
      "invalid": 1,
      "duplicates": 1,
      "created": 1,
      "skipped": 1
    }
  },
  "requestId": "req_f1e2d3c4b5"
}

webhooks

Webhooks de eventos (envíos y recolecciones) — alternativa al polling

Listar webhooks

get/webhooks

Todos los webhooks de la cuenta en el modo de la llave (live o sandbox), incluidos los desactivados automáticamente — status, failure_count (fallos consecutivos) y last_delivery_* explican por qué dejaron de llegar eventos. Nunca incluye secretos. Requiere scope webhooks:read.

Respuestas

HTTPCódigoQué significa
200Lista + el catálogo de eventos y el tope activo.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDRuta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • webhooksWebhook[]requerido
    Mostrar 11 campos
    • idstringrequerido

      wh_ + 24 hex.

    • urlstringrequerido
    • eventsWebhookEvent[]requerido
    • mode"live" | "test"requerido

      Lane de la llave que lo registró. live recibe eventos reales; test solo shipment.created/shipment.cancelled del sandbox y el ping.

    • status"active" | "disabled"requerido

      disabled = desactivado automáticamente: tras 20 fallos consecutivos, o porque la llave API que lo registró fue revocada (disabled_reason: key_revoked — un webhook muere con su llave). Al desactivarse, sus eventos pendientes se descartan. No cuenta para el tope; elimínalo y regístralo de nuevo.

    • failure_countintegerrequerido

      Fallos CONSECUTIVOS de entrega (cualquier respuesta no 2xx o error de transporte). Vuelve a 0 con cada 2xx. Los pings de prueba no lo afectan.

    • last_delivery_atstring | null

      ISO-8601 UTC (Z). Último intento (éxito o fallo), pings incluidos.

    • last_delivery_statusinteger | null

      Status HTTP del último intento; 0 = fallo de transporte (timeout/TLS/DNS).

    • created_atstring | null

      ISO-8601 UTC (Z).

    • disabled_atstring | null

      ISO-8601 UTC (Z).

    • disabled_reasonstring | null

      key_revoked cuando la llave que lo registró fue revocada; texto explicativo cuando fue por fallos consecutivos.

  • max_activeintegerrequerido

    Tope de webhooks activos por cuenta y modo.

  • eventsstring[]requerido

    Catálogo completo de eventos suscribibles.

Petición
curl "https://api.enviadores.com.mx/api/v1/webhooks" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "webhooks": [
      {
        "id": "wh_0f3a9c1d2e4b5a6978c0d1e2",
        "url": "https://hooks.mitienda.com/enviadores",
        "events": [
          "shipment.created",
          "shipment.delivered"
        ],
        "mode": "live",
        "status": "active",
        "failure_count": 0,
        "last_delivery_at": "2026-09-04T16:05:12Z",
        "last_delivery_status": 200,
        "created_at": "2026-09-04T16:00:00Z",
        "disabled_at": null,
        "disabled_reason": null
      }
    ],
    "max_active": 5,
    "events": [
      "shipment.created",
      "shipment.collected",
      "shipment.in_transit",
      "shipment.out_for_delivery",
      "shipment.delivered",
      "shipment.exception",
      "shipment.returned",
      "shipment.cancelled",
      "pickup.resolved"
    ]
  },
  "requestId": "req_a7b8c9d0e1"
}

Registrar un webhook

post/webhooks

Registra un endpoint https:// público que recibirá un POST firmado por cada evento suscrito.

›Ver reglas

El `secret` se devuelve UNA sola vez en esta respuesta: no se almacena (se deriva bajo una llave del servidor) y no vuelve a mostrarse; guárdalo al recibirlo. Máximo 5 webhooks activos por cuenta y modo (409 WEBHOOK_LIMIT_REACHED). La URL se valida contra SSRF: solo https://, hostname público (se rechazan localhost, .local, .internal, literales IP privadas/loopback/link-local y hosts que resuelvan a ellas) y sin credenciales embebidas; en cada entrega se vuelve a resolver el host y la conexión se fija a la IP pública verificada. Con llave ek_test_ el webhook es de sandbox (solo shipment.created/shipment.cancelled del carril de pruebas + ping). Requiere scope webhooks:write.

›Campos del cuerpo (2)
  • urlstringrequerido

    Endpoint https:// público en el puerto 443 u 8443 (cualquier otro puerto se rechaza). Se rechazan http, credenciales embebidas, fragmentos, localhost/.local/.internal, literales IP privadas, hosts que resuelvan a rangos privados y hosts que apunten al propio API de Enviadores.

  • eventsWebhookEvent[]requerido

    Eventos a recibir (duplicados se descartan).

Respuestas

HTTPCódigoQué significa
201Webhook registrado. secret aparece SOLO aquí.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDLos webhooks no están habilitados en este despliegue (respuesta idéntica a una ruta inexistente).
409WEBHOOK_LIMIT_REACHEDYa hay 5 webhooks activos en este modo (details {max_active, mode}). Elimina uno con DELETE /webhooks/{id}.
422VALIDATION_ERRORdetails.fields nombra el problema: URL no https://, puerto distinto de 443/8443, host privado/no resoluble/propio del API, credenciales embebidas, events vacío o con un evento desconocido, campo desconocido.
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 201
  • webhookWebhookrequerido
    Mostrar 11 campos
    • idstringrequerido

      wh_ + 24 hex.

    • urlstringrequerido
    • eventsWebhookEvent[]requerido
    • mode"live" | "test"requerido

      Lane de la llave que lo registró. live recibe eventos reales; test solo shipment.created/shipment.cancelled del sandbox y el ping.

    • status"active" | "disabled"requerido

      disabled = desactivado automáticamente: tras 20 fallos consecutivos, o porque la llave API que lo registró fue revocada (disabled_reason: key_revoked — un webhook muere con su llave). Al desactivarse, sus eventos pendientes se descartan. No cuenta para el tope; elimínalo y regístralo de nuevo.

    • failure_countintegerrequerido

      Fallos CONSECUTIVOS de entrega (cualquier respuesta no 2xx o error de transporte). Vuelve a 0 con cada 2xx. Los pings de prueba no lo afectan.

    • last_delivery_atstring | null

      ISO-8601 UTC (Z). Último intento (éxito o fallo), pings incluidos.

    • last_delivery_statusinteger | null

      Status HTTP del último intento; 0 = fallo de transporte (timeout/TLS/DNS).

    • created_atstring | null

      ISO-8601 UTC (Z).

    • disabled_atstring | null

      ISO-8601 UTC (Z).

    • disabled_reasonstring | null

      key_revoked cuando la llave que lo registró fue revocada; texto explicativo cuando fue por fallos consecutivos.

  • secretstringrequerido

    Secreto de firma — se muestra SOLO aquí. Guárdalo: no se almacena y no se puede recuperar; para obtener otro hay que eliminar el webhook y registrarlo de nuevo.

Petición
curl -X POST "https://api.enviadores.com.mx/api/v1/webhooks" \  -H "Authorization: Bearer $ENVIADORES_API_KEY" \  -H "Content-Type: application/json" \  -d '{  "url": "https://hooks.mitienda.com/enviadores",  "events": [    "shipment.created",    "shipment.delivered",    "shipment.exception"  ]}'
Respuesta 201
{
  "success": true,
  "data": {
    "webhook": {
      "id": "wh_0f3a9c1d2e4b5a6978c0d1e2",
      "url": "https://hooks.mitienda.com/enviadores",
      "events": [
        "shipment.created",
        "shipment.delivered",
        "shipment.exception"
      ],
      "mode": "live",
      "status": "active",
      "failure_count": 0,
      "last_delivery_at": null,
      "last_delivery_status": null,
      "created_at": "2026-09-04T16:00:00Z",
      "disabled_at": null,
      "disabled_reason": null
    },
    "secret": "whsec_9f2c…(64 hex)"
  },
  "requestId": "req_a7b8c9d0e1"
}

Consultar un webhook

get/webhooks/{id}

Un webhook propio (misma cuenta y mismo modo que la llave). Requiere scope webhooks:read.

Parámetros

  • idstring· pathrequerido
    Id del webhook (wh_ + 24 hex), devuelto por POST /webhooks.

Respuestas

HTTPCódigoQué significa
200El webhook.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDNo existe, es de otra cuenta o de otro modo (respuesta idéntica).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • webhookWebhookrequerido
    Mostrar 11 campos
    • idstringrequerido

      wh_ + 24 hex.

    • urlstringrequerido
    • eventsWebhookEvent[]requerido
    • mode"live" | "test"requerido

      Lane de la llave que lo registró. live recibe eventos reales; test solo shipment.created/shipment.cancelled del sandbox y el ping.

    • status"active" | "disabled"requerido

      disabled = desactivado automáticamente: tras 20 fallos consecutivos, o porque la llave API que lo registró fue revocada (disabled_reason: key_revoked — un webhook muere con su llave). Al desactivarse, sus eventos pendientes se descartan. No cuenta para el tope; elimínalo y regístralo de nuevo.

    • failure_countintegerrequerido

      Fallos CONSECUTIVOS de entrega (cualquier respuesta no 2xx o error de transporte). Vuelve a 0 con cada 2xx. Los pings de prueba no lo afectan.

    • last_delivery_atstring | null

      ISO-8601 UTC (Z). Último intento (éxito o fallo), pings incluidos.

    • last_delivery_statusinteger | null

      Status HTTP del último intento; 0 = fallo de transporte (timeout/TLS/DNS).

    • created_atstring | null

      ISO-8601 UTC (Z).

    • disabled_atstring | null

      ISO-8601 UTC (Z).

    • disabled_reasonstring | null

      key_revoked cuando la llave que lo registró fue revocada; texto explicativo cuando fue por fallos consecutivos.

Petición
curl "https://api.enviadores.com.mx/api/v1/webhooks/ID" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"

Eliminar un webhook

delete/webhooks/{id}

Elimina el webhook. Las entregas se detienen de inmediato y los eventos pendientes en cola para él se descartan. Volver a registrar la misma URL genera un id y un secret NUEVOS. Requiere scope webhooks:write.

Parámetros

  • idstring· pathrequerido
    Id del webhook (wh_ + 24 hex), devuelto por POST /webhooks.

Respuestas

HTTPCódigoQué significa
200Eliminado.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDNo existe, es de otra cuenta o de otro modo (respuesta idéntica).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • deletedtruerequerido
  • idstringrequerido
Petición
curl -X DELETE "https://api.enviadores.com.mx/api/v1/webhooks/ID" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "deleted": true,
    "id": "wh_0f3a9c1d2e4b5a6978c0d1e2"
  },
  "requestId": "req_a7b8c9d0e1"
}

Probar un webhook (ping)

post/webhooks/{id}/test

Entrega un {"event":"ping"} firmado de forma síncrona (mismos headers y misma firma que un evento real;

›Ver reglas

data = {webhook_id}) y reporta el resultado. Un ping fallido NO cuenta para la desactivación automática — repítelo con libertad mientras ajustas el endpoint. Sin cuerpo. Requiere scope webhooks:write.

Parámetros

  • idstring· pathrequerido
    Id del webhook (wh_ + 24 hex), devuelto por POST /webhooks.

Respuestas

HTTPCódigoQué significa
200Resultado del ping (200 aunque el endpoint haya fallado — mira delivered).
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDNo existe, es de otra cuenta o de otro modo (respuesta idéntica).
409WEBHOOK_DISABLEDEl webhook fue desactivado automáticamente (details.disabled_reason); elimínalo y regístralo de nuevo.
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • deliveredbooleanrequerido

    true si el endpoint respondió 2xx dentro de 10 s.

  • response_statusinteger | nullrequerido

    Status HTTP recibido; null si no hubo respuesta (timeout, TLS, DNS).

  • error"timeout" | "connection failed" | "non-2xx status" | "endpoint rejected" | nullrequerido

    Clase del fallo, deliberadamente gruesa (el detalle de red no se expone para que el ping no sirva como sonda de puertos): timeout, connection failed (rechazo/reset/TLS/DNS), non-2xx status (ver response_status), endpoint rejected (la URL ya no pasa la política); null si se entregó.

  • event_idstringrequerido

    evt_ + 24 hex — el id del cuerpo enviado y el header X-Enviadores-Delivery.

Petición
curl -X POST "https://api.enviadores.com.mx/api/v1/webhooks/ID/test" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "delivered": false,
    "response_status": 500,
    "error": "HTTP 500",
    "event_id": "evt_4a5b6c7d8e9f0a1b2c3d4e5f"
  },
  "requestId": "req_a7b8c9d0e1"
}

account

La cuenta y la llave: identidad, capacidades y movimientos de saldo

Identificar la llave y la cuenta (whoami)

get/me

Quién eres desde el punto de vista de la API: la cuenta, si la conexión es de prueba (ek_test_) o de producción (ek_live_), los scopes que la llave realmente porta en ESTA petición, el saldo disponible y las capacidades de la superficie.

›Ver reglas

Es el endpoint de autodiagnóstico: cuando algo responde 403 INSUFFICIENT_SCOPE, compara key.scopes con el scope que nombró el error; cuando dudes si una guía es real, mira account.mode.

Cualquier llave válida puede llamarlo, sin importar sus scopes — un whoami que pudiera responder 403 por falta de permiso no serviría para su único propósito. Aun así no expone nada nuevo: balance.available es el mismo número de GET /balance (misma lectura), y el resto son hechos sobre tu propia llave. Nunca devuelve el secreto ni su hash ni su prefijo, ni la allowlist de PDVs, ni correo/teléfono/rol.

key.scopes es el conjunto EFECTIVO: una llave creada con "los permisos por omisión" (scopes_mode: default) se re-resuelve contra los scopes por omisión vigentes en cada petición, así que aquí ves lo que el gate acaba de aplicar. Una llave que alguien restringió a mano (scopes_mode: explicit) queda congelada como se creó.

account.tier_label es el escalón de verificación de las cuentas de registro propio (T1…T4); es null para cuentas creadas por nuestro equipo, que no están en esa escalera, y también si la consulta de verificación no está disponible.

capabilities describe la superficie, no la cuenta: pickups sigue el interruptor de recolecciones (siempre true en sandbox), y international/multi_package se derivan de las mismas validaciones que aplica POST /shipments, así que no pueden desincronizarse de la realidad.

En modo de prueba, balance.available es el saldo COMPUTADO del sandbox.

Parámetros

  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

Respuestas

HTTPCódigoQué significa
200Identidad de la llave y la cuenta.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDRuta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • accountobjectrequerido
    Mostrar 5 campos
    • idstringrequerido

      Id del usuario dueño de la llave.

    • namestring | nullrequerido

      Nombre visible de la cuenta (razón social, nombre de la persona, o el usuario). Nunca correo ni teléfono.

    • mode"test" | "live"requerido

      test = sandbox (nada es real); live = producción.

    • account_type"customer" | "staff"requerido

      Dos valores públicos: cuenta de cliente, o cuenta de nuestro mostrador. El rol interno no se expone.

    • tier_label"T0" | "T1" | "T2" | "T3" | "T4" | nullrequerido

      Escalón de verificación de una cuenta de registro propio. null si la cuenta no está en la escalera (creada por nuestro equipo) o si la consulta no está disponible.

  • keyobjectrequerido
    Mostrar 7 campos
    • idstringrequerido

      Id público de la llave (no es el secreto).

    • labelstring | nullrequerido

      Etiqueta que le puso su dueño.

    • mode"test" | "live"requerido
    • scopesstring[]requerido

      Los scopes EFECTIVOS de esta petición — lo que el gate acaba de aplicar.

    • scopes_mode"default" | "explicit"requerido

      default: la llave se creó con los permisos por omisión y se actualiza sola cuando aparecen nuevos. explicit: alguien la restringió al crearla; queda congelada así.

    • spend_cap_daily_mxnnumber | nullrequerido

      Tope de gasto en ventana móvil de 24 h, o null si no tiene.

    • rate_limit_tier"standard" | "elevated"requerido
  • balanceobjectrequerido
    Mostrar 2 campos
    • availablenumberrequerido

      Lo gastable ahora mismo — el mismo número que GET /balance. Para el desglose (held, total, scope) usa ese endpoint.

    • currency"MXN"requerido
  • capabilitiesobjectrequerido
    Mostrar 4 campos
    • pickupsbooleanrequerido

      Si esta conexión puede solicitar recolecciones. Siempre true en sandbox.

    • internationalbooleanrequerido

      Si la API acepta destinos fuera de México. Hoy false (v1 es doméstico MX).

    • multi_packagebooleanrequerido

      Si una solicitud puede llevar más de un paquete. Se deriva de la misma validación que aplica POST /shipments.

    • webhooksbooleanrequerido

      Si existen webhooks salientes en esta superficie. Hoy false: consulta con GET /shipments o GET /tracking/{guia}.

Petición
curl "https://api.enviadores.com.mx/api/v1/me" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "account": {
      "id": "U0000001",
      "name": "Comercializadora Ejemplo SA de CV",
      "mode": "live",
      "account_type": "customer",
      "tier_label": "T3"
    },
    "key": {
      "id": "AK0123456789abcdef",
      "label": "Servidor de producción",
      "mode": "live",
      "scopes": [
        "rates:read",
        "shipments:write",
        "shipments:read",
        "tracking:read",
        "cancellations:write",
        "cancellations:read",
        "balance:read",
        "labels:read",
        "addresses:read",
        "addresses:write",
        "pickups:write",
        "pickups:read"
      ],
      "scopes_mode": "default",
      "spend_cap_daily_mxn": 5000,
      "rate_limit_tier": "standard"
    },
    "balance": {
      "available": 4675,
      "currency": "MXN"
    },
    "capabilities": {
      "pickups": true,
      "international": false,
      "multi_package": false,
      "webhooks": false
    }
  },
  "requestId": "req_d4e5f6a7b8"
}

Listar los movimientos de saldo

get/transactions

El libro mayor detrás de GET /balance: cargos por guías, reembolsos por cancelación, recargas y ajustes manuales, del más reciente al más antiguo.

›Ver reglas

Es la respuesta a "¿en qué se fue el saldo?".

Mismo principal que `GET /balance`, por construcción. Sin header: el saldo PERSONAL del usuario de la llave. Con X-PDV-ID validado (llaves admin, misma allowlist): el libro mayor de ese fondo de punto de venta. Las consultas cruzadas por user_id del endpoint interno NO existen aquí: una llave lee exactamente el fondo que cargarían sus envíos.

amount viene con SIGNO en MXN (negativo = salió dinero) y balance_after es el saldo tras ese movimiento. type usa un vocabulario público de cuatro palabras (charge, refund, topup, adjustment) que resume el enum interno; los detalles internos (qué reventa, qué pierna de una corrección, qué programa) no se exponen.

En modo de prueba el listado se DERIVA de los envíos sandbox — un cargo al crear, un reembolso al cancelar — de modo que la suma cuadra exactamente con el saldo computado del sandbox. Requiere scope balance:read.

Parámetros

  • pageinteger· query
  • limitinteger· query
  • type"charge" | "refund" | "topup" | "adjustment"· query
    Filtra por tipo público de movimiento — exactamente el mismo valor que devuelve el campo type de cada fila. más

    charge = cargo (salió saldo), refund = devolución, topup = recarga en línea, adjustment = cualquier otro abono (alta manual, promoción, liquidación). Un valor fuera del enum es 422 VALIDATION_ERROR, nunca una lista vacía silenciosa.

  • fromstring· query
    Día calendario INCLUSIVO (zona horaria de negocio America/Mexico_City) desde el cual listar movimientos. Solo YYYY-MM-DD.
  • tostring· query
    Día calendario INCLUSIVO (America/Mexico_City) hasta el cual listar — el día completo. from posterior a to es 422.
  • X-PDV-IDstring· header
    Solo llaves de cuentas admin: el punto de venta cuyo fondo se carga/consulta. más

    Requiere que la llave tenga una allowlist de PDVs configurada por un administrador (pdv_allowlist); sin allowlist, todo PDV real es denegado por defecto. Validado además contra los PDV activos. Cualquier violación → 403 PDV_NOT_ALLOWED. El valor personal_account equivale a omitir el header.

Respuestas

HTTPCódigoQué significa
200Página de movimientos.
401UNAUTHORIZEDCredencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
403INSUFFICIENT_SCOPEA la llave le falta el scope requerido
PDV_NOT_ALLOWED
Violación del carril X-PDV-ID. más

Nota de orden: el limitador por llave corre ANTES del gate de scope, así que una petición con scope incorrecto y el bucket agotado responde 429 RATE_LIMITED (el 429 tiene precedencia sobre este 403).

404NOT_FOUNDRuta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
422VALIDATION_ERRORtype fuera del enum, fecha mal formada o from posterior a to (campos en details.fields).
429RATE_LIMITED
Bucket de la llave agotado. más

Espera Retry-After s (details.retry_after_ms para el hint fino). El limitador corre antes del gate de scope, por lo que un 429 tiene precedencia sobre un 403 INSUFFICIENT_SCOPE.

500SERVER_ERRORFalla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.
›Campos de la respuesta 200
  • transactionsTransaction[]requerido
    Mostrar 7 campos
    • idstringrequerido

      Id del movimiento (estable; el que conviene citar a soporte).

    • type"charge" | "refund" | "topup" | "adjustment"requerido

      Vocabulario público: cargo, devolución, recarga en línea, o cualquier otro abono.

    • amountnumberrequerido

      Importe en MXN CON SIGNO: negativo si salió saldo, positivo si entró.

    • balance_afternumber | nullrequerido

      Saldo del principal después de este movimiento.

    • shipment_idstring | nullrequerido

      Envío relacionado, cuando el movimiento nació de uno.

    • descriptionstring | nullrequerido

      Texto del movimiento. Las correcciones de cargo (cuando reasignamos un cobro al fondo correcto) se reportan siempre como Ajuste: el detalle interno describe nuestra propia contabilidad y nombra al operador que la hizo, y nada de eso viaja a esta superficie.

    • created_atstring | nullrequerido

      ISO-8601 en UTC (Z).

  • paginationPaginationrequerido
    Mostrar 3 campos
    • pageintegerrequerido
    • limitintegerrequerido
    • totalintegerrequerido
Petición
curl "https://api.enviadores.com.mx/api/v1/transactions" \  -H "Authorization: Bearer $ENVIADORES_API_KEY"
Respuesta 200
{
  "success": true,
  "data": {
    "transactions": [
      {
        "id": "TX00012345",
        "type": "charge",
        "amount": -184.5,
        "balance_after": 4675,
        "shipment_id": "20260830-000123",
        "description": "Guía Estafeta Terrestre",
        "created_at": "2026-08-30T10:00:02Z"
      },
      {
        "id": "TX00012344",
        "type": "topup",
        "amount": 2000,
        "balance_after": 4859.5,
        "shipment_id": null,
        "description": "Recarga en línea",
        "created_at": "2026-08-29T18:12:00Z"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 2
    }
  },
  "requestId": "req_b1c2d3e4f5"
}

Modelos

Esquemas que usan las peticiones y respuestas. Abre uno para ver sus campos.

›SuccessEnvelope
  • successtruerequerido
  • dataobjectrequerido
  • requestIdstringrequerido

    Correlación de logs; también en el header X-Request-Id.

›ErrorEnvelope
  • successfalserequerido
  • errorobjectrequerido
    Mostrar 3 campos
    • codestringrequerido

      Código de máquina estable (p. ej. RATE_EXPIRED, CREDIT_ERROR). 409 REQUEST_IN_PROGRESS: la misma Idempotency-Key sigue procesándose — espera y reintenta con la MISMA llave.

    • messagestringrequerido
    • detailsobject

      Contexto adicional por código (p. ej. {required, available, shortfall} en CREDIT_ERROR; {fields} en VALIDATION_ERROR; {committed: true} en POST_COMMIT_ERROR; {suggestions} en el NOT_FOUND de /postal-codes).

  • requestIdstringrequerido
›RatesRequestOrigen: exactamente UNO de from (inline) o from_id (remitente del directorio).

Origen: exactamente UNO de from (inline) o from_id (remitente del directorio). Destino: exactamente UNO de to o to_id. Mandar ambos miembros de un par → 422; un id no visible para la llave → 404 (respuesta ciega). Cotizar por id usa los datos completos guardados (ciudad/estado/colonia), así que la resolución de zona es igual o mejor que con CP suelto. Bultos: exactamente UNO de package (una caja) o packages (2–10 cajas en un mismo envío).

  • Mostrar 5 campos
    • postal_codestringrequerido

      CP de 5 dígitos (MX).

    • country"MX"

      Opcional. Solo MX (sin distinción de mayúsculas/minúsculas; vacío = MX) — cualquier otro valor responde 422 (v1 es doméstico MX).

    • statestring
    • citystring
    • neighborhoodstring
  • from_idstring

    Id de un remitente del directorio (GET /senders). Mutuamente excluyente con from.

  • Mostrar 5 campos
    • postal_codestringrequerido

      CP de 5 dígitos (MX).

    • country"MX"

      Opcional. Solo MX (sin distinción de mayúsculas/minúsculas; vacío = MX) — cualquier otro valor responde 422 (v1 es doméstico MX).

    • statestring
    • citystring
    • neighborhoodstring
  • to_idstring

    Id de un destinatario del directorio (GET /recipients). Mutuamente excluyente con to.

  • packageobject
    Mostrar 4 campos
    • weight_kgnumberrequerido

      Peso real en kg. El facturable es max(real, volumétrico L*W*H/5000); máximo 70 kg.

    • length_cmnumber

      Largo exterior de la caja ya cerrada (cm). Manda las tres medidas: sin ellas la cotización se calcula para una caja de 10×10×10 cm, llega con size_estimated: true y POST /shipments la rechaza (422 PACKAGE_DIMENSIONS_REQUIRED). *Outer size of the closed box; without all three the price is only an estimate and create is refused.*

    • width_cmnumber

      Ancho exterior de la caja ya cerrada (cm).

    • height_cmnumber

      Alto exterior de la caja ya cerrada (cm).

  • packagesPackagesList
    Mostrar 4 campos
    • weight_kgnumberrequerido

      Peso real del bulto (kg).

    • length_cmnumber

      Largo exterior del bulto cerrado (cm). Obligatorio para POST /shipments; en POST /rates, sin las tres medidas el precio es estimado (size_estimated).

    • width_cmnumber

      Ancho exterior del bulto cerrado (cm).

    • height_cmnumber

      Alto exterior del bulto cerrado (cm).

  • Mostrar 2 campos
    • insured_valuenumber

      Valor a asegurar (MXN). La prima queda incluida en pricing.total_price de cada tarifa.

    • declared_valuenumber

      OBSOLETO — alias de insured_value (compatibilidad con integradores previos). Si mandas ambos deben coincidir.

›PackagesListVARIOS bultos que viajan como UN solo envío (un cargo, una cotización).

VARIOS bultos que viajan como UN solo envío (un cargo, una cotización). Mutuamente excluyente con package. La lista cotizada y la reservada deben ser IDÉNTICAS (mismo orden, mismas medidas) o POST /shipments responde 409 RATE_PACKAGE_MISMATCH. Solo se cotizan/reservan servicios que soportan multi-bulto.

›InsuranceRequestCobertura para el envío.

Cobertura para el envío. insured_value contrata la póliza de la PLATAFORMA (prima incluida en pricing.total_price) o, cuando esa póliza no está disponible, la cobertura de la propia paquetería — el precio mostrado ya la incluye; cada tarifa indica insurance_included / insurance_not_supported. NO es el valor declarado a la paquetería (declared_value en POST /shipments). *Insurance: insured_value buys coverage for the shipment; when the platform policy is unavailable the carrier's own coverage is used and the price shown already includes it.*

  • insured_valuenumber

    Valor a asegurar (MXN). La prima queda incluida en pricing.total_price de cada tarifa.

  • declared_valuenumber

    OBSOLETO — alias de insured_value (compatibilidad con integradores previos). Si mandas ambos deben coincidir.

›RatesGroupedResponseRespuesta de POST /rates?view=grouped: cada servicio distinto (paquetería + nivel de servicio; para priority también la ventana horaria) aparece UNA vez con su rango de precios y sus opciones reservables.

Respuesta de POST /rates?view=grouped: cada servicio distinto (paquetería + nivel de servicio; para priority también la ventana horaria) aparece UNA vez con su rango de precios y sus opciones reservables. Mismo unlock_notice/fetched_at/stale_after/meta/rate_id_expires_in_seconds/incomplete/size_estimated/size_notice que la forma plana.

  • servicesRateGroup[]requerido

    Ordenados por price_from ascendente.

    Mostrar 14 campos
    • service_keystringrequerido

      Clave estable del servicio: carrier|tier (+ |ventana para priority).

    • carrierstringrequerido

      Nombre canónico de la paquetería.

    • tier"priority" | "express" | "standard_economy" | "international"requerido
    • delivery_windowstring | nullrequerido
    • service_labelstringrequerido

      Etiqueta legible, p. ej. DHL · Express, Estafeta · Prioritario 10:30 AM.

    • estimated_daysstring | number | nullrequerido

      De options[0] (la opción reservable más barata).

    • guaranteed_window"2_days" | "ground" | null

      De options[0]; ver options[].guaranteed_window.

    • guaranteed_window_daysstring | null

      De options[0].

    • pickup_availablebooleanrequerido

      true si ALGUNA opción incluye recolección.

    • booking_lockedbooleanrequerido

      true solo si TODAS las opciones están bloqueadas para esta cuenta.

    • price_fromnumber | nullrequerido
    • price_tonumber | nullrequerido
    • currency"MXN"requerido
    • optionsobject[]requerido

      Precios de este servicio: primero los que esta cuenta puede reservar (booking_locked: false), cada grupo de menor a mayor, así que options[0] es la opción reservable más barata. Reserva options[0].rate_id salvo que el usuario pida otra. price_from/price_to abarcan todas las opciones.

      Mostrar 12 campos
      • rate_idstringrequerido

        Para POST /shipments. OPACO.

      • service_namestring | nullrequerido

        Nombre comercial del servicio tal como lo publica la paquetería.

      • total_pricenumber | nullrequerido
      • estimated_daysstring | number | nullrequerido
      • guaranteed_window"2_days" | "ground" | null

        Tiempo de entrega esperado según la cobertura oficial de Estafeta en el destino, cuando la tarifa vende un servicio más rápido de lo que Estafeta garantiza ahí: 2_days (normalmente 2 días) o ground (terrestre, normalmente guaranteed_window_days días). null si no aplica o no hay datos de cobertura. La tarifa sigue siendo válida y reservable; preséntalo al cliente final como el tiempo de entrega esperado, no como una advertencia.

      • guaranteed_window_daysstring | null

        Días de esa garantía: 2 para 2_days, 3-5 para ground; null cuando guaranteed_window es null.

      • pickup_includedbooleanrequerido
      • booking_lockedbooleanrequerido
      • insurance_includedbooleanrequerido

        La opción incluye cobertura (ya dentro de total_price).

      • insurance_not_supportedbooleanrequerido

        La paquetería no ofrece cobertura para esta opción.

      • insurance_premiumnumberrequerido

        Prima de plataforma itemizada (0 sin póliza).

      • insured_valuenumber | nullrequerido
  • view"grouped"requerido
  • unlock_noticestring | null
  • fetched_atstring | nullrequerido
  • stale_afterstring | nullrequerido
  • metaobjectrequerido
  • rate_id_expires_in_seconds1800requerido
  • incompletebooleanrequerido

    true cuando algunas cotizaciones de paqueterías no llegaron dentro del plazo de cotización (15 s): las opciones listadas siguen siendo válidas y reservables; cotizar de nuevo puede mostrar más. *Some carrier quotes did not arrive within the 15 s quote window; listed options remain bookable, a new quote may list more.*

  • size_estimatedbooleanrequerido

    true cuando alguna caja llegó sin largo, ancho o alto: todos los precios son para una caja de 10×10×10 cm, no el precio real, y POST /shipments rechaza la cotización (422 PACKAGE_DIMENSIONS_REQUIRED). Pide las medidas y cotiza de nuevo. *Some box had no length/width/height: prices are for a 10×10×10 cm box and create is refused.*

  • size_noticestring | nullrequerido

    Aviso bilingüe para mostrar cuando size_estimated es true; null en otro caso.

›RateGroup
  • service_keystringrequerido

    Clave estable del servicio: carrier|tier (+ |ventana para priority).

  • carrierstringrequerido

    Nombre canónico de la paquetería.

  • tier"priority" | "express" | "standard_economy" | "international"requerido
  • delivery_windowstring | nullrequerido
  • service_labelstringrequerido

    Etiqueta legible, p. ej. DHL · Express, Estafeta · Prioritario 10:30 AM.

  • estimated_daysstring | number | nullrequerido

    De options[0] (la opción reservable más barata).

  • guaranteed_window"2_days" | "ground" | null

    De options[0]; ver options[].guaranteed_window.

  • guaranteed_window_daysstring | null

    De options[0].

  • pickup_availablebooleanrequerido

    true si ALGUNA opción incluye recolección.

  • booking_lockedbooleanrequerido

    true solo si TODAS las opciones están bloqueadas para esta cuenta.

  • price_fromnumber | nullrequerido
  • price_tonumber | nullrequerido
  • currency"MXN"requerido
  • optionsobject[]requerido

    Precios de este servicio: primero los que esta cuenta puede reservar (booking_locked: false), cada grupo de menor a mayor, así que options[0] es la opción reservable más barata. Reserva options[0].rate_id salvo que el usuario pida otra. price_from/price_to abarcan todas las opciones.

    Mostrar 12 campos
    • rate_idstringrequerido

      Para POST /shipments. OPACO.

    • service_namestring | nullrequerido

      Nombre comercial del servicio tal como lo publica la paquetería.

    • total_pricenumber | nullrequerido
    • estimated_daysstring | number | nullrequerido
    • guaranteed_window"2_days" | "ground" | null

      Tiempo de entrega esperado según la cobertura oficial de Estafeta en el destino, cuando la tarifa vende un servicio más rápido de lo que Estafeta garantiza ahí: 2_days (normalmente 2 días) o ground (terrestre, normalmente guaranteed_window_days días). null si no aplica o no hay datos de cobertura. La tarifa sigue siendo válida y reservable; preséntalo al cliente final como el tiempo de entrega esperado, no como una advertencia.

    • guaranteed_window_daysstring | null

      Días de esa garantía: 2 para 2_days, 3-5 para ground; null cuando guaranteed_window es null.

    • pickup_includedbooleanrequerido
    • booking_lockedbooleanrequerido
    • insurance_includedbooleanrequerido

      La opción incluye cobertura (ya dentro de total_price).

    • insurance_not_supportedbooleanrequerido

      La paquetería no ofrece cobertura para esta opción.

    • insurance_premiumnumberrequerido

      Prima de plataforma itemizada (0 sin póliza).

    • insured_valuenumber | nullrequerido
›RatesEndpointParty
  • postal_codestringrequerido

    CP de 5 dígitos (MX).

  • country"MX"

    Opcional. Solo MX (sin distinción de mayúsculas/minúsculas; vacío = MX) — cualquier otro valor responde 422 (v1 es doméstico MX).

  • statestring
  • citystring
  • neighborhoodstring
›RatesResponse
  • servicesRate[]requerido
    Mostrar 16 campos
    • idstringrequerido

      rate_id — pásalo tal cual a POST /shipments. OPACO: su formato no es parte del contrato (difiere entre el carril de prueba y el live); no lo interpretes ni lo valides.

    • carrierstring | nullrequerido
    • service_namestring | nullrequerido

      Nombre comercial del servicio tal como lo publica la paquetería.

    • service_typestring | null

      Clasificación por niveles: priority | express | standard_economy…

    • tierstring | null

      Igual a service_type (alias de compatibilidad).

    • delivery_windowstring | null

      Ventana de entrega normalizada (p. ej. next_day, ground).

    • pickup_includedboolean

      La tarifa incluye recolección.

    • address_deliveryboolean

      Entrega a domicilio (false = entrega en sucursal/ocurre).

    • pricingobjectrequerido
      Mostrar 5 campos
      • total_pricenumber | nullrequerido

        Total a cobrar en MXN, 2 decimales (IVA incluido; incluye la prima de seguro de plataforma cuando aplique).

      • currency"MXN"requerido
      • iva_includedboolean
      • insurance_premiumnumber

        Prima itemizada ya incluida en total_price (0 sin póliza).

      • insured_valuenumber | null

        Valor declarado asegurado (null sin póliza).

    • deliveryobject
      Mostrar 4 campos
      • estimated_daysstring | number | null
      • estimated_datestring | null
      • min_daysinteger | null
      • max_daysinteger | null
    • insurance_includedboolean
    • insurance_not_supportedboolean

      La paquetería no acepta seguro para esta tarifa.

    • guaranteed_window"2_days" | "ground" | null

      Tiempo de entrega esperado según la cobertura oficial de Estafeta en el destino, cuando la tarifa vende un servicio más rápido de lo que Estafeta garantiza ahí: 2_days (normalmente 2 días) o ground (terrestre, normalmente guaranteed_window_days días). null si no aplica o no hay datos de cobertura. La tarifa sigue siendo válida y reservable; preséntalo al cliente final como el tiempo de entrega esperado, no como una advertencia.

    • guaranteed_window_daysstring | null

      Días de esa garantía: 2 para 2_days, 3-5 para ground; null cuando guaranteed_window es null.

    • zona_extendidaboolean

      Destino en zona extendida: puede implicar cargos de reexpedición. Cuando la paquetería los cotiza por adelantado ya vienen dentro de total_price; si no, pueden facturarse después como cargo de paquetería.

    • booking_lockedboolean

      La tarifa es VISIBLE pero aún no reservable para esta cuenta: POST /shipments responde 403 SERVICE_UNLOCK_REQUIRED. Solo afecta a cuentas de registro propio sin identidad verificada (INE) y con menos de $2,500 MXN en recargas acumuladas. Para esas cuentas solo quedan abiertos iMile Terrestre y Estafeta Terrestre y Estafeta Día Siguiente sin recolección (estas se dejan en una sucursal Estafeta y los cargos extra se pagan ahí); todo lo demás llega bloqueado, incluidos los servicios sin recolección de otros transportistas. Las rutas de desbloqueo vienen en unlock_notice.

  • unlock_noticestring | null

    Mensaje es-MX para mostrar al usuario cuando su catálogo está limitado (hay tarifas con booking_locked) o cuando se desbloqueó por recargas acumuladas y la verificación de identidad sigue pendiente. null para cuentas sin restricción.

  • fetched_atstring | nullrequerido

    ISO-8601.

  • stale_afterstring | nullrequerido

    ISO-8601 (fetched_at + 5 min): re-cotiza para PRECIOS frescos; el rate_id sigue siendo enviable hasta los 30 min.

  • metaobjectrequerido
    Mostrar 3 campos
    • billable_weightnumber | null

      Peso facturable (kg): max(real, volumétrico).

    • volumetric_weightnumber | null

      L×W×H / 5000 (kg).

    • zoneinteger | null
  • rate_id_expires_in_seconds1800requerido

    TTL del rate_id: crea el envío dentro de esta ventana o recibirás 409 RATE_EXPIRED.

  • incompletebooleanrequerido

    true cuando algunas cotizaciones de paqueterías no llegaron dentro del plazo de 15 s y la respuesta se envió con las que sí llegaron. Cada tarifa listada es completa y reservable; una nueva cotización puede incluir más paqueterías o servicios. *The quote answers within 15 s with the carrier quotes that arrived; listed rates remain bookable, a new quote may list more.*

  • size_estimatedbooleanrequerido

    true cuando alguna caja llegó sin largo, ancho o alto: todos los precios son para una caja de 10×10×10 cm, no el precio real, y POST /shipments rechaza la cotización (422 PACKAGE_DIMENSIONS_REQUIRED). Pide las medidas y cotiza de nuevo. *Some box had no length/width/height: prices are for a 10×10×10 cm box and create is refused.*

  • size_noticestring | nullrequerido

    Aviso bilingüe para mostrar cuando size_estimated es true; null en otro caso.

›RateUna tarifa enviable.

Una tarifa enviable. id es el rate_id para POST /shipments. La forma es IDÉNTICA para toda llave (los costos internos de proveedor nunca se incluyen) y es una whitelist estricta: no aparecerán campos no documentados.

  • idstringrequerido

    rate_id — pásalo tal cual a POST /shipments. OPACO: su formato no es parte del contrato (difiere entre el carril de prueba y el live); no lo interpretes ni lo valides.

  • carrierstring | nullrequerido
  • service_namestring | nullrequerido

    Nombre comercial del servicio tal como lo publica la paquetería.

  • service_typestring | null

    Clasificación por niveles: priority | express | standard_economy…

  • tierstring | null

    Igual a service_type (alias de compatibilidad).

  • delivery_windowstring | null

    Ventana de entrega normalizada (p. ej. next_day, ground).

  • pickup_includedboolean

    La tarifa incluye recolección.

  • address_deliveryboolean

    Entrega a domicilio (false = entrega en sucursal/ocurre).

  • pricingobjectrequerido
    Mostrar 5 campos
    • total_pricenumber | nullrequerido

      Total a cobrar en MXN, 2 decimales (IVA incluido; incluye la prima de seguro de plataforma cuando aplique).

    • currency"MXN"requerido
    • iva_includedboolean
    • insurance_premiumnumber

      Prima itemizada ya incluida en total_price (0 sin póliza).

    • insured_valuenumber | null

      Valor declarado asegurado (null sin póliza).

  • deliveryobject
    Mostrar 4 campos
    • estimated_daysstring | number | null
    • estimated_datestring | null
    • min_daysinteger | null
    • max_daysinteger | null
  • insurance_includedboolean
  • insurance_not_supportedboolean

    La paquetería no acepta seguro para esta tarifa.

  • guaranteed_window"2_days" | "ground" | null

    Tiempo de entrega esperado según la cobertura oficial de Estafeta en el destino, cuando la tarifa vende un servicio más rápido de lo que Estafeta garantiza ahí: 2_days (normalmente 2 días) o ground (terrestre, normalmente guaranteed_window_days días). null si no aplica o no hay datos de cobertura. La tarifa sigue siendo válida y reservable; preséntalo al cliente final como el tiempo de entrega esperado, no como una advertencia.

  • guaranteed_window_daysstring | null

    Días de esa garantía: 2 para 2_days, 3-5 para ground; null cuando guaranteed_window es null.

  • zona_extendidaboolean

    Destino en zona extendida: puede implicar cargos de reexpedición. Cuando la paquetería los cotiza por adelantado ya vienen dentro de total_price; si no, pueden facturarse después como cargo de paquetería.

  • booking_lockedboolean

    La tarifa es VISIBLE pero aún no reservable para esta cuenta: POST /shipments responde 403 SERVICE_UNLOCK_REQUIRED. Solo afecta a cuentas de registro propio sin identidad verificada (INE) y con menos de $2,500 MXN en recargas acumuladas. Para esas cuentas solo quedan abiertos iMile Terrestre y Estafeta Terrestre y Estafeta Día Siguiente sin recolección (estas se dejan en una sucursal Estafeta y los cargos extra se pagan ahí); todo lo demás llega bloqueado, incluidos los servicios sin recolección de otros transportistas. Las rutas de desbloqueo vienen en unlock_notice.

›ShipmentCreateRequestRemitente: exactamente UNO de from (inline) o from_id (directorio).

Remitente: exactamente UNO de from (inline) o from_id (directorio). Destinatario: exactamente UNO de to o to_id. Ambos miembros de un par → 422; id no visible → 404 ciego. to_id requiere from_id y el destinatario debe pertenecer a ese remitente (un destinatario de otro remitente responde 404). Un lado referenciado usa la fila guardada tal cual (sin create-or-reuse). Bultos: exactamente UNO de package o packages — los MISMOS que se cotizaron.

  • rate_idstringrequerido

    El id de una tarifa de POST /rates cotizada con esta misma llave (y mismo X-PDV-ID, si se usó), con menos de 30 minutos. Trátalo como OPACO: cópialo tal cual; su formato no es parte del contrato (difiere entre el carril de prueba y el live) y no debe interpretarse ni validarse.

  • Mostrar 14 campos
    • namestringrequerido
    • apellido_paternostring

      DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

    • apellido_maternostring

      DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

    • companystring

      Razón social (solo remitente; ignorado en destinatario).

    • phonestringrequerido
    • emailstring
    • streetstringrequerido
    • numberstring

      Número exterior.

    • coloniastringrequerido
    • citystringrequerido
    • statestringrequerido
    • postal_codestringrequerido
    • country"MX"

      Opcional. Solo MX (sin distinción de mayúsculas/minúsculas; vacío = MX) — cualquier otro valor responde 422 (v1 es doméstico MX).

    • rfcstring

      Solo remitente; usado por algunas paqueterías.

  • from_idstring

    Id de un remitente del directorio. Mutuamente excluyente con from.

  • Mostrar 14 campos
    • namestringrequerido
    • apellido_paternostring

      DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

    • apellido_maternostring

      DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

    • companystring

      Razón social (solo remitente; ignorado en destinatario).

    • phonestringrequerido
    • emailstring
    • streetstringrequerido
    • numberstring

      Número exterior.

    • coloniastringrequerido
    • citystringrequerido
    • statestringrequerido
    • postal_codestringrequerido
    • country"MX"

      Opcional. Solo MX (sin distinción de mayúsculas/minúsculas; vacío = MX) — cualquier otro valor responde 422 (v1 es doméstico MX).

    • rfcstring

      Solo remitente; usado por algunas paqueterías.

  • to_idstring

    Id de un destinatario del directorio (debe pertenecer al from_id enviado). Mutuamente excluyente con to; requiere from_id.

  • packageobject

    La MISMA caja que se cotizó, con sus tres medidas: sin largo, ancho y alto → 422 PACKAGE_DIMENSIONS_REQUIRED (no se cobra nada).

    Mostrar 5 campos
    • weight_kgnumberrequerido
    • length_cmnumberrequerido
    • width_cmnumberrequerido
    • height_cmnumberrequerido
    • type"paquete"

      Opcional. Solo paquete en v1 (sin distinción de mayúsculas; vacío/null = paquete). Cualquier otro valor — p. ej. sobre — responde 422 tanto en /rates como en /shipments (envelope aún no soportado).

  • packagesPackagesList
    Mostrar 4 campos
    • weight_kgnumberrequerido

      Peso real del bulto (kg).

    • length_cmnumber

      Largo exterior del bulto cerrado (cm). Obligatorio para POST /shipments; en POST /rates, sin las tres medidas el precio es estimado (size_estimated).

    • width_cmnumber

      Ancho exterior del bulto cerrado (cm).

    • height_cmnumber

      Alto exterior del bulto cerrado (cm).

  • insuranceobject

    La cobertura que YA se cotizó (insured_value buys coverage; when the platform policy is unavailable the carrier's own coverage is used and the price shown already includes it). Omitir insurance acepta la cobertura cotizada; mandar un insured_value DISTINTO → 422 INSURANCE_MISMATCH (el total firmado nunca se recalcula al crear). Una tarifa cuya paquetería no ofrece cobertura (insurance_not_supported: true) → 422 INSURANCE_NOT_AVAILABLE.

    Mostrar 1 campo
    • insured_valuenumberrequerido

      Valor asegurado (MXN) — el mismo de la cotización.

  • declared_valuenumber

    Valor de la mercancía DECLARADO A LA PAQUETERÍA (MXN). NO es seguro, pero la paquetería puede cobrarlo: debe ser EXACTAMENTE el valor con el que se cotizó (el asegurado) — un valor que la cotización no llevaba responde 422 DECLARED_VALUE_MISMATCH, salvo en servicios que lo tratan como informativo (la recuperación de DECLARED_VALUE_REQUIRED). Si se omite se declara el valor cotizado; si la cotización no llevaba ninguno, no se inventa. *declared_value must equal the value the rate was quoted with; a carrier may price it.*

  • contenidostring

    Descripción del contenido (aparece en la guía cuando la paquetería lo soporta).

  • referencestring

    RESERVADO — aceptado pero no persistido en v1.

  • metadataobject

    RESERVADO en el carril live (aceptado, no persistido). En el carril SANDBOX, metadata.test_scenario fuerza una falla para rehearsal (ver x-test-mode).

    Mostrar 1 campo
    • test_scenario"insufficient_funds" | "rate_expired" | "vendor_error"

      Solo llaves ek_test_, y solo sobre ofertas SIMULADAS del catálogo: sobre una tarifa del carril FedEx de prueba (que va al entorno real de FedEx) responde 422 TEST_SCENARIO_NOT_AVAILABLE. insufficient_funds → 402 CREDIT_ERROR (details {required, available, shortfall}) sin importar el saldo; rate_expired → 409 RATE_EXPIRED; vendor_error → 422 VENDOR_ERROR (retryable=false).

›ShipmentPartyLos datos se registran en el directorio del usuario de la llave con dedupe exacto (normalizado por espacios/mayúsculas), así que repetir el mismo remitente/destinatario no crea filas nuevas.

Los datos se registran en el directorio del usuario de la llave con dedupe exacto (normalizado por espacios/mayúsculas), así que repetir el mismo remitente/destinatario no crea filas nuevas. Los campos exceden-longitud se recortan al límite de columna.

  • namestringrequerido
  • apellido_paternostring

    DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

  • apellido_maternostring

    DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

  • companystring

    Razón social (solo remitente; ignorado en destinatario).

  • phonestringrequerido
  • emailstring
  • streetstringrequerido
  • numberstring

    Número exterior.

  • coloniastringrequerido
  • citystringrequerido
  • statestringrequerido
  • postal_codestringrequerido
  • country"MX"

    Opcional. Solo MX (sin distinción de mayúsculas/minúsculas; vacío = MX) — cualquier otro valor responde 422 (v1 es doméstico MX).

  • rfcstring

    Solo remitente; usado por algunas paqueterías.

›ShipmentCreateResponse
  • shipmentobjectrequerido
    Mostrar 10 campos
    • idstringrequerido
    • guiastring | null

      Null cuando la paquetería publica la guía con retraso (tracking_pending: true); consúltala después vía GET /shipments/{id}.

    • carrierstring | null

      Null posible en respuestas duplicate: true.

    • servicestring | null
    • statusstringrequerido

      Estatus canónico (created | collected | in_transit | out_for_delivery | ready_for_pickup | delivered | exception | returned | cancelled | unknown).

    • totalnumber | null

      Monto cobrado (MXN).

    • currency"MXN"requerido
    • label_urlstring | nullrequerido

      Ruta de NUESTRO endpoint autenticado de etiquetas (/api/v1/labels/{id}) — nunca la URL cruda del proveedor.

    • tracking_pendingbooleanrequerido
    • created_atstring | null

      ISO-8601 UTC (Z).

  • duplicateboolean

    Presente (true) cuando la paquetería devolvió una guía ya registrada y la plataforma deduplicó sin doble cargo.

  • recoveredboolean

    Presente (true) cuando la etiqueta se recuperó tras un error transitorio del proveedor.

›Shipment
  • idstringrequerido
  • guiastring | nullrequerido
  • carrierstring | nullrequerido

    Nombre canónico de la paquetería física (SSOT de la plataforma).

  • servicestring | nullrequerido
  • status"created" | "collected" | "in_transit" | "out_for_delivery" | "delivered" | "exception" | "returned" | "cancelled" | "unknown"requerido
  • status_labelstringrequerido

    Etiqueta en español del estatus canónico.

  • totalnumber | nullrequerido
  • currency"MXN"requerido
  • created_atstring | nullrequerido

    ISO-8601 UTC (Z).

  • tracking_pendingbooleanrequerido
  • label_urlstringrequerido
  • packagesobject[]requerido

    Lo que se registró al crear el envío, en el mismo vocabulario que usa POST /shipments (weight_kg, length_cm, …). Para envíos multi-paquete es la lista completa por bulto, no el agregado. Lista vacía si el envío no tiene medidas registradas.

    Envíos anteriores al 2026-07-28: las dimensiones no se persistían (solo se usaban para calcular el peso volumétrico y se descartaban), así que length_cm/width_cm/height_cm llegan en null en todo envío creado antes de esa fecha. weight_kg sí está disponible en todo el histórico. Un null significa "no registrado", nunca "cero": las dimensiones no declaradas tampoco se rellenan con un valor por defecto.

    Mostrar 4 campos
    • weight_kgnumber | nullrequerido
    • length_cmnumber | nullrequerido
    • width_cmnumber | nullrequerido
    • height_cmnumber | nullrequerido
  • billable_weight_kgnumber | nullrequerido

    Peso facturable con el que se cotizó — max(peso real, peso volumétrico). Es el número contra el que hay que comparar cuando la paquetería aplica un sobrepeso; el peso real por sí solo no explica el cargo. El repeso de la paquetería NO vive en esta API (está en su reporte de facturación).

  • declared_valuenumber | nullrequerido

    Valor declarado (MXN).

  • contentstring | nullrequerido

    Descripción del contenido capturada al crear el envío.

  • senderobjectrequerido

    Remitente, con la misma forma mínima que recipient: solo nombre + ciudad/estado/CP — nunca la dirección completa ni teléfonos.

    Mostrar 4 campos
    • namestring | nullrequerido
    • citystring | nullrequerido
    • statestring | nullrequerido
    • postal_codestring | nullrequerido
  • recipientobjectrequerido

    Solo nombre + ciudad/estado/CP — nunca la dirección completa ni teléfonos.

    Mostrar 4 campos
    • namestring | nullrequerido
    • citystring | nullrequerido
    • statestring | nullrequerido
    • postal_codestring | nullrequerido
›Pagination
  • pageintegerrequerido
  • limitintegerrequerido
  • totalintegerrequerido
›Tracking
  • guiastringrequerido
  • carrierstring | nullrequerido
  • statusstringrequerido
  • status_labelstringrequerido
  • originobjectrequerido
    Mostrar 2 campos
    • citystring | null
    • statestring | null
  • destinationobjectrequerido
    Mostrar 2 campos
    • citystring | null
    • statestring | null
  • created_atstring | nullrequerido
  • eventsobject[]requerido
    Mostrar 3 campos
    • timestampstring | null
    • descriptionstring | null

      Nombres de receptor (POD) depurados.

    • locationstring | null
›CancellationCreated
  • idintegerrequerido
  • display_idstringrequerido

    Live: formato CR%08d. Sandbox: SBX-CR-<id>.

  • status"pending" | "cancelled"requerido

    Live devuelve pending (la solicitud entra a la cola de staff). Sandbox devuelve cancelled (cancelación síncrona y terminal — ver x-test-mode).

  • refund_status"not_applicable" | "refunded"requerido

    Live: not_applicable (reembolso lo resuelve el staff). Sandbox: refunded (reembolso inmediato).

  • refund_amount_expectednumber | null
  • cancellation_deadlinestring | null

    ISO-8601 UTC (Z). Siempre null en sandbox.

›Pickup
  • idintegerrequerido

    Id numérico de la recolección.

  • display_idstringrequerido

    Live: formato PR%08d. Sandbox: SBX-PU-<id>.

  • shipment_idstring | nullrequerido
  • status"pending" | "scheduled" | "awaiting_confirmation" | "failed" | "cancelled"requerido

    Una solicitud SIEMPRE nace en awaiting_confirmation (se está gestionando; NO la presentes como confirmada). Pasa a scheduled cuando el equipo la confirma con la paquetería — ahí llegan confirmed_window_* y confirmation_number, y se envía el correo al solicitante. failed = no se pudo agendar (entregar en sucursal). cancelled = la canceló quien la pidió. pending no se usa. El sandbox devuelve awaiting_confirmation, igual que producción.

  • carrierstring | null

    Paquetería física que hará la recolección.

  • requested_window_startstring | null

    ISO-8601 UTC (Z). Inicio de la ventana solicitada.

  • requested_window_endstring | null

    ISO-8601 UTC (Z). Fin de la ventana solicitada.

  • confirmed_window_startstring | null

    ISO-8601 UTC (Z). Ventana confirmada por la paquetería, cuando la devuelve.

  • confirmed_window_endstring | null

    ISO-8601 UTC (Z).

  • confirmation_numberstring | null

    Folio/referencia de recolección de la paquetería.

  • created_atstring | null
  • updated_atstring | null
›Cancellation
  • idintegerrequerido
  • display_idstringrequerido
  • shipment_idstring | nullrequerido
  • status"pending" | "approved" | "in_progress" | "cancelled" | "rejected_by_carrier" | "not_cancellable" | "reported_to_carrier" | "rejected" | "failed" | "expired"requerido
  • refund_status"not_applicable" | "refund_in_progress" | "refunded" | "refund_denied"requerido
  • reason_codestring | nullrequerido
  • reason_textstring | nullrequerido
  • refund_amount_expectednumber | nullrequerido
  • refund_amount_actualnumber | nullrequerido
  • cancellation_deadlinestring | nullrequerido
  • created_atstring | nullrequerido
  • updated_atstring | nullrequerido
  • resolved_atstring | nullrequerido
›Balance
  • balancenumberrequerido

    available + held (total del principal en el libro mayor).

  • heldnumberrequerido

    Autorizaciones pendientes (holds).

  • availablenumberrequerido

    Lo gastable por un envío nuevo.

  • currency"MXN"requerido
  • scopeobjectrequerido
    Mostrar 2 campos
    • type"user" | "pdv"requerido
    • idstringrequerido
›SenderCreateRequestEl mismo vocabulario de campos que el from de POST /shipments, con city/state opcionales aquí (se autocompletan del catálogo SEPOMEX según el CP — la misma validación de exactitud que POST /contacts/import).

El mismo vocabulario de campos que el from de POST /shipments, con city/state opcionales aquí (se autocompletan del catálogo SEPOMEX según el CP — la misma validación de exactitud que POST /contacts/import). name es el nombre completo; apellido_paterno/apellido_materno están deprecados (se aceptan y se integran a name). Campos desconocidos → 422. Los campos exceden-longitud se recortan al límite de columna.

  • namestringrequerido

    Nombre de pila (nombre).

  • apellido_paternostring

    DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

  • apellido_maternostring

    DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

  • companystring

    Razón social.

  • rfcstring
  • phonestringrequerido
  • emailstring
  • streetstringrequerido
  • numberstring

    Número exterior.

  • coloniastringrequerido
  • citystring

    Opcional: si se omite se autocompleta del catálogo SEPOMEX según el CP; si viene, debe coincidir con el CP (si no → 422 nombrando la ciudad esperada).

  • statestring

    Opcional: si se omite se autocompleta del catálogo SEPOMEX según el CP; si viene, debe coincidir con el CP (si no → 422 nombrando el estado esperado).

  • postal_codestringrequerido

    5 dígitos; debe EXISTIR en el catálogo postal (SEPOMEX).

  • country"MX"

    Opcional. Solo MX — cualquier otro valor responde 422.

›SenderUpdateRequestActualización PARCIAL: solo los campos presentes cambian.

Actualización PARCIAL: solo los campos presentes cambian. Se debe enviar al menos uno. Un campo requerido (name/phone/street/colonia/city/state/postal_code) puede cambiarse pero NO vaciarse. Campos opcionales aceptan null para limpiarse. Campos desconocidos → 422. Si la edición dejaría la dirección idéntica a OTRA visible → 409 DUPLICATE_ADDRESS.

  • namestring
  • apellido_paternostring | null

    DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

  • apellido_maternostring | null

    DEPRECATED (2026-09-21): el nombre completo va en name. Aún se acepta y se integra a name al guardar.

  • companystring | null
  • rfcstring | null
  • phonestring
  • emailstring | null
  • streetstring
  • numberstring | null
  • coloniastring
  • citystring
  • statestring
  • postal_codestring
  • country"MX"
›RecipientCreateRequestEl mismo vocabulario de campos que el to de POST /shipments + sender_id, con city/state opcionales aquí (se autocompletan del catálogo SEPOMEX según el CP — la misma validación de exactitud que POST /contacts/import).

El mismo vocabulario de campos que el to de POST /shipments + sender_id, con city/state opcionales aquí (se autocompletan del catálogo SEPOMEX según el CP — la misma validación de exactitud que POST /contacts/import). alias/referencia/delivery_instructions solo se aplican al CREAR (una reutilización devuelve la fila existente intacta).

  • sender_idstringrequerido

    Remitente dueño (visible para la llave; si no → 404 ciego).

  • aliasstring

    Nombre descriptivo ("Casa", "Oficina").

  • namestringrequerido
  • phonestringrequerido
  • emailstring
  • streetstringrequerido
  • numberstring
  • coloniastringrequerido
  • citystring

    Opcional: si se omite se autocompleta del catálogo SEPOMEX según el CP; si viene, debe coincidir con el CP (si no → 422 nombrando la ciudad esperada).

  • statestring

    Opcional: si se omite se autocompleta del catálogo SEPOMEX según el CP; si viene, debe coincidir con el CP (si no → 422 nombrando el estado esperado).

  • postal_codestringrequerido

    5 dígitos; debe EXISTIR en el catálogo postal (SEPOMEX).

  • country"MX"
  • referenciastring

    Referencias del domicilio.

  • delivery_instructionsstring
›RecipientUpdateRequestActualización PARCIAL.

Actualización PARCIAL. sender_id NO puede cambiarse (los destinatarios no se re-asignan de remitente) → 422 si se envía. Misma disciplina que SenderUpdateRequest: al menos un campo, no vaciar requeridos, null limpia opcionales, unificar con OTRO destinatario del MISMO remitente → 409 DUPLICATE_ADDRESS.

  • aliasstring | null
  • namestring
  • phonestring
  • emailstring | null
  • streetstring
  • numberstring | null
  • coloniastring
  • citystring
  • statestring
  • postal_codestring
  • country"MX"
  • referenciastring | null
  • delivery_instructionsstring | null
›SenderRecurso remitente.

Recurso remitente. Su id sirve como from_id en POST /rates y POST /shipments.

  • idstringrequerido
  • namestring | nullrequerido
  • apellido_paternonullrequerido

    Siempre null: el nombre completo está en name.

  • apellido_maternonullrequerido

    Siempre null: el nombre completo está en name.

  • companystring | nullrequerido
  • rfcstring | nullrequerido
  • phonestring | nullrequerido
  • emailstring | nullrequerido
  • streetstring | nullrequerido
  • numberstring | nullrequerido
  • coloniastring | nullrequerido
  • citystring | nullrequerido
  • statestring | nullrequerido
  • postal_codestring | nullrequerido
  • created_atstring | nullrequerido

    ISO-8601 UTC (Z).

›RecipientRecurso destinatario.

Recurso destinatario. Su id sirve como to_id en POST /rates y POST /shipments (requiere from_id del remitente dueño).

  • idstringrequerido
  • sender_idstringrequerido
  • aliasstring | nullrequerido
  • namestring | nullrequerido
  • phonestring | nullrequerido
  • emailstring | nullrequerido
  • streetstring | nullrequerido
  • numberstring | nullrequerido
  • coloniastring | nullrequerido
  • citystring | nullrequerido
  • statestring | nullrequerido
  • postal_codestring | nullrequerido
  • referenciastring | nullrequerido
  • delivery_instructionsstring | nullrequerido
  • created_atstring | nullrequerido

    ISO-8601 UTC (Z).

›Colonia
  • namestringrequerido

    Nombre del asentamiento (d_asenta de SEPOMEX). Úsalo como colonia/neighborhood.

  • settlement_typestring | nullrequerido

    Tipo de asentamiento SEPOMEX: Colonia, Fraccionamiento, Pueblo, Barrio, etc. Gran usuario = CP propio de una institución (ver gran_usuario).

›PostalCodeResultado de GET /postal-codes/{postal_code}.

Resultado de GET /postal-codes/{postal_code}. Datos de referencia SEPOMEX (públicos, solo lectura).

  • postal_codestringrequerido

    El CP consultado (5 dígitos).

  • statestring | nullrequerido

    Estado (d_estado). Úsalo como state.

  • state_codestring | nullrequerido

    Código de estado normalizado (mismo esquema que state_code del directorio; p. ej. DF, JAL, NL).

  • municipalitystring | nullrequerido

    Municipio/alcaldía (d_mnpio). Suele ser el mejor valor para city.

  • citystring | nullrequerido

    Ciudad (d_ciudad) cuando SEPOMEX la distingue del municipio; null en la mayoría de los CP.

  • coloniasColonia[]requerido

    Asentamientos únicos del CP, ordenados por nombre. Un CP mapea a un estado/municipio pero a muchas colonias.

    Mostrar 2 campos
    • namestringrequerido

      Nombre del asentamiento (d_asenta de SEPOMEX). Úsalo como colonia/neighborhood.

    • settlement_typestring | nullrequerido

      Tipo de asentamiento SEPOMEX: Colonia, Fraccionamiento, Pueblo, Barrio, etc. Gran usuario = CP propio de una institución (ver gran_usuario).

  • gran_usuarioobject | nullrequerido

    Presente cuando el CP es de «gran usuario»: un código que Correos de México asignó a una sola institución (p. ej. 80129, Palacio de Gobierno del Estado de Sinaloa) y que ya no publica en su catálogo, aunque la institución lo sigue usando. Es válido, pero varias paqueterías no lo atienden y cotizarlo trae menos tarifas; cotiza y envía con parent_postal_code y parent_settlement como colonia. null en un CP ordinario.

    Mostrar 3 campos
    • institutionstringrequerido

      La institución dueña del CP.

    • parent_postal_codestring | nullrequerido

      CP oficial SEPOMEX de la colonia donde está la institución (80129 → 80000). Úsalo en lugar del CP especial. null si no se conoce.

    • parent_settlementstring | nullrequerido

      La colonia de parent_postal_code donde está la institución (p. ej. Centro Sinaloa). null si no se conoce.

›WebhookEventEventos suscribibles.

Eventos suscribibles. shipment.created se emite al comprar la guía; shipment.collected…shipment.exception cuando el rastreo observa una transición REAL del estatus canónico — un estatus no terminal puede repetirse legítimamente (exception → in_transit → exception produce dos shipment.exception), pero la misma transición observada dos veces se entrega una sola vez; shipment.delivered, shipment.returned y shipment.cancelled son terminales y se entregan a lo sumo una vez por envío (cancelled también cuando la cancelación queda en firme); pickup.resolved cuando nuestro equipo cierra una solicitud de recolección (scheduled o failed). El evento ping solo existe para POST /webhooks/{id}/test y no es suscribible.

›WebhookCreateRequest
  • urlstringrequerido

    Endpoint https:// público en el puerto 443 u 8443 (cualquier otro puerto se rechaza). Se rechazan http, credenciales embebidas, fragmentos, localhost/.local/.internal, literales IP privadas, hosts que resuelvan a rangos privados y hosts que apunten al propio API de Enviadores.

  • eventsWebhookEvent[]requerido

    Eventos a recibir (duplicados se descartan).

›WebhookRecurso webhook.

Recurso webhook. NUNCA incluye el secreto: solo existe en la respuesta de POST /webhooks.

  • idstringrequerido

    wh_ + 24 hex.

  • urlstringrequerido
  • eventsWebhookEvent[]requerido
  • mode"live" | "test"requerido

    Lane de la llave que lo registró. live recibe eventos reales; test solo shipment.created/shipment.cancelled del sandbox y el ping.

  • status"active" | "disabled"requerido

    disabled = desactivado automáticamente: tras 20 fallos consecutivos, o porque la llave API que lo registró fue revocada (disabled_reason: key_revoked — un webhook muere con su llave). Al desactivarse, sus eventos pendientes se descartan. No cuenta para el tope; elimínalo y regístralo de nuevo.

  • failure_countintegerrequerido

    Fallos CONSECUTIVOS de entrega (cualquier respuesta no 2xx o error de transporte). Vuelve a 0 con cada 2xx. Los pings de prueba no lo afectan.

  • last_delivery_atstring | null

    ISO-8601 UTC (Z). Último intento (éxito o fallo), pings incluidos.

  • last_delivery_statusinteger | null

    Status HTTP del último intento; 0 = fallo de transporte (timeout/TLS/DNS).

  • created_atstring | null

    ISO-8601 UTC (Z).

  • disabled_atstring | null

    ISO-8601 UTC (Z).

  • disabled_reasonstring | null

    key_revoked cuando la llave que lo registró fue revocada; texto explicativo cuando fue por fallos consecutivos.

›WebhookCreated
  • webhookWebhookrequerido
    Mostrar 11 campos
    • idstringrequerido

      wh_ + 24 hex.

    • urlstringrequerido
    • eventsWebhookEvent[]requerido
    • mode"live" | "test"requerido

      Lane de la llave que lo registró. live recibe eventos reales; test solo shipment.created/shipment.cancelled del sandbox y el ping.

    • status"active" | "disabled"requerido

      disabled = desactivado automáticamente: tras 20 fallos consecutivos, o porque la llave API que lo registró fue revocada (disabled_reason: key_revoked — un webhook muere con su llave). Al desactivarse, sus eventos pendientes se descartan. No cuenta para el tope; elimínalo y regístralo de nuevo.

    • failure_countintegerrequerido

      Fallos CONSECUTIVOS de entrega (cualquier respuesta no 2xx o error de transporte). Vuelve a 0 con cada 2xx. Los pings de prueba no lo afectan.

    • last_delivery_atstring | null

      ISO-8601 UTC (Z). Último intento (éxito o fallo), pings incluidos.

    • last_delivery_statusinteger | null

      Status HTTP del último intento; 0 = fallo de transporte (timeout/TLS/DNS).

    • created_atstring | null

      ISO-8601 UTC (Z).

    • disabled_atstring | null

      ISO-8601 UTC (Z).

    • disabled_reasonstring | null

      key_revoked cuando la llave que lo registró fue revocada; texto explicativo cuando fue por fallos consecutivos.

  • secretstringrequerido

    Secreto de firma — se muestra SOLO aquí. Guárdalo: no se almacena y no se puede recuperar; para obtener otro hay que eliminar el webhook y registrarlo de nuevo.

›WebhookTestResult
  • deliveredbooleanrequerido

    true si el endpoint respondió 2xx dentro de 10 s.

  • response_statusinteger | nullrequerido

    Status HTTP recibido; null si no hubo respuesta (timeout, TLS, DNS).

  • error"timeout" | "connection failed" | "non-2xx status" | "endpoint rejected" | nullrequerido

    Clase del fallo, deliberadamente gruesa (el detalle de red no se expone para que el ping no sirva como sonda de puertos): timeout, connection failed (rechazo/reset/TLS/DNS), non-2xx status (ver response_status), endpoint rejected (la URL ya no pasa la política); null si se entregó.

  • event_idstringrequerido

    evt_ + 24 hex — el id del cuerpo enviado y el header X-Enviadores-Delivery.

›WebhookPayloadCuerpo de cada POST a tu endpoint.

Cuerpo de cada POST a tu endpoint. Headers: Content-Type: application/json, User-Agent: enviadores-webhooks/1.0, X-Enviadores-Signature: t=<unix>,v1=<hex> (v1 = HMAC-SHA256(secret, "<t>.<cuerpo crudo>")), X-Enviadores-Event, X-Enviadores-Delivery (= id). No se siguen redirecciones; responde 2xx en <10 s.

  • idstringrequerido

    evt_ + 24 hex. Estable entre reintentos de la misma entrega — deduplica con él.

  • eventstringrequerido

    Nombre del evento (WebhookEvent, o ping).

  • mode"live" | "test"requerido
  • created_atstringrequerido

    ISO-8601 UTC (Z). Momento en que se emitió el evento.

  • dataobjectrequerido

    {shipment: Shipment} para shipment.* (misma forma que GET /shipments/{id}), {pickup: Pickup} para pickup.resolved (misma forma que GET /pickups/{id}), {webhook_id} para ping.

›Me
  • accountobjectrequerido
    Mostrar 5 campos
    • idstringrequerido

      Id del usuario dueño de la llave.

    • namestring | nullrequerido

      Nombre visible de la cuenta (razón social, nombre de la persona, o el usuario). Nunca correo ni teléfono.

    • mode"test" | "live"requerido

      test = sandbox (nada es real); live = producción.

    • account_type"customer" | "staff"requerido

      Dos valores públicos: cuenta de cliente, o cuenta de nuestro mostrador. El rol interno no se expone.

    • tier_label"T0" | "T1" | "T2" | "T3" | "T4" | nullrequerido

      Escalón de verificación de una cuenta de registro propio. null si la cuenta no está en la escalera (creada por nuestro equipo) o si la consulta no está disponible.

  • keyobjectrequerido
    Mostrar 7 campos
    • idstringrequerido

      Id público de la llave (no es el secreto).

    • labelstring | nullrequerido

      Etiqueta que le puso su dueño.

    • mode"test" | "live"requerido
    • scopesstring[]requerido

      Los scopes EFECTIVOS de esta petición — lo que el gate acaba de aplicar.

    • scopes_mode"default" | "explicit"requerido

      default: la llave se creó con los permisos por omisión y se actualiza sola cuando aparecen nuevos. explicit: alguien la restringió al crearla; queda congelada así.

    • spend_cap_daily_mxnnumber | nullrequerido

      Tope de gasto en ventana móvil de 24 h, o null si no tiene.

    • rate_limit_tier"standard" | "elevated"requerido
  • balanceobjectrequerido
    Mostrar 2 campos
    • availablenumberrequerido

      Lo gastable ahora mismo — el mismo número que GET /balance. Para el desglose (held, total, scope) usa ese endpoint.

    • currency"MXN"requerido
  • capabilitiesobjectrequerido
    Mostrar 4 campos
    • pickupsbooleanrequerido

      Si esta conexión puede solicitar recolecciones. Siempre true en sandbox.

    • internationalbooleanrequerido

      Si la API acepta destinos fuera de México. Hoy false (v1 es doméstico MX).

    • multi_packagebooleanrequerido

      Si una solicitud puede llevar más de un paquete. Se deriva de la misma validación que aplica POST /shipments.

    • webhooksbooleanrequerido

      Si existen webhooks salientes en esta superficie. Hoy false: consulta con GET /shipments o GET /tracking/{guia}.

›Transaction
  • idstringrequerido

    Id del movimiento (estable; el que conviene citar a soporte).

  • type"charge" | "refund" | "topup" | "adjustment"requerido

    Vocabulario público: cargo, devolución, recarga en línea, o cualquier otro abono.

  • amountnumberrequerido

    Importe en MXN CON SIGNO: negativo si salió saldo, positivo si entró.

  • balance_afternumber | nullrequerido

    Saldo del principal después de este movimiento.

  • shipment_idstring | nullrequerido

    Envío relacionado, cuando el movimiento nació de uno.

  • descriptionstring | nullrequerido

    Texto del movimiento. Las correcciones de cargo (cuando reasignamos un cobro al fondo correcto) se reportan siempre como Ajuste: el detalle interno describe nuestra propia contabilidad y nombra al operador que la hizo, y nada de eso viaja a esta superficie.

  • created_atstring | nullrequerido

    ISO-8601 en UTC (Z).