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#

  • canada sends "test": true requests 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 follow debug_mode, which test turns on, so send test rather than debug_mode alone or those two will go to the development host while the rest goes to production.
  • canada-rest has no test host and ignores test. 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_code is the origin postal code. Spaces are stripped.
  • The destination is to_code in Canada and the US, and to_country alone elsewhere (Canada Post prices international parcels by country).
  • contract_id asks for your contract prices; without it you get the prices for your customer number.
  • ship_date is the expected mailing date.
  • insured_value on the package adds coverage (COV) for that amount; signature_type DIRECT adds signature (SO). Both are priced.
  • Only the first package is rated. Rate each parcel separately.
  • canada-rest rounds weight to 0.001 kg and dimensions to 0.1 cm; canada truncates dimensions to whole centimetres.

One rate comes back per service:

  • service_code (DOM.EP) and desc (Canada Post's service name).
  • rate - the amount due in CAD, taxes included; currency is CAD.
  • rate_detail - the breakdown: BASE base charge, TAXGST, TAXHST and TAXPST, and one entry per adjustment (fuel surcharge, coverage, discounts) as code:name.
  • est_delivery_time - the expected delivery date.
  • est_delivery_days and guaranteed - transit days and whether the date is guaranteed (canada-rest only).

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_id you ship on your parcel services contract, billed to your account (account_number). On canada, a billing entry's account bills a different customer, and payment_method picks Account, CreditCard or SupplierAccount.
  • Without it the shipment is non-contract and is paid with the credit card on your Canada Post profile. On canada this is the ncshipment service, which ignores signature, coverage, label format, to_company and to_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.

  • canada with contract_id creates one shipment per entry in packages. The top level tracking_number and shipment_id are the first parcel's, each packages[i] has its own, and charges is 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-rest and non-contract canada ship 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_type PDF or ZPL. ZPL labels are always 4x6.
  • canada contract shipments take label_stock_type 8.5x11 (default for PDF) or 4x6 (default and required for ZPL).
  • canada-rest with no image_type uses 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_amount is the value of one unit. Without it, customs_value is divided by customs_quantity. Documents with no value go as 0.01 CAD, the value Canada Post requires for documents.
  • customs_weight is the weight of one unit in kg.
  • reason_for_export is SOG (sale of goods, the default), DOC (documents, the default when documents_only is true), SAM (commercial sample), REP (repair or warranty) or OTH.
  • non_delivery_code is what Canada Post does with an undeliverable parcel: RASE return at sender's expense (default), RTS return to sender, ABAN abandon.
  • customs_hs_tariff is sent by canada-rest. canada contract shipments do not send country_of_manufacture.
  • zonos_account_key is sent as Canada Post's X-CPC-Zonos-Key header, 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-rest tries the delete, and when Canada Post rejects it and ship_email is set, requests a refund instead. The response is status Voided, or RefundRequested with refund_requested: true.
  • canada deletes the shipment and then requests a refund to ship_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. On canada-rest the OAuth failure is reported with code auth-failed when Canada Post gives no detail. Check that a [Test] app's credentials aren't being used for production, or the other way round.
  • 2206 or 11001 - the contract number is missing or does not belong to the customer number. Check contract_id and account_number.
  • 9128 - a customs line has no value per unit.

Examples#