---
title: "Listar envíos (paginado)"
method: GET
path: "/shipments"
tags: ["shipments"]
---

# Listar envíos (paginado)

`GET /shipments`

## Request

- Base URL: `https://api.enviadores.com.mx/api/v1`
- URL: `https://api.enviadores.com.mx/api/v1/shipments`
- Auth: HTTP bearer

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.

**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).

## Query parameters

- `page` integer
- `limit` integer
- `guia` string
- `status` 'created' | 'collected' | 'in_transit' | 'out_for_delivery' | 'delivered' | 'exception' | 'returned' | 'cancelled' | 'unknown'
- `from` string, date
- `to` string, date

## Headers

- `X-PDV-ID` string

## Response `200`

Página de envíos.

- object
  - `success` unknown, required
  - `data` object, required
    - `shipments` Shipment[], required
      - `id` string, required
      - `guia` string, nullable, required
      - `carrier` string, nullable, required: Nombre canónico de la paquetería física (SSOT de la plataforma).
      - `service` string, nullable, required
      - `status` 'created' | 'collected' | 'in_transit' | 'out_for_delivery' | 'delivered' | 'exception' | 'returned' | 'cancelled' | 'unknown', required
      - `status_label` string, required: Etiqueta en español del estatus canónico.
      - `total` number, nullable, required
      - `currency` 'MXN', required
      - `created_at` string, nullable, required: ISO-8601 UTC (Z).
      - `tracking_pending` boolean, required
      - `label_url` string, required
      - `packages` object[], required: 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.
        - `weight_kg` number, nullable, required
        - `length_cm` number, nullable, required
        - `width_cm` number, nullable, required
        - `height_cm` number, nullable, required
      - `billable_weight_kg` number, nullable, required: 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_value` number, nullable, required: Valor declarado (MXN).
      - `content` string, nullable, required: Descripción del contenido capturada al crear el envío.
      - `sender` object, required: Remitente, con la misma forma mínima que `recipient`: solo nombre + ciudad/estado/CP — nunca la dirección completa ni teléfonos.
        - `name` string, nullable, required
        - `city` string, nullable, required
        - `state` string, nullable, required
        - `postal_code` string, nullable, required
      - `recipient` object, required: Solo nombre + ciudad/estado/CP — nunca la dirección completa ni teléfonos.
        - `name` string, nullable, required
        - `city` string, nullable, required
        - `state` string, nullable, required
        - `postal_code` string, nullable, required
    - `pagination` Pagination, required
      - `page` integer, required
      - `limit` integer, required
      - `total` integer, required
  - `requestId` string, required: Correlación de logs; también en el header `X-Request-Id`.

## Other responses

- `401`: `UNAUTHORIZED` — credencial Bearer ausente/mal formada o llave desconocida/revocada (respuesta idéntica en ambos casos).
- `403`: `INSUFFICIENT_SCOPE` — a la llave le falta el scope requerido · `PDV_NOT_ALLOWED` — violación del carril X-PDV-ID. 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).
- `404`: `NOT_FOUND` — ruta desconocida, recurso ajeno/inexistente, o la API pública no está habilitada (respuestas indistinguibles por diseño).
- `422`: `VALIDATION_ERROR` — filtro inválido (details.fields).
- `429`: `RATE_LIMITED` — bucket de la llave agotado. 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`.
- `500`: `SERVER_ERROR` — falla interna (p. ej. lectura de base de datos). Es transitoria; reintenta con backoff.

---

[API](https://skmtc.dev/enviadores/apis/enviadores-public-api.md) · [All operations](https://skmtc.dev/enviadores/apis/enviadores-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/enviadores/apis/enviadores-public-api/revisions/3633f08c889c?raw)
