Saltar al contenido
SendIt está en desarrollo y todavía no opera comercialmente · SendIt is in development and not yet commercially available.
SendItdocs
Español
Esc
navigateopen⌘Jpreview
En esta página

Guías

Compra una guía a partir de una cotización: el precio cotizado es el precio cobrado, sin sorpresas.

Comprar una guía convierte una cotización en una etiqueta lista para imprimir. El monedero se debita al precio cotizado, nunca más y nunca menos. Hay dos maneras de comprar. En dos pasos comparas cotizaciones y compras con un rateId, como se muestra aquí abajo. En una llamada agregas purchase al crear el envío. Ver Compra en una llamada.

Compra una guía

Necesitas un envío con cotizaciones vigentes y el rateId elegido. El encabezado Idempotency-Key es opcional pero recomendado: reintentar con la misma llave reproduce el resultado y evita un doble cargo (ver Idempotencia).

Parámetros del cuerpo

PropType
rateIdstring

El id de la tarifa elegida, tomado de rates[]. Vigencia: 24 horas.

Typestring
labelFormat?string

PDF o ZPL. Si lo omites, se usa PDF.

Typestring
DefaultPDF
externalReference?string

Tu propia referencia para esta compra. Máximo 255 caracteres.

Typestring
async?boolean

true devuelve 202 y un intento que consultas después. Ver Compra asíncrona.

Typeboolean
Defaultfalse
curl -X POST https://api.sendit.mx/v1/shipments/clxq1w2e3r4t5y6u7i8o9p0a/label \
  -H "X-API-Key: sk_test_..." \
  -H "Idempotency-Key: 1f0e6f0e-59a4-4a6f-9d2e-9b1a7b2c3d4e" \
  -H "Content-Type: application/json" \
  -d '{ "rateId": "DHL_standard_a1b2c3", "labelFormat": "PDF" }'
const res = await fetch(
  `https://api.sendit.mx/v1/shipments/${shipmentId}/label`,
  {
    method: "POST",
    headers: {
      "X-API-Key": process.env.SENDIT_API_KEY,
      "Idempotency-Key": idempotencyKey, // generada y persistida por ti
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ rateId, labelFormat: "PDF" }),
  }
);

const { data: label } = await res.json();
{
  "success": true,
  "data": {
    "shipmentId": "clxq1w2e3r4t5y6u7i8o9p0a",
    "attemptId": "lat_550e8400-e29b-41d4-a716-446655440000",
    "labelId": "clxlbl456abc789def012ghi",
    "trackingNumber": "1234567890",
    "labelUrl": "https://labels.sendit.mx/clxq1w2e3r4t5y6u7i8o9p0a/1234567890.pdf",
    "carrierCode": "DHL",
    "serviceCode": "EXPRESS_WORLDWIDE",
    "serviceName": "DHL Express Nacional",
    "charged": "326.82",
    "currency": "MXN",
    "walletBalanceAfter": "12158.43",
    "breakdown": {
      "quotedTotal": "326.82",
      "ivaAmount": "45.08",
      "overageCharge": "0.00",
      "total": "326.82"
    }
  }
}

El envío pasa a LABEL_PURCHASED, el rastreo queda activo y labelUrl apunta al PDF listo para imprimir.

Compra en una llamada

Si ya sabes con qué paquetería y servicio enviarás, o solo quieres la más barata, sáltate el segundo paso. Agrega un objeto purchase al crear el envío y recibe la guía en la misma respuesta. Elige carrierCode + serviceCode para el producto exacto, o strategy: "cheapest".

curl -X POST https://api.sendit.mx/v1/shipments \
  -H "X-API-Key: sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "fromAddressId": "clx_direccion_origen",
    "toAddress": {
      "contactName": "María López",
      "contactPhone": "+5213312345678",
      "street": "Av. López Mateos",
      "exteriorNumber": "45",
      "neighborhood": "Jardines del Sol",
      "city": "Zapopan",
      "state": "JAL",
      "postalCode": "45050",
      "country": "MX"
    },
    "parcel": { "length": 30, "width": 20, "height": 15, "weight": 2.5 },
    "purchase": { "carrierCode": "DHL", "serviceCode": "EXPRESS_WORLDWIDE", "labelFormat": "PDF" }
  }'
const { data: shipment } = await fetch("https://api.sendit.mx/v1/shipments", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.SENDIT_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    fromAddressId: "clx_direccion_origen",
    toAddress: { /* ...destino... */ },
    parcel: { length: 30, width: 20, height: 15, weight: 2.5 },
    purchase: { strategy: "cheapest", labelFormat: "PDF" },
  }),
}).then((r) => r.json());

// La guía llega en la misma respuesta
console.log(shipment.label.trackingNumber, shipment.label.labelUrl);

La respuesta incluye purchasedRate y label, con trackingNumber, labelUrl y charged. El envío siempre se crea primero, así que una compra fallida te deja un DRAFT recuperable. La semántica de fallos completa está en Envíos. Con una llave de alcance acotado necesitas labels:write además de shipments:write.

El contrato de precio

La compra te da tres garantías:

  1. Si el saldo no alcanza, no pasa nada. La compra falla con 402 INSUFFICIENT_BALANCE. No hay cargo, no hay guía.
  2. Se cobra el precio cotizado. Se debita exactamente el totalPrice de la tarifa elegida, con IVA incluido. El monto no se recalcula al comprar.
  3. Una falla comprobada devuelve el cargo. El intento termina en failed después de acreditar el reembolso.

Un resultado no concluyente termina en action_required. Ese estado no confirma una guía ni un reembolso. No inicies otra compra para el mismo envío.

Reintentar con la misma Idempotency-Key reproduce el resultado original. Nunca genera un segundo cargo. Detalles en Idempotencia.

Compra asíncrona

Por defecto la compra es síncrona: esperas y recibes la guía en la respuesta. Con async: true la petición regresa de inmediato y tú consultas el resultado después. Sirve cuando compras en volumen y no quieres mantener una conexión abierta por cada guía. Ver Operaciones asíncronas.

curl -X POST https://api.sendit.mx/v1/shipments/clxq1w2e3r4t5y6u7i8o9p0a/label \
  -H "X-API-Key: sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{ "rateId": "DHL_standard_a1b2c3", "async": true }'

La respuesta es 202 Accepted. El encabezado Location apunta al intento y Retry-After: 2 te sugiere cada cuánto consultar:

{
  "success": true,
  "data": {
    "id": "lat_550e8400-e29b-41d4-a716-446655440000",
    "object": "label_purchase_attempt",
    "shipmentId": "clxq1w2e3r4t5y6u7i8o9p0a",
    "externalReference": null,
    "status": "pending",
    "livemode": false,
    "async": true,
    "pricing": {
      "quotedTotal": "326.82",
      "ivaAmount": "45.08",
      "overageCharge": "0.00",
      "netWalletCost": "326.82"
    },
    "charged": "326.82",
    "currency": "MXN",
    "walletBalanceAfter": "12158.43",
    "refundedAmount": null,
    "label": null,
    "error": null,
    "statusUrl": "/v1/label-purchase-attempts/lat_550e8400-e29b-41d4-a716-446655440000",
    "createdAt": "2026-08-01T10:00:00.000Z",
    "updatedAt": "2026-08-01T10:00:00.000Z",
    "completedAt": null
  }
}

Consulta el intento hasta que llegue a un estado terminal:

GET /v1/label-purchase-attempts/lat_550e8400-e29b-41d4-a716-446655440000
status Significado
pending Aceptado, aún no empieza
processing En curso
succeeded Listo. El intento trae la guía
failed La falla terminó y el cargo se reembolsó. No hay guía
action_required El resultado no es concluyente. No hay guía ni reembolso implícito

Tres cosas que conviene saber:

  • El saldo se valida antes de aceptar. Si no alcanza, recibes 402 INSUFFICIENT_BALANCE de inmediato y no queda ningún intento a medias.
  • Repetir la misma compra te devuelve el mismo intento, siempre que coincidan rateId, formato, referencia externa y preferencia de async. Si cambia algo, recibes 409 SHIPMENT_LABEL_IN_PROGRESS.
  • Para enterarte sin sondear, suscríbete al webhook label.purchase.completed: se dispara en los tres desenlaces terminales.

Formatos de guía

labelFormat Uso
PDF Por defecto. Tamaño carta, listo para imprimir
ZPL Impresoras térmicas Zebra (raw)

Si no envías labelFormat, se usa PDF.

Errores de compra

Código Cuándo Cómo resolverlo
402 INSUFFICIENT_BALANCE El saldo no cubre el totalPrice Fondea tu monedero; details incluye el faltante
410 RATES_EXPIRED La cotización venció (24 h) GET /v1/shipments/:id/rates y compra con el nuevo rateId
409 SHIPMENT_ALREADY_PROCESSED El envío ya tiene guía o no está en DRAFT Consulta el envío; si necesitas otra guía, crea otro envío
409 SHIPMENT_LABEL_IN_PROGRESS Ya existe un intento activo para el envío Consulta el intento existente; no inicies otra compra
502 CARRIER_ERROR La paquetería rechazó la compra de forma concluyente Confirma que el intento terminó en failed antes de volver a comprar

Después de la compra

¿Te ha resultado útil esta página?