API reference
The calls a widget's key makes, with their requests and answers.
Every call a widget may make on the API host, with its parameters, request body and answers. A widget makes only the calls of its own type, as listed on each widget's page; the runtime makes them.
Every call carries the widget's key and the page's origin. The calls of a signed-in visitor also carry the visitor's token:
X-App-Key: sp_widget_…
Origin: https://www.example.com
Authorization: Bearer <the visitor's token>
REST
POST /api/v1/apps/swidget/auth/two-factorFinish a sign-in held at its second factorPOST /api/v1/apps/swidget/auth/two-factor/resendSend the code of a held sign-in againPOST /api/v1/apps/swidget/clients/{clientCode}/auth/emailSign in to an app with emailPOST /api/v1/apps/swidget/clients/{clientCode}/auth/joinJoin this client with the identityPOST /api/v1/apps/swidget/clients/{clientCode}/auth/logoutSign out of an appGET /api/v1/apps/swidget/clients/{clientCode}/guest/pickup-pointsThe Client's pickup points without signing inGET /api/v1/apps/swidget/clients/{clientCode}/guest/shipmentsList a verified contact's guest shipmentsPOST /api/v1/apps/swidget/clients/{clientCode}/guest/shipmentsSubmit a guest shipment for paymentPOST /api/v1/apps/swidget/clients/{clientCode}/guest/shipments/ratePrice a guest shipmentGET /api/v1/apps/swidget/clients/{clientCode}/guest/shipments/{guestOrderId}Get a guest shipmentPOST /api/v1/apps/swidget/clients/{clientCode}/guest/shipments/{guestOrderId}/cancelCancel a guest shipmentGET /api/v1/apps/swidget/clients/{clientCode}/guest/shipments/{guestOrderId}/labelGet the label of a guest shipmentPOST /api/v1/apps/swidget/clients/{clientCode}/guest/trackTrack any code (guest)POST /api/v1/apps/swidget/clients/{clientCode}/guest/verify/completeCheck a guest verification codePOST /api/v1/apps/swidget/clients/{clientCode}/guest/verify/requestSend a guest a verification codeGET /api/v1/apps/swidget/clients/{clientCode}/quotes/schemaThe quote form a client offers without signing inPOST /api/v1/apps/swidget/clients/{clientCode}/quotes/{line}Quote a line without signing inGET /api/v1/apps/swidget/clients/{clientCode}/registration-detailsRegistration details still to fill inPOST /api/v1/apps/swidget/clients/{clientCode}/registration-detailsFill in the registration detailsPOST /api/v1/apps/swidget/clients/{clientCode}/registration/completeRegister a Client customerPOST /api/v1/apps/swidget/clients/{clientCode}/registration/email/requestRequest a registration email verification codeGET /api/v1/apps/swidget/clients/{clientCode}/swidgetA website widget's configuration (sWidget)
POST /api/v1/apps/swidget/auth/two-factor
Finish a sign-in held at its second factor
Unified identity: an email, Google or Apple sign-in of a person with two-step verification (or whose client requires it) answers 403 TWO_FACTOR_REQUIRED with data.challenge_id instead of a session. Send the code here from the same device. method is totp (authenticator app; a recovery code is also accepted), email or sms. setup = true: the client requires two-step verification and the person had none; the emailed code turns it on. trust_device = true returns two_factor_device_token: send it as two_factor_device_token with later sign-ins from this device and the code is not asked for 30 days. The response is the sign-in's session (same envelope as email login).
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
Request body
{
"challenge_id": "r9Qm...",
"code": "123456",
"trust_device": true,
"device_id": "0f3d1c43-7f53-4c16-ae90-62a2bc921812",
"device_platform": "ios",
"app_version": "1.0.0"
}
Answer 200 — Session issued
{
"result": true,
"access_token": "passport-access-token",
"token_type": "Bearer",
"two_factor_device_token": "k2v...64 chars"
}
Answer 401 — TWO_FACTOR_CODE_INVALID
Answer 410 — TWO_FACTOR_CHALLENGE_INVALID: unknown, expired, used, or from another device
Answer 429 — TWO_FACTOR_TOO_MANY_ATTEMPTS: start the sign-in again
Answer 422 — Invalid request
POST /api/v1/apps/swidget/auth/two-factor/resend
Send the code of a held sign-in again
For method email or sms. Returns a new challenge_id; the previous one stops working.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
Request body
{
"challenge_id": "r9Qm...",
"device_id": "0f3d1c43-7f53-4c16-ae90-62a2bc921812",
"device_platform": "ios"
}
Answer 200 — Code sent
{
"result": true,
"data": {
"challenge_id": "x7Lp...",
"method": "email",
"target": "a***@example.com",
"expires_in": 600
}
}
Answer 410 — TWO_FACTOR_CHALLENGE_INVALID
Answer 429 — TWO_FACTOR_RESEND_TOO_SOON
Answer 503 — TWO_FACTOR_DELIVERY_FAILED
POST /api/v1/apps/swidget/clients/{clientCode}/auth/email
Sign in to an app with email
Authenticates the normal Superroute account password, then applies the selected app's role and Client ownership policy before issuing an app session.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
Request body
{
"email": "customer@example.com",
"password": "account-password",
"device_id": "0f3d1c43-7f53-4c16-ae90-62a2bc921812",
"device_platform": "android",
"app_version": "1.0.0"
}
Answer 200 — App session issued; response matches Google login
Answer 401 — Invalid credentials
Answer 403 — App, method, role or Client access is not allowed
Answer 422 — Invalid request
POST /api/v1/apps/swidget/clients/{clientCode}/auth/join
Join this client with the identity
Unified identity: an email, Google or Apple sign-in in a client's own app by a person whose identity holds no account of this client yet answers 409 IDENTITY_JOIN_REQUIRED with data.join_token (10 minutes, same device), data.fields (required) and data.values (only the name prefilled). Send the client's registration details here. The client's app registration decides: 403 IDENTITY_JOIN_CLOSED when it takes no registrations, 202 pending_review when it reviews new accounts, otherwise the session (or TWO_FACTOR_REQUIRED). Nothing is copied from the person's accounts at other clients.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | q8w2e6r1t5y9u3i7o0p4a2sd |
Request body
{
"join_token": "t4Kp...64 chars",
"name": "Ann Lee",
"telephone": "+15145550123",
"address": "100 Rue Main",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1Y4",
"country": "Canada",
"device_id": "0f3d1c43-7f53-4c16-ae90-62a2bc921812",
"device_platform": "ios"
}
Answer 200 — Joined; session issued (same envelope as email login)
Answer 202 — Joined; the client reviews new accounts first
{
"result": true,
"pending_review": true
}
Answer 403 — IDENTITY_JOIN_CLOSED, or TWO_FACTOR_REQUIRED
Answer 409 — IDENTITY_ALREADY_JOINED
Answer 410 — IDENTITY_JOIN_INVALID: unknown or expired token, other device
Answer 422 — VALIDATION_FAILED with errors per field
POST /api/v1/apps/swidget/clients/{clientCode}/auth/logout
Sign out of an app — needs the visitor's token
Revokes the current Passport token and marks the matching app-device session as revoked.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
Answer 200 — Session revoked
{
"result": true,
"message": "Signed out successfully."
}
Answer 401 — Invalid or expired app session
Answer 403 — App access is no longer allowed
GET /api/v1/apps/swidget/clients/{clientCode}/guest/pickup-points
The Client's pickup points without signing in
The Client's smart locker locations (ids `L…`) and pickup point counters (ids `C…`) a visitor may choose, nearest first when `latitude` and `longitude` are given, with nothing personal. A pickup point widget's key (`appCode` `swidget`, see `platformAppSwidgetConfig`) narrows the list to the kinds the widget keeps. Public. GraphQL twin: `platformAppGuestPickupPoints`.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | |
latitude | query | ||
longitude | query | ||
search | query | ||
page | query | ||
per_page | query | ||
X-App-Key | header | ||
Origin | header |
Answer 200 — The pickup points
{
"result": true,
"data": {
"items": [
{
"id": "L12",
"kind": "locker",
"location_id": 12,
"name": "Station locker",
"address": "1 Front St",
"city": "Toronto",
"latitude": 43.6,
"longitude": -79.4,
"distance_km": 1.2
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 1,
"has_more": false
}
}
}
Answer 404 — The app is not available for this Client
{
"result": false,
"code": "APP_NOT_AVAILABLE"
}
GET /api/v1/apps/swidget/clients/{clientCode}/guest/shipments
List a verified contact's guest shipments
Public; needs X-Guest-Contact-Token. The guest shipments of that contact at this Client from the last 90 days, newest first, at most 50: for a guest on a new device.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
X-Guest-Contact-Token | header | ✓ | |
locale | query | en |
Answer 200 — Guest shipments
{
"result": true,
"items": [
{
"id": 42,
"kind": "self_delivery",
"status": "pending_payment",
"amount": "12.40",
"currency": "CAD",
"contact": "g****@example.com",
"created_at": "2026-10-09T10:00:00-04:00",
"pay_url": "https://ship.example.com/pay/r/3f9c0d6a1b2e4f5a6b7c8d9e0f1a2b3c",
"pay_before": "2026-10-09T10:30:00-04:00",
"can_cancel": true,
"order": null
}
]
}
Answer 401 — GUEST_CONTACT_REQUIRED
Answer 404 — APP_CONTEXT_NOT_FOUND: the Client does not offer guest shipping in this app
POST /api/v1/apps/swidget/clients/{clientCode}/guest/shipments
Submit a guest shipment for payment
Public; needs X-Guest-Contact-Token. The body is the rated shipment, exactly as rated, with rate_id from POST .../guest/shipments/rate, and optionally note and need_pick_up (self delivery). Nothing is created yet: the shipment waits for its payment on the shared payment page (pay_url, gateways only, no wallet) until pay_before. Once paid, the payment is credited to the Client's attribution customer and the order is created through uniorder (local delivery dispatched, label bought); if that fails the payment is refunded. The guest receives the outcome by email or SMS on the verified contact; the link in it carries no token. order_token is shown once and opens this shipment later.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
X-Guest-Contact-Token | header | ✓ |
Request body
{
"from_name": "Ann Lee",
"from_telephone": "+14165550100",
"from_address": "1 King St W",
"from_city": "Toronto",
"from_province": "ON",
"from_postcode": "M5H 1A1",
"from_country": "CA",
"to_name": "Bo Chen",
"to_telephone": "+14165550101",
"to_address": "300 Front St W",
"to_city": "Toronto",
"to_province": "ON",
"to_postcode": "M5V 2T6",
"to_country": "CA",
"packages": [
{
"weight": 2,
"length": 30,
"width": 20,
"height": 10,
"weight_unit": 1,
"dimension_unit": 1
}
],
"rate_id": "eyJpdiI6Ikd1ZXN0UXVvdGUi...",
"note": "Back door",
"locale": "en"
}
Answer 201 — Waiting for payment
{
"result": true,
"order_token": "k3J9xQ2mV8pL5tR1wZ7cN4bH6dF0gS2aY9eU3iO8",
"guest_order": {
"id": 42,
"kind": "self_delivery",
"status": "pending_payment",
"amount": "12.40",
"currency": "CAD",
"contact": "g****@example.com",
"created_at": "2026-10-09T10:00:00-04:00",
"pay_url": "https://ship.example.com/pay/r/3f9c0d6a1b2e4f5a6b7c8d9e0f1a2b3c",
"pay_before": "2026-10-09T10:30:00-04:00",
"can_cancel": true,
"order": null
}
}
Answer 401 — GUEST_CONTACT_REQUIRED
Answer 404 — APP_CONTEXT_NOT_FOUND: the Client does not offer guest shipping in this app
Answer 422 — RATE_ID_INVALID: expired, another Client, or a different shipment
Answer 429 — GUEST_ORDER_LIMIT: per contact per day, per IP address per hour
POST /api/v1/apps/swidget/clients/{clientCode}/guest/shipments/rate
Price a guest shipment
Public. Prices a local delivery and carrier labels for a guest of a Client that offers guest shipping (capability guest.shipping), as the Client's attribution customer: its price plan, label accounts and service switches. The body is the shipment (from_* and to_* name, telephone, address, city, province, postcode, country; packages; signature_option); any other field, such as a customer or a channel, is ignored. Each rate_id is a guest quote: valid for this Client and this exact shipment only, for 30 minutes. A self delivery rate without a price is reported in errors with code NOT_PRICED: a guest pays before the order exists. Carrier quotes: six per minute per caller and Client.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
Request body
{
"from_name": "Ann Lee",
"from_telephone": "+14165550100",
"from_address": "1 King St W",
"from_city": "Toronto",
"from_province": "ON",
"from_postcode": "M5H 1A1",
"from_country": "CA",
"to_name": "Bo Chen",
"to_telephone": "+14165550101",
"to_address": "300 Front St W",
"to_city": "Toronto",
"to_province": "ON",
"to_postcode": "M5V 2T6",
"to_country": "CA",
"packages": [
{
"weight": 2,
"length": 30,
"width": 20,
"height": 10,
"weight_unit": 1,
"dimension_unit": 1
}
],
"locale": "en"
}
Answer 200 — Rates, as POST /v1/uniorder/rate, with guest rate ids
{
"result": true,
"rates": [
{
"rate_id": "eyJpdiI6Ikd1ZXN0UXVvdGUi...",
"type": "self_delivery",
"shipping_price": "12.40",
"currency": "CAD"
},
{
"rate_id": "eyJpdiI6Ikd1ZXN0TGFiZWwi...",
"type": "label_service",
"shipping_price": "20.00",
"currency": "CAD",
"carrier_name": "UPS",
"service_name": "Ground",
"transit_days": 2
}
],
"errors": []
}
Answer 400 — Invalid shipment: result false with message
Answer 403 — SERVICE_NOT_AVAILABLE: the attribution customer may not use these services
Answer 404 — APP_CONTEXT_NOT_FOUND: the Client does not offer guest shipping in this app
Answer 429 — TOO_MANY_REQUESTS: carrier quote limit
GET /api/v1/apps/swidget/clients/{clientCode}/guest/shipments/{guestOrderId}
Get a guest shipment
Public; with X-Guest-Order-Token or an X-Guest-Contact-Token of the same contact (90 days). status is pending_payment, paid, placing, placed, expired, cancelled, refunded or refund_required. While unpaid, pay_url and pay_before; once placed, order carries the order's reference, status_group and status_label, the two addresses, packages with tracking numbers and, for a label, label.carrier_name and carrier_tracking_numbers.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
guestOrderId | path | ✓ | 42 |
X-Guest-Order-Token | header | ||
X-Guest-Contact-Token | header | ||
locale | query | en |
Answer 200 — The guest shipment
{
"result": true,
"guest_order": {
"id": 42,
"kind": "self_delivery",
"status": "placed",
"amount": "12.40",
"currency": "CAD",
"contact": "g****@example.com",
"created_at": "2026-10-09T10:00:00-04:00",
"pay_url": null,
"pay_before": null,
"can_cancel": false,
"order": {
"type": "self_delivery",
"ref": "SR41000123",
"status_group": "active",
"status_label": "Out for delivery",
"packages": [
{
"tracking_number": "SR41000123-1"
}
]
}
}
}
Answer 404 — GUEST_ORDER_NOT_FOUND, or APP_CONTEXT_NOT_FOUND
POST /api/v1/apps/swidget/clients/{clientCode}/guest/shipments/{guestOrderId}/cancel
Cancel a guest shipment
Public; with X-Guest-Order-Token or an X-Guest-Contact-Token of the same contact. An unpaid shipment is withdrawn and its payment page closes. A created one is cancelled while its order still can be (no pickup yet, label not used; can_cancel tells): the order is cancelled, its charge returned to the Client's attribution customer, the guest refunded in full and told by email or SMS; when the gateway cannot refund on its own the status is refund_required.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
guestOrderId | path | ✓ | 42 |
X-Guest-Order-Token | header | ||
X-Guest-Contact-Token | header |
Answer 200 — Withdrawn
{
"result": true,
"guest_order": {
"id": 42,
"kind": "self_delivery",
"status": "cancelled",
"amount": "12.40",
"currency": "CAD",
"contact": "g****@example.com",
"created_at": "2026-10-09T10:00:00-04:00",
"pay_url": null,
"pay_before": null,
"can_cancel": false,
"order": null
}
}
Answer 404 — GUEST_ORDER_NOT_FOUND, or APP_CONTEXT_NOT_FOUND
Answer 409 — GUEST_ORDER_NOT_CANCELLABLE: being paid, being created, or the order can no longer be cancelled
GET /api/v1/apps/swidget/clients/{clientCode}/guest/shipments/{guestOrderId}/label
Get the label of a guest shipment
Public; with X-Guest-Order-Token or an X-Guest-Contact-Token of the same contact. The label PDF of a placed label shipment, base64 in pdf_data.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
guestOrderId | path | ✓ | 42 |
X-Guest-Order-Token | header | ||
X-Guest-Contact-Token | header |
Answer 200 — The label
{
"result": true,
"format": "pdf",
"tracking_numbers": [
"1Z999AA10123456784"
],
"pdf_data": "JVBERi0xLjQKJ..."
}
Answer 404 — GUEST_ORDER_NOT_FOUND for any other shipment, or APP_CONTEXT_NOT_FOUND
Answer 409 — LABEL_NOT_AVAILABLE: the carrier has not produced it yet
POST /api/v1/apps/swidget/clients/{clientCode}/guest/track
Track any code (guest)
Universal tracking (docs/APP-UNIVERSAL-TRACKING-DESIGN.md): one code of this Client in, whatever it is — `kind` parcel (tracking number), moving (moving code), storage (storage code), return (return code), shipping (shipping order code) or carrier_package (carrier tracking number). `tracking` is the OTEP public view (as /oteptrack); its `details` (addresses, dates, proof of delivery) and `summary.code` (pickup code) are present once `verified`. Send `answer` (last 4 digits of a phone on the shipment, or its postcode) to verify; `can_verify` says whether that is possible. A storage, return or shipping code is the drop-off code the sender holds, so `summary.code` is shown for those. `wallet` holds absolute Apple (.pkpass) and Google (save link) card links valid 2 h, or null when the Client has wallet cards off or the shipment offers no card. Errors: 404 TRACKING_NOT_FOUND (unknown code), 422 TRACKING_VERIFY_FAILED (wrong answer), 422 TRACKING_INVALID, 429 TRACKING_TOO_MANY. Public, no sign-in. Pickup codes are never answered here.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
Answer 200 — What the code is and where it stands
{
"result": true,
"data": {
"kind": "return",
"identifier": "R8K2Q7",
"owned": false,
"verified": false,
"can_verify": true,
"summary": {
"stage": "send",
"status": "awaiting_dropoff",
"reference": "RMA-1042",
"place": null,
"deadline": null,
"code": "R8K2Q7"
},
"tracking": {
"otep_version": "0.1",
"profile": "parcel",
"subject": {
"identifier": "R8K2Q7",
"profile": "parcel"
},
"current_status": "booking_confirmed",
"current_phase": "pre_shipment",
"delivered": false,
"events": [
{
"occurred_at": "2026-10-18T09:12:00-04:00",
"status_code": "information_submitted",
"phase": "pre_shipment",
"location": null
},
{
"occurred_at": "2026-10-18T09:40:00-04:00",
"status_code": "booking_confirmed",
"phase": "pre_shipment",
"location": null
}
]
},
"wallet": {
"apple": "https://track.example.com/wallet-card/en/return_request/1042/apple?code=1&expires=1760800000&signature=...",
"google": "https://track.example.com/wallet-card/en/return_request/1042/google?code=1&expires=1760800000&signature=..."
},
"pickup_id": null
}
}
Answer 404 — TRACKING_NOT_FOUND, or the App / Client is not available
Answer 422 — TRACKING_INVALID or TRACKING_VERIFY_FAILED
Answer 429 — TRACKING_TOO_MANY
POST /api/v1/apps/swidget/clients/{clientCode}/guest/verify/complete
Check a guest verification code
Public. Checks the code of a challenge and returns a contact token: proof, for 20 minutes and at this Client only, that the guest holds the contact. It is not a sign-in, and a sign-in token is never accepted in its place. Five wrong codes void the challenge.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
Request body
{
"challenge_id": "Qm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2ll",
"code": "482913"
}
Answer 200 — Contact verified
{
"result": true,
"contact_token": "eyJpdiI6IkNvbnRhY3RUb2tlbiIs...",
"channel": "email",
"contact": "g****@example.com",
"expires_in": 1200
}
Answer 404 — APP_CONTEXT_NOT_FOUND: the Client does not offer guest shipping in this app
Answer 422 — INVALID_VERIFICATION_CODE, VERIFICATION_ATTEMPTS_EXCEEDED or INVALID_VERIFICATION_CHALLENGE
POST /api/v1/apps/swidget/clients/{clientCode}/guest/verify/request
Send a guest a verification code
Public. Sends a six-digit code to an email address or a phone number, on the channel the Client chose for guest shipping (settings guest.verification_channel; shown in GET /v1/apps/swidget/guest/clients). The answer is the same for any contact. The code expires in 10 minutes; one code per contact and Client per minute and 20 per IP address per hour.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
Request body
{
"contact": "guest@example.com",
"locale": "en"
}
Answer 200 — Code sent
{
"result": true,
"message": "A verification code was sent.",
"challenge_id": "Qm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2ll",
"channel": "email",
"contact": "g****@example.com",
"expires_in": 600,
"resend_after": 60
}
Answer 404 — APP_CONTEXT_NOT_FOUND: the Client does not offer guest shipping in this app
Answer 422 — INVALID_EMAIL or INVALID_PHONE_NUMBER
Answer 429 — GUEST_VERIFICATION_RATE_LIMITED
Answer 503 — SMS_SERVICE_NOT_CONFIGURED, EMAIL_DELIVERY_FAILED or SMS_DELIVERY_FAILED
GET /api/v1/apps/swidget/clients/{clientCode}/quotes/schema
The quote form a client offers without signing in
Public. The lines and their fields, the same schema the /quotes page renders from. A request with a bearer token of a customer of this Client sees only what that account may use.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
locale | query | en |
Answer 200 — The quote form
{
"result": true,
"schema": {
"business_name": "Northstar Courier",
"currency": "CAD",
"valid_days": 7,
"lines": []
}
}
Answer 404 — APP_CONTEXT_NOT_FOUND: the Client does not offer quotes in this app
POST /api/v1/apps/swidget/clients/{clientCode}/quotes/{line}
Quote a line without signing in
Public. The same service and per-Client settings as the /quotes page, so the app and the page quote alike; every quote is recorded. The body carries the line's fields as listed in the schema. A request with a bearer token of a customer of this Client is quoted on that account, anyone else as an anonymous visitor. Carrier lines (label, express) are limited to six per minute per caller and Client.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
line | path | ✓ |
Request body
{
"to_postcode": "M5V 2T6",
"weight": 2.4,
"locale": "en"
}
Answer 200 — result true with quote, or result false with not_covered_reason
{
"result": true,
"quote": {
"line": "delivery",
"currency": "CAD",
"total": 12.4,
"token": "abcdefghijabcdefghijabcdefghijabcdefghij",
"valid_until": "2026-10-15T12:00:00-04:00",
"drive_minutes": 18,
"drive_from": "Toronto Depot"
}
}
Answer 404 — APP_CONTEXT_NOT_FOUND: the Client does not offer quotes in this app
Answer 422 — Invalid input, or a line the Client does not offer: result false with errors
Answer 429 — Carrier-line limit: result false, not_covered_reason too_many
GET /api/v1/apps/swidget/clients/{clientCode}/registration-details
Registration details still to fill in — needs the visitor's token
Unified identity: a customer a client's staff created without the registration details (the client has identity sign-in on). Until they are filled in, every other API call of the account (REST, GraphQL, MCP) answers 403 {"error": "registration_incomplete", "code": "REGISTRATION_INCOMPLETE"}; generic API logins refuse with the same body, and an app login still issues the session with registration_incomplete: true so the app can show this form.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
Answer 200 — Required fields, missing ones and values on file
{
"result": true,
"data": {
"complete": false,
"fields": [
"name",
"telephone",
"address",
"city",
"province",
"postcode",
"country"
],
"missing": [
"telephone",
"address"
],
"values": {
"name": "Ann Lee",
"telephone": "",
"address": "",
"address_2": "",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1Y4",
"country": "Canada"
}
}
}
POST /api/v1/apps/swidget/clients/{clientCode}/registration-details
Fill in the registration details — needs the visitor's token
Required: the fields listed by GET (province only where the client's area has provinces). Once complete, the account's API works again.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
Request body
{
"telephone": "+15145550123",
"address": "100 Rue Main"
}
Answer 200 — Saved; same body as GET
{
"result": true,
"data": {
"complete": true,
"fields": [
"name",
"telephone",
"address",
"city",
"province",
"postcode",
"country"
],
"missing": [],
"values": {
"name": "Ann Lee",
"telephone": "",
"address": "",
"address_2": "",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1Y4",
"country": "Canada"
}
}
}
Answer 422 — VALIDATION_FAILED with errors per field
POST /api/v1/apps/swidget/clients/{clientCode}/registration/complete
Register a Client customer
Creates the normal Superroute User and Customer records and either issues an app session or returns a pending-review result. Existing accounts are never overwritten or duplicated. Two shapes, chosen by the Client's `registration.require_phone_verification` setting (published as `authentication.registration.phone_verification_required` on bootstrap). When it is on, send `challenge_id` and `code` from /registration/phone/request and the phone is recorded as verified. When it is off, send `phone` instead and the number is stored unverified. A challenge is always honoured if supplied. For an app whose Clients choose the verification channel (the SuperRoute customer app), a challenge is always required and the Client's channel (`authentication.registration.verification_channel` on bootstrap) decides: phone as above, or email — send `channel: email` with `challenge_id` and `code` from /registration/email/request, the same `email`, and `phone` (a contact number, stored unverified and never compared with other accounts); the answer then also carries `email_verified: true`. `channel` defaults to phone. Optionally send `google_id_token` and/or `apple_id_token` (with `apple_authorization_code` and `apple_name`) to link the Google / Apple account the person signed in with; the answer lists what was linked in `linked_providers`. The address is mandatory in both shapes. The new account starts from the customer template the Client selected for this app, or from the business default template when it selected none.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
Request body
{
"challenge_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"code": "123456",
"name": "MiBox Customer",
"email": "customer@example.com",
"password": "account-password",
"password_confirmation": "account-password",
"address": "123 King Street West",
"address_2": "Unit 5",
"city": "Toronto",
"province": "ON",
"postcode": "M5H 1A1",
"country": "Canada",
"device_id": "0f3d1c43-7f53-4c16-ae90-62a2bc921812",
"device_platform": "ios",
"app_version": "1.0.0"
}
Answer 201 — Customer registered. Returns either a Passport app session or REGISTRATION_PENDING_REVIEW.
{
"result": true,
"message": "Registration and phone verification completed.",
"phone_verified": true,
"requires_approval": false,
"access_token": "passport-access-token",
"token_type": "Bearer",
"linked_providers": []
}
Answer 403 — Registration is disabled for this App and Client
Answer 409 — Existing account, linked phone, concurrent registration, or GOOGLE_ACCOUNT_LINK_CONFLICT / APPLE_ACCOUNT_LINK_CONFLICT for a Google / Apple account already linked to another account
Answer 410 — Verification challenge expired
Answer 422 — Invalid request, missing address, invalid verification code, or PHONE_VERIFICATION_REQUIRED when the Client requires SMS verification and no challenge was sent
Answer 429 — Verification attempt limit exceeded
POST /api/v1/apps/swidget/clients/{clientCode}/registration/email/request
Request a registration email verification code
For an app whose Clients choose the sign-up verification channel (the SuperRoute customer app, appCode customer) and a Client that chose email (bootstrap authentication.registration.verification_channel). Creates a ten-minute, App- and Client-scoped challenge and emails a six-digit code. The answer is the same whether or not the address already has an account: an address that has one is sent no code, only a note that it is registered, so the challenge cannot be completed. Codes are never returned by the API.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | n4x7q2g8w9r3k6m1p5t0z8ab |
Request body
{
"email": "jane.doe@example.com",
"locale": "en"
}
Answer 200 — Verification challenge created (the same answer for a registered address)
{
"result": true,
"message": "A verification email was sent.",
"challenge_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"email": "j*******@example.com",
"expires_in": 600,
"resend_after": 60
}
Answer 403 — REGISTRATION_DISABLED (sign-up closed for this App and Client) or EMAIL_VERIFICATION_DISABLED (the Client chose phone, or the app has no channels, as MiBox)
Answer 422 — Invalid email address
Answer 429 — EMAIL_VERIFICATION_RATE_LIMITED: another code for this address within 60 seconds, or the request rate limit
Answer 503 — EMAIL_DELIVERY_FAILED: the email could not be sent
GET /api/v1/apps/swidget/clients/{clientCode}/swidget
A website widget's configuration (sWidget)
The configuration of the website widget its key names (docs/SWIDGET-DESIGN.md W28, W32): kind, options, branding, language, the Client's country, and where every later call goes (`api`: base URL, app code and client code). A signed-in order widget's `options.sign_in` says how visitors sign in (`method` `password`), whether sign-up is offered (`registration`: the Client's self-registration switch and the app's sign-up both on) and the Client's password reset page. Every call naming the app `swidget` needs the widget key in `X-App-Key` and a browser `Origin` that is one of the widget's websites. Errors: 401 `invalid_app_key`, 403 `origin_not_allowed`, 403 `widget_call_not_allowed`, 403 `registration_disabled` (sign-up calls), 404 `invalid_client`, 429 `too_many_requests` (with `Retry-After`). GraphQL twin: `platformAppSwidgetConfig`.
| Parameter | In | Required | Example |
|---|---|---|---|
appCode | path | ✓ | swidget |
clientCode | path | ✓ | |
X-App-Key | header | ||
Origin | header |
Answer 200 — The widget's configuration
{
"result": true,
"data": {
"widget": {
"id": "k2m9q7x4b1c8",
"type": "order",
"name": "Ship a parcel",
"locale": null,
"options": {
"order_mode": "sign_in",
"sign_in": {
"method": "password",
"registration": true,
"password_reset_url": "https://ship.acme.example/portal/password/forgot"
}
}
},
"branding": {
"display_name": "Acme Delivery",
"primary_color": "#E02020",
"logo_url": null
},
"country": "CA",
"app_code": "swidget",
"client_code": "k3m9q2",
"api": {
"base": "https://api.superroute.ca",
"app_code": "swidget",
"client_code": "k3m9q2"
}
}
}
Answer 401 — invalid_app_key
{
"error": "invalid_app_key",
"error_description": "A valid widget key is required."
}
Answer 403 — origin_not_allowed
{
"error": "origin_not_allowed",
"error_description": "This origin is not registered for the widget."
}
Answer 404 — invalid_client
{
"error": "invalid_client",
"error_description": "This app is not available for this service provider."
}
Answer 429 — too_many_requests
{
"error": "too_many_requests",
"error_description": "Too many requests from this widget."
}
GraphQL
The same calls as GraphQL operations, sent to POST /api/graphql
platformAppCompleteRegistrationFill in the registration detailsplatformAppEmailLoginSign in to an app with emailplatformAppGuestPickupPointsThe Client's pickup points without signing inplatformAppGuestShipmentGet a guest shipmentplatformAppGuestShipmentCancelCancel a guest shipmentplatformAppGuestShipmentLabelGet the label of a guest shipmentplatformAppGuestShipmentRatePrice a guest shipmentplatformAppGuestShipmentSubmitSubmit a guest shipment for paymentplatformAppGuestShipmentsList a verified contact's guest shipmentsplatformAppGuestTrackTrack any code (guest)platformAppGuestVerifyCompleteCheck a guest verification codeplatformAppGuestVerifyRequestSend a guest a verification codeplatformAppIdentityJoinJoin this client with the identityplatformAppPublicQuoteQuote a line without signing inplatformAppPublicQuoteSchemaThe quote form a client offers without signing inplatformAppRegisterCustomerRegister a Client customerplatformAppRegistrationDetailsRegistration details still to fill inplatformAppRequestRegistrationEmailCodeRequest a registration email verification codeplatformAppSwidgetConfigA website widget's configuration (sWidget)platformAppTwoFactorResendSend the code of a held sign-in againplatformAppTwoFactorVerifyFinish a sign-in held at its second factor
platformAppCompleteRegistration mutation
Fill in the registration details
Required: the fields listed by platformAppRegistrationDetails. Once complete, the account's API works again.
Query
mutation PlatformAppCompleteRegistration($app_code: String!, $client_code: String!, $name: String, $telephone: String, $address: String, $address_2: String, $city: String, $province: String, $postcode: String, $country: String) {
platformAppCompleteRegistration(app_code: $app_code, client_code: $client_code, name: $name, telephone: $telephone, address: $address, address_2: $address_2, city: $city, province: $province, postcode: $postcode, country: $country)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"name": "",
"telephone": "",
"address": "",
"address_2": "",
"city": "",
"province": "",
"postcode": "",
"country": ""
}
Answer
{
"result": true,
"data": {
"complete": true,
"fields": [
"name",
"telephone",
"address",
"city",
"province",
"postcode",
"country"
],
"missing": [],
"values": {
"name": "Ann Lee",
"telephone": "",
"address": "",
"address_2": "",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1Y4",
"country": "Canada"
}
}
}
platformAppEmailLogin mutation
Sign in to an app with email
Omit client_code for the normal mobile flow. Superroute resolves the Client from the authenticated Customer membership and returns APP_NOT_ENABLED_FOR_PROVIDER when that provider has not enabled the app. Supplying client_code preserves the existing scoped contract. When the person signs in with their identity (unified identity), the response also carries identity: { choice_required, accounts[] } (additive); platformAppSwitchAccount changes account. A person with two-step verification (or whose client requires it) gets TWO_FACTOR_REQUIRED with data.challenge_id: finish with platformAppTwoFactorVerify. Optional two_factor_device_token: a trusted device skips the code.
Query
mutation PlatformAppEmailLogin($app_code: String!, $client_code: String, $email: String!, $password: String!, $device_id: String!, $device_platform: String!, $app_version: String) {
platformAppEmailLogin(app_code: $app_code, client_code: $client_code, email: $email, password: $password, device_id: $device_id, device_platform: $device_platform, app_version: $app_version)
}
Variables
{
"app_code": "swidget",
"email": "customer@example.com",
"password": "account-password",
"device_id": "0f3d1c43-7f53-4c16-ae90-62a2bc921812",
"device_platform": "android",
"app_version": "1.0.0"
}
Answer
{
"result": true,
"access_token": "passport-access-token",
"token_type": "Bearer"
}
platformAppGuestPickupPoints query
The Client's pickup points without signing in
The Client's smart locker locations (ids `L…`) and pickup point counters (ids `C…`) a visitor may choose, nearest first when latitude and longitude are given, with nothing personal. A pickup point widget's key narrows the list to the kinds the widget keeps. Public.
Query
query ($app_code: String!, $client_code: String!, $latitude: Float, $longitude: Float) {
platformAppGuestPickupPoints(app_code: $app_code, client_code: $client_code, latitude: $latitude, longitude: $longitude)
}
Variables
{
"app_code": "swidget",
"client_code": "k3m9q2",
"latitude": 43.6,
"longitude": -79.4
}
Answer
{
"result": true,
"data": {
"items": [
{
"id": "L12",
"kind": "locker",
"location_id": 12,
"name": "Station locker",
"address": "1 Front St",
"city": "Toronto",
"latitude": 43.6,
"longitude": -79.4,
"distance_km": 1.2
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 1,
"has_more": false
}
}
}
platformAppGuestShipment query
Get a guest shipment
Public; with the order token (REST: header X-Guest-Order-Token) or a contact token of the same contact (90 days). status is pending_payment, paid, placing, placed, expired, cancelled, refunded or refund_required. While unpaid, pay_url and pay_before; once placed, order carries the order's reference, status_group and status_label, the two addresses, packages with tracking numbers and, for a label, label.carrier_name and carrier_tracking_numbers. 404 GUEST_ORDER_NOT_FOUND.
Query
query PlatformAppGuestShipment($app_code: String!, $client_code: String!, $id: Int!, $order_token: String, $contact_token: String, $locale: String) {
platformAppGuestShipment(app_code: $app_code, client_code: $client_code, id: $id, order_token: $order_token, contact_token: $contact_token, locale: $locale)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"id": 42,
"order_token": "k3J9xQ2mV8pL5tR1wZ7cN4bH6dF0gS2aY9eU3iO8"
}
Answer
{
"result": true,
"guest_order": {
"id": 42,
"kind": "self_delivery",
"status": "placed",
"amount": "12.40",
"currency": "CAD",
"contact": "g****@example.com",
"created_at": "2026-10-09T10:00:00-04:00",
"pay_url": null,
"pay_before": null,
"can_cancel": false,
"order": {
"type": "self_delivery",
"ref": "SR41000123",
"status_group": "active",
"status_label": "Out for delivery",
"packages": [
{
"tracking_number": "SR41000123-1"
}
]
}
}
}
platformAppGuestShipmentCancel mutation
Cancel a guest shipment
Public; with the order token (REST: header X-Guest-Order-Token) or a contact token of the same contact. An unpaid shipment is withdrawn and its payment page closes. A created one is cancelled while its order still can be (no pickup yet, label not used; can_cancel tells): the order is cancelled, its charge returned to the Client's attribution customer, the guest refunded in full and told by email or SMS; when the gateway cannot refund on its own the status is refund_required. 409 GUEST_ORDER_NOT_CANCELLABLE when being paid, being created, or no longer cancellable; 404 GUEST_ORDER_NOT_FOUND.
Query
mutation PlatformAppGuestShipmentCancel($app_code: String!, $client_code: String!, $id: Int!, $order_token: String, $contact_token: String, $locale: String) {
platformAppGuestShipmentCancel(app_code: $app_code, client_code: $client_code, id: $id, order_token: $order_token, contact_token: $contact_token, locale: $locale)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"id": 42,
"order_token": "k3J9xQ2mV8pL5tR1wZ7cN4bH6dF0gS2aY9eU3iO8"
}
Answer
{
"result": true,
"guest_order": {
"id": 42,
"kind": "self_delivery",
"status": "cancelled",
"amount": "12.40",
"currency": "CAD",
"contact": "g****@example.com",
"created_at": "2026-10-09T10:00:00-04:00",
"pay_url": null,
"pay_before": null,
"can_cancel": false,
"order": null
}
}
platformAppGuestShipmentLabel query
Get the label of a guest shipment
Public; with the order token (REST: header X-Guest-Order-Token) or a contact token of the same contact. The label PDF of a placed label shipment, base64 in pdf_data. 404 GUEST_ORDER_NOT_FOUND for any other shipment, LABEL_NOT_AVAILABLE while the carrier has not produced it.
Query
query PlatformAppGuestShipmentLabel($app_code: String!, $client_code: String!, $id: Int!, $order_token: String, $contact_token: String, $locale: String) {
platformAppGuestShipmentLabel(app_code: $app_code, client_code: $client_code, id: $id, order_token: $order_token, contact_token: $contact_token, locale: $locale)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"id": 42,
"order_token": "k3J9xQ2mV8pL5tR1wZ7cN4bH6dF0gS2aY9eU3iO8"
}
Answer
{
"result": true,
"format": "pdf",
"tracking_numbers": [
"1Z999AA10123456784"
],
"pdf_data": "JVBERi0xLjQKJ..."
}
platformAppGuestShipmentRate mutation
Price a guest shipment
Public. Prices a local delivery and carrier labels for a guest of a Client that offers guest shipping (capability guest.shipping), as the Client's attribution customer: its price plan, label accounts and service switches. input is the shipment (from_* and to_* name, telephone, address, city, province, postcode, country; packages; signature_option); any other field, such as a customer or a channel, is ignored. Each rate_id is a guest quote: valid for this Client and this exact shipment only, for 30 minutes. A self delivery rate without a price is reported in errors with code NOT_PRICED: a guest pays before the order exists. Carrier quotes are limited to six per minute per caller and Client (429 TOO_MANY_REQUESTS). 404 APP_CONTEXT_NOT_FOUND when the Client does not offer guest shipping in this app.
Query
mutation PlatformAppGuestShipmentRate($app_code: String!, $client_code: String!, $input: Json, $locale: String) {
platformAppGuestShipmentRate(app_code: $app_code, client_code: $client_code, input: $input, locale: $locale)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"input": {
"from_name": "Ann Lee",
"from_telephone": "+14165550100",
"from_address": "1 King St W",
"from_city": "Toronto",
"from_province": "ON",
"from_postcode": "M5H 1A1",
"from_country": "CA",
"to_name": "Bo Chen",
"to_telephone": "+14165550101",
"to_address": "300 Front St W",
"to_city": "Toronto",
"to_province": "ON",
"to_postcode": "M5V 2T6",
"to_country": "CA",
"packages": [
{
"weight": 2,
"length": 30,
"width": 20,
"height": 10,
"weight_unit": 1,
"dimension_unit": 1
}
]
}
}
Answer
{
"result": true,
"rates": [
{
"rate_id": "eyJpdiI6Ikd1ZXN0UXVvdGUi...",
"type": "self_delivery",
"shipping_price": "12.40",
"currency": "CAD"
},
{
"rate_id": "eyJpdiI6Ikd1ZXN0TGFiZWwi...",
"type": "label_service",
"shipping_price": "20.00",
"currency": "CAD",
"carrier_name": "UPS",
"service_name": "Ground",
"transit_days": 2
}
],
"errors": []
}
platformAppGuestShipmentSubmit mutation
Submit a guest shipment for payment
Public; needs a contact token (REST: header X-Guest-Contact-Token). input is the rated shipment, exactly as rated, with rate_id from platformAppGuestShipmentRate, and optionally note and need_pick_up (self delivery). Nothing is created yet: the shipment waits for its payment on the shared payment page (pay_url, gateways only, no wallet) until pay_before. Once paid, the payment is credited to the Client's attribution customer and the order is created through uniorder (local delivery dispatched, label bought); if that fails the payment is refunded. The guest receives the outcome by email or SMS on the verified contact; the link in it carries no token. order_token is shown once and opens this shipment later. 401 GUEST_CONTACT_REQUIRED, 422 RATE_ID_INVALID (expired, another Client, or a different shipment), 429 GUEST_ORDER_LIMIT (per contact per day, per IP address per hour).
Query
mutation PlatformAppGuestShipmentSubmit($app_code: String!, $client_code: String!, $contact_token: String!, $input: Json, $locale: String) {
platformAppGuestShipmentSubmit(app_code: $app_code, client_code: $client_code, contact_token: $contact_token, input: $input, locale: $locale)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"contact_token": "eyJpdiI6IkNvbnRhY3RUb2tlbiIs...",
"input": {
"from_name": "Ann Lee",
"from_telephone": "+14165550100",
"from_address": "1 King St W",
"from_city": "Toronto",
"from_province": "ON",
"from_postcode": "M5H 1A1",
"from_country": "CA",
"to_name": "Bo Chen",
"to_telephone": "+14165550101",
"to_address": "300 Front St W",
"to_city": "Toronto",
"to_province": "ON",
"to_postcode": "M5V 2T6",
"to_country": "CA",
"packages": [
{
"weight": 2,
"length": 30,
"width": 20,
"height": 10,
"weight_unit": 1,
"dimension_unit": 1
}
],
"rate_id": "eyJpdiI6Ikd1ZXN0UXVvdGUi..."
}
}
Answer
{
"result": true,
"order_token": "k3J9xQ2mV8pL5tR1wZ7cN4bH6dF0gS2aY9eU3iO8",
"guest_order": {
"id": 42,
"kind": "self_delivery",
"status": "pending_payment",
"amount": "12.40",
"currency": "CAD",
"contact": "g****@example.com",
"created_at": "2026-10-09T10:00:00-04:00",
"pay_url": "https://ship.example.com/pay/r/3f9c0d6a1b2e4f5a6b7c8d9e0f1a2b3c",
"pay_before": "2026-10-09T10:30:00-04:00",
"can_cancel": true,
"order": null
}
}
platformAppGuestShipments query
List a verified contact's guest shipments
Public; needs a contact token (REST: header X-Guest-Contact-Token). The guest shipments of that contact at this Client from the last 90 days, newest first, at most 50: for a guest on a new device. 401 GUEST_CONTACT_REQUIRED.
Query
query PlatformAppGuestShipments($app_code: String!, $client_code: String!, $contact_token: String!, $locale: String) {
platformAppGuestShipments(app_code: $app_code, client_code: $client_code, contact_token: $contact_token, locale: $locale)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"contact_token": "eyJpdiI6IkNvbnRhY3RUb2tlbiIs..."
}
Answer
{
"result": true,
"items": [
{
"id": 42,
"kind": "self_delivery",
"status": "pending_payment",
"amount": "12.40",
"currency": "CAD",
"contact": "g****@example.com",
"created_at": "2026-10-09T10:00:00-04:00",
"pay_url": "https://ship.example.com/pay/r/3f9c0d6a1b2e4f5a6b7c8d9e0f1a2b3c",
"pay_before": "2026-10-09T10:30:00-04:00",
"can_cancel": true,
"order": null
}
]
}
platformAppGuestTrack query
Track any code (guest)
Universal tracking (docs/APP-UNIVERSAL-TRACKING-DESIGN.md): one code of this Client in, whatever it is — `kind` parcel (tracking number), moving (moving code), storage (storage code), return (return code), shipping (shipping order code) or carrier_package (carrier tracking number). `tracking` is the OTEP public view (as /oteptrack); its `details` (addresses, dates, proof of delivery) and `summary.code` (pickup code) are present once `verified`. Send `answer` (last 4 digits of a phone on the shipment, or its postcode) to verify; `can_verify` says whether that is possible. A storage, return or shipping code is the drop-off code the sender holds, so `summary.code` is shown for those. `wallet` holds absolute Apple (.pkpass) and Google (save link) card links valid 2 h, or null when the Client has wallet cards off or the shipment offers no card. Errors: 404 TRACKING_NOT_FOUND (unknown code), 422 TRACKING_VERIFY_FAILED (wrong answer), 422 TRACKING_INVALID, 429 TRACKING_TOO_MANY. Public, no sign-in. Pickup codes are never answered here.
Query
query PlatformAppGuestTrack($app_code: String!, $client_code: String!, $identifier: String!, $answer: String, $locale: String) {
platformAppGuestTrack(app_code: $app_code, client_code: $client_code, identifier: $identifier, answer: $answer, locale: $locale)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"identifier": "R8K2Q7"
}
Answer
{
"result": true,
"data": {
"kind": "return",
"identifier": "R8K2Q7",
"owned": false,
"verified": false,
"can_verify": true,
"summary": {
"stage": "send",
"status": "awaiting_dropoff",
"reference": "RMA-1042",
"place": null,
"deadline": null,
"code": "R8K2Q7"
},
"tracking": {
"otep_version": "0.1",
"profile": "parcel",
"subject": {
"identifier": "R8K2Q7",
"profile": "parcel"
},
"current_status": "booking_confirmed",
"current_phase": "pre_shipment",
"delivered": false,
"events": [
{
"occurred_at": "2026-10-18T09:12:00-04:00",
"status_code": "information_submitted",
"phase": "pre_shipment",
"location": null
},
{
"occurred_at": "2026-10-18T09:40:00-04:00",
"status_code": "booking_confirmed",
"phase": "pre_shipment",
"location": null
}
]
},
"wallet": {
"apple": "https://track.example.com/wallet-card/en/return_request/1042/apple?code=1&expires=1760800000&signature=...",
"google": "https://track.example.com/wallet-card/en/return_request/1042/google?code=1&expires=1760800000&signature=..."
},
"pickup_id": null
}
}
platformAppGuestVerifyComplete mutation
Check a guest verification code
Public. Checks the code of a challenge and returns a contact token: proof, for 20 minutes and at this Client only, that the guest holds the contact. It is not a sign-in, and a sign-in token is never accepted in its place. Five wrong codes void the challenge. 422 INVALID_VERIFICATION_CODE, VERIFICATION_ATTEMPTS_EXCEEDED or INVALID_VERIFICATION_CHALLENGE.
Query
mutation PlatformAppGuestVerifyComplete($app_code: String!, $client_code: String!, $challenge_id: String!, $code: String!, $locale: String) {
platformAppGuestVerifyComplete(app_code: $app_code, client_code: $client_code, challenge_id: $challenge_id, code: $code, locale: $locale)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"challenge_id": "Qm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2ll",
"code": "482913"
}
Answer
{
"result": true,
"contact_token": "eyJpdiI6IkNvbnRhY3RUb2tlbiIs...",
"channel": "email",
"contact": "g****@example.com",
"expires_in": 1200
}
platformAppGuestVerifyRequest mutation
Send a guest a verification code
Public. Sends a six-digit code to an email address or a phone number, on the channel the Client chose for guest shipping (settings guest.verification_channel; shown in platformAppGuestClients). The answer is the same for any contact. The code expires in 10 minutes; one code per contact and Client per minute (429 GUEST_VERIFICATION_RATE_LIMITED) and 20 per IP address per hour. 422 INVALID_EMAIL or INVALID_PHONE_NUMBER, 503 SMS_SERVICE_NOT_CONFIGURED, EMAIL_DELIVERY_FAILED or SMS_DELIVERY_FAILED.
Query
mutation PlatformAppGuestVerifyRequest($app_code: String!, $client_code: String!, $contact: String!, $locale: String) {
platformAppGuestVerifyRequest(app_code: $app_code, client_code: $client_code, contact: $contact, locale: $locale)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"contact": "guest@example.com"
}
Answer
{
"result": true,
"message": "A verification code was sent.",
"challenge_id": "Qm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2ll",
"channel": "email",
"contact": "g****@example.com",
"expires_in": 600,
"resend_after": 60
}
platformAppIdentityJoin mutation
Join this client with the identity
Unified identity: an email, Google or Apple sign-in in a client's own app by a person whose identity holds no account of this client yet answers 409 IDENTITY_JOIN_REQUIRED with data.join_token (10 minutes, same device), data.fields (required) and data.values (only the name prefilled). Send the client's registration details here. The client's app registration decides: 403 IDENTITY_JOIN_CLOSED when it takes no registrations, 202 pending_review when it reviews new accounts, otherwise the session (or TWO_FACTOR_REQUIRED). Nothing is copied from the person's accounts at other clients.
Query
mutation PlatformAppIdentityJoin($app_code: String!, $client_code: String!, $join_token: String!, $name: String, $telephone: String, $address: String, $address_2: String, $city: String, $province: String, $postcode: String, $country: String, $device_id: String!, $device_platform: String!, $app_version: String) {
platformAppIdentityJoin(app_code: $app_code, client_code: $client_code, join_token: $join_token, name: $name, telephone: $telephone, address: $address, address_2: $address_2, city: $city, province: $province, postcode: $postcode, country: $country, device_id: $device_id, device_platform: $device_platform, app_version: $app_version)
}
Variables
{
"app_code": "swidget",
"client_code": "q8w2e6r1t5y9u3i7o0p4a2sd",
"join_token": "t4Kp...",
"name": "",
"telephone": "",
"address": "",
"address_2": "",
"city": "",
"province": "",
"postcode": "",
"country": "",
"device_id": "0f3d1c43-7f53-4c16-ae90-62a2bc921812",
"device_platform": "ios",
"app_version": "1.0.0"
}
Answer
{
"result": true,
"pending_review": true
}
platformAppPublicQuote mutation
Quote a line without signing in
Public. The same service and per-Client settings as the /quotes page, so the app and the page quote alike; every quote is recorded. line is one of the schema's lines (delivery, pickup, p2p, express, shipping, label, moving, storage); input carries that line's fields (the REST body). A signed-in customer of the Client is quoted on their own account, anyone else as an anonymous visitor. Carrier lines (label, express) are limited to six per minute per caller and Client (HTTP 429, not_covered_reason too_many). Answers result true with quote, result false with not_covered_reason, 422 with errors, or 404 APP_CONTEXT_NOT_FOUND when the Client does not offer quotes in this app.
Query
mutation PlatformAppPublicQuote($app_code: String!, $client_code: String!, $line: String!, $input: Json) {
platformAppPublicQuote(app_code: $app_code, client_code: $client_code, line: $line, input: $input)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"line": "delivery",
"input": {
"to_postcode": "M5V 2T6",
"weight": 2.4
}
}
Answer
{
"result": true,
"quote": {
"line": "delivery",
"currency": "CAD",
"total": 12.4,
"token": "abcdefghijabcdefghijabcdefghijabcdefghij",
"valid_until": "2026-10-15T12:00:00-04:00",
"drive_minutes": 18,
"drive_from": "Toronto Depot"
}
}
platformAppPublicQuoteSchema query
The quote form a client offers without signing in
Public. The lines and their fields, the same schema the /quotes page renders from (PublicQuoteService). A signed-in customer of the Client sees only what their own account may use. 404 APP_CONTEXT_NOT_FOUND when the Client does not offer quotes in this app.
Query
query PlatformAppPublicQuoteSchema($app_code: String!, $client_code: String!) {
platformAppPublicQuoteSchema(app_code: $app_code, client_code: $client_code)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab"
}
Answer
{
"result": true,
"schema": {
"business_name": "Northstar Courier",
"currency": "CAD",
"valid_days": 7,
"lines": []
}
}
platformAppRegisterCustomer mutation
Register a Client customer
Creates the normal Superroute User and Customer records and either issues an app session or returns a pending-review result. Two shapes, chosen by the Client's registration.require_phone_verification setting. When it is on, send challenge_id and code from platformAppRequestRegistrationCode and the phone is recorded as verified. When it is off, send phone instead and the number is stored unverified. A challenge is always honoured if supplied. For an app whose Clients choose the verification channel (the SuperRoute customer app), a challenge is always required and the Client's channel decides: phone as above, or email — send channel email with challenge_id and code from platformAppRequestRegistrationEmailCode, the same email, and phone (a contact number, stored unverified and never compared with other accounts); the answer then also carries email_verified true. channel defaults to phone. Optionally send google_id_token and/or apple_id_token (with apple_authorization_code and apple_name) to link the Google / Apple account the person signed in with; the answer lists what was linked in linked_providers. The address is mandatory in both shapes. The new account starts from the customer template the Client selected for this app, or from the business default template when it selected none. challenge_id, code and country stay nullable in the schema so an existing query document keeps parsing.
Query
mutation PlatformAppRegisterCustomer($app_code: String!, $client_code: String!, $challenge_id: String, $code: String, $phone: String, $name: String!, $email: String!, $password: String!, $password_confirmation: String!, $address: String, $address_2: String, $city: String, $province: String, $postcode: String, $country: String, $device_id: String!, $device_platform: String!) {
platformAppRegisterCustomer(app_code: $app_code, client_code: $client_code, challenge_id: $challenge_id, code: $code, phone: $phone, name: $name, email: $email, password: $password, password_confirmation: $password_confirmation, address: $address, address_2: $address_2, city: $city, province: $province, postcode: $postcode, country: $country, device_id: $device_id, device_platform: $device_platform)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"challenge_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"code": "123456",
"name": "MiBox Customer",
"email": "customer@example.com",
"password": "account-password",
"password_confirmation": "account-password",
"device_id": "0f3d1c43-7f53-4c16-ae90-62a2bc921812",
"device_platform": "ios",
"phone": "+14165550123",
"address": "123 King Street West",
"address_2": "Unit 5",
"city": "Toronto",
"province": "ON",
"postcode": "M5H 1A1",
"country": "Canada"
}
Answer
{
"result": true,
"phone_verified": true,
"requires_approval": false,
"access_token": "passport-access-token",
"token_type": "Bearer",
"linked_providers": []
}
platformAppRegistrationDetails query
Registration details still to fill in
Unified identity: a customer a client's staff created without the registration details (the client has identity sign-in on). Until they are filled in, every other API call of the account (REST, GraphQL, MCP) answers 403 {"error": "registration_incomplete", "code": "REGISTRATION_INCOMPLETE"}; generic API logins refuse with the same body, and an app login still issues the session with registration_incomplete: true so the app can show this form.
Query
query PlatformAppRegistrationDetails($app_code: String!, $client_code: String!) {
platformAppRegistrationDetails(app_code: $app_code, client_code: $client_code)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab"
}
Answer
{
"result": true,
"data": {
"complete": false,
"fields": [
"name",
"telephone",
"address",
"city",
"province",
"postcode",
"country"
],
"missing": [
"telephone",
"address"
],
"values": {
"name": "Ann Lee",
"telephone": "",
"address": "",
"address_2": "",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1Y4",
"country": "Canada"
}
}
}
platformAppRequestRegistrationEmailCode mutation
Request a registration email verification code
For an app whose Clients choose the sign-up verification channel (the SuperRoute customer app, app_code customer) and a Client that chose email. Creates a ten-minute App- and Client-scoped challenge and emails a six-digit code. The answer is the same whether or not the address already has an account: an address that has one is sent no code, only a note that it is registered, so the challenge cannot be completed. Refused with EMAIL_VERIFICATION_DISABLED for a Client on the phone channel and for apps without channels (MiBox), REGISTRATION_DISABLED when sign-up is closed, EMAIL_VERIFICATION_RATE_LIMITED within 60 seconds of the previous request for the same address.
Query
mutation PlatformAppRequestRegistrationEmailCode($app_code: String!, $client_code: String!, $email: String!, $locale: String) {
platformAppRequestRegistrationEmailCode(app_code: $app_code, client_code: $client_code, email: $email, locale: $locale)
}
Variables
{
"app_code": "swidget",
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"email": "jane.doe@example.com",
"locale": "en"
}
Answer
{
"result": true,
"message": "A verification email was sent.",
"challenge_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"email": "j*******@example.com",
"expires_in": 600,
"resend_after": 60
}
platformAppSwidgetConfig query
A website widget's configuration (sWidget)
The configuration of the website widget its key names (header `X-App-Key`, docs/SWIDGET-DESIGN.md W28, W32): kind, options, branding, language, the Client's country and where every later call goes (`api`). With `app_code` `swidget` every operation needs the widget key and a browser `Origin` that is one of the widget's websites, and runs only the operations of the widget's kind. Refused with `invalid_app_key`, `origin_not_allowed`, `invalid_client`, `widget_call_not_allowed`, `registration_disabled` (sign-up operations) or `too_many_requests`.
Query
query ($app_code: String!, $client_code: String!) {
platformAppSwidgetConfig(app_code: $app_code, client_code: $client_code)
}
Variables
{
"app_code": "swidget",
"client_code": "k3m9q2"
}
Answer
{
"result": true,
"data": {
"widget": {
"id": "k2m9q7x4b1c8",
"type": "tracking",
"name": "Track a parcel",
"locale": null,
"options": []
},
"branding": {
"display_name": "Acme Delivery",
"primary_color": "#E02020",
"logo_url": null
},
"country": "CA",
"app_code": "swidget",
"client_code": "k3m9q2",
"api": {
"base": "https://api.superroute.ca",
"app_code": "swidget",
"client_code": "k3m9q2"
}
}
}
platformAppTwoFactorResend mutation
Send the code of a held sign-in again
For method email or sms; returns a new challenge_id. Errors: TWO_FACTOR_CHALLENGE_INVALID, TWO_FACTOR_RESEND_TOO_SOON, TWO_FACTOR_DELIVERY_FAILED.
Query
mutation PlatformAppTwoFactorResend($app_code: String!, $challenge_id: String!, $device_id: String!, $device_platform: String!, $app_version: String) {
platformAppTwoFactorResend(app_code: $app_code, challenge_id: $challenge_id, device_id: $device_id, device_platform: $device_platform, app_version: $app_version)
}
Variables
{
"app_code": "swidget",
"challenge_id": "r9Qm...",
"device_id": "0f3d1c43-7f53-4c16-ae90-62a2bc921812",
"device_platform": "ios",
"app_version": "1.0.0"
}
Answer
{
"result": true,
"data": {
"challenge_id": "x7Lp...",
"method": "email",
"target": "a***@example.com",
"expires_in": 600
}
}
platformAppTwoFactorVerify mutation
Finish a sign-in held at its second factor
Unified identity: a sign-in that answered TWO_FACTOR_REQUIRED (data.challenge_id) is finished with the code, from the same device. trust_device returns two_factor_device_token; pass it as two_factor_device_token to the login mutations to skip the code for 30 days. Errors: TWO_FACTOR_CODE_INVALID, TWO_FACTOR_CHALLENGE_INVALID, TWO_FACTOR_TOO_MANY_ATTEMPTS.
Query
mutation PlatformAppTwoFactorVerify($app_code: String!, $challenge_id: String!, $code: String!, $trust_device: Boolean, $device_id: String!, $device_platform: String!, $app_version: String) {
platformAppTwoFactorVerify(app_code: $app_code, challenge_id: $challenge_id, code: $code, trust_device: $trust_device, device_id: $device_id, device_platform: $device_platform, app_version: $app_version)
}
Variables
{
"app_code": "swidget",
"challenge_id": "r9Qm...",
"code": "123456",
"trust_device": true,
"device_id": "0f3d1c43-7f53-4c16-ae90-62a2bc921812",
"device_platform": "ios",
"app_version": "1.0.0"
}
Answer
{
"result": true,
"access_token": "passport-access-token",
"token_type": "Bearer",
"two_factor_device_token": "k2v..."
}