Draft then confirm

Updated May 31, 20265 min read

Create a draft shipment as a placeholder the moment an order arrives, before you've decided which location will fulfill it or how it'll be packed. Confirm it later, supplying the pickup location and parcels, when it's ready to book. The draft is visible in Carriyo immediately, so the customer-facing tracking page is live from checkout and you can watch order-processing SLAs from the moment the order lands, even if you don't push orders into Carriyo.

When to use this vs book a shipment in one call

UseWhen
Book a shipmentYou already know the pickup location and parcels, and want to book and print the label now (just-in-time). Simpler.
Draft then confirm (this recipe)You want the shipment in Carriyo from the moment the order arrives, for live customer tracking and SLA monitoring, before the fulfillment location or parcels are known. You confirm it later with those details.

Drafts cost nothing, no carrier booking, no label generation, no fees. They're cheap to create and edit.

Scenario

Common patterns:

  • Early customer tracking. Create the draft as the order arrives. The tracking page is live immediately, so shoppers can land on it right after checkout, before the parcel is booked.
  • Processing-SLA visibility. With the shipment in Carriyo from order time, you can see how long orders wait before they're processed and flag SLA breaches, even without pushing orders into Carriyo.
  • Details settle later. At order time you often don't know the fulfillment location (if you ship from several) or the parcel breakdown (the items aren't packed). Create the draft anyway and supply those when you confirm.

If you use Carriyo Orders

This recipe creates shipments directly, the path for clients not using the Carriyo Order object. If you do use Carriyo Orders, you don't create shipments yourself: fulfilling a fulfillment order creates and books the shipment for you, and the order already gives the early tracking and SLA visibility a draft would, so drafts are redundant.

Prerequisites

Step 1, create the draft

Create the shipment with draft=true and the data you have at order time, the customer address, the items, the payment. You don't need the pickup location or parcels yet.

curl -X POST 'https://api.carriyo.com/shipments?draft=true' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'tenant-id: YOUR_TENANT_ID' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "merchant": "ACME",
    "references": {
      "partner_order_reference": "YOUR_ORDER_REF",
      "partner_shipment_reference": "YOUR_ORDER_REF-1"
    },
    "dropoff": {
      "address1": "350 5th Avenue",
      "city": "New York",
      "state": "NY",
      "country": "US",
      "postcode": "10118",
      "contact_name": "Alex Chen",
      "contact_phone": "+12125550100"
    },
    "items": [
      {
        "sku": "WIDGET-RED-M",
        "quantity": 2,
        "description": "Red widget",
        "price": { "amount": 125, "currency": "USD" }
      }
    ],
    "payment": { "total_amount": 250, "currency": "USD" }
  }'

Response, note the draft status:

{
  "shipment_id": "MPQK7X2N9VABD3",
  "merchant": "ACME",
  "references": {
    "partner_order_reference": "YOUR_ORDER_REF",
    "partner_shipment_reference": "YOUR_ORDER_REF-1"
  },
  "post_shipping_info": {
    "status": "draft"
  },
  "...": "(no pickup or parcels yet, no carrier, no label)"
}

No carrier has been called and no label exists. The shipment is a placeholder holding the order data, and its tracking page is already live for the customer.

Step 2, confirm and book

When the order is ready, location picked, parcel packed, confirm it. Pass the pickup location and parcels you now have in the confirm body. Confirm applies them and submits the booking in one call; there's no need for a separate update step.

curl -X POST 'https://api.carriyo.com/shipments/MPQK7X2N9VABD3/confirm' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'tenant-id: YOUR_TENANT_ID' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "pickup": { "partner_location_code": "NJ-WH-01" },
    "parcels": [
      {
        "weight": { "value": 1.7, "unit": "kg" },
        "dimension": { "width": 22, "height": 17, "depth": 32, "unit": "cm" }
      }
    ]
  }'

The confirm body accepts the same fields as the PATCH update endpoint, so beyond pickup and parcels you can amend anything else that still needs fixing (a corrected address, final weights) in the same call. It's a partial update: the top-level fields you include are applied, and everything you set at draft time (dropoff, items, payment) is kept. An included object is replaced as a whole, so pass each object complete. A pickup with only the postcode would blank its other fields.

Response, note the status flip to pending. Confirm submits the booking but doesn't wait for the carrier:

{
  "shipment_id": "MPQK7X2N9VABD3",
  "references": {
    "partner_order_reference": "YOUR_ORDER_REF",
    "partner_shipment_reference": "YOUR_ORDER_REF-1"
  },
  "carrier_account": {
    "carrier": "UPS",
    "carrier_id": "c1053e4b-f2e7-4cee-917f-a390e73887a6",
    "carrier_account_name": "UPS US"
  },
  "post_shipping_info": {
    "status": "pending",
    "key_milestones": {}
  }
}

This is the moment that costs real money: Carriyo assigns a carrier and submits the booking. The outcome then resolves asynchronously, pendingbooked (tracking number attached, label generated) or pendingerror (carrier rejected). Subscribe to those status events by webhook rather than expecting a label in this response, see Book a shipment for the booking-outcome webhook.

If you ever need to edit a draft without booking it, for example a correction while it's on hold, use PATCH /shipments/{shipment_id} (partial) or PUT /shipments/{shipment_id} (full replace). But you don't need a separate edit just to add the pickup and parcels, the confirm call above does both.

What data to pass at each stage

You don't need every field at draft time. The minimum at each step:

StageRequired fieldsOptional / recommended
Draft (Step 1)merchant, references (order + shipment reference)dropoff, items, payment, supply what you have at order time so the tracking page is useful
Confirm (Step 2)merchant, references, payment, dropoff, items, pickup, parcels (most carriers)pass the still-missing fields (pickup, parcels) and any edits in the confirm body

Everything required at confirm must be present on the shipment by confirm time: from the original draft, the confirm body, or an in-between PATCH/PUT. Spread the data across the steps as suits your workflow.

What flows where

order arrives
       │
       ▼
POST /shipments?draft=true             ← placeholder; tracking live, SLA clock starts
       │                                  (pickup and parcels not needed yet)
       ▼
POST /shipments/{id}/confirm           ← add pickup + parcels here; booking submitted ($$$), status → pending
       │
       ▼
booked or error (async, via webhook)   ← carrier accepts → label; rejects → error

The partner_shipment_reference you set at draft time stays through confirm. Webhooks and reconciliation keep working.

Pitfalls

  • An included object is replaced wholesale. On confirm (and PATCH), top-level fields you omit are left alone, but any object you do include is replaced as a whole, so send it complete. Custom attributes are the exception: they merge key-by-key (set one to null to remove it).
  • Drafts can be edited; confirmed shipments can't via PATCH/PUT. Once you confirm, changes go through POST /shipments/{id}/reprocess instead, see Fix or reassign a shipment.
  • Drafts don't expire but they pollute the shipment list. If you create drafts and never confirm them, they sit forever as status=draft. Clean them up periodically: list drafts older than a cutoff and cancel them.
  • No label until the carrier books it. A draft has no label, and neither does a pending shipment. post_shipping_info.default_label_url appears only once the status reaches booked.
  • Confirmation can fail. Confirm runs full validation: if the shipment is still missing required data it returns error immediately, and if the carrier later rejects it goes to error asynchronously. Either way, resolving it is usually easiest in the Dashboard (Resolve shipment errors).