---
title: "Cotizar en todas las paqueterías"
method: POST
path: "/rates"
tags: ["rates"]
---

# Cotizar en todas las paqueterías

`POST /rates`

## Request

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

Cotiza con todas las paqueterías activas y devuelve tarifas con `id` (`rate_id`) listas para `POST /shipments`. 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.*

## Query parameters

- `view` 'grouped'

## Headers

- `X-PDV-ID` string

## Request body

- RatesRequest: 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).
  - `from` RatesEndpointParty
    - `postal_code` string, required: 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).
    - `state` string
    - `city` string
    - `neighborhood` string
  - `from_id` string: Id de un remitente del directorio (`GET /senders`). Mutuamente excluyente con `from`.
  - `to` RatesEndpointParty
    - `postal_code` string, required: 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).
    - `state` string
    - `city` string
    - `neighborhood` string
  - `to_id` string: Id de un destinatario del directorio (`GET /recipients`). Mutuamente excluyente con `to`.
  - `package` object
    - `weight_kg` number, required: Peso real en kg. El facturable es max(real, volumétrico L*W*H/5000); máximo 70 kg.
    - `length_cm` number
    - `width_cm` number
    - `height_cm` number
  - `packages` object[]: 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.
    - `weight_kg` number, required: Peso real del bulto (kg).
    - `length_cm` number
    - `width_cm` number
    - `height_cm` number
  - `insurance` InsuranceRequest: 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_value` number: Valor a asegurar (MXN). La prima queda incluida en `pricing.total_price` de cada tarifa.
    - `declared_value` number: OBSOLETO — alias de `insured_value` (compatibilidad con integradores previos). Si mandas ambos deben coincidir.

## Response `200`

Cotización exitosa (forma autenticada multi-paquetería).

- object
  - `success` unknown, required
  - `data` union, required
    - object
      - `services` Rate[], required
        - `id` string, required: 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.
        - `carrier` string, nullable, required
        - `service_name` string, nullable, required: Nombre comercial del servicio tal como lo publica la paquetería.
        - `service_type` string, nullable: Clasificación por niveles: priority | express | standard_economy…
        - `tier` string, nullable: Igual a service_type (alias de compatibilidad).
        - `delivery_window` string, nullable: Ventana de entrega normalizada (p. ej. next_day, ground).
        - `pickup_included` boolean: La tarifa incluye recolección.
        - `address_delivery` boolean: Entrega a domicilio (false = entrega en sucursal/ocurre).
        - `pricing` object, required
          - `total_price` number, nullable, required: Total a cobrar en MXN, 2 decimales (IVA incluido; incluye la prima de seguro de plataforma cuando aplique).
          - `currency` 'MXN', required
          - `iva_included` boolean
          - `insurance_premium` number: Prima itemizada ya incluida en total_price (0 sin póliza).
          - `insured_value` number, nullable: Valor declarado asegurado (null sin póliza).
        - `delivery` object
          - `estimated_days` union
            - string
            - number
          - `estimated_date` string, nullable
          - `min_days` integer, nullable
          - `max_days` integer, nullable
        - `insurance_included` boolean
        - `insurance_not_supported` boolean: La paquetería no acepta seguro para esta tarifa.
        - `zona_extendida` boolean: 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_locked` boolean: 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; los servicios sin recolección (entrega en sucursal, cargos extra en mostrador) nunca se bloquean. Las rutas de desbloqueo vienen en `unlock_notice`.
      - `unlock_notice` string, nullable: 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_at` string, nullable, required: ISO-8601.
      - `stale_after` string, nullable, required: ISO-8601 (fetched_at + 5 min): re-cotiza para PRECIOS frescos; el rate_id sigue siendo enviable hasta los 30 min.
      - `meta` object, required
        - `billable_weight` number, nullable: Peso facturable (kg): max(real, volumétrico).
        - `volumetric_weight` number, nullable: L×W×H / 5000 (kg).
        - `zone` integer, nullable
      - `rate_id_expires_in_seconds` 1800, required: TTL del rate_id: crea el envío dentro de esta ventana o recibirás 409 RATE_EXPIRED.
      - `incomplete` boolean, required: `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.*
    - object: 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` que la forma plana.
      - `services` RateGroup[], required: Ordenados por `price_from` ascendente.
        - `service_key` string, required: Clave estable del servicio: `carrier|tier` (+ `|ventana` para priority).
        - `carrier` string, required: Nombre canónico de la paquetería.
        - `tier` 'priority' | 'express' | 'standard_economy' | 'international', required
        - `delivery_window` string, nullable, required
        - `service_label` string, required: Etiqueta legible, p. ej. `DHL · Express`, `Estafeta · Prioritario 10:30 AM`.
        - `estimated_days` union, required: De la opción más barata.
          - string
          - number
        - `pickup_available` boolean, required: true si ALGUNA opción incluye recolección.
        - `booking_locked` boolean, required: true solo si TODAS las opciones están bloqueadas para esta cuenta.
        - `price_from` number, nullable, required
        - `price_to` number, nullable, required
        - `currency` 'MXN', required
        - `options` object[], required: Precios reservables de este servicio, de menor a mayor. Reserva `options[0].rate_id` salvo que el usuario pida otra.
          - `rate_id` string, required: Para `POST /shipments`. OPACO.
          - `service_name` string, nullable, required: Nombre comercial del servicio tal como lo publica la paquetería.
          - `total_price` number, nullable, required
          - `estimated_days` union, required
            - string
            - number
          - `pickup_included` boolean, required
          - `booking_locked` boolean, required
          - `insurance_included` boolean, required: La opción incluye cobertura (ya dentro de total_price).
          - `insurance_not_supported` boolean, required: La paquetería no ofrece cobertura para esta opción.
          - `insurance_premium` number, required: Prima de plataforma itemizada (0 sin póliza).
          - `insured_value` number, nullable, required
      - `view` 'grouped', required
      - `unlock_notice` string, nullable
      - `fetched_at` string, nullable, required
      - `stale_after` string, nullable, required
      - `meta` object, required
      - `rate_id_expires_in_seconds` 1800, required
      - `incomplete` boolean, required: `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.*
  - `requestId` string, required: Correlación de logs; también en el header `X-Request-Id`.

## Other responses

- `400`: `INVALID_JSON` (cuerpo vacío/no-JSON) · `MISSING_ROUTE` · `MISSING_PACKAGE` · `WEIGHT_EXCEEDED` (peso facturable > 70 kg).
- `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` (details.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_HIGH` (valor asegurado sobre el máximo asegurable).
- `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`: `RATES_ERROR` — la cotización falló; reintenta.

---

[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)
