For businesses
Shipping API
Book Amasyl Express shipments from your own website, store or warehouse system, and get a print-ready 4×6 label back. Same routes, same business rate and same drivers as booking on our site — without anyone retyping an address.
Getting started
- Have an approved business account. If you don't, apply from your dashboard — we'll approve it and set your rate.
- In Dashboard → API access, save a card with Stripe. Every shipment you book over the API is charged to it at the moment it's booked, at your business rate. The card is held by Stripe; we never see the number.
- Create an API key on the same page. It's shown once — copy it straight into your system's secrets.
Send the key on every request as a bearer token. Requests and responses are JSON. All amounts are CAD.
Authorization: Bearer amx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Base URL: https://amasylexpress.ca/api/v1Get a price — POST /quote
What a shipment would cost, without booking it. Use it to show shipping at your own checkout. It runs the same checks and pricing as creating a shipment, so the quote is what you'll be charged.recipient_name anditem_descriptionaren't needed for a quote.
curl https://amasylexpress.ca/api/v1/quote \
-H "Authorization: Bearer $AMASYL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from_address": "123 9 Ave SE, Calgary, AB T2G 0S7",
"to_address": "200 Portage Ave, Winnipeg, MB R3C 3X1",
"package_type": "box",
"weight_kg": 8,
"quantity": 2,
"pickup_date": "2026-10-01"
}'200 OK
{
"quote": {
"route": "Calgary ↔ Winnipeg",
"pickup_date": "2026-09-30",
"pickup_window": "day-before-afternoon-evening",
"pickup_time": "Day before, 1:00 PM - 10:00 PM",
"currency": "CAD",
"subtotal": 105.00,
"business_discount": 10.00,
"volume_discount": 0,
"insurance": 0,
"tax": 5.25,
"total": 110.25
}
}Figures above are illustrative. Note pickup_date in the answer can be the day before the one you asked for: long-haul trucks leave overnight, so their collection happens the evening before.
Book a shipment — POST /shipments
Books the shipment and charges your card. Returns the tracking number and label link.
| Field | What it is | |
|---|---|---|
| from_address | required | Full pickup address: street, city, province. "3616 14 Ave North, Lethbridge, AB T1H 6E7". |
| to_address | required | Full delivery address, same format. |
| package_type | required | envelope, box, pallet or cold_pack (frozen, on our scheduled refrigerated run). |
| weight_kg | required | Weight of ONE box, in kilograms. Pallets start at 500 kg; frozen boxes top out at 25 kg. |
| quantity | optional | Number of boxes, 1–500. Defaults to 1. You get one label page per box. |
| pickup_date | required | YYYY-MM-DD. Must be a day we run the route — the error tells you which days do. |
| pickup_window | optional | Collection window. Leave it out to take the earliest one still open; send one we don't run and the error lists the valid values. |
| recipient_name | required* | Who it's going to. Printed large on the label. |
| recipient_phone | optional | Strongly recommended — the driver calls it if nobody answers the door. |
| recipient_email | optional | |
| item_description | required* | What's in the box, in plain words. Printed on the label. |
| sender_name | optional | Defaults to your company name. |
| sender_phone | optional | |
| priority | optional | standard (default) or priority. |
| fragile | optional | true to mark the shipment fragile. |
| signature_required | optional | true to require a signature on delivery. |
| declared_value_cad | optional | Declared value for insurance. |
| dimensions | optional | Free text, e.g. "40x30x30 cm". |
| notes | optional | For the driver: buzzer code, side door, who to ask for. |
| external_ref | optional | YOUR order number. Makes retries safe — see below. Strongly recommended. |
* Required to book; not needed for a quote.
curl https://amasylexpress.ca/api/v1/shipments \
-H "Authorization: Bearer $AMASYL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_ref": "ORDER-10432",
"from_address": "123 9 Ave SE, Calgary, AB T2G 0S7",
"to_address": "200 Portage Ave, Winnipeg, MB R3C 3X1",
"package_type": "box",
"weight_kg": 8,
"quantity": 2,
"pickup_date": "2026-10-01",
"recipient_name": "Ada Obi",
"recipient_phone": "204-555-0123",
"item_description": "Auto parts"
}'201 Created
{
"shipment": {
"tracking_number": "AMSYWG093001",
"external_ref": "ORDER-10432",
"status": "pending",
"from_address": "123 9 Ave SE, Calgary, AB T2G 0S7",
"to_address": "200 Portage Ave, Winnipeg, MB R3C 3X1",
"package_type": "box",
"quantity": 2,
"weight_kg": 8,
"pickup_date": "2026-09-30",
"pickup_time": "Day before, 1:00 PM - 10:00 PM",
"delivered_at": null,
"currency": "CAD",
"subtotal": 105.00,
"tax": 5.25,
"total": 110.25,
"paid": true,
"tracking_url": "https://amasylexpress.ca/track?tracking=AMSYWG093001",
"label_pdf_url": "https://amasylexpress.ca/api/v1/shipments/AMSYWG093001/label"
},
"quote": { ... }
}You get a confirmation email with the invoice attached, the same as a website booking. If the card is declined, nothing is booked and you get a 402 — see errors.
Print the label — GET /shipments/{tracking_number}/label
A 4×6 PDF, one page per box, each marked “Box 2 of 3”. Send it straight to a thermal label printer or print it on plain paper and tape it on. Every box needs its own label — a box without one can't be collected.
curl https://amasylexpress.ca/api/v1/shipments/AMSYWG093001/label \
-H "Authorization: Bearer $AMASYL_API_KEY" \
-o label.pdfTrack — GET /shipments/{tracking_number}
Returns the same shipment object as above with its current status: pending, confirmed, in_transit, delivered or cancelled. You can only see shipments booked on your own account.
curl https://amasylexpress.ca/api/v1/shipments/AMSYWG093001 \
-H "Authorization: Bearer $AMASYL_API_KEY"
# Or by your own order number:
curl "https://amasylexpress.ca/api/v1/shipments/lookup?external_ref=ORDER-10432" \
-H "Authorization: Bearer $AMASYL_API_KEY"Retries and double charges
Networks drop connections. If your request to create a shipment times out, you don't know whether it was booked — and retrying blindly could book it twice.
Always send external_ref with your own order number. Then a retry with the same key and the same external_ref returns the original shipment with "replayed": true — never a second shipment and never a second charge. If your first attempt was declined, the same external_ref can be used again once the card is sorted out.
Errors
Every error has the same shape. Branch on code; show or log message.
{ "error": { "code": "card_declined", "message": "Your card was declined. Nothing was booked." } }| HTTP | code | What to do |
|---|---|---|
| 401 | missing_api_key / invalid_api_key / revoked_api_key | Check the Authorization header, or create a new key. |
| 402 | no_card_on_file | Save a card with Stripe in Dashboard → API access. |
| 402 | card_declined | The card was declined. Nothing was booked. |
| 402 | card_authentication_required | The bank wants the cardholder to approve this one. Book it on the website, or save a different card. |
| 403 | not_an_approved_business | API access is for approved business accounts. |
| 404 | not_found | No shipment with that reference on your account. |
| 409 | already_in_progress | Same external_ref, still being created. Look it up in a few seconds. |
| 422 | outside_service_area | An address we don't normally cover. Contact us and we'll arrange it. |
| 400 | not_an_operating_day, invalid_pickup_window, pickup_window_closed, … | The message says exactly what to change. |
| 429 | rate_limited | Over 120 requests a minute on one key. Wait retry_after_seconds. |
| 503 | bookings_paused / stripe_unreachable | Temporary. Nothing was booked or charged — retry. |
Limit: 120 requests a minute per key.
Questions?
Call us on (403) 635-0666 or get in touch. We're happy to talk to your developer directly.