---
title: "Tu primera guía"
description: "Crea un envío, compara cotizaciones y compra tu primera guía en modo de prueba — de cero a PDF en minutos."
---

Aquí creas tu primera guía de envío en modo de prueba. El modo de prueba usa paqueterías simuladas y saldo virtual. No se mueve dinero real. Los pasos en producción son los mismos. Para pasar a producción, cambia la llave.

Necesitas una cuenta de SendIt y tu llave de prueba (`sk_test_...`). La encuentras en el dashboard, en **Configuración → Llaves de API**, o vía `GET /v1/api-keys`.

1. **Crea un envío**

    Un envío describe origen, destino y paquete. La respuesta trae el envío recién creado y `rates[]`. En `rates[]` vienen las cotizaciones de todas las paqueterías disponibles para esa ruta.

    <CodeGroup>

    ```bash curl
    curl -X POST https://api.sendit.mx/v1/shipments \
      -H "X-API-Key: sk_test_..." \
      -H "Content-Type: application/json" \
      -d '{
        "from": {
          "contactName": "Almacén CDMX",
          "contactPhone": "+5215512345678",
          "street": "Av. Insurgentes Sur",
          "exteriorNumber": "1602",
          "neighborhood": "Crédito Constructor",
          "city": "Ciudad de México",
          "state": "CDMX",
          "postalCode": "03940",
          "country": "MX"
        },
        "to": {
          "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 }
      }'
    ```

    ```js Node.js
    const response = await fetch("https://api.sendit.mx/v1/shipments", {
      method: "POST",
      headers: {
        "X-API-Key": process.env.SENDIT_API_KEY, // sk_test_...
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        from: {
          contactName: "Almacén CDMX",
          contactPhone: "+5215512345678",
          street: "Av. Insurgentes Sur",
          exteriorNumber: "1602",
          neighborhood: "Crédito Constructor",
          city: "Ciudad de México",
          state: "CDMX",
          postalCode: "03940",
          country: "MX",
        },
        to: {
          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 },
      }),
    });

    const { data: shipment } = await response.json();
    console.log(shipment.id, shipment.rates.length);
    ```

    </CodeGroup>

    La respuesta (recortada) trae el envío en estado `DRAFT` y sus cotizaciones:

    ```json
    {
      "success": true,
      "data": {
        "id": "clxq1w2e3r4t5y6u7i8o9p0a",
        "status": "DRAFT",
        "rates": [
          {
            "id": "DHL_standard_a1b2c3",
            "carrierCode": "DHL",
            "serviceCode": "EXPRESS_WORLDWIDE",
            "serviceName": "DHL Express Nacional",
            "serviceLevel": "standard",
            "totalPrice": 326.82,
            "currency": "MXN",
            "estimatedDays": { "min": 1, "max": 2 },
            "expiresAt": "2026-07-18T14:30:00.000Z"
          },
          {
            "id": "ESTAFETA_economy_x9y8z7",
            "carrierCode": "ESTAFETA",
            "serviceCode": "TERRESTRE",
            "serviceName": "Estafeta Terrestre",
            "serviceLevel": "economy",
            "totalPrice": 289.50,
            "currency": "MXN",
            "estimatedDays": { "min": 3, "max": 5 },
            "expiresAt": "2026-07-18T14:30:00.000Z"
          }
        ],
        "ratesStatus": "ready",
        "ratesExpiresAt": "2026-07-18T14:30:00.000Z"
      }
    }
    ```

2. **Elige una cotización**

    Cada elemento de `rates[]` es una oferta en firme. Su `totalPrice` incluye IVA y es **exactamente** lo que se debitará de tu monedero. Elige por precio, por velocidad o por paquetería. Para el siguiente paso solo necesitas el `id` de la tarifa.

    ```js Node.js
    const cheapest = shipment.rates
      .slice()
      .sort((a, b) => a.totalPrice - b.totalPrice)[0];

    console.log(cheapest.id); // "ESTAFETA_economy_x9y8z7"
    ```

    Las cotizaciones son válidas 24 horas. Si expiran, pide unas frescas con `GET /v1/shipments/:id/rates?refresh=true`.

    :::note
    Si ya sabes con qué paquetería enviar, o quieres la más barata, agrega el objeto `purchase` al crear el envío. La guía llega en la misma respuesta. Ver [Compra en una llamada](/shipping/shipments#compra-en-una-llamada).
    :::

3. **Compra la guía**

    Envía el `rateId` elegido. El encabezado `Idempotency-Key` es opcional, pero mándalo en esta llamada. Si tu petición se interrumpe y la reintentas con la misma llave, no habrá doble cargo.

    <CodeGroup>

    ```bash curl
    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": "ESTAFETA_economy_x9y8z7" }'
    ```

    ```js Node.js
    const purchase = await fetch(
      `https://api.sendit.mx/v1/shipments/${shipment.id}/label`,
      {
        method: "POST",
        headers: {
          "X-API-Key": process.env.SENDIT_API_KEY,
          "Idempotency-Key": crypto.randomUUID(),
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ rateId: cheapest.id }),
      }
    );

    const { data: label } = await purchase.json();
    console.log(label.trackingNumber, label.labelUrl);
    ```

    </CodeGroup>

    ```json
    {
      "success": true,
      "data": {
        "shipmentId": "clxq1w2e3r4t5y6u7i8o9p0a",
        "labelId": "clxlbl456abc789def012ghi",
        "trackingNumber": "TEST-ESTAFETA-A1B2C3D4",
        "labelUrl": "https://labels.sendit.mx/test/clxq1w2e3r4t5y6u7i8o9p0a/TEST-ESTAFETA-A1B2C3D4.pdf",
        "carrierCode": "ESTAFETA",
        "serviceName": "Estafeta Terrestre",
        "charged": "289.50",
        "currency": "MXN",
        "walletBalanceAfter": "9710.50",
        "breakdown": {
          "ivaAmount": "39.93",
          "overageCharge": "0.00",
          "total": "289.50"
        }
      }
    }
    ```

    Descarga el PDF de `labelUrl`. Esa es tu guía. El cargo salió del saldo virtual de prueba, no de dinero real.

4. **Ve tu envío avanzar**

    En modo de prueba el paquete simula su viaje solo. Va de `PICKED_UP` a `DELIVERED` en menos de media hora. Para no esperar, avanza el estado a mano:

    ```bash
    curl -X POST https://api.sendit.mx/v1/shipments/clxq1w2e3r4t5y6u7i8o9p0a/test/advance-status \
      -H "X-API-Key: sk_test_..."
    ```

    Cada llamada avanza un paso:

    ```text
    LABEL_PURCHASED → READY_FOR_PICKUP → PICKED_UP → IN_TRANSIT → OUT_FOR_DELIVERY → DELIVERED
    ```

    Consulta el estado y el historial de eventos en cualquier momento con `GET /v1/shipments/:id`.

## Siguientes pasos

<CardGroup cols={2}>
  <Card title="Modo de prueba a fondo" href="/getting-started/test-mode" icon="flask-conical">
    Escenarios de falla, saldo virtual y todo lo que simula el sandbox.
  </Card>
  <Card title="Webhooks" href="/webhooks-and-events/webhooks" icon="webhook">
    Deja el polling. Recibe cada evento en tu servidor.
  </Card>
  <Card title="Envíos a fondo" href="/shipping/shipments" icon="package">
    Direcciones guardadas, seguro, referencias externas y más.
  </Card>
  <Card title="Idempotencia" href="/api-conventions/idempotency" icon="repeat">
    Cómo reintentar sin riesgo en los endpoints que mueven dinero.
  </Card>
</CardGroup>
