REST API

Ship from your own code

Everything in the ParcelHub dashboard is available over HTTPS at https://api.parcelhub.ca/v1/<resource>. Your key works inside your own account, with your prices and your wallet.

Base path: there is no /my in API paths any more (removed 31 October 2026). Use /v1/shipments, not /v1/my/shipments. Old paths answer 404 endpoint_moved with the new path in error.moved_to.

1. Create an API key

Sign in at my.parcelhub.ca → Account → API keys and create a key. Choose Full access to buy labels and spend wallet funds, or Read-only to look things up (read-only keys can still get quotes). A key belongs to the account you're working in when you create it, and it can do what you can do there. Copy the secret straight away — it's shown once.

2. Exchange it for a token

Trade the key for a short-lived bearer token (about 15 minutes). Get a new one when a call returns 401.

curl -s https://api.parcelhub.ca/v1/api-clients/token \
  -H 'Content-Type: application/json' \
  -d '{"client_id":"ph_client_…","client_secret":"…"}'
{
  "ok": true,
  "data": {
    "access_token": "eyJhbGciOi…",
    "expires_at": "2026-10-31T15:04:05Z",
    "kid": "b"
  }
}

Send it on every call: Authorization: Bearer <access_token>.

3. Requests and responses

  • JSON in, JSON out. Money is always integer cents with a currency (CAD unless stated).
  • Success: { "ok": true, "data": … }. Errors: { "ok": false, "error": { "code", "message" } }.
  • 400 validation — something in the request is wrong (the message says what). 401 — token missing or expired. 403 — this key may not do that (read-only, your role on the account, or an action only the account owner can take in the dashboard). 404 — not on this account. 429 — slow down and retry after the Retry-After seconds.
  • Weights: lb or kg. Dimensions: in or cm. Countries: ISO 2-letter codes. Provinces/states: 2-letter codes.

4. Get rates

curl -s https://api.parcelhub.ca/v1/rates \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{
    "from": { "name": "Warehouse", "street": "120 Adelaide St W", "city": "Toronto",
              "state": "ON", "postal_code": "M5H 1T1", "country": "CA", "phone": "4165550100" },
    "to":   { "name": "Sam Lee", "street": "1 Yonge St", "city": "Toronto",
              "state": "ON", "postal_code": "M5E 1W7", "country": "CA", "phone": "4165550199" },
    "packages": [ { "weight": 2, "weight_unit": "lb",
                    "length": 10, "width": 8, "height": 4, "dimension_unit": "in" } ]
  }'
{
  "ok": true,
  "data": {
    "quote_id": "9ed7329c-…",
    "items": [
      { "carrier": "uniuni", "carrier_display_name": "UniUni", "service_code": "…",
        "service_name": "Next Day", "amount_cents": 588, "currency": "CAD",
        "estimated_days": 1, "estimated_delivery": "2026-11-01", "pickup_supported": false },
      …
    ],
    "carriers_with_no_options": [ … ]
  }
}

Prices are before tax. For the exact total with tax and any discount, post the chosen amount_cents to /v1/shipments/price-preview. Add "ship_date": "YYYY-MM-DD" (up to 7 days ahead) or "signature_option": "required" | "adult" when you need them.

5. Buy the label

Post the same addresses and packages with the rate you picked. The label is paid from your wallet balance. Send an Idempotency-Key (your order number works well) so a retry never buys twice.

curl -s https://api.parcelhub.ca/v1/shipments \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-1042' \
  -d '{
    "carrier": "uniuni", "service_code": "…", "service_name": "Next Day",
    "quote_id": "9ed7329c-…",
    "from": { … same as the quote … },
    "to":   { … same as the quote … },
    "packages": [ { "weight": 2, "weight_unit": "lb", "length": 10, "width": 8,
                    "height": 4, "dimension_unit": "in", "items": [] } ],
    "reference_number": "Order 1042",
    "email_notifications": true
  }'

6. Label, tracking and voiding

# A download link for the label PDF (valid ~15 minutes) + tracking number
curl -s https://api.parcelhub.ca/v1/shipments/$SHIPMENT_ID/label -H "Authorization: Bearer $TOKEN"

# Tracking events
curl -s https://api.parcelhub.ca/v1/shipments/$SHIPMENT_ID/tracking -H "Authorization: Bearer $TOKEN"

# Void an unused label (refunded to the wallet when the carrier allows)
curl -s -X POST https://api.parcelhub.ca/v1/shipments/$SHIPMENT_ID/cancel \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"reason":"Order cancelled"}'

7. Cross-border shipments

Parcels leaving Canada need a customs declaration. Create it first, then pass its id when quoting and buying. Use 10-digit HTS codes for the US — find them with GET /v1/public/hs-codes?q=t-shirt&country=US.

curl -s https://api.parcelhub.ca/v1/customs/declarations \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{
    "contents_type": "merchandise", "declaration_currency": "CAD", "incoterm": "DAP",
    "signed_by": "Sam Lee", "from_country": "CA", "to_country": "US", "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": 180 } ]
  }'
# → data.id: pass it as "customs_declaration_id" to POST /v1/rates and POST /v1/shipments

8. Pickups

GET /v1/pickups/carriers and /v1/pickups/options say which carriers, days and times can be booked; /v1/pickups/cutoffs?postal_code=… gives the cut-off time.

# Fee first (no charge)
curl -s https://api.parcelhub.ca/v1/pickups/quote -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"carrier":"ups","pickup_date":"2026-11-02","address":{"state":"ON","country":"CA"}}'

# Book it (charges the wallet)
curl -s https://api.parcelhub.ca/v1/pickups -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"carrier":"ups","pickup_date":"2026-11-02","time_window_start":"13:00:00",
       "time_window_end":"17:00:00","address":{ … full pickup address … },
       "shipment_ids":["…"]}'

Main resources

PathWhat it's for
/v1/ratesLive quotes from every carrier (POST)
/v1/shipmentsBuy labels, list and search shipments, /{id}/label, /{id}/tracking, /{id}/cancel, /{id}/return
/v1/shipments/draft, /v1/shipments/draftsSave shipments to buy later; /draft/{id}/process buys one
/v1/shipments/price-previewExact total with tax and discounts for a chosen rate
/v1/customs/declarationsContents of cross-border parcels
/v1/batchesGroup labels; /{id}/labels gives one merged PDF
/v1/pickupsQuote, book, cancel and list carrier pickups; /carriers, /options, /cutoffs
/v1/rates/dropoffsNearest drop-off locations
/v1/addressesAddress book; /defaults for the default sender
/v1/package-presets, /v1/item-presetsSaved boxes and customs items
/v1/walletBalance, /transactions, /top-up (saved card), /cards, /refunds
/v1/invoices, /v1/receiptsAdjustment invoices (and /{id}/pay) and receipts, with PDF links
/v1/integrationsConnected stores and their orders; /import turns orders into drafts
/v1/returnsReturnHub items in storage and their disposition
/v1/support/ticketsSupport conversations
/v1/notificationsNotifications and settings
/v1/searchSearch across the account
/v1/openapi.jsonThe full spec for your key (GET, signed in)

See the API reference for every endpoint and field, or fetch GET /v1/openapi.json with your token for the complete spec, including which operations your key may call (x-caller-allowed).

What keys can't do

For safety, a key can't manage API keys, change the login email, phone or password, invite or remove team members, submit identity verification, or add a payment card (cards are added on Stripe's form in the dashboard). It can use cards already saved — for example to top up the wallet. Read-only keys can only read, plus get quotes (POST /v1/rates, /v1/rates/quick, /v1/shipments/price-preview, /v1/pickups/quote).

Guides

Using an AI agent?

You don't need to write code: connect Claude, ChatGPT or Cursor through MCP and the agent gets ready-made tools for all of this. If your agent calls the REST API directly, point it at this page and at docs.parcelhub.ca/llms.txt.