Overview Flow Guide

The end-to-end path for shipping with the Easyship API: compare rates, create a shipment, buy the label, hand the parcel to the courier, and track it to delivery. Most integrations need just three calls.

Base URL
public-api.easyship.com
Version
2024-09 (in the path)
Auth
Authorization: Bearer <token>
Minimum calls
3: rates, shipment + label, tracking

The shipment lifecycle

Step 1
Get rates
POST /rates
→
Step 2
Create shipment
POST /shipments
→
Step 3
Buy label
POST /shipments/{id}/label
→
Step 4
Hand over
POST /pickups or drop-off
→
Step 5
Track
webhooks or GET

Steps 2 and 3 can be a single call: create the shipment with buy_label: true and the label comes back in the same response.

Before you start

Credentials

ItemValue
TokenCreate an API integration in the Easyship dashboard. Sandbox tokens start with sand_, production tokens with prod_. See Authentication
HeaderAuthorization: Bearer <token> and Content-Type: application/json
Scopespublic.rate:read, public.shipment:write, public.label:write, public.pickup:write, public.track:read. See Scopes
TestingUse a sandbox token to test without real labels or charges. See Sandbox

Data you need for each shipment

DataNotes
Origin and destination addressesInclude city and state for the US, CA, MX and AU. Validate them first with Address Validation
ParcelTotal weight plus box dimensions, or weight and dimensions per item
ItemsDescription, quantity and value. For international shipments, add hs_code and origin_country_alpha2
Delivery termsincoterms: DDU (buyer pays import tax) or DDP (you pay). See Taxes and Duties
Account balance. Buying a label charges your Easyship balance. If it's too low, label generation fails.

Choose your integration path

FASTEST
Shipment and label in one call

POST /shipments with courier_service_id and buy_label: true. Add buy_label_synchronous: true to get the label in the response.

Best for: checkout flows where the courier is already chosen.

STEP BY STEP
Create, review, then buy

Create the shipment, review its rates, then call POST /shipments/{id}/label.

Best for: back-office tools where someone confirms the courier.

HIGH VOLUME
Batches

POST /batches/shipments and POST /batches/labels run in the background. Results arrive by webhook.

Best for: hundreds of orders at once.

Step by step

  1. Get rates

    Compare every courier available for the parcel. Each rate has a courier_service.id, a total_charge, delivery days and ranking fields. Full details: How to Get Shipping Rates.

    cURLPOST /2024-09/rates
    curl -X POST https://public-api.easyship.com/2024-09/rates \ -H "Authorization: Bearer $EASYSHIP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "origin_address": { "line_1": "1 Market St", "city": "San Francisco", "state": "CA", "postal_code": "94105", "country_alpha2": "US" }, "destination_address": { "line_1": "10 Downing Street", "city": "London", "postal_code": "SW1A 2AA", "country_alpha2": "GB" }, "incoterms": "DDU", "parcels": [{ "total_actual_weight": 1.2, "box": { "length": 30, "width": 20, "height": 10 }, "items": [{ "description": "T-shirt", "category": "fashion", "hs_code": "61091000", "quantity": 2, "declared_currency": "USD", "declared_customs_value": 25 }] }] }'

    Pick a rate and keep its courier_service.id.

    Optional. If you skip this step, POST /shipments still returns all rates and assigns the best-value courier.
  2. Create the shipment

    Send the same addresses and parcels, plus the chosen courier in courier_settings. Add contact details (contact_name, contact_phone, contact_email) to both addresses; couriers need them on the label.

    cURLPOST /2024-09/shipments
    curl -X POST https://public-api.easyship.com/2024-09/shipments \ -H "Authorization: Bearer $EASYSHIP_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-1001" \ -d '{ "origin_address": { "...": "as in step 1, plus contact details" }, "destination_address": { "...": "as in step 1, plus contact details" }, "incoterms": "DDU", "courier_settings": { "courier_service_id": "01563646-58c1-4607-8fe0-cae3e33c0001", "allow_fallback": false }, "shipping_settings": { "buy_label": false }, "parcels": [{ "...": "as in step 1" }] }'
    Response201 Created
    { "shipment": { "easyship_shipment_id": "ESUS10006002", "shipment_state": "created", "label_state": "not_created", "courier_service": { "id": "01563646-58c1-4607-8fe0-cae3e33c0001", "name": "DHL Express Worldwide" }, "rates": [ ... ], "tracking_page_url": "https://www.trackmyshipment.co/shipment-tracking/ESUS10006002" } }
    OptionEffect
    courier_settings.allow_fallbackIf the chosen courier can't be used, let Easyship pick another instead of failing
    courier_settings.apply_shipping_rulesApply the shipping rules set up on your account
    shipping_settings.buy_labelBuy the label in the same call (skips step 3)
    shipping_settings.buy_label_synchronousWith buy_label: wait and return the label in the response. Not available for batches
    Idempotency-Key headerRetry safely: a repeated request with the same key returns the first successful response instead of creating a second shipment

    To change the shipment before buying the label, use Update a Shipment.

  3. Buy the label

    This books the courier, charges your balance and returns the label and other shipping documents. You can pass courier_service_id here to set or change the courier; if none is set, Easyship assigns the best-value courier.

    cURLPOST /2024-09/shipments/ESUS10006002/label
    curl -X POST https://public-api.easyship.com/2024-09/shipments/ESUS10006002/label \ -H "Authorization: Bearer $EASYSHIP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "courier_service_id": "01563646-58c1-4607-8fe0-cae3e33c0001" }'
    Response201 Created
    { "shipment": { "easyship_shipment_id": "ESUS10006002", "label_state": "generated", "label_paid_at": "2026-10-15T10:21:00Z", "shipping_documents": [ { "category": "label", "format": "pdf", "page_size": "4x6", "base64_encoded_strings": ["JVBERi0xLjUK..."] } ], "trackings": [ ... ] } }

    Decode base64_encoded_strings to get the file, or fetch it later with GET /shipments/{id}/documents. Need a different format or page size? Pass printing_options.

    Buying the label commits the shipment. From here the courier is booked. To stop it, use Cancel a Shipment.
  4. Hand the parcel to the courier

    Drop it off, or book a collection with POST /pickups if the courier offers pickups. Send the courier_service_id, a selected_date, a time window (or a time_slot_id) and the shipment IDs.

    cURLPOST /2024-09/pickups
    curl -X POST https://public-api.easyship.com/2024-09/pickups \ -H "Authorization: Bearer $EASYSHIP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "courier_service_id": "01563646-58c1-4607-8fe0-cae3e33c0001", "selected_date": "2026-10-16", "selected_from_time": "12:00", "selected_to_time": "16:00", "easyship_shipment_ids": ["ESUS10006002"] }'
  5. Track the shipment

    Tracking starts automatically once the label exists. Subscribe to the tracking webhooks below, or look it up:

    cURLGET /2024-09/shipments/trackings
    curl "https://public-api.easyship.com/2024-09/shipments/trackings?easyship_shipment_id=ESUS10006002&include_checkpoints=true" \ -H "Authorization: Bearer $EASYSHIP_TOKEN"

    You can also share tracking_page_url with your buyer. Full details: How to Track a Shipment.

Webhooks to subscribe to

Create a webhook with the Webhooks API and list these names in event_types. In each delivery, the payload's event_type uses the dotted form shown under each name.

EventFires when
shipment_label_createdarrives as shipment.label.createdA label is ready (useful for batches and async labels)
shipment_label_failedarrives as shipment.label.failedLabel generation failed
shipment_tracking_status_changedarrives as shipment.tracking.status.changedThe delivery status changes, e.g. in transit → delivered
shipment_tracking_checkpoints_createdarrives as shipment.tracking.checkpoints.createdThe courier reports a new tracking event
shipment_cancelledarrives as shipment.cancelledA shipment is cancelled

Shipment states

Every shipment response carries four state fields. These are the values you'll see most:

FieldTracksCommon values
shipment_stateThe shipment recordcreated, cancelled
label_stateThe labelnot_created → generating → generated / printed, or failed
pickup_stateHand-over to the couriernot_requested until a pickup is booked
delivery_stateCourier to buyerin_transit_to_customer, out_for_delivery, delivered, exception, returned_to_shipper

FAQ and common errors

POST /shipments returned 202 instead of 201.
The shipment was created, but something else failed, usually the label when buy_label was set. meta.status is partial_success and meta.errors explains why. Fix the cause, then call POST /shipments/{id}/label.
The label failed.
The usual causes are an insufficient account balance, missing contact details, or missing customs data (hs_code, item values) for international shipments. Check label_state and last_failure_http_response_messages on the shipment.
My chosen courier wasn't used.
The courier may not serve that route or parcel. Set allow_fallback: false to get an error instead of a substitute, or use GET /shipments/{id}/unavailable_couriers to see why.
Should I poll for updates?
Prefer webhooks. Every poll counts against your rate limits.

Did this page help you?