Skip to content

Documentation

Quotes

Ask every courier enabled on your account what they would charge, concurrently, in one request.

POST /v1/quotes

Shipstack asks each enabled courier in parallel under a fixed latency budget and merges the answers. Rates come back sorted cheapest first. A courier that fails or times out is reported in failures, it never fails the request.

Request

FieldTypeNotes
fromaddressRequired. Collection address.
toaddressRequired. The recipient, even when delivering to a pickup point.
parcelsarrayRequired. At least one, at most 50.
ship_datestringRFC 3339. Defaults to today.
currencystringISO 4217. Defaults to the account's currency.
customsobjectRequired for international consignments.
optionsobjectValue-added services. Validated against carrier capabilities.
deliver_toobjectA pickup point, when not delivering to the door.

Address

FieldTypeNotes
namestringRequired.
line1stringRequired.
citystringRequired.
postcodestringRequired. Normalised for you.
countrystringRequired. ISO 3166-1 alpha-2.
company, line2, line3, countystringOptional.
phone, emailstringOptional. Used by carriers for delivery notifications.
residentialbooleanAffects pricing with some carriers.
vat_number, eoristringOptional. Used on international lanes.

Parcel

FieldTypeNotes
weight_gintegerRequired. Grams, greater than zero.
length_mm, width_mm, height_mmintegerRequired. Millimetres, all greater than zero.
referencestringYour own reference, printed on the label where supported.
contentsarrayItemised contents. Required for customs.

A domestic quote

Request
curl -X POST https://api.shipstack.example/v1/quotes \
  -H "Authorization: Bearer $SHIPSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": {
      "name": "Acme Supplies", "line1": "1 Mill Lane",
      "city": "Leeds", "postcode": "LS1 4DL", "country": "GB"
    },
    "to": {
      "name": "Sam Reed", "line1": "44 Bright Street",
      "city": "Bristol", "postcode": "BS1 5TR", "country": "GB",
      "residential": true
    },
    "parcels": [
      { "weight_g": 1200, "length_mm": 300, "width_mm": 200,
        "height_mm": 100, "reference": "ORDER-10432" }
    ],
    "options": { "signature_required": true }
  }'

Response

Each rate carries the carrier's own service_code, which is what you quote back when buying the label. surcharges itemises what makes up the total beyond the base rate.

200 OK
{
  "rates": [
    {
      "courier": "royalmail_clickanddrop",
      "service_code": "TPS48",
      "service_name": "Royal Mail Tracked 48 Signature",
      "total": { "amount": 489, "currency": "GBP" },
      "estimated_days": 2
    },
    {
      "courier": "dpd",
      "service_code": "1^12",
      "service_name": "DPD Next Day by 12:00",
      "total": { "amount": 899, "currency": "GBP" },
      "estimated_days": 1,
      "delivery_by": "2026-09-02T12:00:00Z",
      "surcharges": [
        { "code": "FUEL", "name": "Fuel surcharge",
          "amount": { "amount": 74, "currency": "GBP" } }
      ]
    }
  ],
  "failures": [
    { "courier": "ups", "reason": "upstream timeout" }
  ]
}

delivery_by appears only when the carrier gives a firm commitment rather than an estimate. Express services do, economy services usually do not.

Service options

Options are validated against what the chosen carrier actually supports before any network call, so an unsupported combination fails immediately rather than as an opaque carrier error.

OptionTypeNotes
signature_requiredbooleanSomeone must sign for the parcel.
age_checkintegerMinimum recipient age, e.g. 18.
insurancemoneyCover level for the consignment.
saturday_deliverybooleanWeekend delivery, where offered.
cash_on_deliverymoneyAmount to collect on delivery.
safe_placestringWhere to leave the parcel if nobody is in.

International and customs

Crossing a customs border requires a customs object and itemised contents on every parcel. Without them the request is rejected before it reaches a carrier.

Customs
{
  "customs": {
    "content_type": "merchandise",
    "incoterm": "DAP",
    "invoice_number": "INV-2026-0912",
    "reason": "sale"
  },
  "parcels": [
    {
      "weight_g": 800, "length_mm": 250, "width_mm": 180, "height_mm": 60,
      "contents": [
        {
          "description": "Cotton t-shirt",
          "quantity": 2,
          "value": { "amount": 1200, "currency": "GBP" },
          "weight_g": 320,
          "hs_code": "610910",
          "origin_country": "GB",
          "sku": "TS-01"
        }
      ]
    }
  ]
}

content_type is one of merchandise, gift, sample, documents or return. incoterm decides who pays duty: DAP means the recipient pays, DDP means you do.

Pickup points

A locker or parcel shop is a destination, not an option. Set deliver_to to route the parcel there, and keep to as the recipient's own address, because carriers still use it for notifications and returns.

Deliver to a locker
{
  "deliver_to": {
    "id": "GB-12345",
    "type": "locker",
    "name": "Bright Street Locker"
  }
}

Types are locker, shop, post_office and depot. A carrier that does not serve the requested type, or a parcel too large for the locker, is rejected before any network call.

Multi-piece consignments

Send several parcels in one parcels array to quote them as a single consignment. Carriers that do not support multi-piece are skipped rather than quoting a wrong price; see the capability matrix for which do.