REST API · Guide

Cross-border shipping

A parcel that leaves the country needs a customs declaration: what is inside, what it's worth and where it was made. Create the declaration first, then pass its id when you quote and when you buy.

1. Find HS codes

Every item needs a Harmonized System code. The lookup is public (no token needed). For parcels to the US, use the 10-digit US codes: some carriers refuse shorter ones.

# 10-digit US tariff codes (use these for parcels to the US)
curl -s "https://api.parcelhub.ca/v1/public/hs-codes?q=t-shirt&country=US"
# 6-digit HS codes for everywhere else
curl -s "https://api.parcelhub.ca/v1/public/hs-codes?q=ceramic+mug"

2. Create the declaration

curl -s https://api.parcelhub.ca/v1/customs/declarations -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{
  "contents_type": "merchandise",
  "reason_for_export": "sale",
  "declaration_currency": "CAD",
  "incoterm": "DAP",
  "signed_by": "Sam Lee",
  "from_country": "CA", "to_country": "US",
  "invoice_number": "1042",
  "tax_ids": {},
  "items": [
    { "package_index": 0, "position": 0, "description": "Cotton t-shirt", "quantity": 2,
      "unit_value_cents": 1500, "currency": "CAD", "hs_code": "6109100012",
      "country_of_origin": "CA", "net_weight_g": 360, "sku": "TS-BLK-M" }
  ]
}'
# → data.id
  • Use one currency for the whole declaration: declaration_currency and each item's currency match.
  • description must be specific: "cotton t-shirt", not "clothing". Vague descriptions get parcels held.
  • net_weight_g is the weight of all units of that item together, in grams. package_index says which box it's in (from 0).
  • contents_type: merchandise, documents, gift, commercial_sample, returned_goods or other.
  • tax_ids: tax numbers where the destination asks for them, e.g. IOSS for the EU, or the recipient's own number as recipient with recipient_type (personal / business), which Brazil, Argentina, Israel and some other countries require.

3. Quote and buy with it

# Quote with the declaration…
curl -s https://api.parcelhub.ca/v1/rates -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{ "from": {…}, "to": {…, "country": "US"}, "packages": [ … ],
  "customs_declaration_id": "<data.id>" }'

# …and buy with it
curl -s https://api.parcelhub.ca/v1/shipments -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'Idempotency-Key: order-1042' -d '{ …,
  "customs_declaration_id": "<data.id>" }'

# The commercial invoice the carrier made (PDF link, ~15 min)
curl -s https://api.parcelhub.ca/v1/shipments/$ID/customs-invoice -H "Authorization: Bearer $TOKEN"

With a declaration, carriers can quote their international services and duties accurately. UPS and FedEx file the commercial invoice electronically; download a copy anyway if your parcel needs one in a pouch or a broker asks.

Prepaid duties (DDP)

By default the recipient pays any duties and import taxes on delivery ("incoterm": "DAP"). Where it's offered, you can prepay them instead with "incoterm": "DDP": rates then include duties_cents, an estimate charged with the label, and the recipient pays nothing on delivery. If customs bills more than the estimate, the difference is charged to your account at cost.

curl -s https://api.parcelhub.ca/v1/shipments/duties-availability -H "Authorization: Bearer $TOKEN"
# → where prepaying is offered right now (US, other countries)

With DDP, carriers that can't prepay duties for that lane are left out of the rates. POST /v1/shipments/price-preview takes duties_cents and shows it as its own line (duties are not taxed again).

Saving items for next time

POST /v1/item-presets saves a product's description, value, HS code and country of origin; GET /v1/item-presets lists them, so repeat products don't need looking up again.