Canada Post API: Rates, Shipping Labels, Tracking, Manifests and Pickups
RocketShipIt talks to both Canada Post APIs with one JSON request format:
| Carrier | Canada Post API | Status |
|---|---|---|
canada-rest |
The JSON REST APIs on the Canada Post Developer Portal | Beta |
canada |
The XML web services (the SOA Gateway, soa-gw.canadapost.ca) | Stable |
Both rate parcels across every Canada Post service, create shipping labels
for domestic, US and international shipments (with customs), group
shipments into an end-of-day manifest, void or refund a shipment and track
a PIN. The canada carrier also books pickups, prints return labels and
splits multi-package contract shipments; canada-rest does not do those
yet. Requests use the same parameter names on both, so moving from
canada to canada-rest is mostly a change of credentials.
Canada Post weights are kilograms and dimensions centimetres.
Neither carrier converts units, so send weight in kg and length,
width and height in cm whatever weight_unit says. Charges are in CAD.
Credentials#
canada-rest#
Create an app on the Canada Post Developer Portal and subscribe it to the rating, shipping and tracking APIs. The app's client id and secret are exchanged for an OAuth token.
| Parameter | Canada Post value |
|---|---|
client_id |
The app's client id |
client_secret |
The app's client secret |
account_number |
Your Canada Post customer number (the mailed-by customer) |
contract_id |
Your parcel services contract number, for contract (commercial) shipping |
key |
Optional: a token from authenticate, to skip the token call |
scope |
Optional, defaults to merchant |
The same values can come from the environment as
RS_CANADA_REST_CLIENT_ID, RS_CANADA_REST_CLIENT_SECRET,
RS_CANADA_REST_ACCOUNT_NUMBER, RS_CANADA_REST_KEY and
RS_CANADA_REST_SCOPE.
When key is empty RocketShipIt fetches a token before each request. To
save the round trip, call authenticate once, cache data.access_token
until data.expires_at, and send it back as key:
{
"carrier": "canada-rest",
"action": "authenticate",
"params": {
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}
}
The response also carries token_type, scope, expires_in and
issued_at. The client id and secret are redacted from
meta.debug_information.
canada#
The XML web services take the API key Canada Post issues on the Developer Program page: a user name and a password, sent as HTTP Basic auth.
| Parameter | Canada Post value |
|---|---|
username |
The API key's user name (the part before the colon) |
password |
The API key's password (the part after the colon) |
account_number |
Your Canada Post customer number |
contract_id |
Your contract number, for contract shipping |
From the environment: RS_CANADA_USERNAME, RS_CANADA_PASSWORD and
RS_CANADA_ACCOUNT_NUMBER.
Test and production#
canadasends"test": truerequests to Canada Post's development host,ct.soa-gw.canadapost.ca, which takes the development key from the Developer Program page, not the production one. Tracking and pickups followdebug_mode, whichtestturns on, so sendtestrather thandebug_modealone or those two will go to the development host while the rest goes to production.canada-resthas no test host and ignorestest. Create a "[Test]" app on the Developer Portal: its client id and secret return stubbed responses from the production URLs. Switch to the production app's credentials to go live.
Supported actions#
| Action | canada-rest |
canada |
|---|---|---|
authenticate |
POST /oauth2/token |
— |
getallrates |
POST /rating/v1/prices |
POST /rs/ship/price |
submitshipment |
POST /shipping/v1/{customer}/{customer}/shipments |
ncshipment (non-contract), shipment (contract), authorizedreturn (returns) |
createmanifest |
POST .../manifests |
POST .../manifest |
voidshipment |
DELETE .../shipments/{id}, then refund |
DELETE .../shipment/{id}, then refund |
track |
GET /tracking/v1/pins/{pin}/details |
GET /vis/track/pin/{pin}/detail |
createpickup |
— | POST /enab/{customer}/pickuprequest |
pickupstatus |
— | GET /enab/{customer}/pickuprequest |
cancelpickup |
— | DELETE .../pickuprequest/{id} |
Anything else returns "unsupported action".
Service codes#
Pass one of these as service. Leave service out of getallrates to
get every service Canada Post can price for the route.
| Code | Service |
|---|---|
DOM.RP |
Regular Parcel |
DOM.EP |
Expedited Parcel |
DOM.XP |
Xpresspost |
DOM.PC |
Priority |
DOM.LIB |
Library Materials |
USA.EP |
Expedited Parcel USA |
USA.XP |
Xpresspost USA |
USA.TP |
Tracked Packet USA |
USA.TP.LVM |
Tracked Packet USA (large volume) |
USA.SP.AIR |
Small Packet USA Air |
USA.PW.ENV |
Priority Worldwide Envelope USA |
USA.PW.PAK |
Priority Worldwide pak USA |
USA.PW.PARCEL |
Priority Worldwide Parcel USA |
INT.XP |
Xpresspost International |
INT.TP |
Tracked Packet International |
INT.IP.AIR |
International Parcel Air |
INT.IP.SURF |
International Parcel Surface |
INT.SP.AIR |
Small Packet International Air |
INT.SP.SURF |
Small Packet International Surface |
INT.PW.ENV |
Priority Worldwide Envelope International |
INT.PW.PAK |
Priority Worldwide pak International |
INT.PW.PARCEL |
Priority Worldwide Parcel International |
Service codes for the other carriers are in Shipping Service Codes.
Rates#
{
"carrier": "canada-rest",
"action": "getallrates",
"params": {
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"account_number": "YOUR_CUSTOMER_NUMBER",
"contract_id": "YOUR_CONTRACT_ID",
"ship_code": "K2B8J6",
"to_country": "CA",
"to_code": "J0E1X0",
"ship_date": "2026-12-15",
"packages": [
{ "weight": 1.5, "length": 30, "width": 20, "height": 10, "insured_value": 100 }
]
}
}
ship_codeis the origin postal code. Spaces are stripped.- The destination is
to_codein Canada and the US, andto_countryalone elsewhere (Canada Post prices international parcels by country). contract_idasks for your contract prices; without it you get the prices for your customer number.ship_dateis the expected mailing date.insured_valueon the package adds coverage (COV) for that amount;signature_typeDIRECTadds signature (SO). Both are priced.- Only the first package is rated. Rate each parcel separately.
canada-restrounds weight to 0.001 kg and dimensions to 0.1 cm;canadatruncates dimensions to whole centimetres.
One rate comes back per service:
service_code(DOM.EP) anddesc(Canada Post's service name).rate- the amount due in CAD, taxes included;currencyisCAD.rate_detail- the breakdown:BASEbase charge,TAXGST,TAXHSTandTAXPST, and one entry per adjustment (fuel surcharge, coverage, discounts) ascode:name.est_delivery_time- the expected delivery date.est_delivery_daysandguaranteed- transit days and whether the date is guaranteed (canada-restonly).
Creating a shipping label#
{
"carrier": "canada-rest",
"action": "submitshipment",
"params": {
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"account_number": "YOUR_CUSTOMER_NUMBER",
"contract_id": "YOUR_CONTRACT_ID",
"service": "DOM.EP",
"shipper": "RocketShipIt",
"ship_name": "Shipping Dept",
"ship_phone": "613-555-0123",
"ship_addr1": "123 Main St",
"ship_city": "Ottawa",
"ship_state": "ON",
"ship_code": "K1A0B1",
"to_name": "John Doe",
"to_addr1": "361A Old Finch Avenue",
"to_city": "Toronto",
"to_state": "ON",
"to_code": "M1B5K7",
"to_country": "CA",
"image_type": "PDF",
"packages": [
{ "weight": 1.5, "length": 30, "width": 20, "height": 10, "reference_value": "ORDER-1001" }
]
}
}
The response has the PIN in tracking_number, Canada Post's
shipment_id (keep it to void the shipment), the charges in charges,
and the label as base64 in packages[0].label with its media type in
label_format. canada-rest also returns billing_weight. Canada Post
generates labels asynchronously, so RocketShipIt retries the label
download for a few seconds before giving up.
Contract and non-contract shipping#
contract_id decides which Canada Post shipping service is used:
- With
contract_idyou ship on your parcel services contract, billed to your account (account_number). Oncanada, abillingentry'saccountbills a different customer, andpayment_methodpicksAccount,CreditCardorSupplierAccount. - Without it the shipment is non-contract and is paid with the credit
card on your Canada Post profile. On
canadathis is thencshipmentservice, which ignores signature, coverage, label format,to_companyandto_addr2; use contract shipping if you need those.
Contract shipments are transmitted immediately, which means Canada
Post charges them at once and they can no longer be voided, only refunded.
To build up a day's shipments and transmit them together, give each one
the same group_id and call createmanifest at the end of the day; see
Manifests.
Several packages#
Canada Post has no multi-piece shipment, so each parcel is its own shipment with its own PIN.
canadawithcontract_idcreates one shipment per entry inpackages. The top leveltracking_numberandshipment_idare the first parcel's, eachpackages[i]has its own, andchargesis the total. If parcel 3 fails, parcels 1 and 2 are already created; the error says so, and you should void them or resend the rest.canada-restand non-contractcanadaship only the first package. Send one request per parcel.
Options#
| Option | Parameter | Canada Post code |
|---|---|---|
| Signature | signature_type: "DIRECT" on the package |
SO |
| Coverage (insurance) | insured_value on the package |
COV |
| Proof of age 18 / 19 | addons: ["PA18"] / ["PA19"] (canada contract, with signature_type set) |
PA18, PA19 |
| Non-delivery handling | non_delivery_code, international only |
RASE (default), RTS, ABAN |
| Pickup by Canada Post | cpc_pickup_indicator |
true / false |
| References | reference_value, reference_value2 on the package |
customer reference 1 and 2 |
On canada contract shipments, coverage is not sent; rate it with
insured_value and buy coverage on canada-rest, or ask RocketShipIt
support. On canada, any signature_type other than DIRECT is passed
through as an option code.
Label formats#
image_typePDForZPL. ZPL labels are always 4x6.canadacontract shipments takelabel_stock_type8.5x11(default for PDF) or4x6(default and required for ZPL).canada-restwith noimage_typeuses the default in your Canada Post profile.
See Thermal Printing for sending ZPL to a printer.
International shipments and customs#
Add a customs line per item. Canada Post requires a value per unit
greater than zero on every line:
"customs": [
{
"customs_description": "books",
"customs_quantity": 2,
"customs_weight": 0.5,
"customs_line_amount": 10.00,
"customs_hs_tariff": "4901.99",
"country_of_manufacture": "CA"
}
],
"reason_for_export": "SOG",
"non_delivery_code": "RASE"
customs_line_amountis the value of one unit. Without it,customs_valueis divided bycustoms_quantity. Documents with no value go as 0.01 CAD, the value Canada Post requires for documents.customs_weightis the weight of one unit in kg.reason_for_exportisSOG(sale of goods, the default),DOC(documents, the default whendocuments_onlyis true),SAM(commercial sample),REP(repair or warranty) orOTH.non_delivery_codeis what Canada Post does with an undeliverable parcel:RASEreturn at sender's expense (default),RTSreturn to sender,ABANabandon.customs_hs_tariffis sent bycanada-rest.canadacontract shipments do not sendcountry_of_manufacture.zonos_account_keyis sent as Canada Post'sX-CPC-Zonos-Keyheader, for US-bound shipments that need duties and taxes collected.
Return labels#
canada prints authorized return labels: set return_code (any value,
for example PRINT_RETURN_LABEL) and send at least one entry in
packages. The ship_* address is the customer sending the parcel back
and the to_* address is you. Labels are 8.5x11 PDF, one per package.
canada-rest does not print return labels yet. More in
Return Labels.
Manifests and end of day#
Shipments created with a group_id wait in that group until you transmit
it. createmanifest transmits one or more groups and returns the manifest
for the driver:
{
"carrier": "canada-rest",
"action": "createmanifest",
"params": {
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"account_number": "YOUR_CUSTOMER_NUMBER",
"group_ids": ["batch-2026-12-15"],
"shipper": "RocketShipIt",
"ship_contact": "Shipping Dept",
"ship_phone": "613-555-0123",
"ship_addr1": "123 Main St",
"ship_city": "Ottawa",
"ship_state": "ON",
"ship_code": "K1A0B1"
}
}
The manifest PDF is in data.documents with code manifest. Canada Post
makes a separate manifest for domestic, US and international shipments,
so one call can return several. canada reads group_ids only;
canada-rest also accepts a single group_id. More in
End of Day.
Voiding and refunds#
voidshipment with shipment_id and account_number deletes a shipment
that has not been transmitted. A transmitted shipment can only be
refunded:
canada-resttries the delete, and when Canada Post rejects it andship_emailis set, requests a refund instead. The response isstatusVoided, orRefundRequestedwithrefund_requested: true.canadadeletes the shipment and then requests a refund toship_email.
Tracking#
{
"carrier": "canada-rest",
"action": "track",
"params": {
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"tracking_number": "1371134583769923"
}
}
The events are in packages[0].activity. Each has Canada
Post's event code in status_code, the description ("Item successfully
delivered") in description, the date and time, and the city and province
in location. The shipment also carries estimated_delivery (the revised
date when Canada Post changed it), service.description, the destination
postal code and your customer references. On canada-rest, a return PIN
comes back under alternative_tracking_ids with type returnPin.
Tracking needs only the credentials and the PIN, not account_number.
See Tracking for the response format shared by every
carrier.
Pickups#
canada books an on-demand pickup at the pickup_* address:
{
"carrier": "canada",
"action": "createpickup",
"params": {
"username": "YOUR_USERNAME",
"password": "YOUR_PASSWORD",
"account_number": "YOUR_CUSTOMER_NUMBER",
"contract_id": "YOUR_CONTRACT_ID",
"pickup_company_name": "RocketShipIt",
"pickup_contact_name": "John Doe",
"pickup_phone": "613-555-0123",
"pickup_email": "[email protected]",
"pickup_addr1": "123 Main St",
"pickup_city": "Ottawa",
"pickup_state": "ON",
"pickup_code": "K1A0B1",
"pickup_date": "2026-12-15",
"ready_time": "09:00",
"close_time": "17:00",
"pickup_quantity": 3
}
}
pickup_quantity is the number of parcels; pickup_date defaults to
today. The pickup request number comes back in data.pickup_id.
pickupstatus lists your pending pickups in data.pending_status, and
cancelpickup with pickup_id cancels one. account_number is required
for all three. canada-rest does not book pickups yet; use canada with
your XML key for them. Other carriers' pickups are in
Schedule a Pickup.
Errors#
Canada Post's messages come back in data.errors with Canada Post's code
and text. The common ones:
- HTTP 401, or code
E002- the key, user name or password was rejected. Oncanada-restthe OAuth failure is reported with codeauth-failedwhen Canada Post gives no detail. Check that a [Test] app's credentials aren't being used for production, or the other way round. 2206or11001- the contract number is missing or does not belong to the customer number. Checkcontract_idandaccount_number.9128- a customs line has no value per unit.