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

Labels

Buy a label (guía) from a rate: the quoted price is the charged price, no surprises.

Buying a label turns a rate into a print-ready shipping label (guía). Your wallet is debited at the quoted price, never more and never less. There are two ways to buy. Two-step compares rates and buys with a rateId, as shown below. One-call adds purchase when you create the shipment. See One-call buy.

Buy a label

You need a shipment with valid rates and the chosen rateId. The Idempotency-Key header is optional but recommended: retrying with the same key replays the result and avoids a double charge (see Idempotency).

Body parameters

PropType
rateIdstring

The chosen rate's id, taken from rates[]. Valid for 24 hours.

Typestring
labelFormat?string

PDF or ZPL. If omitted, PDF is used.

Typestring
DefaultPDF
externalReference?string

Your own reference for this purchase. Up to 255 characters.

Typestring
async?boolean

true returns 202 and an attempt you poll later. See Asynchronous purchase.

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, // generated and persisted by you
      "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"
    }
  }
}

The shipment moves to LABEL_PURCHASED, tracking goes live, and labelUrl points at the print-ready PDF.

One-call buy

If you already know the carrier and service, or just want the cheapest, skip the second step. Add a purchase object when you create the shipment. Choose carrierCode + serviceCode for the exact product, or 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_origin_address",
    "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_origin_address",
    toAddress: { /* ...destination... */ },
    parcel: { length: 30, width: 20, height: 15, weight: 2.5 },
    purchase: { strategy: "cheapest", labelFormat: "PDF" },
  }),
}).then((r) => r.json());

// The label arrives in the same response
console.log(shipment.label.trackingNumber, shipment.label.labelUrl);

The response includes purchasedRate and label, with trackingNumber, labelUrl, and charged. The shipment is always created first, so a failed purchase leaves you a recoverable DRAFT. The full failure semantics are in Shipments. With a scoped API key you need labels:write in addition to shipments:write.

The price contract

A purchase gives you three guarantees:

  1. If the balance is short, nothing happens. The purchase fails with 402 INSUFFICIENT_BALANCE. There is no charge and no label.
  2. You are charged the quoted price. The chosen rate’s totalPrice is debited exactly, IVA included. The amount is not recalculated at purchase.
  3. A proven failure returns the charge. The attempt reaches failed after the refund is credited.

An inconclusive outcome reaches action_required. That status confirms neither a label nor a refund. Do not start another purchase for the shipment.

Retrying with the same Idempotency-Key replays the original result. It never creates a second charge. Details in Idempotency.

Asynchronous purchase

A purchase is synchronous by default: you wait and the label comes back in the response. With async: true the request returns at once and you read the outcome later. Use it for volume without one open connection per label. See Asynchronous operations.

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 }'

The response is 202 Accepted. The Location header points at the attempt, and Retry-After: 2 is a polling hint:

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

Poll the attempt until it reaches a terminal status:

GET /v1/label-purchase-attempts/lat_550e8400-e29b-41d4-a716-446655440000
status Meaning
pending Accepted, not started
processing In progress
succeeded Done. The attempt carries the label
failed The failure ended and the charge was refunded. No label exists
action_required The outcome is inconclusive. No label or refund is implied

Three things worth knowing:

  • Funds are checked before the attempt is accepted. If the balance is short you get 402 INSUFFICIENT_BALANCE immediately, and no half-finished attempt is left behind.
  • Repeating the same purchase returns the same attempt, as long as rateId, format, external reference, and the async preference all match. Change any of them and you get 409 SHIPMENT_LABEL_IN_PROGRESS.
  • To hear about it without polling, subscribe to the label.purchase.completed webhook: it fires on all three terminal outcomes.

Label formats

labelFormat Use
PDF Default. Letter size, print-ready
ZPL Zebra thermal printers (raw)

If you omit labelFormat, PDF is used.

Purchase errors

Code When How to resolve it
402 INSUFFICIENT_BALANCE Balance can’t cover totalPrice Fund your wallet; details includes the shortfall
410 RATES_EXPIRED The rate expired (24 h) GET /v1/shipments/:id/rates and buy with the fresh rateId
409 SHIPMENT_ALREADY_PROCESSED Shipment already has a label or isn’t DRAFT Check the shipment; for another label, create another shipment
409 SHIPMENT_LABEL_IN_PROGRESS An active attempt already owns the shipment Retrieve the existing attempt; do not start another purchase
502 CARRIER_ERROR The carrier conclusively rejected the purchase Confirm that the attempt reached failed before buying again

After the purchase

  • Tracking: the trackingNumber starts producing tracking events and webhooks.
  • Pickup: schedule the carrier to collect the package. See Pickups.
  • If you made a mistake: void an unused label for a full refund. See Refunds & voids.

¿Te ha resultado útil esta página?