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.
/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 theRetry-Afterseconds.- Weights:
lborkg. Dimensions:inorcm. 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
| Path | What it's for |
|---|---|
/v1/rates | Live quotes from every carrier (POST) |
/v1/shipments | Buy labels, list and search shipments, /{id}/label, /{id}/tracking, /{id}/cancel, /{id}/return |
/v1/shipments/draft, /v1/shipments/drafts | Save shipments to buy later; /draft/{id}/process buys one |
/v1/shipments/price-preview | Exact total with tax and discounts for a chosen rate |
/v1/customs/declarations | Contents of cross-border parcels |
/v1/batches | Group labels; /{id}/labels gives one merged PDF |
/v1/pickups | Quote, book, cancel and list carrier pickups; /carriers, /options, /cutoffs |
/v1/rates/dropoffs | Nearest drop-off locations |
/v1/addresses | Address book; /defaults for the default sender |
/v1/package-presets, /v1/item-presets | Saved boxes and customs items |
/v1/wallet | Balance, /transactions, /top-up (saved card), /cards, /refunds |
/v1/invoices, /v1/receipts | Adjustment invoices (and /{id}/pay) and receipts, with PDF links |
/v1/integrations | Connected stores and their orders; /import turns orders into drafts |
/v1/returns | ReturnHub items in storage and their disposition |
/v1/support/tickets | Support conversations |
/v1/notifications | Notifications and settings |
/v1/search | Search across the account |
/v1/openapi.json | The 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
Rates, buying, labels, statuses, voiding, returns, drafts, batches Cross-border
HS codes, customs declarations, prepaid duties Pickups & drop-off
Cutoffs, quotes, booking, changes, nearest counters Wallet & billing
Top-ups, cards, invoices, receipts, refunds Stores
Connect a store, import orders, tracking back to the store Errors & retries
Codes, safe retries, paging
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.