GraphQL reference
271 operations at /app/graphql, in their own schema.
Account
partnerAppMe
query
REST twin: GET /app/v1/me
The app, the service provider, the person and the token's capabilities.
query PartnerAppMe {
partnerAppMe {
app_code
audience
client_code
user_id
user_name
scopes
}
}
Variables
{}
Response example
{
"data": {
"partnerAppMe": {
"app_code": "p_acme_parcels",
"audience": "customer",
"client_code": "AB12CD34",
"user_id": 700,
"user_name": "Customer",
"scopes": [
"orders.shipping",
"tracking"
]
}
}
}
platformAppPrivacy
query
REST twin: GET /app/v1/privacy
Uses the same shared Platform App application service and policy as the REST operation. data.account.can_request_deletion is false for an account the app must not offer to close (staff and sub-accounts).
query PlatformAppPrivacy {
platformAppPrivacy
}
Variables
{}
Response example
{
"result": true,
"data": []
}
platformAppRequestAccountDeletion
mutation
REST twin: POST /app/v1/privacy/account-deletion
Opens a reviewed account closure through the same shared service as REST. The account is suspended immediately — sign-in and API access both stop and every live token is revoked — and the business is notified to approve or reject it. The customer is emailed a withdrawal link, and withdrawal_token is returned here once so the app can offer the same withdrawal itself. Backward compatible: status keeps its original values (pending, cancelled, completed) and gained one new value (rejected); the added fields are additive.
mutation PlatformAppRequestAccountDeletion($confirmation: Boolean!, $reason: String) {
platformAppRequestAccountDeletion(confirmation: $confirmation, reason: $reason)
}
Variables
{
"confirmation": true,
"reason": "No longer using MiBox"
}
Response example
{
"result": true,
"message": "Account deletion was requested and all MiBox sessions were signed out.",
"data": {
"id": 42,
"status": "pending",
"requested_at": "2026-09-05T10:00:00+00:00",
"cancelled_at": null,
"completed_at": null,
"handled_at": null,
"reason": "No longer using MiBox",
"decision_reason": null,
"requires_review": true,
"withdrawal_token": "one-shot-withdrawal-token"
}
}
platformAppCancelAccountDeletion
mutation
REST twin: DELETE /app/v1/privacy/account-deletion
Withdraws the pending closure request while a session still exists. Once the request has taken the session away, use platformAppWithdrawAccountClosure with the one-shot token instead.
mutation PlatformAppCancelAccountDeletion {
platformAppCancelAccountDeletion
}
Variables
{}
Response example
{
"result": true,
"data": []
}
platformAppVerifyPhone
mutation
REST twin: POST /app/v1/privacy/phone/verify
Uses the same verified phone identity service and conflict rules as the REST operation.
mutation PlatformAppVerifyPhone($challenge_id: String!, $code: String!, $locale: String) {
platformAppVerifyPhone(challenge_id: $challenge_id, code: $code, locale: $locale)
}
Variables
{
"challenge_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"code": "123456",
"locale": "en"
}
Response example
{
"result": true,
"message": "Your phone number was verified.",
"data": {
"verified_phone": true,
"phone_e164": "+14****0100"
}
}
platformAppRequestPhoneVerification
mutation
REST twin: POST /app/v1/privacy/phone/request
Uses the same authenticated customer-bound SMS challenge service as the REST operation.
mutation PlatformAppRequestPhoneVerification($phone: String!, $locale: String) {
platformAppRequestPhoneVerification(phone: $phone, locale: $locale)
}
Variables
{
"phone": "+14165550100",
"locale": "en"
}
Response example
{
"result": true,
"message": "A verification code was sent.",
"challenge_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"phone_e164": "+14****0100",
"expires_in": 300,
"resend_after": 60
}
platformAppHome
query
REST twin: GET /app/v1/home
The app home screen in one call. Each section comes from the service behind its own list endpoint and has that endpoint's shape ({"items": [...]}; nearby_locker is one location). pickups: ready or partly collected; returns: approved, holding a code, not dropped off; luggage: current rentals; handovers: open deals (from the deals list, under items); nearby_locker: the nearest favourite, else the nearest (origin as on locker-locations: latitude/longitude or origin_*); recent_orders: the latest orders of every type. A section is null when the Client does not run its service, and null with its code in errors when its service refused or failed; one section failing never fails the call.
query PlatformAppHome($latitude: Float, $longitude: Float) {
platformAppHome(latitude: $latitude, longitude: $longitude)
}
Variables
{
"latitude": 45.5017,
"longitude": -73.5673
}
Response example
{
"result": true,
"data": {
"pickups": {
"items": [
{
"id": 72,
"delivery_number": "PLD-72",
"pickup_status": "ready",
"pickup_code": "482913",
"remaining_count": 1
}
]
},
"returns": {
"items": [
{
"id": 12,
"status": 1,
"return_code": "R7K2P9",
"custom_channel_name": "Amazon"
}
]
},
"luggage": null,
"handovers": null,
"nearby_locker": {
"id": 81,
"name": "Downtown locker",
"distance_km": 0.4,
"is_favorite": true
},
"recent_orders": {
"items": [
{
"type": "shipping",
"id": 710,
"status": "2",
"status_name": "Confirmed",
"reference": "SHIP-710"
}
]
},
"errors": []
}
}
platformAppRegistrationDetails
query
REST twin: GET /app/v1/registration-details
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 PlatformAppRegistrationDetails {
platformAppRegistrationDetails
}
Variables
{}
Response example
{
"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"
}
}
}
platformAppNotifications
query
REST twin: GET /app/v1/notifications
Uses the same shared Platform App application service and policy as the REST operation. Needs the notifications.in_app capability on the token.
query PlatformAppNotifications($filter: String, $category: String, $page: Int, $per_page: Int) {
platformAppNotifications(filter: $filter, category: $category, page: $page, per_page: $per_page)
}
Variables
{
"filter": "unread",
"category": "pickup",
"page": 1,
"per_page": 20
}
Response example
{
"result": true,
"data": []
}
platformAppMarkNotificationRead
mutation
REST twin: POST /app/v1/notifications/{notificationId}/read
Uses the same shared Platform App application service and policy as the REST operation. Needs the notifications.in_app capability on the token.
mutation PlatformAppMarkNotificationRead($notification_id: String!) {
platformAppMarkNotificationRead(notification_id: $notification_id)
}
Variables
{
"notification_id": "550e8400-e29b-41d4-a716-446655440000"
}
Response example
{
"result": true,
"data": []
}
platformAppMarkAllNotificationsRead
mutation
REST twin: POST /app/v1/notifications/read-all
Uses the same shared Platform App application service and policy as the REST operation. Needs the notifications.in_app capability on the token.
mutation PlatformAppMarkAllNotificationsRead {
platformAppMarkAllNotificationsRead
}
Variables
{}
Response example
{
"result": true,
"data": []
}
platformAppNotificationPreferences
query
REST twin: GET /app/v1/notifications/preferences
Uses the same shared Platform App application service and policy as the REST operation.
query PlatformAppNotificationPreferences {
platformAppNotificationPreferences
}
Variables
{}
Response example
{
"result": true,
"data": []
}
platformAppUpdateNotificationPreferences
mutation
REST twin: PUT /app/v1/notifications/preferences
Uses the same shared Platform App application service and policy as the REST operation.
mutation PlatformAppUpdateNotificationPreferences($in_app: Boolean!, $push: Boolean!, $email: Boolean!, $sms: Boolean!, $pickup: Boolean!, $orders: Boolean!, $returns: Boolean!, $wallet: Boolean!, $security: Boolean!) {
platformAppUpdateNotificationPreferences(in_app: $in_app, push: $push, email: $email, sms: $sms, pickup: $pickup, orders: $orders, returns: $returns, wallet: $wallet, security: $security)
}
Variables
{
"in_app": true,
"push": true,
"email": false,
"sms": false,
"pickup": true,
"orders": true,
"returns": true,
"wallet": true,
"security": true
}
Response example
{
"result": true,
"data": []
}
platformAppCompleteRegistration
mutation
REST twin: POST /app/v1/registration-details
Required: the fields listed by platformAppRegistrationDetails. Once complete, the account's API works again.
mutation PlatformAppCompleteRegistration($name: String, $telephone: String, $address: String, $address_2: String, $city: String, $province: String, $postcode: String, $country: String) {
platformAppCompleteRegistration(name: $name, telephone: $telephone, address: $address, address_2: $address_2, city: $city, province: $province, postcode: $postcode, country: $country)
}
Variables
{
"name": "",
"telephone": "",
"address": "",
"address_2": "",
"city": "",
"province": "",
"postcode": "",
"country": ""
}
Response example
{
"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"
}
}
}
platformAppLinkResolve
query
REST twin: GET /app/v1/links/resolve
What a scanned link of the client opens in the app: store, store_table, store_category, product, mall, mall_category, group_buy or unknown. Accepts the client's white-label host and the platform host (mall by /m/{code}); kind is store | store_table | store_category (slug, category_id: a category page of the multi-page style, added with docs/STORE-STYLES-DESIGN.md P2) | product | mall (page home|stores|search|group_buy) | mall_category | group_buy (campaign_code, leader_code) | unknown. 404 APP_CONTEXT_NOT_FOUND for an unknown app/client (guest). Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
query PlatformAppLinkResolve($locale: String, $url: String!) {
platformAppLinkResolve(locale: $locale, url: $url)
}
Variables
{
"locale": "en",
"url": "https://shop.example.com/s/bistro/t/5"
}
Response example
{
"result": true,
"data": {
"kind": "store_table",
"slug": "bistro",
"table_code": "T5",
"campaign_code": null,
"leader_code": null,
"category_id": null,
"product_id": null,
"page": null
}
}
platformAppNotificationRealtime
query
REST twin: GET /app/v1/notifications/realtime
Returns short-lived subscribe-only credentials scoped to the authenticated app assignment, customer and device. API refresh remains the fallback. Each message carries v, event_id, type, category, entity_type, entity_id, deep_link and created_at, and since the customer app also title, body (the notification text) and alert (true when the user's push channel is on). Needs notifications.in_app on the token.
query PlatformAppNotificationRealtime($device_id: String!) {
platformAppNotificationRealtime(device_id: $device_id)
}
Variables
{
"device_id": "ios-device-1"
}
Response example
{
"result": true,
"data": {
"enabled": true,
"mqtt_url": "wss://api.superroute.ca/mqtt",
"client_id": "pa-a1b2c3d4e5f6g7h8i9j0",
"username": "app-1-assignment-10-user-100-device-a1b2c3d4e5f6g7h8",
"password": "short-lived-jwt",
"topic": "sr/apps/1/assignments/10/users/100/notifications",
"qos": 1,
"contract_version": 1,
"expires_at": "2026-07-25T18:00:00Z"
}
}
platformAppRegisterDevice
mutation
REST twin: POST /app/v1/devices
Uses the same shared Platform App application service and policy as the REST operation. Additive: live_pickup_enabled (the lock-screen pickup code switch; omitted keeps the device's value, on by default) and live_activity_start_token (iOS Live Activity push-to-start token). Needs notifications.push on the token. Pushes go out with the APNs key and FCM service account uploaded for the app; Live Activity and lock-screen pickup fields are ignored.
mutation PlatformAppRegisterDevice($push_token: String!, $device_id: String!, $device_platform: String!, $preferred_locale: String, $app_version: String) {
platformAppRegisterDevice(push_token: $push_token, device_id: $device_id, device_platform: $device_platform, preferred_locale: $preferred_locale, app_version: $app_version)
}
Variables
{
"push_token": "fcm-token",
"device_id": "device-uuid",
"device_platform": "android",
"preferred_locale": "en",
"app_version": "1.0.0"
}
Response example
{
"result": true,
"data": []
}
platformAppRevokeDevice
mutation
REST twin: DELETE /app/v1/devices/{deviceId}
Uses the same shared Platform App application service and policy as the REST operation. Needs notifications.push on the token. Pushes go out with the APNs key and FCM service account uploaded for the app; Live Activity and lock-screen pickup fields are ignored.
mutation PlatformAppRevokeDevice($device_id: String!) {
platformAppRevokeDevice(device_id: $device_id)
}
Variables
{
"device_id": "device-uuid"
}
Response example
{
"result": true,
"data": []
}
partnerAppSessions
query
REST twin: GET /app/v1/sessions
The person's sign-ins to this app at this client, one per token family, newest first.
query { partnerAppSessions }
Variables
{}
Response example
{
"data": {
"partnerAppSessions": {
"result": true,
"data": {
"items": [
{
"id": "01JBX8V6Q4K2N7R9T3W5Y8Z1A2",
"device_id": "ios-7f3a",
"auth_method": "password",
"current": true,
"active": true,
"last_seen_at": "2026-10-28T14:05:00-04:00",
"created_at": "2026-10-20T09:12:00-04:00"
}
]
}
}
}
}
partnerAppRevokeSession
mutation
REST twin: DELETE /app/v1/sessions/{sessionId}
End one of the person's sign-ins to this app (every token of it). Another app's or person's answers result false.
mutation ($session_id: String!) { partnerAppRevokeSession(session_id: $session_id) }
Variables
{
"session_id": "01JBX8V6Q4K2N7R9T3W5Y8Z1A2"
}
Response example
{
"data": {
"partnerAppRevokeSession": {
"result": true
}
}
}
Wallet
platformAppWallet
query
REST twin: GET /app/v1/wallet
Returns the existing Superroute balance, credit allowance, available spending amount, online-payment policy and tax mode for the authenticated customer and selected Client. Needs the wallet.balance capability on the token.
query PlatformAppWallet($locale: String) {
platformAppWallet(locale: $locale)
}
Variables
{
"locale": "en"
}
Response example
{
"result": true,
"data": {
"currency": "CAD",
"balance": "-25.00",
"credit_limit": "100.00",
"credit_remaining": "75.00",
"available_to_spend": "75.00"
}
}
platformAppWalletTransactions
query
REST twin: GET /app/v1/wallet/transactions
Returns existing Client-scoped transactions with matching payment metadata and paginated filters. Needs the wallet.transactions capability on the token.
query PlatformAppWalletTransactions($category: String, $status: Int, $transaction_type: Int, $per_page: Int, $page: Int) {
platformAppWalletTransactions(category: $category, status: $status, transaction_type: $transaction_type, per_page: $per_page, page: $page)
}
Variables
{
"category": "payment",
"per_page": 20,
"page": 1
}
Response example
{
"result": true,
"data": {
"items": [
{
"id": 501,
"category": "payment",
"balance_effect": "50.00",
"direction": "credit"
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 1
}
}
}
platformAppWalletPaymentMethods
query
REST twin: GET /app/v1/wallet/payment-methods
Returns configured App methods and masked saved instruments without gateway credentials. Needs the wallet.payment_methods capability on the token.
query PlatformAppWalletPaymentMethods($locale: String) {
platformAppWalletPaymentMethods(locale: $locale)
}
Variables
{
"locale": "en"
}
Response example
{
"result": true,
"data": {
"configured_methods": [
{
"id": "alphapay:wechat_pay",
"display_name": "WeChat Pay",
"supports_top_up": true
}
],
"saved_methods": [
{
"id": 701,
"brand": "visa",
"last_four": "4242"
}
]
}
}
platformAppWalletTaxPreview
mutation
REST twin: POST /app/v1/wallet/tax-preview
Uses the Client's existing tax zones and customer tax mode without writing a transaction. Needs the wallet.tax capability on the token.
mutation PlatformAppWalletTaxPreview($amount: Float!, $country: String, $province: String, $postcode: String) {
platformAppWalletTaxPreview(amount: $amount, country: $country, province: $province, postcode: $postcode)
}
Variables
{
"amount": 100,
"country": "Canada",
"province": "Ontario",
"postcode": "M5V 2T6"
}
Response example
{
"result": true,
"data": {
"subtotal": "100.00",
"total_tax": "13.00",
"total": "113.00",
"tax_zone_id": 20
}
}
platformAppWalletCreateTopUp
mutation
REST twin: POST /app/v1/wallet/top-ups
Creates a payment through a Client-configured and App-approved gateway. AlphaPay and GlobePay settle through their own verified webhooks; every other configured gateway (Stripe cards, Apple Pay and Google Pay through the Stripe native sheet, PayPal, ...) is collected through the shared payment layer and returns `next_action.type = client_secret` (with `publishable_key` and `merchant_country`) or `external_redirect`, plus `payment_request_token` and `payment_page_url`. Needs the wallet.top_up capability on the token.
mutation PlatformAppWalletCreateTopUp($payment_method_id: String!, $amount: Float!, $idempotency_key: String!, $locale: String) {
platformAppWalletCreateTopUp(payment_method_id: $payment_method_id, amount: $amount, idempotency_key: $idempotency_key, locale: $locale)
}
Variables
{
"payment_method_id": "stripe:card",
"amount": 50,
"idempotency_key": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"locale": "en"
}
Response example
{
"result": true,
"code": "OK",
"message": null,
"data": {
"id": 7001,
"status": "pending",
"gateway": "stripe",
"payment_method": "card",
"amount": "50.00",
"currency": "CAD",
"external_order_id": "AP_100_20260927120000_A1B2C3",
"gateway_payment_id": "pi_3QxampleIntent",
"transaction_id": null,
"next_action": {
"type": "client_secret",
"client_secret": "pi_3QxampleIntent_secret_abc",
"publishable_key": "pk_live_example",
"merchant_country": "CA",
"payment_request_token": "9f2c41ab7d6e40518c3a0b17e5d94a26",
"payment_page_url": "https://admin.example.com/pay/r/9f2c41ab7d6e40518c3a0b17e5d94a26"
},
"created_at": "2026-09-27T12:00:00-04:00",
"updated_at": "2026-09-27T12:00:00-04:00"
}
}
platformAppWalletTopUpStatus
query
REST twin: GET /app/v1/wallet/top-ups/{operationId}
Returns the current state of a top-up operation. While the operation is pending, a poll settles it when possible: AlphaPay and GlobePay operations are matched against the payment their webhook posted; every other gateway is asked directly through its payment request (Stripe, PayPal, ...), so a lost webhook never leaves a paid top-up pending. A payment request that expired unpaid moves the operation to `failed` with an explanatory message; no credit is applied. `status` is one of `creating`, `pending`, `succeeded`, `failed`, `refunded` (credit reversed after a refund). Needs the wallet.top_up capability on the token.
query PlatformAppWalletTopUpStatus($operation_id: Int!) {
platformAppWalletTopUpStatus(operation_id: $operation_id)
}
Variables
{
"operation_id": 7001
}
Response example
{
"result": true,
"code": "OK",
"message": null,
"data": {
"id": 7001,
"status": "succeeded",
"gateway": "stripe",
"payment_method": "card",
"amount": "50.00",
"currency": "CAD",
"external_order_id": "AP_100_20260927120000_A1B2C3",
"gateway_payment_id": "pi_3QxampleIntent",
"transaction_id": 88001,
"next_action": {
"type": "client_secret",
"client_secret": "pi_3QxampleIntent_secret_abc",
"publishable_key": "pk_live_example",
"merchant_country": "CA",
"payment_request_token": "9f2c41ab7d6e40518c3a0b17e5d94a26",
"payment_page_url": "https://admin.example.com/pay/r/9f2c41ab7d6e40518c3a0b17e5d94a26"
},
"created_at": "2026-09-27T12:00:00-04:00",
"updated_at": "2026-09-27T12:03:10-04:00"
}
}
Orders
platformAppOrders
query
REST twin: GET /app/v1/orders
Normalized view over existing shipping, storage, return, moving, local delivery and label records. Additive on every item: status_group (pending_payment | active | completed | exception | cancelled) and status_label, the status name in the request language; the raw status is unchanged and is what the status filter matches.
query PlatformAppOrders($order_type: String, $status: String, $search: String, $per_page: Int, $page: Int) {
platformAppOrders(order_type: $order_type, status: $status, search: $search, per_page: $per_page, page: $page)
}
Variables
{
"order_type": "all",
"per_page": 20,
"page": 1
}
Response example
{
"result": true,
"data": {
"items": [
{
"type": "shipping",
"id": 710,
"status": 1,
"reference": "TRACK-001",
"status_group": "active",
"status_label": "Confirmed"
}
]
}
}
platformAppOrder
query
REST twin: GET /app/v1/orders/{orderType}/{orderId}
Routes to the existing shipping, storage or return detail service. Additive for shipping orders: data.tracking_url, the public tracking page in the request language (null until the order has a tracking number). Additive: `wallet` — absolute Apple (.pkpass) and Google (save link) card links valid 2 h, or null when the Client has wallet cards off (docs/APP-UNIVERSAL-TRACKING-DESIGN.md UT7).
query PlatformAppOrder($order_type: String!, $order_id: Int!) {
platformAppOrder(order_type: $order_type, order_id: $order_id)
}
Variables
{
"order_type": "shipping",
"order_id": 710
}
Response example
{
"result": true,
"data": {
"type": "shipping",
"order": {
"id": 710
}
}
}
platformAppReturns
query
REST twin: GET /app/v1/returns
Lists existing customer-owned ReturnRequest records. Needs the locker.return capability on the token.
query PlatformAppReturns($status: Int, $per_page: Int, $page: Int) {
platformAppReturns(status: $status, per_page: $per_page, page: $page)
}
Variables
{
"per_page": 20,
"page": 1
}
Response example
{
"result": true,
"data": {
"items": [
{
"id": 901,
"status": 1,
"return_code": "RTN-TEST"
}
]
}
}
platformAppReturn
query
REST twin: GET /app/v1/returns/{returnId}
Shows an existing customer-owned ReturnRequest. Additive: `wallet` — absolute Apple (.pkpass) and Google (save link) card links valid 2 h, or null when the Client has wallet cards off (docs/APP-UNIVERSAL-TRACKING-DESIGN.md UT7). Needs the locker.return capability on the token.
query PlatformAppReturn($return_id: Int!) {
platformAppReturn(return_id: $return_id)
}
Variables
{
"return_id": 901
}
Response example
{
"result": true,
"data": {
"id": 901,
"status": 1,
"return_code": "RTN-TEST"
}
}
platformAppCreateReturn
mutation
REST twin: POST /app/v1/returns
Creates the normal ReturnRequest through the selected Client’s return-center configuration, under the same rules as the customer web form: return_type 1-4 (self-paid, free return, own label, return barcode), return_channel_id as a listed id or "other" (self-paid only, with custom_channel_name and a typed return address), a label upload as self_label_file_base64 + self_label_file_name for types 3 and 4, and form_data for a channel's bound form. See the REST endpoint for the field list and error codes. return_channel_id: required for self-paid and free returns; for an own label (type 3) or a return barcode (type 4) it is optional and names the platform the return is for — one of the customer's channels whose allowed_return_types include that type. With it, that channel's fee is charged and the channel decides whether a return reason and a pickup address are asked; without it, the customer's channels decide together as before. A return barcode naming a channel follows that channel: self_label_file is required, optional or ignored per its return_code_upload_mode (an own label always needs its file), reference_number is kept (and required) when the channel asks for one, and form_data answers the channel's bound form. pickup_telephone and email are both optional: an empty one falls back to the account's phone / email, and PHONE_OR_EMAIL_REQUIRED is returned only when neither exists. Once the return holds a return code the customer is emailed the code, its QR code and a status link, using the Client's "Return Confirmation" email template or the built-in wording. input.coupon_code: a coupon for the channel's fee; a priced channel charges the fee to the customer's balance in the same step, refusing with COUPON_INVALID or INSUFFICIENT_BALANCE (errors amount, balance, shortfall). Whether the return waits for approval follows the channel's approval_mode (1 only free returns, 2 all, 3 none). input.home_pickup (optional boolean) asks for the parcel to be picked up at the pickup address: the channel must offer it (home_pickup not null in the configuration) or HOME_PICKUP_NOT_OFFERED is returned; pickup_address_1, pickup_city, pickup_country and pickup_postcode are then required whatever the channel's pickup_address_mode; an address that cannot be served returns HOME_PICKUP_UNAVAILABLE (errors home_pickup = message, reason = disabled, address_required, out_of_area or not_priced). The total (return fee after its promotion plus the pickup fee) is charged to the balance. Once the return is approved a pickup order is created and its id is return_order_id. The return carries home_pickup (boolean), and fee carries pickup_fee (the pickup part of charged, null without a pickup). Needs the locker.return capability on the token.
mutation PlatformAppCreateReturn($input: Json!) {
platformAppCreateReturn(input: $input)
}
Variables
{
"input": {
"return_type": 1,
"return_channel_id": 4,
"return_address_id": 6,
"return_reason_id": 3,
"pickup_name": "MiBox Customer",
"pickup_address_1": "100 King Street West",
"pickup_city": "Toronto",
"pickup_province": "ON",
"pickup_country": "CA",
"pickup_postcode": "M5X 1A9",
"pickup_telephone": "+14165550123",
"return_description": "Package is unopened",
"home_pickup": true
}
}
Response example
{
"result": true,
"data": {
"id": 901,
"status": 1,
"return_code": "RTN-TEST",
"return_order_id": 55012,
"home_pickup": true,
"fee": {
"amount": 20,
"pickup_fee": 8,
"discount": 5,
"charged": 23,
"currency": "CAD",
"promotion_name": "Spring returns",
"coupon_code": null,
"refunded": false,
"refunded_at": null
}
}
}
platformAppCancelReturn
mutation
REST twin: POST /app/v1/returns/{returnId}/cancel
Cancels an eligible existing ReturnRequest and records the operation. Needs the locker.return capability on the token.
mutation PlatformAppCancelReturn($return_id: Int!) {
platformAppCancelReturn(return_id: $return_id)
}
Variables
{
"return_id": 901
}
Response example
{
"result": true,
"data": {
"id": 901,
"status": 4,
"can_cancel": false
}
}
platformAppReturnConfiguration
query
REST twin: GET /app/v1/returns/config
Existing return channels, addresses, reasons and effective methods. Each channel carries logo_url (absolute URL of its logo, or null). guide holds the Client's own wording for the app's return guide pages (platform, upload, confirm, success), each with title and description in the requested locale; null means the app shows its built-in text. Each channel also carries its step 2 of the return guide: return_code_upload_mode (1 image required, 2 optional, 3 not asked), upload_step {tab, title, description} in the requested locale (null where the channel left it empty) and reference_number_description. data.contact carries the account's name, telephone and email (each null when unset), which the confirm step can start from. Each channel carries fee ({amount, discount, total, currency, promotion_name, coupon_code}) or null when free. Each channel carries approval_mode: which returns through it wait for the Client's approval (1 only free returns, 2 all, 3 none). Each channel carries home_pickup: null when the channel does not offer home pickup, otherwise {fee_mode (fixed, price_plan or free), fee (the fixed amount, 0 when free, null on price_plan: priced per address through platformAppReturnFeeQuote), charges_return_fee (false: the pickup fee replaces the return fee), limited_to_regions}. Needs the locker.return capability on the token.
query PlatformAppReturnConfiguration($locale: String) {
platformAppReturnConfiguration(locale: $locale)
}
Variables
{
"locale": "en"
}
Response example
{
"result": true,
"data": {
"channels": [
{
"id": 4,
"return_methods": [
1,
2
],
"allowed_return_types": [
1,
2
],
"pickup_address_mode": 1,
"requires_return_reason": true,
"approval_mode": 1,
"home_pickup": {
"fee_mode": "fixed",
"fee": 8,
"charges_return_fee": true,
"limited_to_regions": false
},
"logo_url": "https://admin.superroute.ca/storage/return-channels/online.png",
"reference_number_description": null,
"return_code_upload_mode": 1,
"upload_step": {
"tab": null,
"title": null,
"description": null
}
}
],
"reasons": [
{
"id": 3,
"name": "Changed mind"
}
],
"guide": {
"platform": {
"title": "Where did you buy it?",
"description": null
},
"upload": {
"title": null,
"description": null
},
"confirm": {
"title": null,
"description": null
},
"success": {
"title": null,
"description": null
}
}
}
}
platformAppReturnFeeQuote
query
REST twin: POST /app/v1/returns/fee-quote
What a return through one channel costs the customer, with the coupon when given. Same body as the REST endpoint: fee (null when free), coupon_status (null, applied, invalid, not_needed) and balance. Optional home_pickup (true) with pickup_postcode and pickup_country prices a home pickup at that address too: data.home_pickup is then {available, reason (null, disabled, address_required, out_of_area or not_priced), message, fee, charges_return_fee, currency}, null when home_pickup is not sent. fee is always the return fee alone (fee.total excludes the pickup fee, which is in home_pickup.fee); when an available pickup has charges_return_fee false it replaces the return fee and fee is null. data.total is everything submitting would charge: the return fee after its promotion plus the pickup fee. Needs the locker.return capability on the token.
query PlatformAppReturnFeeQuote($return_channel_id: Int!, $coupon_code: String, $home_pickup: Boolean, $pickup_postcode: String, $pickup_country: String) {
platformAppReturnFeeQuote(return_channel_id: $return_channel_id, coupon_code: $coupon_code, home_pickup: $home_pickup, pickup_postcode: $pickup_postcode, pickup_country: $pickup_country)
}
Variables
{
"return_channel_id": 12,
"coupon_code": "SPRING5",
"home_pickup": true,
"pickup_postcode": "M5X 1A9",
"pickup_country": "CA"
}
Response example
{
"result": true,
"data": {
"return_channel_id": 12,
"fee": {
"amount": 20,
"discount": 5,
"total": 15,
"currency": "CAD",
"promotion_name": "Spring returns",
"coupon_code": "SPRING5"
},
"coupon_status": "applied",
"balance": 42.5,
"home_pickup": {
"available": true,
"reason": null,
"message": null,
"fee": 8,
"charges_return_fee": true,
"currency": "CAD"
},
"total": 23
}
}
platformAppResendReturnConfirmation
mutation
REST twin: POST /app/v1/returns/{returnId}/confirmation-email
Sends the return code, its QR code and the status link to the email the return was created with, again, in the request's language, using the Client's "Return Confirmation" email template or the built-in wording. At most once a minute per return. Refused with RETURN_EMAIL_NOT_AVAILABLE (422) when the return has no usable code or email or the Client switched the email off (can_resend_confirmation is false on the return), and RETURN_EMAIL_RECENTLY_SENT (429) within the minute. Needs the locker.return capability on the token.
mutation PlatformAppResendReturnConfirmation($return_id: Int!) {
platformAppResendReturnConfirmation(return_id: $return_id)
}
Variables
{
"return_id": 901
}
Response example
{
"result": true,
"message": "The confirmation email has been sent again to jane@example.com.",
"data": {
"confirmation_email": "jane@example.com"
}
}
platformAppTrack
query
REST twin: POST /app/v1/track
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. Signed in: the customer's own shipments are `owned` and verified without an answer, and the customer's own pickup codes resolve too (a pickup code of anyone else reads as not found); a partner locker pickup answers `kind` partner_delivery with `pickup_id` for GET .../pickups/{deliveryId}. Needs the tracking capability on the token.
query PlatformAppTrack($identifier: String!, $answer: String, $locale: String) {
platformAppTrack(identifier: $identifier, answer: $answer, locale: $locale)
}
Variables
{
"identifier": "R8K2Q7"
}
Response example
{
"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
}
}
platformAppPublicQuote
mutation
REST twin: POST /app/v1/quotes/{line}
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. Needs the quotes.public capability on the token.
mutation PlatformAppPublicQuote($line: String!, $input: Json) {
platformAppPublicQuote(line: $line, input: $input)
}
Variables
{
"line": "delivery",
"input": {
"to_postcode": "M5V 2T6",
"weight": 2.4
}
}
Response example
{
"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
REST twin: GET /app/v1/quotes/schema
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. Needs the quotes.public capability on the token.
query PlatformAppPublicQuoteSchema {
platformAppPublicQuoteSchema
}
Variables
{}
Response example
{
"result": true,
"schema": {
"business_name": "Northstar Courier",
"currency": "CAD",
"valid_days": 7,
"lines": []
}
}
Lockers
platformAppLockerLocations
query
REST twin: GET /app/v1/locker-locations
Client-owned SmartLocker discovery with server-side search, viewport bounds, service/status/capacity filters, pagination, customer preference ranking and optional distance sorting from direct coordinates or a customer address. Each result is an address location; locker_status_summary counts the actual locker devices below it, while status remains an aggregate for compatibility. The pickup, return_service, online, available and favorite flags only narrow the catalog when true; false or null returns the unfiltered catalog.
query PlatformAppLockerLocations($latitude: Float, $longitude: Float, $origin_address_1: String, $origin_address_2: String, $origin_city: String, $origin_province: String, $origin_country: String, $origin_postcode: String, $search: String, $limit: Int, $per_page: Int, $page: Int, $north: Float, $south: Float, $east: Float, $west: Float, $pickup: Boolean, $return_service: Boolean, $online: Boolean, $available: Boolean, $favorite: Boolean, $include_status: Boolean, $sort: String) {
platformAppLockerLocations(latitude: $latitude, longitude: $longitude, origin_address_1: $origin_address_1, origin_address_2: $origin_address_2, origin_city: $origin_city, origin_province: $origin_province, origin_country: $origin_country, origin_postcode: $origin_postcode, search: $search, limit: $limit, per_page: $per_page, page: $page, north: $north, south: $south, east: $east, west: $west, pickup: $pickup, return_service: $return_service, online: $online, available: $available, favorite: $favorite, include_status: $include_status, sort: $sort)
}
Variables
{
"origin_address_1": "50 Front Street",
"origin_city": "Toronto",
"origin_province": "ON",
"origin_country": "CA",
"origin_postcode": "M5J 1E6",
"search": "Toronto",
"limit": 20,
"per_page": 20,
"page": 1,
"north": 43.8,
"south": 43.5,
"east": -79.1,
"west": -79.7,
"include_status": true,
"sort": "distance"
}
Response example
{
"result": true,
"data": {
"items": [
{
"id": 81,
"name": "Downtown locker",
"address": "100 King Street",
"city": "Toronto",
"latitude": 43.6487,
"longitude": -79.3817,
"distance_km": 1.3,
"locker_count": 2,
"services": {
"pickup": true,
"return": true
},
"status": "online",
"locker_status_summary": {
"total": 2,
"online": 1,
"offline": 1,
"maintenance": 0,
"unknown": 0
},
"capacity": {
"estimated": true,
"source": "superroute_records",
"total": 24,
"available": 16,
"occupied": 6,
"reserved": 2,
"unavailable": 0,
"unknown": 0,
"occupancy_percent": 33.3
},
"is_favorite": true,
"view_count": 4,
"last_viewed_at": "2026-07-25T00:30:00-04:00"
}
],
"origin": {
"latitude": 43.6453,
"longitude": -79.3806,
"source": "geocoded_address"
},
"pagination": {
"current_page": 1,
"per_page": 20,
"total": 1,
"last_page": 1,
"has_more": false
}
}
}
platformAppLockerLocation
query
REST twin: GET /app/v1/locker-locations/{locationId}
Returns the distance from an optional origin plus a status count for the actual locker devices below the address, live status, door summaries, compartment dimensions, estimated occupancy and services at a Client-owned location. Capacity uses the same shared Superroute inventory and active-allocation calculation as REST and Web. The location carries guide: its indoor directions summary, or null.
query PlatformAppLockerLocation($location_id: Int!, $latitude: Float, $longitude: Float, $origin_address_1: String, $origin_address_2: String, $origin_city: String, $origin_province: String, $origin_country: String, $origin_postcode: String) {
platformAppLockerLocation(location_id: $location_id, latitude: $latitude, longitude: $longitude, origin_address_1: $origin_address_1, origin_address_2: $origin_address_2, origin_city: $origin_city, origin_province: $origin_province, origin_country: $origin_country, origin_postcode: $origin_postcode)
}
Variables
{
"location_id": 81,
"origin_address_1": "50 Front Street",
"origin_city": "Toronto",
"origin_province": "ON",
"origin_country": "CA",
"origin_postcode": "M5J 1E6"
}
Response example
{
"result": true,
"data": {
"id": 81,
"distance_km": 0.55,
"status": "online",
"locker_status_summary": {
"total": 1,
"online": 1,
"offline": 0,
"maintenance": 0,
"unknown": 0
},
"lockers": [
{
"id": 12,
"name": "Main entrance locker",
"code": "LK-12",
"provider": "superbox",
"status": "online",
"compartment_count": 24,
"compartments": {
"open": 0,
"closed": 23,
"fault": 1,
"unknown": 0
},
"capacity": {
"estimated": true,
"source": "superroute_records",
"total": 24,
"available": 16,
"occupied": 6,
"reserved": 1,
"unavailable": 1,
"unknown": 0,
"occupancy_percent": 29.2,
"sizes": [
{
"length_mm": 400,
"width_mm": 500,
"height_mm": 300,
"total": 12,
"available": 7,
"occupied": 4,
"reserved": 1,
"unavailable": 0,
"unknown": 0,
"occupancy_percent": 41.7
}
]
},
"features": [
"pickup_package",
"dropoff"
]
}
]
}
}
platformAppLockerLocationGuide
query
REST twin: GET /app/v1/locker-locations/{locationId}/guide
The indoor directions a location's operator wrote for finding its smart lockers: floor, entrance and summary, ordered steps each with an optional photo or video (with a cover image, size and duration for videos), and a floor plan with the position of each locker as a percentage of the image's width and height. Texts come in the Accept-Language language, else English, else the first language written. Only lockers this app lists at the location are marked. 404 LOCATION_GUIDE_NOT_FOUND when the location has no directions shown to customers; 404 LOCKER_LOCATION_NOT_FOUND when the location is not one this app lists. Each step's media_type is image, video (an uploaded file) or youtube (a YouTube video: media_url is its watch link, poster_url its thumbnail and youtube_id its id; open it in YouTube or embed it); youtube_id is null for other steps.
query PlatformAppLockerLocationGuide($location_id: Int!) {
platformAppLockerLocationGuide(location_id: $location_id)
}
Variables
{
"location_id": 81
}
Response example
{
"result": true,
"data": {
"location_id": 81,
"floor": "B1",
"entrance": "North entrance, next to the parking lot",
"summary": "The lockers are next to the food court, on the left of the escalator.",
"steps": [
{
"id": 11,
"position": 1,
"text": "Enter through the north entrance.",
"media_type": "image",
"media_url": "https://admin.superroute.ca/storage/location-guides/3/81/step1.jpg",
"poster_url": null,
"media_width": 1600,
"media_height": 1200,
"duration_seconds": null,
"file_size": 412345,
"youtube_id": null
},
{
"id": 12,
"position": 2,
"text": "Take the escalator down to B1 and turn left.",
"media_type": "video",
"media_url": "https://admin.superroute.ca/storage/location-guides/3/81/step2.mp4",
"poster_url": "https://admin.superroute.ca/storage/location-guides/3/81/step2.jpg",
"media_width": 1080,
"media_height": 1920,
"duration_seconds": 18,
"file_size": 9876543,
"youtube_id": null
},
{
"id": 13,
"position": 3,
"text": "The lockers are on your left.",
"media_type": "youtube",
"media_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"poster_url": "https://img.youtube.com/vi/dQw4w9WgXcQ/hqdefault.jpg",
"youtube_id": "dQw4w9WgXcQ",
"media_width": 16,
"media_height": 9,
"duration_seconds": null,
"file_size": null
}
],
"floor_plan": {
"url": "https://admin.superroute.ca/storage/location-guides/3/81/plan.png",
"width": 2000,
"height": 1400,
"markers": [
{
"locker_id": 12,
"locker_name": "Main entrance locker",
"x_percent": 62.5,
"y_percent": 41.2
}
]
},
"updated_at": "2026-09-28T10:15:00-04:00"
}
}
platformAppLockerLocationGuideFeedback
mutation
REST twin: POST /app/v1/locker-locations/{locationId}/guide/feedback
Answer whether the lockers of a location were found with its indoor directions (found: true or false). One answer per user, location and day; a second answer the same day replaces the first. The counts are shown to the location's operator. 404 LOCATION_GUIDE_NOT_FOUND when the location has no directions shown to customers.
mutation PlatformAppLockerLocationGuideFeedback($location_id: Int!, $found: Boolean!) {
platformAppLockerLocationGuideFeedback(location_id: $location_id, found: $found)
}
Variables
{
"location_id": 81,
"found": true
}
Response example
{
"result": true,
"data": {
"location_id": 81,
"found": true
}
}
platformAppUpdateLockerPreference
mutation
REST twin: PUT /app/v1/locker-locations/{locationId}/preference
Favorites or unfavorites a SmartLocker and records a visit for recent and frequently used ranking.
mutation PlatformAppUpdateLockerPreference($location_id: Int!, $favorite: Boolean, $viewed: Boolean) {
platformAppUpdateLockerPreference(location_id: $location_id, favorite: $favorite, viewed: $viewed)
}
Variables
{
"location_id": 81,
"favorite": true,
"viewed": true
}
Response example
{
"result": true,
"data": {
"location_id": 81,
"is_favorite": true,
"view_count": 4,
"last_viewed_at": "2026-07-25T00:30:00-04:00"
}
}
platformAppPickups
query
REST twin: GET /app/v1/pickups
Lists customer-owned PartnerLocker deliveries. Additive: `can_hand_over` and `handoff` (null, or {name, channel, contact (masked), handed_over_at}); while handed over `pickup_code` and `pickup_qr_value` are null and `remote_open_available` is false. The pickup's location carries guide: the summary of the indoor directions to its lockers (available, step_count, has_video, has_floor_plan, updated_at), or null; GET .../locker-locations/{locationId}/guide returns them. Needs the locker.pickup capability on the token.
query PlatformAppPickups($status: String, $per_page: Int, $page: Int) {
platformAppPickups(status: $status, per_page: $per_page, page: $page)
}
Variables
{
"status": "ready",
"per_page": 20,
"page": 1
}
Response example
{
"result": true,
"data": {
"items": [
{
"id": 72,
"pickup_status": "ready",
"pickup_code": "482913"
}
]
}
}
platformAppPickup
query
REST twin: GET /app/v1/pickups/{deliveryId}
Returns QR, code, location, compartment and package state. Additive: `can_hand_over` and `handoff` (null, or {name, channel, contact (masked), handed_over_at}); while handed over `pickup_code` and `pickup_qr_value` are null and `remote_open_available` is false. The pickup's location carries guide: the summary of the indoor directions to its lockers (available, step_count, has_video, has_floor_plan, updated_at), or null; GET .../locker-locations/{locationId}/guide returns them. Additive: `wallet` — absolute Apple (.pkpass) and Google (save link) card links valid 2 h, or null when the Client has wallet cards off (docs/APP-UNIVERSAL-TRACKING-DESIGN.md UT7). Needs the locker.pickup capability on the token.
query PlatformAppPickup($delivery_id: Int!) {
platformAppPickup(delivery_id: $delivery_id)
}
Variables
{
"delivery_id": 72
}
Response example
{
"result": true,
"data": {
"id": 72,
"pickup_qr_value": "482913"
}
}
platformAppCollectPickup
mutation
REST twin: POST /app/v1/pickups/{deliveryId}/collect
Requires locker.remote_open and reuses existing SmartLocker safety and settlement services. Needs the locker.remote_open capability on the token.
mutation PlatformAppCollectPickup($delivery_id: Int!, $user_confirmed: Boolean!) {
platformAppCollectPickup(delivery_id: $delivery_id, user_confirmed: $user_confirmed)
}
Variables
{
"delivery_id": 72,
"user_confirmed": true
}
Response example
{
"result": true,
"data": {
"picked_up_count": 1,
"partial_success": false
}
}
platformAppHandOverPickup
mutation
REST twin: POST /app/v1/pickups/{deliveryId}/handoff
Same rules as REST: the pickup code is rotated and the new code is sent only to the named person by SMS or email; exactly one of phone or email; while handed over the owner's pickup carries no code and cannot be collected remotely. Needs the locker.pickup capability on the token.
mutation PlatformAppHandOverPickup($delivery_id: Int!, $name: String, $phone: String, $email: String, $language: String) {
platformAppHandOverPickup(delivery_id: $delivery_id, name: $name, phone: $phone, email: $email, language: $language)
}
Variables
{
"delivery_id": 72,
"name": "Ann",
"phone": "+14165550100",
"language": "en"
}
Response example
{
"result": true,
"data": {
"id": 72,
"pickup_status": "ready",
"pickup_code": null,
"pickup_qr_value": null,
"remote_open_available": false,
"can_hand_over": true,
"handoff": {
"name": "Ann",
"channel": "sms",
"contact": "***0100",
"handed_over_at": "2026-09-27T10:15:00-04:00"
}
}
}
platformAppTakeBackPickup
mutation
REST twin: POST /app/v1/pickups/{deliveryId}/handoff/revoke
The pickup code is rotated again; the other person's code stops working and the owner's pickup shows the new code. Needs the locker.pickup capability on the token.
mutation PlatformAppTakeBackPickup($delivery_id: Int!) {
platformAppTakeBackPickup(delivery_id: $delivery_id)
}
Variables
{
"delivery_id": 72
}
Response example
{
"result": true,
"data": {
"id": 72,
"pickup_code": "K7Q2MX",
"handoff": null
}
}
platformAppLockerStorageRentals
query
REST twin: GET /app/v1/locker-storage/rentals
Bags the signed-in customer stored in the business's smart lockers, newest first. A rental is the customer's when it is bound to the account, or bound to nobody and stored under the account's e-mail or a verified phone number. scope=current lists bags still held (in a compartment or away with the operator), scope=past the rest. Each item carries its bill (amount so far, and how to pay it), its relay (where a bag away from its cabinet is) and any weight difference owed. No pickup code and no contact are returned; the compartment number only while the bag is in it. Requires the locker.storage capability (403 APP_CAPABILITY_DISABLED otherwise). Needs the locker.storage capability on the token.
query PlatformAppLockerStorageRentals($scope: String, $page: Int, $per_page: Int) {
platformAppLockerStorageRentals(scope: $scope, page: $page, per_page: $per_page)
}
Variables
{
"scope": "current",
"page": 1,
"per_page": 20
}
Response example
{
"result": true,
"data": {
"items": [
{
"id": 8801,
"status": "relayed",
"status_label": "Relayed",
"site": {
"id": 700,
"name": "Central Station",
"address": "1 Main St",
"city": "Montreal",
"province": "QC",
"postcode": "H3B 1A1",
"country": "CA",
"latitude": 45.5,
"longitude": -73.57
},
"cabinet": {
"id": 312,
"code": "CAB-A"
},
"compartment_number": null,
"compartment_type": "Medium",
"bound_to_account": true,
"started_at": "2026-09-24T09:10:00-04:00",
"expires_at": "2026-09-26T09:10:00-04:00",
"ended_at": null,
"declared_return_at": "2026-09-27T12:00:00-04:00",
"bill": {
"charged": true,
"paid": false,
"amount": 24,
"currency": "CAD",
"paid_at": null,
"due_now": true,
"payment": {
"subject_type": "locker",
"subject_id": 8801,
"amount": 24,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/pay/locker-storage/8801?expires=1790000000&signature=…"
}
},
"relay": {
"id": 55,
"rental_id": 8801,
"rental_status": "relayed",
"status": "at_hub",
"from_site": {
"id": 700,
"name": "Central Station"
},
"return": {
"kind": "shipment",
"site": null,
"address": {
"name": "A. Renter",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"address_line": "10 Rue Sainte-Catherine, Montreal QC H2X 1K4",
"paid": false,
"shipment": {
"carrier": null,
"tracking_number": null
}
},
"onward": {
"kind": "shipment",
"status": "weight_hold",
"mode": "table",
"channel": "label",
"weight_tier_kg": 10,
"weighed_kg": 12.4,
"declared": {
"weight_kg": 9.5
},
"measured": {
"weight_kg": 12.4
},
"option": {
"key": "t12",
"mode": "table",
"channel": "label",
"carrier": "Canada Post",
"service": "Expedited",
"service_code": "DOM.EP",
"transit_days": 2,
"price": 32,
"currency": "CAD"
},
"price": 32,
"currency": "CAD",
"valid_until": "2026-10-01T09:10:00-04:00",
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"extra": 8,
"paid": false
}
},
"return_by": "2026-09-27T10:00:00-04:00",
"declared_return_at": "2026-09-27T12:00:00-04:00",
"warn_until": null,
"taken_out_at": "2026-09-24T15:02:00-04:00",
"hub_received_at": "2026-09-24T17:40:00-04:00",
"hub_shipped_at": null,
"returned_at": null,
"cancelled_at": null,
"cancel_reason": null,
"hub_days": null,
"price": {
"hub_per_day": 3,
"relay_fee": 5,
"address_return_fee": null,
"alt_site_fee": null,
"shipment_fee": 32,
"free_hub_hours": 24,
"currency": "CAD"
},
"created_at": "2026-09-24T14:00:00-04:00"
},
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"subject_type": "luggage_hold",
"subject_id": 55,
"amount": 8,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/luggage-relay/8801/hold-pay?expires=1790000000&signature=…"
}
}
],
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 20,
"total": 1,
"has_more": false
}
}
}
platformAppLockerStorageRental
query
REST twin: GET /app/v1/locker-storage/rentals/{id}
One of the renter's stored bags, with the choices open now under options: keep it during the warning window, collect at another cabinet (pickup_site), change the return cabinet or time while it is away, have it delivered, ship it, resend the code. Times are ISO 8601; timezone is the business's, in which times the renter types are read. Requires the locker.storage capability (403 APP_CAPABILITY_DISABLED otherwise). Needs the locker.storage capability on the token.
query PlatformAppLockerStorageRental($rental_id: Int!) {
platformAppLockerStorageRental(rental_id: $rental_id)
}
Variables
{
"rental_id": 8801
}
Response example
{
"result": true,
"data": {
"id": 8801,
"status": "relayed",
"status_label": "Relayed",
"site": {
"id": 700,
"name": "Central Station",
"address": "1 Main St",
"city": "Montreal",
"province": "QC",
"postcode": "H3B 1A1",
"country": "CA",
"latitude": 45.5,
"longitude": -73.57
},
"cabinet": {
"id": 312,
"code": "CAB-A"
},
"compartment_number": null,
"compartment_type": "Medium",
"bound_to_account": true,
"started_at": "2026-09-24T09:10:00-04:00",
"expires_at": "2026-09-26T09:10:00-04:00",
"ended_at": null,
"declared_return_at": "2026-09-27T12:00:00-04:00",
"bill": {
"charged": true,
"paid": false,
"amount": 24,
"currency": "CAD",
"paid_at": null,
"due_now": true,
"payment": {
"subject_type": "locker",
"subject_id": 8801,
"amount": 24,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/pay/locker-storage/8801?expires=1790000000&signature=…"
}
},
"relay": {
"id": 55,
"rental_id": 8801,
"rental_status": "relayed",
"status": "at_hub",
"from_site": {
"id": 700,
"name": "Central Station"
},
"return": {
"kind": "shipment",
"site": null,
"address": {
"name": "A. Renter",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"address_line": "10 Rue Sainte-Catherine, Montreal QC H2X 1K4",
"paid": false,
"shipment": {
"carrier": null,
"tracking_number": null
}
},
"onward": {
"kind": "shipment",
"status": "weight_hold",
"mode": "table",
"channel": "label",
"weight_tier_kg": 10,
"weighed_kg": 12.4,
"declared": {
"weight_kg": 9.5
},
"measured": {
"weight_kg": 12.4
},
"option": {
"key": "t12",
"mode": "table",
"channel": "label",
"carrier": "Canada Post",
"service": "Expedited",
"service_code": "DOM.EP",
"transit_days": 2,
"price": 32,
"currency": "CAD"
},
"price": 32,
"currency": "CAD",
"valid_until": "2026-10-01T09:10:00-04:00",
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"extra": 8,
"paid": false
}
},
"return_by": "2026-09-27T10:00:00-04:00",
"declared_return_at": "2026-09-27T12:00:00-04:00",
"warn_until": null,
"taken_out_at": "2026-09-24T15:02:00-04:00",
"hub_received_at": "2026-09-24T17:40:00-04:00",
"hub_shipped_at": null,
"returned_at": null,
"cancelled_at": null,
"cancel_reason": null,
"hub_days": null,
"price": {
"hub_per_day": 3,
"relay_fee": 5,
"address_return_fee": null,
"alt_site_fee": null,
"shipment_fee": 32,
"free_hub_hours": 24,
"currency": "CAD"
},
"created_at": "2026-09-24T14:00:00-04:00"
},
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"subject_type": "luggage_hold",
"subject_id": 55,
"amount": 8,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/luggage-relay/8801/hold-pay?expires=1790000000&signature=…"
},
"options": {
"keep": false,
"warn_until": null,
"pickup_site": {
"choices": [],
"fee": 0,
"cancellable": false
},
"return_site": {
"changeable": false,
"choices": []
},
"return_time": {
"changeable": true,
"latest": "2026-10-08T17:40:00-04:00"
},
"delivery": {
"available": false,
"fee": null,
"chosen": false,
"paid": false,
"default_country": "CA"
},
"shipment": {
"available": false,
"channels": [
"label",
"courier"
],
"weight_tiers_kg": [
5,
10,
20,
32
],
"tier_required": false,
"chosen": true,
"paid": false,
"shipped": false
},
"resend_code": true
},
"timezone": "America/Toronto"
}
}
platformAppLockerStorageClaim
mutation
REST twin: POST /app/v1/locker-storage/rentals/claim
For a bag stored under a contact the account does not hold: the pickup code plus the last four digits of the phone it was stored under, or that whole e-mail. Binds the rental to the account; from then on a pickup at the cabinet is paid from the balance when it covers the bill. Every miss answers the same 404. Throttled to 5 requests a minute. Requires the locker.storage capability (403 APP_CAPABILITY_DISABLED otherwise). Needs the locker.storage capability on the token.
mutation PlatformAppLockerStorageClaim($code: String!, $contact: String!) {
platformAppLockerStorageClaim(code: $code, contact: $contact)
}
Variables
{
"code": "K7Q2M9",
"contact": "0100"
}
Response example
{
"result": true,
"data": {
"id": 8801,
"status": "relayed",
"status_label": "Relayed",
"site": {
"id": 700,
"name": "Central Station",
"address": "1 Main St",
"city": "Montreal",
"province": "QC",
"postcode": "H3B 1A1",
"country": "CA",
"latitude": 45.5,
"longitude": -73.57
},
"cabinet": {
"id": 312,
"code": "CAB-A"
},
"compartment_number": null,
"compartment_type": "Medium",
"bound_to_account": true,
"started_at": "2026-09-24T09:10:00-04:00",
"expires_at": "2026-09-26T09:10:00-04:00",
"ended_at": null,
"declared_return_at": "2026-09-27T12:00:00-04:00",
"bill": {
"charged": true,
"paid": false,
"amount": 24,
"currency": "CAD",
"paid_at": null,
"due_now": true,
"payment": {
"subject_type": "locker",
"subject_id": 8801,
"amount": 24,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/pay/locker-storage/8801?expires=1790000000&signature=…"
}
},
"relay": {
"id": 55,
"rental_id": 8801,
"rental_status": "relayed",
"status": "at_hub",
"from_site": {
"id": 700,
"name": "Central Station"
},
"return": {
"kind": "shipment",
"site": null,
"address": {
"name": "A. Renter",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"address_line": "10 Rue Sainte-Catherine, Montreal QC H2X 1K4",
"paid": false,
"shipment": {
"carrier": null,
"tracking_number": null
}
},
"onward": {
"kind": "shipment",
"status": "weight_hold",
"mode": "table",
"channel": "label",
"weight_tier_kg": 10,
"weighed_kg": 12.4,
"declared": {
"weight_kg": 9.5
},
"measured": {
"weight_kg": 12.4
},
"option": {
"key": "t12",
"mode": "table",
"channel": "label",
"carrier": "Canada Post",
"service": "Expedited",
"service_code": "DOM.EP",
"transit_days": 2,
"price": 32,
"currency": "CAD"
},
"price": 32,
"currency": "CAD",
"valid_until": "2026-10-01T09:10:00-04:00",
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"extra": 8,
"paid": false
}
},
"return_by": "2026-09-27T10:00:00-04:00",
"declared_return_at": "2026-09-27T12:00:00-04:00",
"warn_until": null,
"taken_out_at": "2026-09-24T15:02:00-04:00",
"hub_received_at": "2026-09-24T17:40:00-04:00",
"hub_shipped_at": null,
"returned_at": null,
"cancelled_at": null,
"cancel_reason": null,
"hub_days": null,
"price": {
"hub_per_day": 3,
"relay_fee": 5,
"address_return_fee": null,
"alt_site_fee": null,
"shipment_fee": 32,
"free_hub_hours": 24,
"currency": "CAD"
},
"created_at": "2026-09-24T14:00:00-04:00"
},
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"subject_type": "luggage_hold",
"subject_id": 55,
"amount": 8,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/luggage-relay/8801/hold-pay?expires=1790000000&signature=…"
},
"options": {
"keep": false,
"warn_until": null,
"pickup_site": {
"choices": [],
"fee": 0,
"cancellable": false
},
"return_site": {
"changeable": false,
"choices": []
},
"return_time": {
"changeable": true,
"latest": "2026-10-08T17:40:00-04:00"
},
"delivery": {
"available": false,
"fee": null,
"chosen": false,
"paid": false,
"default_country": "CA"
},
"shipment": {
"available": false,
"channels": [
"label",
"courier"
],
"weight_tiers_kg": [
5,
10,
20,
32
],
"tier_required": false,
"chosen": true,
"paid": false,
"shipped": false
},
"resend_code": true
},
"timezone": "America/Toronto"
}
}
platformAppLockerStorageDelivery
mutation
REST twin: POST /app/v1/locker-storage/rentals/{id}/delivery
Delivery by the operator's own drivers, by deliver_at (business local time). The bag leaves its compartment if it is still there; the bill, including the delivery fee, is due before the delivery is planned — see data.bill.payment (pay in the app with pay/requests when payable_in_app, or at pay_url). Requires the locker.storage capability (403 APP_CAPABILITY_DISABLED otherwise). Needs the locker.storage capability on the token.
mutation PlatformAppLockerStorageDelivery($rental_id: Int!, $address: Json!, $deliver_at: String!) {
platformAppLockerStorageDelivery(rental_id: $rental_id, address: $address, deliver_at: $deliver_at)
}
Variables
{
"rental_id": 8801,
"address": {
"name": "A. Renter",
"telephone": "+15145550100",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"deliver_at": "2026-09-27 18:00"
}
Response example
{
"result": true,
"message": "Delivery to the address is requested. Pay the bill on this page to confirm it.",
"data": {
"id": 8801,
"status": "relayed",
"status_label": "Relayed",
"site": {
"id": 700,
"name": "Central Station",
"address": "1 Main St",
"city": "Montreal",
"province": "QC",
"postcode": "H3B 1A1",
"country": "CA",
"latitude": 45.5,
"longitude": -73.57
},
"cabinet": {
"id": 312,
"code": "CAB-A"
},
"compartment_number": null,
"compartment_type": "Medium",
"bound_to_account": true,
"started_at": "2026-09-24T09:10:00-04:00",
"expires_at": "2026-09-26T09:10:00-04:00",
"ended_at": null,
"declared_return_at": "2026-09-27T12:00:00-04:00",
"bill": {
"charged": true,
"paid": false,
"amount": 24,
"currency": "CAD",
"paid_at": null,
"due_now": true,
"payment": {
"subject_type": "locker",
"subject_id": 8801,
"amount": 24,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/pay/locker-storage/8801?expires=1790000000&signature=…"
}
},
"relay": {
"id": 55,
"rental_id": 8801,
"rental_status": "relayed",
"status": "at_hub",
"from_site": {
"id": 700,
"name": "Central Station"
},
"return": {
"kind": "shipment",
"site": null,
"address": {
"name": "A. Renter",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"address_line": "10 Rue Sainte-Catherine, Montreal QC H2X 1K4",
"paid": false,
"shipment": {
"carrier": null,
"tracking_number": null
}
},
"onward": {
"kind": "shipment",
"status": "weight_hold",
"mode": "table",
"channel": "label",
"weight_tier_kg": 10,
"weighed_kg": 12.4,
"declared": {
"weight_kg": 9.5
},
"measured": {
"weight_kg": 12.4
},
"option": {
"key": "t12",
"mode": "table",
"channel": "label",
"carrier": "Canada Post",
"service": "Expedited",
"service_code": "DOM.EP",
"transit_days": 2,
"price": 32,
"currency": "CAD"
},
"price": 32,
"currency": "CAD",
"valid_until": "2026-10-01T09:10:00-04:00",
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"extra": 8,
"paid": false
}
},
"return_by": "2026-09-27T10:00:00-04:00",
"declared_return_at": "2026-09-27T12:00:00-04:00",
"warn_until": null,
"taken_out_at": "2026-09-24T15:02:00-04:00",
"hub_received_at": "2026-09-24T17:40:00-04:00",
"hub_shipped_at": null,
"returned_at": null,
"cancelled_at": null,
"cancel_reason": null,
"hub_days": null,
"price": {
"hub_per_day": 3,
"relay_fee": 5,
"address_return_fee": null,
"alt_site_fee": null,
"shipment_fee": 32,
"free_hub_hours": 24,
"currency": "CAD"
},
"created_at": "2026-09-24T14:00:00-04:00"
},
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"subject_type": "luggage_hold",
"subject_id": 55,
"amount": 8,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/luggage-relay/8801/hold-pay?expires=1790000000&signature=…"
},
"options": {
"keep": false,
"warn_until": null,
"pickup_site": {
"choices": [],
"fee": 0,
"cancellable": false
},
"return_site": {
"changeable": false,
"choices": []
},
"return_time": {
"changeable": true,
"latest": "2026-10-08T17:40:00-04:00"
},
"delivery": {
"available": false,
"fee": null,
"chosen": false,
"paid": false,
"default_country": "CA"
},
"shipment": {
"available": false,
"channels": [
"label",
"courier"
],
"weight_tiers_kg": [
5,
10,
20,
32
],
"tier_required": false,
"chosen": true,
"paid": false,
"shipped": false
},
"resend_code": true
},
"timezone": "America/Toronto"
}
}
platformAppLockerStorageKeep
mutation
REST twin: POST /app/v1/locker-storage/rentals/{id}/keep
Answers the warning sent before an operator rule moves a long-stored bag to the hub (options.keep is true until options.warn_until). Requires the locker.storage capability (403 APP_CAPABILITY_DISABLED otherwise). Needs the locker.storage capability on the token.
mutation PlatformAppLockerStorageKeep($rental_id: Int!) {
platformAppLockerStorageKeep(rental_id: $rental_id)
}
Variables
{
"rental_id": 8801
}
Response example
{
"result": true,
"message": "Your luggage stays in its compartment.",
"data": {
"id": 8801,
"status": "relayed",
"status_label": "Relayed",
"site": {
"id": 700,
"name": "Central Station",
"address": "1 Main St",
"city": "Montreal",
"province": "QC",
"postcode": "H3B 1A1",
"country": "CA",
"latitude": 45.5,
"longitude": -73.57
},
"cabinet": {
"id": 312,
"code": "CAB-A"
},
"compartment_number": null,
"compartment_type": "Medium",
"bound_to_account": true,
"started_at": "2026-09-24T09:10:00-04:00",
"expires_at": "2026-09-26T09:10:00-04:00",
"ended_at": null,
"declared_return_at": "2026-09-27T12:00:00-04:00",
"bill": {
"charged": true,
"paid": false,
"amount": 24,
"currency": "CAD",
"paid_at": null,
"due_now": true,
"payment": {
"subject_type": "locker",
"subject_id": 8801,
"amount": 24,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/pay/locker-storage/8801?expires=1790000000&signature=…"
}
},
"relay": {
"id": 55,
"rental_id": 8801,
"rental_status": "relayed",
"status": "at_hub",
"from_site": {
"id": 700,
"name": "Central Station"
},
"return": {
"kind": "shipment",
"site": null,
"address": {
"name": "A. Renter",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"address_line": "10 Rue Sainte-Catherine, Montreal QC H2X 1K4",
"paid": false,
"shipment": {
"carrier": null,
"tracking_number": null
}
},
"onward": {
"kind": "shipment",
"status": "weight_hold",
"mode": "table",
"channel": "label",
"weight_tier_kg": 10,
"weighed_kg": 12.4,
"declared": {
"weight_kg": 9.5
},
"measured": {
"weight_kg": 12.4
},
"option": {
"key": "t12",
"mode": "table",
"channel": "label",
"carrier": "Canada Post",
"service": "Expedited",
"service_code": "DOM.EP",
"transit_days": 2,
"price": 32,
"currency": "CAD"
},
"price": 32,
"currency": "CAD",
"valid_until": "2026-10-01T09:10:00-04:00",
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"extra": 8,
"paid": false
}
},
"return_by": "2026-09-27T10:00:00-04:00",
"declared_return_at": "2026-09-27T12:00:00-04:00",
"warn_until": null,
"taken_out_at": "2026-09-24T15:02:00-04:00",
"hub_received_at": "2026-09-24T17:40:00-04:00",
"hub_shipped_at": null,
"returned_at": null,
"cancelled_at": null,
"cancel_reason": null,
"hub_days": null,
"price": {
"hub_per_day": 3,
"relay_fee": 5,
"address_return_fee": null,
"alt_site_fee": null,
"shipment_fee": 32,
"free_hub_hours": 24,
"currency": "CAD"
},
"created_at": "2026-09-24T14:00:00-04:00"
},
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"subject_type": "luggage_hold",
"subject_id": 55,
"amount": 8,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/luggage-relay/8801/hold-pay?expires=1790000000&signature=…"
},
"options": {
"keep": false,
"warn_until": null,
"pickup_site": {
"choices": [],
"fee": 0,
"cancellable": false
},
"return_site": {
"changeable": false,
"choices": []
},
"return_time": {
"changeable": true,
"latest": "2026-10-08T17:40:00-04:00"
},
"delivery": {
"available": false,
"fee": null,
"chosen": false,
"paid": false,
"default_country": "CA"
},
"shipment": {
"available": false,
"channels": [
"label",
"courier"
],
"weight_tiers_kg": [
5,
10,
20,
32
],
"tier_required": false,
"chosen": true,
"paid": false,
"shipped": false
},
"resend_code": true
},
"timezone": "America/Toronto"
}
}
platformAppLockerStorageOnwardQuote
query
REST twin: POST /app/v1/locker-storage/rentals/{id}/onward-quote
Reads only. own_delivery answers delivery by the operator's drivers (only inside its delivery area); shipment lists carrier label and courier options by price, for the weight band given or derived from weight_kg. A choice is made with /delivery or /shipment. Throttled to 20 requests a minute. Requires the locker.storage capability (403 APP_CAPABILITY_DISABLED otherwise). Needs the locker.storage capability on the token.
query PlatformAppLockerStorageOnwardQuote($rental_id: Int!, $address: Json!, $weight_tier_kg: Int, $weight_kg: Float, $dimensions_cm: Json) {
platformAppLockerStorageOnwardQuote(rental_id: $rental_id, address: $address, weight_tier_kg: $weight_tier_kg, weight_kg: $weight_kg, dimensions_cm: $dimensions_cm)
}
Variables
{
"rental_id": 8801,
"address": {
"name": "A. Renter",
"telephone": "+15145550100",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"weight_tier_kg": 10,
"weight_kg": 9.5,
"dimensions_cm": {
"length": 70,
"width": 45,
"height": 30
}
}
Response example
{
"result": true,
"data": {
"own_delivery": {
"available": true,
"reason": null,
"fee": 18,
"currency": "CAD",
"mode": "flat"
},
"shipment": {
"available": true,
"reason": null,
"mode": "table",
"currency": "CAD",
"weight_tier_kg": 10,
"weight_tiers_kg": [
5,
10,
20,
32
],
"measure": {
"weight_kg": 9.5,
"length_cm": null,
"width_cm": null,
"height_cm": null
},
"options": [
{
"key": "t12",
"mode": "table",
"channel": "label",
"carrier": "Canada Post",
"service": "Expedited",
"service_code": "DOM.EP",
"transit_days": 2,
"price": 32,
"currency": "CAD"
},
{
"key": "s4",
"mode": "courier",
"channel": "courier",
"carrier": "City Courier",
"service": "Same day",
"service_code": null,
"transit_days": 0,
"price": 41.5,
"currency": "CAD"
}
]
}
}
}
platformAppLockerStoragePickupSite
mutation
REST twin: POST /app/v1/locker-storage/rentals/{id}/pickup-site
While the bag is in its compartment: collect it at one of options.pickup_site.choices instead (the operator carries it through its hub; options.pickup_site.fee is added to the bill, paid at pickup). While it is away and its return is not yet planned: bring it back to one of options.return_site.choices. Requires the locker.storage capability (403 APP_CAPABILITY_DISABLED otherwise). Needs the locker.storage capability on the token.
mutation PlatformAppLockerStoragePickupSite($rental_id: Int!, $warehouse_id: Int!) {
platformAppLockerStoragePickupSite(rental_id: $rental_id, warehouse_id: $warehouse_id)
}
Variables
{
"rental_id": 8801,
"warehouse_id": 701
}
Response example
{
"result": true,
"message": "Your luggage will be moved to the chosen cabinet. Your code stays the same.",
"data": {
"id": 8801,
"status": "relayed",
"status_label": "Relayed",
"site": {
"id": 700,
"name": "Central Station",
"address": "1 Main St",
"city": "Montreal",
"province": "QC",
"postcode": "H3B 1A1",
"country": "CA",
"latitude": 45.5,
"longitude": -73.57
},
"cabinet": {
"id": 312,
"code": "CAB-A"
},
"compartment_number": null,
"compartment_type": "Medium",
"bound_to_account": true,
"started_at": "2026-09-24T09:10:00-04:00",
"expires_at": "2026-09-26T09:10:00-04:00",
"ended_at": null,
"declared_return_at": "2026-09-27T12:00:00-04:00",
"bill": {
"charged": true,
"paid": false,
"amount": 24,
"currency": "CAD",
"paid_at": null,
"due_now": true,
"payment": {
"subject_type": "locker",
"subject_id": 8801,
"amount": 24,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/pay/locker-storage/8801?expires=1790000000&signature=…"
}
},
"relay": {
"id": 55,
"rental_id": 8801,
"rental_status": "relayed",
"status": "at_hub",
"from_site": {
"id": 700,
"name": "Central Station"
},
"return": {
"kind": "shipment",
"site": null,
"address": {
"name": "A. Renter",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"address_line": "10 Rue Sainte-Catherine, Montreal QC H2X 1K4",
"paid": false,
"shipment": {
"carrier": null,
"tracking_number": null
}
},
"onward": {
"kind": "shipment",
"status": "weight_hold",
"mode": "table",
"channel": "label",
"weight_tier_kg": 10,
"weighed_kg": 12.4,
"declared": {
"weight_kg": 9.5
},
"measured": {
"weight_kg": 12.4
},
"option": {
"key": "t12",
"mode": "table",
"channel": "label",
"carrier": "Canada Post",
"service": "Expedited",
"service_code": "DOM.EP",
"transit_days": 2,
"price": 32,
"currency": "CAD"
},
"price": 32,
"currency": "CAD",
"valid_until": "2026-10-01T09:10:00-04:00",
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"extra": 8,
"paid": false
}
},
"return_by": "2026-09-27T10:00:00-04:00",
"declared_return_at": "2026-09-27T12:00:00-04:00",
"warn_until": null,
"taken_out_at": "2026-09-24T15:02:00-04:00",
"hub_received_at": "2026-09-24T17:40:00-04:00",
"hub_shipped_at": null,
"returned_at": null,
"cancelled_at": null,
"cancel_reason": null,
"hub_days": null,
"price": {
"hub_per_day": 3,
"relay_fee": 5,
"address_return_fee": null,
"alt_site_fee": null,
"shipment_fee": 32,
"free_hub_hours": 24,
"currency": "CAD"
},
"created_at": "2026-09-24T14:00:00-04:00"
},
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"subject_type": "luggage_hold",
"subject_id": 55,
"amount": 8,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/luggage-relay/8801/hold-pay?expires=1790000000&signature=…"
},
"options": {
"keep": false,
"warn_until": null,
"pickup_site": {
"choices": [],
"fee": 0,
"cancellable": false
},
"return_site": {
"changeable": false,
"choices": []
},
"return_time": {
"changeable": true,
"latest": "2026-10-08T17:40:00-04:00"
},
"delivery": {
"available": false,
"fee": null,
"chosen": false,
"paid": false,
"default_country": "CA"
},
"shipment": {
"available": false,
"channels": [
"label",
"courier"
],
"weight_tiers_kg": [
5,
10,
20,
32
],
"tier_required": false,
"chosen": true,
"paid": false,
"shipped": false
},
"resend_code": true
},
"timezone": "America/Toronto"
}
}
platformAppLockerStoragePickupSiteCancel
mutation
REST twin: POST /app/v1/locker-storage/rentals/{id}/pickup-site/cancel
Undo a pickup-site choice before the bag has left its compartment. Requires the locker.storage capability (403 APP_CAPABILITY_DISABLED otherwise). Needs the locker.storage capability on the token.
mutation PlatformAppLockerStoragePickupSiteCancel($rental_id: Int!) {
platformAppLockerStoragePickupSiteCancel(rental_id: $rental_id)
}
Variables
{
"rental_id": 8801
}
Response example
{
"result": true,
"message": "The move is cancelled. Collect your luggage where you left it.",
"data": {
"id": 8801,
"status": "relayed",
"status_label": "Relayed",
"site": {
"id": 700,
"name": "Central Station",
"address": "1 Main St",
"city": "Montreal",
"province": "QC",
"postcode": "H3B 1A1",
"country": "CA",
"latitude": 45.5,
"longitude": -73.57
},
"cabinet": {
"id": 312,
"code": "CAB-A"
},
"compartment_number": null,
"compartment_type": "Medium",
"bound_to_account": true,
"started_at": "2026-09-24T09:10:00-04:00",
"expires_at": "2026-09-26T09:10:00-04:00",
"ended_at": null,
"declared_return_at": "2026-09-27T12:00:00-04:00",
"bill": {
"charged": true,
"paid": false,
"amount": 24,
"currency": "CAD",
"paid_at": null,
"due_now": true,
"payment": {
"subject_type": "locker",
"subject_id": 8801,
"amount": 24,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/pay/locker-storage/8801?expires=1790000000&signature=…"
}
},
"relay": {
"id": 55,
"rental_id": 8801,
"rental_status": "relayed",
"status": "at_hub",
"from_site": {
"id": 700,
"name": "Central Station"
},
"return": {
"kind": "shipment",
"site": null,
"address": {
"name": "A. Renter",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"address_line": "10 Rue Sainte-Catherine, Montreal QC H2X 1K4",
"paid": false,
"shipment": {
"carrier": null,
"tracking_number": null
}
},
"onward": {
"kind": "shipment",
"status": "weight_hold",
"mode": "table",
"channel": "label",
"weight_tier_kg": 10,
"weighed_kg": 12.4,
"declared": {
"weight_kg": 9.5
},
"measured": {
"weight_kg": 12.4
},
"option": {
"key": "t12",
"mode": "table",
"channel": "label",
"carrier": "Canada Post",
"service": "Expedited",
"service_code": "DOM.EP",
"transit_days": 2,
"price": 32,
"currency": "CAD"
},
"price": 32,
"currency": "CAD",
"valid_until": "2026-10-01T09:10:00-04:00",
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"extra": 8,
"paid": false
}
},
"return_by": "2026-09-27T10:00:00-04:00",
"declared_return_at": "2026-09-27T12:00:00-04:00",
"warn_until": null,
"taken_out_at": "2026-09-24T15:02:00-04:00",
"hub_received_at": "2026-09-24T17:40:00-04:00",
"hub_shipped_at": null,
"returned_at": null,
"cancelled_at": null,
"cancel_reason": null,
"hub_days": null,
"price": {
"hub_per_day": 3,
"relay_fee": 5,
"address_return_fee": null,
"alt_site_fee": null,
"shipment_fee": 32,
"free_hub_hours": 24,
"currency": "CAD"
},
"created_at": "2026-09-24T14:00:00-04:00"
},
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"subject_type": "luggage_hold",
"subject_id": 55,
"amount": 8,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/luggage-relay/8801/hold-pay?expires=1790000000&signature=…"
},
"options": {
"keep": false,
"warn_until": null,
"pickup_site": {
"choices": [],
"fee": 0,
"cancellable": false
},
"return_site": {
"changeable": false,
"choices": []
},
"return_time": {
"changeable": true,
"latest": "2026-10-08T17:40:00-04:00"
},
"delivery": {
"available": false,
"fee": null,
"chosen": false,
"paid": false,
"default_country": "CA"
},
"shipment": {
"available": false,
"channels": [
"label",
"courier"
],
"weight_tiers_kg": [
5,
10,
20,
32
],
"tier_required": false,
"chosen": true,
"paid": false,
"shipped": false
},
"resend_code": true
},
"timezone": "America/Toronto"
}
}
platformAppLockerStorageResendCode
mutation
REST twin: POST /app/v1/locker-storage/rentals/{id}/resend-code
To the contact the bag was stored under, never to the account asking. Throttled to 3 requests in 10 minutes. Requires the locker.storage capability (403 APP_CAPABILITY_DISABLED otherwise). Needs the locker.storage capability on the token.
mutation PlatformAppLockerStorageResendCode($rental_id: Int!) {
platformAppLockerStorageResendCode(rental_id: $rental_id)
}
Variables
{
"rental_id": 8801
}
Response example
{
"result": true,
"message": "Your code has been sent again.",
"data": {
"id": 8801,
"status": "relayed",
"status_label": "Relayed",
"site": {
"id": 700,
"name": "Central Station",
"address": "1 Main St",
"city": "Montreal",
"province": "QC",
"postcode": "H3B 1A1",
"country": "CA",
"latitude": 45.5,
"longitude": -73.57
},
"cabinet": {
"id": 312,
"code": "CAB-A"
},
"compartment_number": null,
"compartment_type": "Medium",
"bound_to_account": true,
"started_at": "2026-09-24T09:10:00-04:00",
"expires_at": "2026-09-26T09:10:00-04:00",
"ended_at": null,
"declared_return_at": "2026-09-27T12:00:00-04:00",
"bill": {
"charged": true,
"paid": false,
"amount": 24,
"currency": "CAD",
"paid_at": null,
"due_now": true,
"payment": {
"subject_type": "locker",
"subject_id": 8801,
"amount": 24,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/pay/locker-storage/8801?expires=1790000000&signature=…"
}
},
"relay": {
"id": 55,
"rental_id": 8801,
"rental_status": "relayed",
"status": "at_hub",
"from_site": {
"id": 700,
"name": "Central Station"
},
"return": {
"kind": "shipment",
"site": null,
"address": {
"name": "A. Renter",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"address_line": "10 Rue Sainte-Catherine, Montreal QC H2X 1K4",
"paid": false,
"shipment": {
"carrier": null,
"tracking_number": null
}
},
"onward": {
"kind": "shipment",
"status": "weight_hold",
"mode": "table",
"channel": "label",
"weight_tier_kg": 10,
"weighed_kg": 12.4,
"declared": {
"weight_kg": 9.5
},
"measured": {
"weight_kg": 12.4
},
"option": {
"key": "t12",
"mode": "table",
"channel": "label",
"carrier": "Canada Post",
"service": "Expedited",
"service_code": "DOM.EP",
"transit_days": 2,
"price": 32,
"currency": "CAD"
},
"price": 32,
"currency": "CAD",
"valid_until": "2026-10-01T09:10:00-04:00",
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"extra": 8,
"paid": false
}
},
"return_by": "2026-09-27T10:00:00-04:00",
"declared_return_at": "2026-09-27T12:00:00-04:00",
"warn_until": null,
"taken_out_at": "2026-09-24T15:02:00-04:00",
"hub_received_at": "2026-09-24T17:40:00-04:00",
"hub_shipped_at": null,
"returned_at": null,
"cancelled_at": null,
"cancel_reason": null,
"hub_days": null,
"price": {
"hub_per_day": 3,
"relay_fee": 5,
"address_return_fee": null,
"alt_site_fee": null,
"shipment_fee": 32,
"free_hub_hours": 24,
"currency": "CAD"
},
"created_at": "2026-09-24T14:00:00-04:00"
},
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"subject_type": "luggage_hold",
"subject_id": 55,
"amount": 8,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/luggage-relay/8801/hold-pay?expires=1790000000&signature=…"
},
"options": {
"keep": false,
"warn_until": null,
"pickup_site": {
"choices": [],
"fee": 0,
"cancellable": false
},
"return_site": {
"changeable": false,
"choices": []
},
"return_time": {
"changeable": true,
"latest": "2026-10-08T17:40:00-04:00"
},
"delivery": {
"available": false,
"fee": null,
"chosen": false,
"paid": false,
"default_country": "CA"
},
"shipment": {
"available": false,
"channels": [
"label",
"courier"
],
"weight_tiers_kg": [
5,
10,
20,
32
],
"tier_required": false,
"chosen": true,
"paid": false,
"shipped": false
},
"resend_code": true
},
"timezone": "America/Toronto"
}
}
platformAppLockerStorageReturnTime
mutation
REST twin: POST /app/v1/locker-storage/rentals/{id}/return-time
For a bag that is away: when the renter will come back for it, in the business's local time (Y-m-d H:i), no later than options.return_time.latest. The return trip is re-planned to arrive before it. Requires the locker.storage capability (403 APP_CAPABILITY_DISABLED otherwise). Needs the locker.storage capability on the token.
mutation PlatformAppLockerStorageReturnTime($rental_id: Int!, $return_at: String!) {
platformAppLockerStorageReturnTime(rental_id: $rental_id, return_at: $return_at)
}
Variables
{
"rental_id": 8801,
"return_at": "2026-09-28 14:00"
}
Response example
{
"result": true,
"message": "Your return time has been changed.",
"data": {
"id": 8801,
"status": "relayed",
"status_label": "Relayed",
"site": {
"id": 700,
"name": "Central Station",
"address": "1 Main St",
"city": "Montreal",
"province": "QC",
"postcode": "H3B 1A1",
"country": "CA",
"latitude": 45.5,
"longitude": -73.57
},
"cabinet": {
"id": 312,
"code": "CAB-A"
},
"compartment_number": null,
"compartment_type": "Medium",
"bound_to_account": true,
"started_at": "2026-09-24T09:10:00-04:00",
"expires_at": "2026-09-26T09:10:00-04:00",
"ended_at": null,
"declared_return_at": "2026-09-27T12:00:00-04:00",
"bill": {
"charged": true,
"paid": false,
"amount": 24,
"currency": "CAD",
"paid_at": null,
"due_now": true,
"payment": {
"subject_type": "locker",
"subject_id": 8801,
"amount": 24,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/pay/locker-storage/8801?expires=1790000000&signature=…"
}
},
"relay": {
"id": 55,
"rental_id": 8801,
"rental_status": "relayed",
"status": "at_hub",
"from_site": {
"id": 700,
"name": "Central Station"
},
"return": {
"kind": "shipment",
"site": null,
"address": {
"name": "A. Renter",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"address_line": "10 Rue Sainte-Catherine, Montreal QC H2X 1K4",
"paid": false,
"shipment": {
"carrier": null,
"tracking_number": null
}
},
"onward": {
"kind": "shipment",
"status": "weight_hold",
"mode": "table",
"channel": "label",
"weight_tier_kg": 10,
"weighed_kg": 12.4,
"declared": {
"weight_kg": 9.5
},
"measured": {
"weight_kg": 12.4
},
"option": {
"key": "t12",
"mode": "table",
"channel": "label",
"carrier": "Canada Post",
"service": "Expedited",
"service_code": "DOM.EP",
"transit_days": 2,
"price": 32,
"currency": "CAD"
},
"price": 32,
"currency": "CAD",
"valid_until": "2026-10-01T09:10:00-04:00",
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"extra": 8,
"paid": false
}
},
"return_by": "2026-09-27T10:00:00-04:00",
"declared_return_at": "2026-09-27T12:00:00-04:00",
"warn_until": null,
"taken_out_at": "2026-09-24T15:02:00-04:00",
"hub_received_at": "2026-09-24T17:40:00-04:00",
"hub_shipped_at": null,
"returned_at": null,
"cancelled_at": null,
"cancel_reason": null,
"hub_days": null,
"price": {
"hub_per_day": 3,
"relay_fee": 5,
"address_return_fee": null,
"alt_site_fee": null,
"shipment_fee": 32,
"free_hub_hours": 24,
"currency": "CAD"
},
"created_at": "2026-09-24T14:00:00-04:00"
},
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"subject_type": "luggage_hold",
"subject_id": 55,
"amount": 8,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/luggage-relay/8801/hold-pay?expires=1790000000&signature=…"
},
"options": {
"keep": false,
"warn_until": null,
"pickup_site": {
"choices": [],
"fee": 0,
"cancellable": false
},
"return_site": {
"changeable": false,
"choices": []
},
"return_time": {
"changeable": true,
"latest": "2026-10-08T17:40:00-04:00"
},
"delivery": {
"available": false,
"fee": null,
"chosen": false,
"paid": false,
"default_country": "CA"
},
"shipment": {
"available": false,
"channels": [
"label",
"courier"
],
"weight_tiers_kg": [
5,
10,
20,
32
],
"tier_required": false,
"chosen": true,
"paid": false,
"shipped": false
},
"resend_code": true
},
"timezone": "America/Toronto"
}
}
platformAppLockerStorageShipment
mutation
REST twin: POST /app/v1/locker-storage/rentals/{id}/shipment
One option key from onward-quote, with the same address and weight. The option is re-quoted and must still be offered. Paid before the bag ships; if the hub weighs it heavier than declared, the difference appears as data.hold and must be paid before it leaves. Needs name, telephone, address_1, city, postcode and country. Requires the locker.storage capability (403 APP_CAPABILITY_DISABLED otherwise). Needs the locker.storage capability on the token.
mutation PlatformAppLockerStorageShipment($rental_id: Int!, $address: Json!, $option: String!, $weight_tier_kg: Int, $weight_kg: Float, $dimensions_cm: Json) {
platformAppLockerStorageShipment(rental_id: $rental_id, address: $address, option: $option, weight_tier_kg: $weight_tier_kg, weight_kg: $weight_kg, dimensions_cm: $dimensions_cm)
}
Variables
{
"rental_id": 8801,
"address": {
"name": "A. Renter",
"telephone": "+15145550100",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"option": "t12",
"weight_tier_kg": 10,
"weight_kg": 9.5
}
Response example
{
"result": true,
"message": "Your shipment is set. Pay the bill on this page to confirm it.",
"data": {
"id": 8801,
"status": "relayed",
"status_label": "Relayed",
"site": {
"id": 700,
"name": "Central Station",
"address": "1 Main St",
"city": "Montreal",
"province": "QC",
"postcode": "H3B 1A1",
"country": "CA",
"latitude": 45.5,
"longitude": -73.57
},
"cabinet": {
"id": 312,
"code": "CAB-A"
},
"compartment_number": null,
"compartment_type": "Medium",
"bound_to_account": true,
"started_at": "2026-09-24T09:10:00-04:00",
"expires_at": "2026-09-26T09:10:00-04:00",
"ended_at": null,
"declared_return_at": "2026-09-27T12:00:00-04:00",
"bill": {
"charged": true,
"paid": false,
"amount": 24,
"currency": "CAD",
"paid_at": null,
"due_now": true,
"payment": {
"subject_type": "locker",
"subject_id": 8801,
"amount": 24,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/pay/locker-storage/8801?expires=1790000000&signature=…"
}
},
"relay": {
"id": 55,
"rental_id": 8801,
"rental_status": "relayed",
"status": "at_hub",
"from_site": {
"id": 700,
"name": "Central Station"
},
"return": {
"kind": "shipment",
"site": null,
"address": {
"name": "A. Renter",
"address_1": "10 Rue Sainte-Catherine",
"city": "Montreal",
"province": "QC",
"postcode": "H2X 1K4",
"country": "CA"
},
"address_line": "10 Rue Sainte-Catherine, Montreal QC H2X 1K4",
"paid": false,
"shipment": {
"carrier": null,
"tracking_number": null
}
},
"onward": {
"kind": "shipment",
"status": "weight_hold",
"mode": "table",
"channel": "label",
"weight_tier_kg": 10,
"weighed_kg": 12.4,
"declared": {
"weight_kg": 9.5
},
"measured": {
"weight_kg": 12.4
},
"option": {
"key": "t12",
"mode": "table",
"channel": "label",
"carrier": "Canada Post",
"service": "Expedited",
"service_code": "DOM.EP",
"transit_days": 2,
"price": 32,
"currency": "CAD"
},
"price": 32,
"currency": "CAD",
"valid_until": "2026-10-01T09:10:00-04:00",
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"extra": 8,
"paid": false
}
},
"return_by": "2026-09-27T10:00:00-04:00",
"declared_return_at": "2026-09-27T12:00:00-04:00",
"warn_until": null,
"taken_out_at": "2026-09-24T15:02:00-04:00",
"hub_received_at": "2026-09-24T17:40:00-04:00",
"hub_shipped_at": null,
"returned_at": null,
"cancelled_at": null,
"cancel_reason": null,
"hub_days": null,
"price": {
"hub_per_day": 3,
"relay_fee": 5,
"address_return_fee": null,
"alt_site_fee": null,
"shipment_fee": 32,
"free_hub_hours": 24,
"currency": "CAD"
},
"created_at": "2026-09-24T14:00:00-04:00"
},
"hold": {
"from_tier_kg": 10,
"tier_kg": 20,
"subject_type": "luggage_hold",
"subject_id": 55,
"amount": 8,
"currency": "CAD",
"payable_in_app": true,
"pay_url": "https://app.example.com/luggage-relay/8801/hold-pay?expires=1790000000&signature=…"
},
"options": {
"keep": false,
"warn_until": null,
"pickup_site": {
"choices": [],
"fee": 0,
"cancellable": false
},
"return_site": {
"changeable": false,
"choices": []
},
"return_time": {
"changeable": true,
"latest": "2026-10-08T17:40:00-04:00"
},
"delivery": {
"available": false,
"fee": null,
"chosen": false,
"paid": false,
"default_country": "CA"
},
"shipment": {
"available": false,
"channels": [
"label",
"courier"
],
"weight_tiers_kg": [
5,
10,
20,
32
],
"tier_required": false,
"chosen": true,
"paid": false,
"shipped": false
},
"resend_code": true
},
"timezone": "America/Toronto"
}
}
platformAppPayOutstanding
query
REST twin: GET /app/v1/pay/outstanding
What the badge on the app's home screen counts: every payable this payer owes across the business, without needing to scan anything. Runs on app open, so a resolver that cannot answer cheaply returns nothing rather than make every launch slow — a short list is therefore not proof that nothing is owed. Needs the pay.scan capability on the token.
query PlatformAppPayOutstanding {
platformAppPayOutstanding
}
Variables
{}
Response example
{
"result": true,
"count": 1,
"total_cents": 1250,
"payables": [
{
"subject_type": "locker",
"subject_id": 8801,
"amount_cents": 1250,
"amount": 12.5,
"currency": "CAD",
"title": "Locker storage — CAB-A",
"description": "3 h 20 min"
}
]
}
platformAppPayResolveTag
mutation
REST twin: POST /app/v1/pay/resolve
Scan-to-pay entry point. The tag token names a device and nothing else; who is standing at it comes from the authenticated session. Returns every payable this payer owes at that device, whether that is nothing, one thing, or several — the app decides from the list length whether to say nothing is due, go straight to paying, or show a chooser. Resolving also records that the sticker was scanned, which is the only way to tell a working sticker from one that peeled off. Needs the pay.scan capability on the token.
mutation PlatformAppPayResolveTag($tag_token: String!) {
platformAppPayResolveTag(tag_token: $tag_token)
}
Variables
{
"tag_token": "7b1e9c04a8f24d6fb0537ce1249ad835"
}
Response example
{
"result": true,
"tag": {
"token": "7b1e9c04a8f24d6fb0537ce1249ad835",
"label": "Cabinet A, front panel",
"target_type": "locker",
"target_id": 312
},
"payables": [
{
"subject_type": "locker",
"subject_id": 8801,
"amount_cents": 1250,
"amount": 12.5,
"currency": "CAD",
"title": "Locker storage — CAB-A",
"description": "3 h 20 min"
}
]
}
platformAppPayResolveLink
mutation
REST twin: POST /app/v1/pay/links
The payment code a smart locker screen shows when a pickup — or a relayed bag's delivery or shipment — has to be paid is a signed link to one bill (/pay/locker-storage/{rental}?expires=…&signature=…), not a sticker token. The app posts the scanned link; the signature is the authorisation, as it is in a browser, so the payer need not be the renter. Raises the payment request and answers in the request shape, ready for pay/requests/{token}/pay. A link from another business answers OTHER_BUSINESS with pay_url (also data.pay_url) for a browser. Throttled to 30 requests a minute. Requires the pay.scan capability. It also accepts a payment request's own page (/pay/r/{token}), which is what the screen shows for a shop sale: a request nobody holds yet (raised by a guest at the device) passes to the signed-in payer; one another payer holds answers REQUEST_HELD_ELSEWHERE and another business's answers OTHER_BUSINESS, both with pay_url; a paid, lapsed or unknown request answers NOTHING_TO_PAY. Needs the pay.scan capability on the token.
mutation PlatformAppPayResolveLink($link: String!) {
platformAppPayResolveLink(link: $link)
}
Variables
{
"link": "https://app.example.com/pay/locker-storage/8801?expires=1790000000&signature=5d1c…"
}
Response example
{
"result": true,
"request": {
"token": "3f9c2a7be51d4c08a6f1e2d3b4c5a6e7",
"subject_type": "locker",
"subject_id": 8801,
"amount_cents": 1250,
"amount": 12.5,
"currency": "CAD",
"description": "Locker storage — CAB-A · 12",
"status": "pending",
"expires_at": "2026-09-25T10:30:00-04:00",
"paid_at": null,
"pay_url": "https://app.example.com/pay/r/3f9c2a7be51d4c08a6f1e2d3b4c5a6e7"
},
"methods": [
{
"kind": "wallet",
"gateway": null,
"method": "wallet",
"label": "Pay with balance",
"completion": "redirect",
"available": true,
"reason": null
},
{
"kind": "gateway",
"gateway": "stripe",
"method": "card",
"label": "Credit / Debit Card",
"completion": "sheet",
"available": true,
"reason": null
}
]
}
platformAppPayCreateRequest
mutation
REST twin: POST /app/v1/pay/requests
Turns one outstanding payable into a request that can be settled. The amount is never taken from the caller: the payable is re-read from the business that owns it, so an app cannot name its own price or raise a charge against a subject it merely guessed. Passing tag_token scopes the re-read to that device; omitting it re-reads everything the payer owes. Each method carries `completion`: `redirect` means it finishes on the provider's web page, `sheet` means it finishes in the provider's native sheet inside the app. A surface that cannot act on a completion must not offer it — the browser page greys out sheet-only methods instead of leading the payer to a dead end at the moment of paying. Needs the pay.scan capability on the token.
mutation PlatformAppPayCreateRequest($subject_type: String!, $subject_id: Int!, $tag_token: String) {
platformAppPayCreateRequest(subject_type: $subject_type, subject_id: $subject_id, tag_token: $tag_token)
}
Variables
{
"subject_type": "locker",
"subject_id": 8801,
"tag_token": "7b1e9c04a8f24d6fb0537ce1249ad835"
}
Response example
{
"result": true,
"request": {
"token": "9f2c41ab7d6e40518c3a0b17e5d94a26",
"subject_type": "locker",
"subject_id": 8801,
"amount_cents": 1250,
"amount": 12.5,
"currency": "CAD",
"description": "Locker storage — CAB-A",
"status": "pending",
"expires_at": "2026-09-04T18:30:00-04:00",
"paid_at": null,
"pay_url": "https://app.superroute.ca/pay/r/9f2c41ab7d6e40518c3a0b17e5d94a26"
},
"methods": [
{
"kind": "wallet",
"gateway": null,
"method": "wallet",
"label": "Account balance",
"icon": "fas fa-wallet",
"badge_color": "secondary",
"available": true,
"reason": null,
"completion": "redirect"
},
{
"kind": "gateway",
"gateway": "stripe",
"method": "card",
"label": "Credit / Debit Card",
"icon": "fas fa-credit-card",
"badge_color": "primary",
"completion": "sheet",
"available": true,
"reason": null
},
{
"kind": "gateway",
"gateway": "alphapay",
"method": "wechat_pay",
"label": "AlphaPay WeChat Pay",
"icon": "fab fa-weixin",
"badge_color": "success",
"available": true,
"reason": null,
"completion": "redirect"
}
]
}
platformAppPayRequest
query
REST twin: GET /app/v1/pay/requests/{publicToken}
What the request is for, what it costs, and what may settle it. The method list is the business's configured gateways narrowed by the methods it left switched on, in its own order, plus the account balance when the payer has one and it covers the amount. A method the payer cannot use is returned with available false and a reason rather than omitted. Each method carries `completion`: `redirect` means it finishes on the provider's web page, `sheet` means it finishes in the provider's native sheet inside the app. A surface that cannot act on a completion must not offer it — the browser page greys out sheet-only methods instead of leading the payer to a dead end at the moment of paying. Needs the pay.scan capability on the token.
query PlatformAppPayRequest($public_token: String!) {
platformAppPayRequest(public_token: $public_token)
}
Variables
{
"public_token": "9f2c41ab7d6e40518c3a0b17e5d94a26"
}
Response example
{
"result": true,
"request": {
"token": "9f2c41ab7d6e40518c3a0b17e5d94a26",
"subject_type": "locker",
"subject_id": 8801,
"amount_cents": 1250,
"amount": 12.5,
"currency": "CAD",
"description": "Locker storage — CAB-A",
"status": "pending",
"expires_at": "2026-09-04T18:30:00-04:00",
"paid_at": null,
"pay_url": "https://app.superroute.ca/pay/r/9f2c41ab7d6e40518c3a0b17e5d94a26"
},
"methods": [
{
"kind": "wallet",
"gateway": null,
"method": "wallet",
"label": "Account balance",
"icon": "fas fa-wallet",
"badge_color": "secondary",
"available": true,
"reason": null,
"completion": "redirect"
},
{
"kind": "gateway",
"gateway": "stripe",
"method": "card",
"label": "Credit / Debit Card",
"icon": "fas fa-credit-card",
"badge_color": "primary",
"completion": "sheet",
"available": true,
"reason": null
},
{
"kind": "gateway",
"gateway": "alphapay",
"method": "wechat_pay",
"label": "AlphaPay WeChat Pay",
"icon": "fab fa-weixin",
"badge_color": "success",
"available": true,
"reason": null,
"completion": "redirect"
}
]
}
platformAppPay
mutation
REST twin: POST /app/v1/pay/requests/{publicToken}/pay
Start paying. A wallet payment finishes here and comes back with paid true. A gateway payment does not: it comes back with a `next` object saying how the payer finishes. `next.kind` is the only field worth branching on — `redirect` means open `next.checkout_url`, `sheet` means hand `next.client_secret` to the provider's mobile SDK, initialised with `next.publishable_key`. The sheet is where Apple Pay and Google Pay appear: they are not separate methods to switch on beside card, they are surfaces the SDK offers on that same payment when the device supports them. Either way the money is not collected until the gateway says so — poll platformAppPayRequest until the status becomes paid. Pass method wallet with no gateway for the balance; otherwise pass the gateway code and the method it offers. Needs the pay.scan capability on the token.
mutation PlatformAppPay($public_token: String!, $method: String!, $gateway: String) {
platformAppPay(public_token: $public_token, method: $method, gateway: $gateway)
}
Variables
{
"public_token": "9f2c41ab7d6e40518c3a0b17e5d94a26",
"method": "wallet"
}
Response example
{
"result": true,
"paid": true,
"request": {
"token": "9f2c41ab7d6e40518c3a0b17e5d94a26",
"subject_type": "locker",
"subject_id": 8801,
"amount_cents": 1250,
"amount": 12.5,
"currency": "CAD",
"description": "Locker storage — CAB-A",
"status": "paid",
"expires_at": "2026-09-04T18:30:00-04:00",
"paid_at": "2026-09-04T18:12:07-04:00",
"pay_url": "https://app.superroute.ca/pay/r/9f2c41ab7d6e40518c3a0b17e5d94a26"
},
"methods": [
{
"kind": "wallet",
"gateway": null,
"method": "wallet",
"label": "Account balance",
"icon": "fas fa-wallet",
"badge_color": "secondary",
"available": true,
"reason": null,
"completion": "redirect"
},
{
"kind": "gateway",
"gateway": "stripe",
"method": "card",
"label": "Credit / Debit Card",
"icon": "fas fa-credit-card",
"badge_color": "primary",
"completion": "sheet",
"available": true,
"reason": null
},
{
"kind": "gateway",
"gateway": "alphapay",
"method": "wechat_pay",
"label": "AlphaPay WeChat Pay",
"icon": "fab fa-weixin",
"badge_color": "success",
"available": true,
"reason": null,
"completion": "redirect"
}
],
"next": {
"kind": "sheet",
"checkout_url": null,
"client_secret": "pi_3Qx0example_secret_9fA2",
"publishable_key": "pk_live_51Hxxxxxx",
"merchant_country": "CA",
"merchant_display_name": "Waypoint Logistics"
}
}
lockerResaleTerms
query
REST twin: GET /app/v1/locker-resale/terms
The terms a buyer agrees to when opening a handover, in their language. The operator's own wording replaces ours whole when they have set it; a language they left blank falls back to ours. The version comes back with the text — the deal records which wording its buyer actually accepted, so a later edit never rewrites what somebody agreed to. Needs the locker.resale capability on the token (granted to the developer as app.locker_resale).
query LockerResaleTerms($locale: String) {
lockerResaleTerms(locale: $locale)
}
Variables
{}
Response example
{
"result": true,
"version": "platform-v1",
"source": "platform",
"text": [
"You are arranging a handover through a locker. The cabinet passes the goods from the seller to you; we are not the seller and we are not part of your sale.",
"We never hold the money for the goods. Whatever you pay the seller, you pay them directly, and any dispute about it is between the two of you.",
"The seller pays a cabinet fee when the goods go in. It is not refunded — not if the deal is called off, and not if you never come."
]
}
lockerResaleDeals
query
REST twin: GET /app/v1/locker-resale/deals
Deals the authenticated caller is a party to, as buyer or as seller. Each deal is presented as that party may see it: the pickup code and the cabinet address reach the buyer only once the seller has released them. When one account is both the buyer and the seller of a deal — the seller needs no account, so a seller contact that matches the caller's own is linked to it — the deal is answered as the seller, which is the larger of the two views, with also_buyer true and the buyer's pickup_code included. Needs the locker.resale capability on the token (granted to the developer as app.locker_resale).
query LockerResaleDeals($status: String, $open: Boolean, $page: Int, $perPage: Int) {
lockerResaleDeals(status: $status, open: $open, page: $page, perPage: $perPage)
}
Variables
{}
Response example
{
"result": true,
"deals": [
{
"id": 5120,
"deal_no": "RS260917A1B2C3",
"status": "awaiting_release",
"status_label": "Awaiting pickup code",
"role": "seller",
"item_title": "Road bike",
"item_price": 420,
"item_currency": "CAD",
"created_at": "2026-09-17 10:02:11",
"dropoff_deadline": "2026-09-20 10:02:11",
"release_due_at": "2026-09-18 11:40:00",
"expires_at": "2026-09-20 11:40:00",
"dropoff_code": null,
"return_code": null,
"outstanding_amount": "0.00",
"can_release": true,
"can_revoke": false,
"can_abort": true,
"location": "Central Mall",
"compartment_number": "A07"
}
],
"pagination": {
"page": 1,
"per_page": 25,
"total": 1
}
}
createLockerResaleDeal
mutation
REST twin: POST /app/v1/locker-resale/deals
The caller is the seller: their own name, phone, e-mail and language come from the account. The buyer needs no account — a phone number or an e-mail address is enough, and one of the two is required; the pickup code is sent there once the seller releases it. The answer carries the seller's drop-off code, which is also sent by SMS or e-mail. Opening a deal as the buyer is no longer supported. Needs the locker.resale capability on the token (granted to the developer as app.locker_resale).
mutation CreateLockerResaleDeal($buyerName: String, $buyerPhone: String, $buyerEmail: String, $itemTitle: String, $itemPrice: Float, $itemCurrency: String) {
createLockerResaleDeal(buyerName: $buyerName, buyerPhone: $buyerPhone, buyerEmail: $buyerEmail, itemTitle: $itemTitle, itemPrice: $itemPrice, itemCurrency: $itemCurrency)
}
Variables
{
"buyerName": "Alex Tremblay",
"buyerPhone": "+15145550188",
"buyerEmail": null,
"itemTitle": "Road bike",
"itemPrice": 420,
"itemCurrency": "CAD"
}
Response example
{
"result": true,
"deal": {
"id": 5120,
"deal_no": "RS260917A1B2C3",
"status": "awaiting_dropoff",
"status_label": "Awaiting drop-off",
"role": "seller",
"item_title": "Road bike",
"item_price": 420,
"item_currency": "CAD",
"created_at": "2026-09-17 10:02:11",
"dropoff_deadline": "2026-09-20 10:02:11",
"release_due_at": null,
"expires_at": null,
"can_cancel": true,
"dropoff_code": "483920",
"return_code": null,
"outstanding_amount": "0.00",
"payment": null,
"can_release": false,
"can_revoke": false,
"can_abort": false,
"location": null,
"compartment_number": null,
"address": null
}
}
lockerResaleDeal
query
REST twin: GET /app/v1/locker-resale/deals/{id}
One deal the caller is a party to, with the part of its timeline that party may read. Failed and refused attempts, and the other party's contact details, are never included. When one account is both the buyer and the seller of a deal — the seller needs no account, so a seller contact that matches the caller's own is linked to it — the deal is answered as the seller, which is the larger of the two views, with also_buyer true and the buyer's pickup_code included. Needs the locker.resale capability on the token (granted to the developer as app.locker_resale).
query LockerResaleDeal($id: Int!) {
lockerResaleDeal(id: $id)
}
Variables
{
"id": 5120
}
Response example
{
"result": true,
"deal": {
"id": 5120,
"deal_no": "RS260917A1B2C3",
"status": "active",
"status_label": "Ready for pickup",
"role": "buyer",
"item_title": "Road bike",
"item_price": 420,
"item_currency": "CAD",
"created_at": "2026-09-17 10:02:11",
"dropoff_deadline": "2026-09-20 10:02:11",
"release_due_at": "2026-09-18 11:40:00",
"expires_at": "2026-09-20 11:40:00",
"pickup_code": "7GQ4M2",
"location": "Central Mall",
"compartment_number": "A07",
"address": "980 Rue Sainte-Catherine, Montreal, QC, H3B 1E5"
},
"timeline": [
{
"type": "created",
"label": "Deal opened",
"role": "buyer",
"occurred_at": "2026-09-17 10:02:11"
},
{
"type": "dropoff_closed",
"label": "Goods placed, door closed",
"role": "seller",
"occurred_at": "2026-09-17 11:40:00"
},
{
"type": "released",
"label": "Pickup code sent to buyer",
"role": "seller",
"occurred_at": "2026-09-17 12:05:44"
}
]
}
releaseLockerResaleCode
mutation
REST twin: POST /app/v1/locker-resale/deals/{id}/release
Seller only. Nothing releases a pickup code automatically: until the seller presses this, the buyer has no code and does not know which cabinet holds the goods. Calling it twice re-sends the same code rather than replacing it. Needs the locker.resale capability on the token (granted to the developer as app.locker_resale).
mutation ReleaseLockerResaleCode($id: Int!) {
releaseLockerResaleCode(id: $id)
}
Variables
{
"id": 5120
}
Response example
{
"result": true,
"deal": {
"id": 5120,
"deal_no": "RS260917A1B2C3",
"status": "active",
"status_label": "Ready for pickup",
"role": "seller",
"item_title": "Road bike",
"item_price": 420,
"item_currency": "CAD",
"created_at": "2026-09-17 10:02:11",
"dropoff_deadline": "2026-09-20 10:02:11",
"release_due_at": "2026-09-18 11:40:00",
"expires_at": "2026-09-20 11:40:00",
"dropoff_code": null,
"return_code": null,
"outstanding_amount": "0.00",
"can_release": false,
"can_revoke": true,
"can_abort": true,
"location": "Central Mall",
"compartment_number": "A07"
}
}
revokeLockerResaleCode
mutation
REST twin: POST /app/v1/locker-resale/deals/{id}/revoke-code
Seller only, and only until the buyer's compartment has actually been opened. The old code stops working at once and the buyer is told immediately; the seller can then send a new one. The send-by deadline is NOT reset by this, so revoking after it has passed leaves the deal to be returned to the seller at the next check. Needs the locker.resale capability on the token (granted to the developer as app.locker_resale).
mutation RevokeLockerResaleCode($id: Int!) {
revokeLockerResaleCode(id: $id)
}
Variables
{
"id": 5120
}
Response example
{
"result": true,
"deal": {
"id": 5120,
"deal_no": "RS260917A1B2C3",
"status": "awaiting_release",
"status_label": "Awaiting pickup code",
"role": "seller",
"item_title": "Road bike",
"item_price": 420,
"item_currency": "CAD",
"created_at": "2026-09-17 10:02:11",
"dropoff_deadline": "2026-09-20 10:02:11",
"release_due_at": "2026-09-18 11:40:00",
"expires_at": "2026-09-20 11:40:00",
"dropoff_code": null,
"return_code": null,
"outstanding_amount": "0.00",
"can_release": true,
"can_revoke": false,
"can_abort": true,
"location": "Central Mall",
"compartment_number": "A07",
"code_revoked_count": 1
}
}
abortLockerResaleDeal
mutation
REST twin: POST /app/v1/locker-resale/deals/{id}/abort
Seller only. The buyer's code stops working, the seller is sent a return code for the goods, and the cabinet fee already paid is not refunded. Needs the locker.resale capability on the token (granted to the developer as app.locker_resale).
mutation AbortLockerResaleDeal($id: Int!, $reason: String) {
abortLockerResaleDeal(id: $id, reason: $reason)
}
Variables
{
"id": 5120,
"reason": "Buyer never paid me"
}
Response example
{
"result": true,
"deal": {
"id": 5120,
"deal_no": "RS260917A1B2C3",
"status": "aborted",
"status_label": "Called off",
"role": "seller",
"item_title": "Road bike",
"item_price": 420,
"item_currency": "CAD",
"created_at": "2026-09-17 10:02:11",
"dropoff_deadline": "2026-09-20 10:02:11",
"release_due_at": "2026-09-18 11:40:00",
"expires_at": "2026-09-20 11:40:00",
"dropoff_code": null,
"return_code": "4KD9P1",
"outstanding_amount": "0.00",
"can_release": false,
"can_revoke": false,
"can_abort": false,
"location": "Central Mall",
"compartment_number": "A07"
}
}
cancelLockerResaleDeal
mutation
REST twin: POST /app/v1/locker-resale/deals/{id}/cancel
Either party, and only while the goods are still with the seller. Once they are in a cabinet the deal can only be called off by the seller, who then has to collect them. Needs the locker.resale capability on the token (granted to the developer as app.locker_resale).
mutation CancelLockerResaleDeal($id: Int!) {
cancelLockerResaleDeal(id: $id)
}
Variables
{
"id": 5120
}
Response example
{
"result": true,
"deal": {
"id": 5120,
"deal_no": "RS260917A1B2C3",
"status": "cancelled",
"status_label": "Cancelled",
"role": "seller",
"item_title": "Road bike",
"item_price": 420,
"item_currency": "CAD",
"created_at": "2026-09-17 10:02:11",
"dropoff_deadline": "2026-09-20 10:02:11",
"release_due_at": "2026-09-18 11:40:00",
"expires_at": "2026-09-20 11:40:00",
"dropoff_code": null,
"return_code": null,
"outstanding_amount": "0.00",
"can_release": false,
"can_revoke": false,
"can_abort": false,
"location": "Central Mall",
"compartment_number": "A07"
}
}
reportLockerResaleDeal
mutation
REST twin: POST /app/v1/locker-resale/deals/{id}/report
The buyer says what came out of the compartment was not what was agreed. Buyer only, only after collecting, and only within seven days — after that a compartment has been reused and the evidence is no longer about this handover. Filing a report changes nothing about the deal: it stays completed, no money moves, nobody is suspended, and the platform does not arbitrate. It records what was said so the operator can read it beside the door logs and photos the cabinet produced by itself. Photos are base64 strings over GraphQL and multipart files (or base64) over REST, up to five. Needs the locker.resale capability on the token (granted to the developer as app.locker_resale).
mutation ReportLockerResaleDeal($id: Int!, $reason: String, $description: String, $photos: [String]) {
reportLockerResaleDeal(id: $id, reason: $reason, description: $description, photos: $photos)
}
Variables
{
"id": 5120,
"reason": "not_as_described",
"description": "The box held a different model."
}
Response example
{
"result": true,
"report": {
"id": 91,
"reason": "not_as_described",
"reason_label": "Not what was agreed",
"status": "open",
"photos": 2,
"created_at": "2026-09-18 09:12:40"
},
"message": "Your report has been passed to the operator of this locker. We do not hold the money for the goods, so anything you paid the seller stays between the two of you."
}
Store
platformAppStorefront
query
REST twin: GET /app/v1/storefront
Signed in (bearer token of the app session). Which home cards the app shows (A3, A4): `mall` (null when the platform gave the client no mall or the client has no marketplace) with `operations_enabled` (false = the mall is its store directory only), `stores_available` (listed stores exist), `group_buy_available` (the app's group_buy capability is on and a group buy can be ordered now). Never 404 for a known client: a client without the marketplace answers all off. Same body as REST.
query PlatformAppStorefront($locale: String) {
platformAppStorefront(locale: $locale)
}
Variables
{}
Response example
{
"result": true,
"data": {
"mall": {
"enabled": true,
"operations_enabled": true,
"name": "Client",
"web_url": "https://shop.example.com/mall"
},
"stores_available": true,
"group_buy_available": true,
"currency": "CAD"
}
}
platformAppMallHome
query
REST twin: GET /app/v1/mall/home
Signed in (bearer token of the app session). The blocks the client shows on the mall home, in the client's order: `banners`, `categories`, `group_buys`, `coupons`, `featured_stores`, `featured_products`, `stores` (first 12 of the directory). An empty block is left out; `group_buys` is left out when the app's group_buy capability is off. Without mall operations or without a mall: `directory_only: true` and no blocks — show mall/stores. 404 STOREFRONT_UNAVAILABLE when the client has no marketplace. Same body as REST. Every product card carries currency: the currency code its prices are in (the store's Client's).
query PlatformAppMallHome($locale: String) {
platformAppMallHome(locale: $locale)
}
Variables
{}
Response example
{
"result": true,
"data": {
"directory_only": false,
"blocks": [
{
"key": "categories",
"items": [
{
"id": 1,
"name": "Food",
"children": [
{
"id": 2,
"name": "Pizza"
}
]
}
]
},
{
"key": "group_buys",
"items": [
{
"code": "PZBOX7",
"title": "Friday pizza box",
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"opens_at": "2026-10-14T14:00:00-04:00",
"cutoff_at": "2026-10-16T15:00:00-04:00",
"url": "https://shop.example.com/portal/group-buy/PZBOX7"
}
]
},
{
"key": "coupons",
"items": [
{
"id": 1,
"name": "5 off your first order",
"description": "Orders over 30",
"code": "WELCOME5",
"ends_on": "2026-12-31"
}
]
},
{
"key": "featured_stores",
"items": [
{
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place",
"description": "",
"store_type": "restaurant",
"store_type_label": "Restaurant",
"tags": [
"pizza",
"halal"
],
"logo_url": "https://shop.example.com/storage/stores/31/logo.png",
"cover_url": "https://shop.example.com/storage/stores/31/cover.jpg",
"rating_avg": 4.6,
"rating_count": 38,
"address": "12 King St W",
"city": "Toronto"
}
]
},
{
"key": "featured_products",
"items": [
{
"id": 1,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Margherita",
"description": "Tomato, mozzarella, basil",
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg",
"price": 12,
"list_price": 14,
"compare_at_price": 14,
"on_sale": true,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [
{
"code": "gluten",
"label": "Gluten"
},
{
"code": "milk",
"label": "Milk"
}
],
"affiliate": false,
"external_url": null
}
]
},
{
"key": "stores",
"items": [
{
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place",
"description": "",
"store_type": "restaurant",
"store_type_label": "Restaurant",
"tags": [
"pizza",
"halal"
],
"logo_url": "https://shop.example.com/storage/stores/31/logo.png",
"cover_url": "https://shop.example.com/storage/stores/31/cover.jpg",
"rating_avg": 4.6,
"rating_count": 38,
"address": "12 King St W",
"city": "Toronto"
}
]
}
]
}
}
platformAppMallStores
query
REST twin: GET /app/v1/mall/stores
Signed in (bearer token of the app session). Every listed store in the mall (every listed store of the client when it has no mall), in the client's order then by rating; filters by store type, tag (case-insensitive) and words (name, names in other languages, tags); 24 a page. `facets` lists the types and tags with their store counts, computed over all stores. Same body as REST.
query PlatformAppMallStores($type: String, $tag: String, $q: String, $page: Int, $locale: String) {
platformAppMallStores(type: $type, tag: $tag, q: $q, page: $page, locale: $locale)
}
Variables
{
"tag": "pizza"
}
Response example
{
"result": true,
"data": {
"stores": [
{
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place",
"description": "",
"store_type": "restaurant",
"store_type_label": "Restaurant",
"tags": [
"pizza",
"halal"
],
"logo_url": "https://shop.example.com/storage/stores/31/logo.png",
"cover_url": "https://shop.example.com/storage/stores/31/cover.jpg",
"rating_avg": 4.6,
"rating_count": 38,
"address": "12 King St W",
"city": "Toronto"
}
],
"facets": {
"types": [
{
"type": "restaurant",
"label": "Restaurant",
"count": 1
}
],
"tags": [
{
"tag": "pizza",
"count": 1
},
{
"tag": "halal",
"count": 1
}
]
},
"filters": {
"type": "",
"tag": "pizza",
"q": ""
},
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 24,
"total": 1,
"has_more": false
}
}
}
platformAppMallSearch
query
REST twin: GET /app/v1/mall/search
Signed in (bearer token of the app session). Stores whose name, other-language names or tags contain the words, and listed products whose name contains them, grouped by store (at most 60 products). Empty words: empty lists. Needs a mall with operations: 404 MALL_UNAVAILABLE otherwise. Same body as REST. Every product card carries currency: the currency code its prices are in (the store's Client's).
query PlatformAppMallSearch($q: String, $locale: String) {
platformAppMallSearch(q: $q, locale: $locale)
}
Variables
{
"q": "marg"
}
Response example
{
"result": true,
"data": {
"q": "marg",
"stores": [],
"products_by_store": [
{
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place",
"description": "",
"store_type": "restaurant",
"store_type_label": "Restaurant",
"tags": [
"pizza",
"halal"
],
"logo_url": "https://shop.example.com/storage/stores/31/logo.png",
"cover_url": "https://shop.example.com/storage/stores/31/cover.jpg",
"rating_avg": 4.6,
"rating_count": 38,
"address": "12 King St W",
"city": "Toronto"
},
"products": [
{
"id": 1,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Margherita",
"description": "Tomato, mozzarella, basil",
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg",
"price": 12,
"list_price": 14,
"compare_at_price": 14,
"on_sale": true,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [
{
"code": "gluten",
"label": "Gluten"
},
{
"code": "milk",
"label": "Milk"
}
],
"affiliate": false,
"external_url": null
}
]
}
]
}
}
platformAppMallCategory
query
REST twin: GET /app/v1/mall/categories/{categoryId}
Signed in (bearer token of the app session). One active mall category (a top category includes its sub-categories) and one page of its products, 24 a page, each with its own store. Needs a mall with operations. Same body as REST. Every product card carries currency: the currency code its prices are in (the store's Client's).
query PlatformAppMallCategory($category_id: Int!, $page: Int, $locale: String) {
platformAppMallCategory(category_id: $category_id, page: $page, locale: $locale)
}
Variables
{
"category_id": 1
}
Response example
{
"result": true,
"data": {
"category": {
"id": 1,
"name": "Food",
"children": [
{
"id": 2,
"name": "Pizza"
}
],
"parent": null
},
"products": [
{
"id": 1,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Margherita",
"description": "Tomato, mozzarella, basil",
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg",
"price": 12,
"list_price": 14,
"compare_at_price": 14,
"on_sale": true,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [
{
"code": "gluten",
"label": "Gluten"
},
{
"code": "milk",
"label": "Milk"
}
],
"affiliate": false,
"external_url": null
}
],
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 24,
"total": 1,
"has_more": false
}
}
}
platformAppMallGroupBuys
query
REST twin: GET /app/v1/mall/group-buys
Signed in (bearer token of the app session). The group buys buyers may order now, soonest cutoff first; each `url` opens its page on the client's white-label host. In a mall with operations `category` narrows them to those carrying a product of that mall category and `categories` is the tree to choose from; otherwise the group buys of every listed store and no categories. Empty when the app's group_buy capability is off. Same body as REST.
query PlatformAppMallGroupBuys($category: Int, $locale: String) {
platformAppMallGroupBuys(category: $category, locale: $locale)
}
Variables
{}
Response example
{
"result": true,
"data": {
"group_buys": [
{
"code": "PZBOX7",
"title": "Friday pizza box",
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"opens_at": "2026-10-14T14:00:00-04:00",
"cutoff_at": "2026-10-16T15:00:00-04:00",
"url": "https://shop.example.com/portal/group-buy/PZBOX7"
}
],
"categories": [
{
"id": 1,
"name": "Food",
"children": [
{
"id": 2,
"name": "Pizza"
}
]
}
],
"category_id": 0
}
}
platformAppStore
query
REST twin: GET /app/v1/stores/{slug}
Signed in (bearer token of the app session). A listed store of the client (also one that is not in the mall): profile, whether it takes orders now (`accepting_orders`, `intake_blockers`), the order types it can take with ASAP and the time slots (store clock, `timezone`), categories, the menu as `sections` (category order; products of no active category last, `label` null) with each item's price, stock state and option groups (each group and value with `image_url`, null without an image), the featured row and banners (unfiltered view only), its group buys and the 10 newest approved reviews. `q`, `cat` (with its sub-categories) and `sort` filter the menu like the web. `table` enters the dine-in context: `dine_in.table_code` is set and only dine_in is offered; an unknown table, or a store without dine-in, answers 404 TABLE_NOT_FOUND. Without `table`, dine_in is never listed. Same body as REST. Every product card carries currency: the currency code its prices are in (the store's Client's). Added (docs/STORE-STYLES-DESIGN.md P2): design {style simple|multi_page — the style this answer is drawn in (a table always gets simple), published_version (0 = never published; the store then looks as before), nav_categories[{id, name}], top_blocks — in the simple style, the blocks above the menu on the unfiltered view of a store that published a design; banners, announcement and featured stay in the answer as before}. In the multi-page style the app opens /home, /categories/{categoryId} and the product page's page_blocks. Each block is {id, type, settings, data}. type is one of banner, announcement, category_entry, product_grid, image_text, video, coupons, group_buys, reviews, category_header, product_list (category pages), buy_box, details, specs, product_reviews, related (product pages). settings holds only how the block is drawn (columns, layout, height, tone, display, image_position, autoplay, page_size, show_*); data holds what it shows, resolved in the answer language with absolute URLs: banner {slides[{image_url, link}]}, announcement {text}, category_entry {title, categories[{id, name, image_url}]}, product_grid and related {title, products[product card]}, image_text {image_url, title, body, link, button_text}, video {title, kind embed|file, url}, coupons {title, coupons[{id, name, description, code, ends_on}]}, group_buys {title, group_buys[]}, reviews {title, reviews[{rating, comment, reviewer, merchant_reply, created_at}]}, category_header {name, image_url, description}, product_list {items[product card], pagination, subcategories[{id, name}], filters{q, sort, sub, in_stock}}, details {title, body, image_urls[]}, specs {title, fields[{label, value}]}, product_reviews {title, reviews[{rating, comment, created_at}]}, buy_box {} (the product answer itself). A block with nothing to show is left out; unknown types should be skipped.
query PlatformAppStore($slug: String!, $q: String, $sort: String, $cat: Int, $table: String, $locale: String) {
platformAppStore(slug: $slug, q: $q, sort: $sort, cat: $cat, table: $table, locale: $locale)
}
Variables
{
"slug": "pizza-place-1"
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place",
"description": "",
"store_type": "restaurant",
"store_type_label": "Restaurant",
"tags": [
"pizza",
"halal"
],
"logo_url": "https://shop.example.com/storage/stores/31/logo.png",
"cover_url": "https://shop.example.com/storage/stores/31/cover.jpg",
"rating_avg": 4.6,
"rating_count": 38,
"address": "12 King St W",
"city": "Toronto",
"phone": "",
"min_order_amount": 15,
"prep_time_minutes": 20,
"timezone": "America/Toronto",
"announcement": "",
"social_links": {
"instagram": "https://instagram.com/pizzaplace"
},
"food": true,
"guest_orders": true,
"currency": "CAD",
"web_url": "https://shop.example.com/s/pizza-place-1"
},
"accepting_orders": true,
"intake_blockers": [],
"order_types": [
{
"type": "collection",
"label": "Pick-up",
"accepting": true,
"asap": true,
"days": [
{
"date": "2026-10-14",
"slots": [
"15:30",
"15:45",
"16:00"
]
}
]
}
],
"dine_in": null,
"banners": [
{
"image_url": "https://shop.example.com/storage/stores/31/banner.jpg",
"link": "https://shop.example.com/s/pizza-place-1/i/1"
}
],
"categories": [
{
"id": 1,
"parent_id": null,
"name": "Pizzas"
}
],
"sections": [
{
"category_id": 1,
"label": "Pizzas",
"items": [
{
"id": 1,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Margherita",
"description": "Tomato, mozzarella, basil",
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg",
"price": 12,
"list_price": 14,
"compare_at_price": 14,
"on_sale": true,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [
{
"code": "gluten",
"label": "Gluten"
},
{
"code": "milk",
"label": "Milk"
}
],
"affiliate": false,
"external_url": null,
"option_groups": [
{
"id": 1,
"label": "Size",
"type": "radio",
"required": true,
"min": 0,
"max": null,
"image_url": null,
"values": [
{
"id": 1,
"label": "12 inch",
"delta": 0,
"default": false,
"image_url": null
},
{
"id": 2,
"label": "16 inch",
"delta": 4,
"default": false,
"image_url": null
}
]
}
]
}
]
},
{
"category_id": null,
"label": null,
"items": [
{
"id": 2,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Cola",
"description": "",
"image_url": null,
"price": 2.5,
"list_price": 2.5,
"compare_at_price": null,
"on_sale": false,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [],
"affiliate": false,
"external_url": null,
"option_groups": []
}
]
}
],
"featured": [
{
"id": 1,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Margherita",
"description": "Tomato, mozzarella, basil",
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg",
"price": 12,
"list_price": 14,
"compare_at_price": 14,
"on_sale": true,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [
{
"code": "gluten",
"label": "Gluten"
},
{
"code": "milk",
"label": "Milk"
}
],
"affiliate": false,
"external_url": null,
"option_groups": [
{
"id": 1,
"label": "Size",
"type": "radio",
"required": true,
"min": 0,
"max": null,
"image_url": null,
"values": [
{
"id": 1,
"label": "12 inch",
"delta": 0,
"default": false,
"image_url": null
},
{
"id": 2,
"label": "16 inch",
"delta": 4,
"default": false,
"image_url": null
}
]
}
]
}
],
"group_buys": [
{
"code": "PZBOX7",
"title": "Friday pizza box",
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"opens_at": "2026-10-14T14:00:00-04:00",
"cutoff_at": "2026-10-16T15:00:00-04:00",
"url": "https://shop.example.com/portal/group-buy/PZBOX7"
}
],
"reviews": [
{
"rating": 5,
"comment": "Great crust",
"reviewer": null,
"merchant_reply": "Thank you!",
"created_at": "2026-10-14T15:00:00-04:00"
}
],
"filters": {
"q": "",
"sort": "",
"cat": 0
},
"design": {
"style": "simple",
"published_version": 2,
"nav_categories": [
{
"id": 5,
"name": "Kitchen"
}
],
"top_blocks": [
{
"id": "b9a8b7c6d5",
"type": "announcement",
"settings": {
"tone": "accent"
},
"data": {
"text": "Closed on Monday"
}
}
]
}
}
}
platformAppStoreHome
query
REST twin: GET /app/v1/stores/{slug}/home
Signed in (bearer token of the app session). The home page of a store in the multi-page style (docs/STORE-STYLES-DESIGN.md P2): the store card, accepting_orders, design {style, published_version, nav_categories[{id, name}] — the top-level categories for the category bar} and blocks in the order the merchant published. A store in the simple style answers 404 STORE_PAGE_NOT_FOUND (its home is the store page); an unknown store 404 STORE_NOT_FOUND. Each block is {id, type, settings, data}. type is one of banner, announcement, category_entry, product_grid, image_text, video, coupons, group_buys, reviews, category_header, product_list (category pages), buy_box, details, specs, product_reviews, related (product pages). settings holds only how the block is drawn (columns, layout, height, tone, display, image_position, autoplay, page_size, show_*); data holds what it shows, resolved in the answer language with absolute URLs: banner {slides[{image_url, link}]}, announcement {text}, category_entry {title, categories[{id, name, image_url}]}, product_grid and related {title, products[product card]}, image_text {image_url, title, body, link, button_text}, video {title, kind embed|file, url}, coupons {title, coupons[{id, name, description, code, ends_on}]}, group_buys {title, group_buys[]}, reviews {title, reviews[{rating, comment, reviewer, merchant_reply, created_at}]}, category_header {name, image_url, description}, product_list {items[product card], pagination, subcategories[{id, name}], filters{q, sort, sub, in_stock}}, details {title, body, image_urls[]}, specs {title, fields[{label, value}]}, product_reviews {title, reviews[{rating, comment, created_at}]}, buy_box {} (the product answer itself). A block with nothing to show is left out; unknown types should be skipped. Same body as REST.
query PlatformAppStoreHome($slug: String!, $locale: String) {
platformAppStoreHome(slug: $slug, locale: $locale)
}
Variables
{
"slug": "home-goods-1"
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "home-goods-1",
"name": "Home Goods",
"description": "",
"store_type": "retail",
"store_type_label": "Retail Store",
"tags": [],
"logo_url": null,
"cover_url": null,
"rating_avg": 0,
"rating_count": 0,
"address": "1 Main St",
"city": "Toronto",
"currency": "CAD",
"web_url": "https://shop.example.com/s/home-goods-1"
},
"accepting_orders": true,
"design": {
"style": "multi_page",
"published_version": 3,
"nav_categories": [
{
"id": 5,
"name": "Kitchen"
},
{
"id": 7,
"name": "Garden"
}
]
},
"blocks": [
{
"id": "b1a2b3c4d5",
"type": "banner",
"settings": {
"height": "medium",
"autoplay": true
},
"data": {
"slides": [
{
"image_url": "https://shop.example.com/storage/merchant_stores/1/banner_1.jpg",
"link": "https://shop.example.com/s/home-goods-1/c/5"
}
]
}
},
{
"id": "b2b3c4d5e6",
"type": "category_entry",
"settings": {
"columns": 4,
"display": "image"
},
"data": {
"title": "Categories",
"categories": [
{
"id": 5,
"name": "Kitchen",
"image_url": null
},
{
"id": 7,
"name": "Garden",
"image_url": null
}
]
}
},
{
"id": "b3c4d5e6f7",
"type": "product_grid",
"settings": {
"limit": 8,
"columns": 4,
"layout": "grid"
},
"data": {
"title": "New arrivals",
"products": [
{
"id": 12,
"store": {
"id": 1,
"slug": "home-goods-1",
"name": "Home Goods"
},
"name": "Copper pan",
"description": "",
"image_url": "https://shop.example.com/storage/products/12/pan.jpg",
"price": 40,
"list_price": 40,
"compare_at_price": null,
"on_sale": false,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [],
"affiliate": false,
"external_url": null
}
]
}
},
{
"id": "b4d5e6f7a8",
"type": "image_text",
"settings": {
"image_position": "left"
},
"data": {
"image_url": "https://shop.example.com/storage/merchant_stores/1/banner_2.jpg",
"title": "Our story",
"body": "Made by hand since 1990",
"link": "",
"button_text": ""
}
},
{
"id": "b5e6f7a8b9",
"type": "video",
"settings": {
"autoplay": false
},
"data": {
"title": "",
"kind": "embed",
"url": "https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
}
}
]
}
}
platformAppStoreCategory
query
REST twin: GET /app/v1/stores/{slug}/categories/{categoryId}
Signed in (bearer token of the app session). A category page of a store in the multi-page style (docs/STORE-STYLES-DESIGN.md P2): store, category {id, parent_id, name}, accepting_orders, design (as the home page) and blocks — the category's own layout when it has one, else the store's; the product_list block holds the category's items and its sub-categories', paged and filtered by sort (price_asc, price_desc, name, newest), sub (a direct sub-category), in_stock and page. 404 STORE_PAGE_NOT_FOUND in the simple style, CATEGORY_NOT_FOUND for a category the store does not have (or has switched off). Each block is {id, type, settings, data}. type is one of banner, announcement, category_entry, product_grid, image_text, video, coupons, group_buys, reviews, category_header, product_list (category pages), buy_box, details, specs, product_reviews, related (product pages). settings holds only how the block is drawn (columns, layout, height, tone, display, image_position, autoplay, page_size, show_*); data holds what it shows, resolved in the answer language with absolute URLs: banner {slides[{image_url, link}]}, announcement {text}, category_entry {title, categories[{id, name, image_url}]}, product_grid and related {title, products[product card]}, image_text {image_url, title, body, link, button_text}, video {title, kind embed|file, url}, coupons {title, coupons[{id, name, description, code, ends_on}]}, group_buys {title, group_buys[]}, reviews {title, reviews[{rating, comment, reviewer, merchant_reply, created_at}]}, category_header {name, image_url, description}, product_list {items[product card], pagination, subcategories[{id, name}], filters{q, sort, sub, in_stock}}, details {title, body, image_urls[]}, specs {title, fields[{label, value}]}, product_reviews {title, reviews[{rating, comment, created_at}]}, buy_box {} (the product answer itself). A block with nothing to show is left out; unknown types should be skipped. Same body as REST.
query PlatformAppStoreCategory($slug: String!, $category_id: Int!, $sort: String, $sub: Int, $in_stock: Boolean, $page: Int, $locale: String) {
platformAppStoreCategory(slug: $slug, category_id: $category_id, sort: $sort, sub: $sub, in_stock: $in_stock, page: $page, locale: $locale)
}
Variables
{
"slug": "home-goods-1",
"category_id": 5
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "home-goods-1",
"name": "Home Goods"
},
"category": {
"id": 5,
"parent_id": null,
"name": "Kitchen"
},
"accepting_orders": true,
"design": {
"style": "multi_page",
"published_version": 3,
"nav_categories": [
{
"id": 5,
"name": "Kitchen"
},
{
"id": 7,
"name": "Garden"
}
]
},
"blocks": [
{
"id": "c1d2e3f4a5",
"type": "category_header",
"settings": {
"show_image": true,
"show_description": true
},
"data": {
"name": "Kitchen",
"image_url": null,
"description": "Pots, pans and knives"
}
},
{
"id": "c2d3e4f5a6",
"type": "product_list",
"settings": {
"page_size": 24,
"columns": 4,
"show_filters": true
},
"data": {
"items": [
{
"id": 12,
"store": {
"id": 1,
"slug": "home-goods-1",
"name": "Home Goods"
},
"name": "Copper pan",
"description": "",
"image_url": "https://shop.example.com/storage/products/12/pan.jpg",
"price": 40,
"list_price": 40,
"compare_at_price": null,
"on_sale": false,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [],
"affiliate": false,
"external_url": null
}
],
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 24,
"total": 1,
"has_more": false
},
"subcategories": [
{
"id": 6,
"name": "Knives"
}
],
"filters": {
"q": "",
"sort": "",
"sub": 0,
"in_stock": false
}
}
}
]
}
}
platformAppStoreProduct
query
REST twin: GET /app/v1/stores/{slug}/products/{productId}
Added (docs/SKU-STRUCTURES-DESIGN.md SX3, SX11): custom_fields [{key, label, value}] — the merchant's custom SKU fields shown to buyers, in the request's language — only when the product has any. Signed in (bearer token of the app session). One product on sale in the store: the card fields plus `sku`, all `images`, active `variants` with their storefront price, `option_groups` (each group and value with `image_url`, null without an image), `group_buy` (lowest group price while an orderable campaign carries it, else null), the 20 newest approved reviews and up to 6 related products of the same category. Same body as REST. Every product card carries currency: the currency code its prices are in (the store's Client's). Added (docs/STORE-STYLES-DESIGN.md P2): page_blocks — in the multi-page style, the product page's blocks in order (the product's own layout, else the store's; buy_box marks where this answer's purchase area goes); null in the simple style. Each block is {id, type, settings, data}. type is one of banner, announcement, category_entry, product_grid, image_text, video, coupons, group_buys, reviews, category_header, product_list (category pages), buy_box, details, specs, product_reviews, related (product pages). settings holds only how the block is drawn (columns, layout, height, tone, display, image_position, autoplay, page_size, show_*); data holds what it shows, resolved in the answer language with absolute URLs: banner {slides[{image_url, link}]}, announcement {text}, category_entry {title, categories[{id, name, image_url}]}, product_grid and related {title, products[product card]}, image_text {image_url, title, body, link, button_text}, video {title, kind embed|file, url}, coupons {title, coupons[{id, name, description, code, ends_on}]}, group_buys {title, group_buys[]}, reviews {title, reviews[{rating, comment, reviewer, merchant_reply, created_at}]}, category_header {name, image_url, description}, product_list {items[product card], pagination, subcategories[{id, name}], filters{q, sort, sub, in_stock}}, details {title, body, image_urls[]}, specs {title, fields[{label, value}]}, product_reviews {title, reviews[{rating, comment, created_at}]}, buy_box {} (the product answer itself). A block with nothing to show is left out; unknown types should be skipped. Added: share — how the store lets buyers share this product, null while it offers none: {url (the product's web page, on the client's white-label host when it has one), title, channels (the store's choice in display order, each one of system, wechat, whatsapp, facebook, x, telegram, line, linkedin, reddit, pinterest, threads, tumblr, viber, weibo, qq, sms, email, copy, qr; system = the phone's own share menu, wechat = WeChat, which has no share address: copy the url and open WeChat (weixin://) on a phone, show the url as a QR code to scan with WeChat elsewhere, copy = copy the url, qr = show the url as a QR code), links (channel => address to open, for the chosen network, sms and email channels)}. Skip a channel the app does not know. Show only the listed channels.
query PlatformAppStoreProduct($slug: String!, $product_id: Int!, $locale: String) {
platformAppStoreProduct(slug: $slug, product_id: $product_id, locale: $locale)
}
Variables
{
"slug": "pizza-place-1",
"product_id": 1
}
Response example
{
"result": true,
"data": {
"id": 1,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Margherita",
"description": "Tomato, mozzarella, basil",
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg",
"price": 12,
"list_price": 14,
"compare_at_price": 14,
"on_sale": true,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [
{
"code": "gluten",
"label": "Gluten"
},
{
"code": "milk",
"label": "Milk"
}
],
"affiliate": false,
"external_url": null,
"option_groups": [
{
"id": 1,
"label": "Size",
"type": "radio",
"required": true,
"min": 0,
"max": null,
"image_url": null,
"values": [
{
"id": 1,
"label": "12 inch",
"delta": 0,
"default": false,
"image_url": null
},
{
"id": 2,
"label": "16 inch",
"delta": 4,
"default": false,
"image_url": null
}
]
}
],
"sku": "MARGHERITA",
"images": [
"https://shop.example.com/storage/products/88/margherita.jpg"
],
"variants": [],
"accepting_orders": true,
"group_buy": {
"price": 10.5,
"code": "PZBOX7",
"title": "Friday pizza box",
"cutoff_at": "2026-10-16T15:00:00-04:00",
"url": "https://shop.example.com/portal/group-buy/PZBOX7"
},
"reviews": [
{
"rating": 5,
"comment": "Best in town",
"created_at": "2026-10-14T15:00:00-04:00"
}
],
"related": [],
"share": {
"url": "https://shop.example.com/s/pizza-place-1/i/1",
"title": "Margherita",
"channels": [
"system",
"whatsapp",
"facebook",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Margherita%20https%3A%2F%2Fshop.example.com%2Fs%2Fpizza-place-1%2Fi%2F1",
"facebook": "https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fshop.example.com%2Fs%2Fpizza-place-1%2Fi%2F1"
}
},
"page_blocks": [
{
"id": "p1a2b3c4d5",
"type": "buy_box",
"settings": {
"show_thumbnails": true
},
"data": []
},
{
"id": "p2b3c4d5e6",
"type": "details",
"settings": [],
"data": {
"title": "Details",
"body": "Heavy copper pan",
"image_urls": []
}
},
{
"id": "p3c4d5e6f7",
"type": "related",
"settings": {
"limit": 6
},
"data": {
"title": "Related products",
"products": [
{
"id": 12,
"store": {
"id": 1,
"slug": "home-goods-1",
"name": "Home Goods"
},
"name": "Copper pan",
"description": "",
"image_url": "https://shop.example.com/storage/products/12/pan.jpg",
"price": 40,
"list_price": 40,
"compare_at_price": null,
"on_sale": false,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [],
"affiliate": false,
"external_url": null
}
]
}
}
]
}
}
platformAppStoreCart
query
REST twin: GET /app/v1/stores/{slug}/cart
Signed in (bearer token of the app session). The buyer's cart in this store (empty when there is none). Signed in: the linked identity's cart — the same cart as on the web — or, for an account without a linked identity, the account's own cart. Guest: the cart of guest_cart_token (REST: header X-Guest-Cart-Token); `guest_cart_token` echoes the token in use (null without one). Line prices are display prices; checkout prices again. Same body as REST.
query PlatformAppStoreCart($slug: String!, $locale: String) {
platformAppStoreCart(slug: $slug, locale: $locale)
}
Variables
{
"slug": "pizza-place-1"
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"items": [
{
"id": 1,
"product_id": 1,
"product_variant_id": null,
"name": "Margherita",
"options_label": "16 inch",
"qty": 2,
"unit_price": 16,
"total": 32,
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg"
}
],
"subtotal": 32,
"count": 2,
"currency": "CAD"
}
}
platformAppStoreCartAdd
mutation
REST twin: POST /app/v1/stores/{slug}/cart/items
Signed in (bearer token of the app session). Put a product in the cart with the web rules: the store takes orders now (only dine-in when `table` names a table), the product is on sale, in stock and inside its mealtimes, a product with variants names its `product_variant_id`, and `options` keep the item's option rules ({product_options.id: [store_option_value_id, ...]}, the ids from option_groups[].id and values[].id). An identical line (product, variant, options) grows. A guest's first write issues the guest cart token: keep it (body `guest_cart_token` and response header guest_cart_token (REST: header X-Guest-Cart-Token)) and send it with every later cart call. Same body as REST.
mutation PlatformAppStoreCartAdd($slug: String!, $product_id: Int!, $product_variant_id: Int, $qty: Int!, $options: Json, $table: String, $locale: String) {
platformAppStoreCartAdd(slug: $slug, product_id: $product_id, product_variant_id: $product_variant_id, qty: $qty, options: $options, table: $table, locale: $locale)
}
Variables
{
"slug": "pizza-place-1",
"product_id": 1,
"qty": 2,
"options": {
"1": [
2
]
}
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"items": [
{
"id": 1,
"product_id": 1,
"product_variant_id": null,
"name": "Margherita",
"options_label": "16 inch",
"qty": 2,
"unit_price": 16,
"total": 32,
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg"
}
],
"subtotal": 32,
"count": 2,
"currency": "CAD"
}
}
platformAppStoreCartUpdate
mutation
REST twin: PATCH /app/v1/stores/{slug}/cart/items/{itemId}
Signed in (bearer token of the app session). Set the quantity of one line (items[].id); 0 removes it. A line that is not in this buyer's cart changes nothing and the cart is answered. Same body as REST.
mutation PlatformAppStoreCartUpdate($slug: String!, $item_id: Int!, $qty: Int!, $locale: String) {
platformAppStoreCartUpdate(slug: $slug, item_id: $item_id, qty: $qty, locale: $locale)
}
Variables
{
"slug": "pizza-place-1",
"item_id": 1,
"qty": 3
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"items": [
{
"id": 1,
"product_id": 1,
"product_variant_id": null,
"name": "Margherita",
"options_label": "16 inch",
"qty": 2,
"unit_price": 16,
"total": 32,
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg"
}
],
"subtotal": 32,
"count": 2,
"currency": "CAD"
}
}
platformAppStoreCartRemove
mutation
REST twin: DELETE /app/v1/stores/{slug}/cart/items/{itemId}
Signed in (bearer token of the app session). Remove one line (items[].id). A line that is not in this buyer's cart changes nothing and the cart is answered. Same body as REST.
mutation PlatformAppStoreCartRemove($slug: String!, $item_id: Int!, $locale: String) {
platformAppStoreCartRemove(slug: $slug, item_id: $item_id, locale: $locale)
}
Variables
{
"slug": "pizza-place-1",
"item_id": 1
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"items": [
{
"id": 1,
"product_id": 1,
"product_variant_id": null,
"name": "Margherita",
"options_label": "16 inch",
"qty": 2,
"unit_price": 16,
"total": 32,
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg"
}
],
"subtotal": 32,
"count": 2,
"currency": "CAD"
}
}
platformAppStoreCheckoutQuote
mutation
REST twin: POST /app/v1/stores/{slug}/checkout/quote
Price the cart of a store for the order the buyer is about to place: totals, errors, order types, payment methods, slots, locker cabinets. The cart is the identity cart of a linked account, otherwise the account's own. table_code puts the order at that table (dine-in only). Errors: 404 STORE_NOT_FOUND, 422 CART_EMPTY, 422 TABLE_NOT_FOUND, 422 VALIDATION_FAILED. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md). payment_methods lists wallet (after the store's own methods) for a signed-in account in a store whose payments the client collects; wallet then holds {balance, covers}, otherwise wallet is null (docs/STORE-PAYMENT-COLLECTION-DESIGN.md).
mutation PlatformAppStoreCheckoutQuote($locale: String, $slug: String!, $input: Json) {
platformAppStoreCheckoutQuote(locale: $locale, slug: $slug, input: $input)
}
Variables
{
"locale": "en",
"slug": "bistro",
"input": {
"order_type": "delivery",
"postcode": "H2X 2Z2"
}
}
Response example
{
"result": true,
"data": {
"ok": true,
"errors": [],
"subtotal": 25,
"delivery_fee": 4,
"tip": 0,
"discount": 0,
"coupon_code": null,
"min_order": 0,
"total": 29,
"tax": 0,
"price_changed": false,
"currency": "CAD",
"order_type": "delivery",
"table_code": null,
"order_types": [
"delivery",
"collection",
"dine_in"
],
"payment_methods": [
"stripe",
"wallet"
],
"wallet": {
"balance": 50,
"covers": true
},
"slots": {
"delivery": {
"asap": true,
"days": [
{
"date": "2026-10-14",
"slots": [
"13:00",
"13:30"
]
}
]
}
},
"locker_cabinets": [],
"tab": null,
"login_required": false
}
}
platformAppStoreCheckoutPlace
mutation
REST twin: POST /app/v1/stores/{slug}/checkout/place
Place the order from the cart; answers the order and the payment (stripe PaymentIntent client secret, offline or tab). payment.method stripe: present the PaymentSheet with client_secret / publishable_key / merchant_country / merchant_display_name, then call confirm-payment; offline and tab: nothing to pay in the app; wallet: paid from the account's balance at once (payment.method wallet, the order already paid), only where the quote listed wallet. Errors: 404 STORE_NOT_FOUND, 422 CART_EMPTY, 403 LOGIN_REQUIRED (guest where the store takes no guest orders), 422 ORDER_TYPE_UNAVAILABLE, 422 TABLE_REQUIRED, 422 TABLE_NOT_FOUND, 422 CHECKOUT_VALIDATION_FAILED with errors[] (price_changed, out_of_delivery_area, store_closed, item_unavailable, cabinet_required, invalid_time_slot, below_min_order, ...), 422 PAYMENT_METHOD_UNAVAILABLE (also wallet where the store does not take the balance, refused before any order), 422 PAYMENT_ERROR (also a balance that does not cover the total, refused before any order), 500 CHECKOUT_FAILED, 422 VALIDATION_FAILED. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md). CHECKOUT_VALIDATION_FAILED also carries reasons: an object {code: message} with the same codes as errors, each with the message the web checkout shows for it in the answer language (an unforeseen code gets the general message); errors stays the list of codes.
mutation PlatformAppStoreCheckoutPlace($locale: String, $slug: String!, $input: Json) {
platformAppStoreCheckoutPlace(locale: $locale, slug: $slug, input: $input)
}
Variables
{
"locale": "en",
"slug": "bistro",
"input": {
"order_type": "delivery",
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com",
"address_1": "10 Rue Test",
"city": "Montreal",
"postcode": "H2X 2Z2"
}
}
Response example
{
"result": true,
"data": {
"order": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "pending_payment",
"status_label": "Pending payment",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": false,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": null,
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": true,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
},
"payment": {
"method": "stripe",
"client_secret": "pi_3Q_secret_abc",
"publishable_key": "pk_live_123",
"merchant_country": "CA",
"merchant_display_name": "Bistro",
"amount": 29,
"currency": "CAD"
},
"tab": null
}
}
platformAppStoreOrders
query
REST twin: GET /app/v1/store-orders
The account's store orders at this client, newest first. Orders of the linked identity, and orders without identity under the account's e-mail or phone; group buy orders and smart-locker cabinet sales have their own lists. scope=open|past, page, per_page (max 50). Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
query PlatformAppStoreOrders($locale: String, $scope: String, $page: Int, $per_page: Int) {
platformAppStoreOrders(locale: $locale, scope: $scope, page: $page, per_page: $per_page)
}
Variables
{
"locale": "en"
}
Response example
{
"result": true,
"data": {
"items": [
{
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00"
}
],
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 20,
"total": 1,
"has_more": false
}
}
}
platformAppStoreOrder
query
REST twin: GET /app/v1/store-orders/{code}
One store order of the account: status, lines, timeline, locker pickup and the actions open to the buyer. Errors: 404 ORDER_NOT_FOUND. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md). items[].options is always a list of the option groups chosen for the line (empty without options): [{attachment_id: int|null, store_option_id: int|null, name: string, values: [{store_option_value_id: int|null, value: string, delta: number}]}] (delta = the price added per unit by that choice, already included in unit_price). locker (locker pickup orders, else null): {cabinet, address, pickup_code, pickup_due_at, remaining, items, door_open, can_open, refund_available}; locker.items is always a list of the order's cells: [{id: int, name: string, status: string (sold = waiting to be opened, opened = door open now, collected, expired, removed), compartment_number: string|null (the lock board's door number), pickup_number: int|null (the number shown on the door)}].
query PlatformAppStoreOrder($locale: String, $code: String!) {
platformAppStoreOrder(locale: $locale, code: $code)
}
Variables
{
"locale": "en",
"code": "EQ0MS2"
}
Response example
{
"result": true,
"data": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
platformAppStoreOrderPay
mutation
REST twin: POST /app/v1/store-orders/{code}/pay
Pay an unpaid order whose payment was left unfinished: the same payment object as place. Keeps the order's PaymentIntent while Stripe still accepts it (reused_intent true), otherwise starts a new one as checkout does; present the PaymentSheet, then call confirm-payment. Only while actions.can_pay is true. Errors: 404 ORDER_NOT_FOUND, 409 NOT_AWAITING_PAYMENT (paid, cancelled, refunded, a tab round or paid at the counter), 409 ORDER_ALREADY_PAID (the intent had already succeeded: the order is settled now, data carries it), 422 PAYMENT_ERROR. payment_method wallet pays the order from the account's balance instead, where the store takes it: the answer carries the paid order and payment.method wallet; a card payment that already succeeded wins (409 ORDER_ALREADY_PAID). Errors for wallet: 422 PAYMENT_METHOD_UNAVAILABLE, 422 INSUFFICIENT_BALANCE. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppStoreOrderPay($locale: String, $code: String!, $payment_method: String) {
platformAppStoreOrderPay(locale: $locale, code: $code, payment_method: $payment_method)
}
Variables
{
"locale": "en",
"code": "EQ0MS2",
"payment_method": "wallet"
}
Response example
{
"result": true,
"data": {
"order": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "pending_payment",
"status_label": "Pending payment",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": false,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": null,
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": true,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
},
"payment": {
"method": "stripe",
"client_secret": "pi_3Q_secret_abc",
"publishable_key": "pk_live_123",
"merchant_country": "CA",
"merchant_display_name": "Bistro",
"amount": 29,
"currency": "CAD"
},
"reused_intent": true
}
}
platformAppStoreOrderConfirmPayment
mutation
REST twin: POST /app/v1/store-orders/{code}/confirm-payment
After the PaymentSheet: ask Stripe about the order's PaymentIntent and settle the order once. Errors: 404 ORDER_NOT_FOUND. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppStoreOrderConfirmPayment($locale: String, $code: String!) {
platformAppStoreOrderConfirmPayment(locale: $locale, code: $code)
}
Variables
{
"locale": "en",
"code": "EQ0MS2"
}
Response example
{
"result": true,
"data": {
"paid": true,
"order": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
}
platformAppStoreOrderCancel
mutation
REST twin: POST /app/v1/store-orders/{code}/cancel
Cancel the order before the store accepts it; a paid order is refunded in full. Errors: 404 ORDER_NOT_FOUND. 409 CANCEL_NOT_ALLOWED, 422 CANCEL_FAILED. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppStoreOrderCancel($locale: String, $code: String!) {
platformAppStoreOrderCancel(locale: $locale, code: $code)
}
Variables
{
"locale": "en",
"code": "EQ0MS2"
}
Response example
{
"result": true,
"message": "Your order has been cancelled and refunded.",
"data": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "refunded",
"status_label": "Refunded",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
platformAppStoreOrderLockerOpen
mutation
REST twin: POST /app/v1/store-orders/{code}/locker-open
Open the next door of a paid locker pickup, on the buyer's own press at the cabinet. Errors: 404 ORDER_NOT_FOUND. 409 NOT_A_LOCKER_ORDER, 404 CABINET_NOT_FOUND, 422/409 with code of the refusal (door not opened). Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md). Needs the locker.remote_open capability on the token.
mutation PlatformAppStoreOrderLockerOpen($locale: String, $code: String!) {
platformAppStoreOrderLockerOpen(locale: $locale, code: $code)
}
Variables
{
"locale": "en",
"code": "EQ0MS2"
}
Response example
{
"result": true,
"message": "Door 3 opened: Cola",
"door_opened": true,
"refund_available": false,
"data": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
platformAppStoreOrderLockerRefund
mutation
REST twin: POST /app/v1/store-orders/{code}/locker-refund
Refund a locker pickup whose door never opened. Errors: 404 ORDER_NOT_FOUND. 409 NOT_A_LOCKER_ORDER, 404 CABINET_NOT_FOUND, 409 REFUND_REFUSED. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppStoreOrderLockerRefund($locale: String, $code: String!) {
platformAppStoreOrderLockerRefund(locale: $locale, code: $code)
}
Variables
{
"locale": "en",
"code": "EQ0MS2"
}
Response example
{
"result": true,
"message": "Refund issued.",
"data": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
platformAppStoreOrderReorder
mutation
REST twin: POST /app/v1/store-orders/{code}/reorder
Put the order's lines back into the buyer's cart at the store. Errors: 404 ORDER_NOT_FOUND. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppStoreOrderReorder($locale: String, $code: String!) {
platformAppStoreOrderReorder(locale: $locale, code: $code)
}
Variables
{
"locale": "en",
"code": "EQ0MS2"
}
Response example
{
"result": true,
"message": "The items were added to your cart.",
"data": {
"added": 1,
"skipped": 0,
"store_slug": "bistro"
}
}
platformAppStoreOrderReturn
mutation
REST twin: POST /app/v1/store-orders/{code}/return
Ask the store to take a paid or completed order back. Errors: 404 ORDER_NOT_FOUND. 409 RETURN_NOT_ALLOWED, 409 RETURN_ALREADY_OPEN, 422 VALIDATION_FAILED. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppStoreOrderReturn($locale: String, $code: String!, $reason: String!) {
platformAppStoreOrderReturn(locale: $locale, code: $code, reason: $reason)
}
Variables
{
"locale": "en",
"code": "EQ0MS2",
"reason": "Cold on arrival"
}
Response example
{
"result": true,
"message": "Return requested.",
"data": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
platformAppStoreOrderReview
mutation
REST twin: POST /app/v1/store-orders/{code}/review/{productId}
Rate one product of the order (1 to 5, optional comment); a second rating replaces the first. Errors: 404 ORDER_NOT_FOUND. 404 PRODUCT_NOT_IN_ORDER, 422 VALIDATION_FAILED. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppStoreOrderReview($locale: String, $code: String!, $product_id: Int!, $rating: Int!, $comment: String) {
platformAppStoreOrderReview(locale: $locale, code: $code, product_id: $product_id, rating: $rating, comment: $comment)
}
Variables
{
"locale": "en",
"code": "EQ0MS2",
"product_id": 41,
"rating": 5
}
Response example
{
"result": true,
"message": "Thanks for your review.",
"data": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
platformAppLockerShopCatalog
query
REST twin: GET /app/v1/locker-locations/{locationId}/shop
Every cabinet at the location with the shop switched on, its stores, and per product the count of units in cells, the unit price and the temperature class. Nothing is held by reading. Requires the locker.shop capability (403 APP_CAPABILITY_DISABLED otherwise) and the operator's shop switch. Needs the locker.shop capability on the token.
query PlatformAppLockerShopCatalog($location_id: Int!, $locale: String) {
platformAppLockerShopCatalog(location_id: $location_id, locale: $locale)
}
Variables
{
"location_id": 700
}
Response example
{
"result": true,
"data": {
"location": {
"id": 700,
"name": "Central Station"
},
"max_items_per_sale": 5,
"cabinets": [
{
"shelf_id": 312,
"shelf_code": "CAB-A",
"stores": [
{
"store": {
"id": 31,
"name": "Corner Shop",
"slug": "corner-shop",
"currency": "CAD"
},
"lines": [
{
"product_id": 81,
"product_variant_id": null,
"name": "Cola",
"variant_label": "",
"display_name": "Cola",
"sku": "COLA-1",
"barcode": null,
"unit_price": 3.5,
"image_url": null,
"storage_temperature": "chilled",
"storage_temperature_label": "Chilled",
"count": 3
}
]
}
]
}
]
}
}
platformAppLockerShopItems
query
REST twin: GET /app/v1/locker-shop/items
What the stores this account runs have in cabinet cells: every occupying item by default, or one status with status. Filter by cabinet or store. Requires the locker.shop capability (403 APP_CAPABILITY_DISABLED otherwise) and the operator's shop switch. Needs the locker.shop capability on the token.
query PlatformAppLockerShopItems($shelf_id: Int, $store_id: Int, $status: String, $page: Int, $per_page: Int) {
platformAppLockerShopItems(shelf_id: $shelf_id, store_id: $store_id, status: $status, page: $page, per_page: $per_page)
}
Variables
{
"shelf_id": 312
}
Response example
{
"result": true,
"data": {
"items": [
{
"id": 5001,
"store": {
"id": 31,
"name": "Corner Shop"
},
"cabinet": {
"id": 312,
"code": "CAB-A"
},
"compartment_number": "3",
"name": "Cola",
"sku": "COLA-1",
"status": "stocked",
"status_label": "For sale",
"expires_at": "2026-10-15T23:59:59-04:00",
"sales_order_id": null,
"pickup_due_at": null
}
],
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 50,
"total": 1,
"has_more": false
}
}
}
platformAppLockerShopCreateOrder
mutation
REST twin: POST /app/v1/locker-shop/orders
Holds the chosen units in that cabinet for the operator's hold time and raises the sale under the account's contact. Nothing is collected: pay it with platformAppPay using data.payment. At most the operator's items-per-sale from ONE store. Held units go back on sale if the hold runs out unpaid. Requires the locker.shop capability (403 APP_CAPABILITY_DISABLED otherwise) and the operator's shop switch. Needs the locker.shop capability on the token.
mutation PlatformAppLockerShopCreateOrder($shelf_id: Int!, $store_id: Int!, $lines: Json!) {
platformAppLockerShopCreateOrder(shelf_id: $shelf_id, store_id: $store_id, lines: $lines)
}
Variables
{
"shelf_id": 312,
"store_id": 31,
"lines": [
{
"product_id": 81,
"qty": 2
}
]
}
Response example
{
"result": true,
"data": {
"id": 9130,
"status": "pending_payment",
"paid": false,
"created_at": "2026-09-27T14:02:11-04:00",
"paid_at": null,
"total": 7.7,
"currency": "CAD",
"store": {
"id": 31,
"name": "Corner Shop"
},
"cabinet": {
"id": 312,
"code": "CAB-A",
"location_id": 700
},
"items_total": 2,
"remaining": 2,
"pickup_due_at": "2026-09-28 14:03:40",
"can_open": false,
"pickup_code": null,
"items": [
{
"id": 5001,
"name": "Cola",
"status": "sold",
"compartment_number": "3",
"pickup_number": 3
},
{
"id": 5002,
"name": "Cola",
"status": "sold",
"compartment_number": "4",
"pickup_number": 4
}
],
"payment": {
"subject_type": "locker_shop",
"subject_id": 9130,
"public_token": "0f7c2b4e9a1d4f0b8c6e3a5d7b9f1c2e",
"amount": 7.7,
"currency": "CAD",
"expires_at": "2026-09-27T14:32:11-04:00",
"payable_in_app": true,
"pay_url": "https://app.example.com/pay/r/0f7c2b4e9a1d4f0b8c6e3a5d7b9f1c2e"
},
"refund_available": false
}
}
platformAppLockerShopOrders
query
REST twin: GET /app/v1/locker-shop/orders
Newest first. scope open keeps the ones still unpaid or awaiting pickup; past the rest. A purchase is the caller's when it was raised under the account's own contact. Requires the locker.shop capability (403 APP_CAPABILITY_DISABLED otherwise) and the operator's shop switch. Needs the locker.shop capability on the token.
query PlatformAppLockerShopOrders($scope: String, $page: Int, $per_page: Int) {
platformAppLockerShopOrders(scope: $scope, page: $page, per_page: $per_page)
}
Variables
{
"scope": "open"
}
Response example
{
"result": true,
"data": {
"items": [
{
"id": 9130,
"status": "paid",
"paid": true,
"created_at": "2026-09-27T14:02:11-04:00",
"paid_at": "2026-09-27T14:03:40-04:00",
"total": 7.7,
"currency": "CAD",
"store": {
"id": 31,
"name": "Corner Shop"
},
"cabinet": {
"id": 312,
"code": "CAB-A",
"location_id": 700
},
"items_total": 2,
"remaining": 2,
"pickup_due_at": "2026-09-28 14:03:40",
"can_open": true
}
],
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 20,
"total": 1,
"has_more": false
}
}
}
platformAppLockerShopOrder
query
REST twin: GET /app/v1/locker-shop/orders/{id}
Its items with their doors, the pickup code once paid, how many doors are left, whether a door may be opened now, and while unpaid the payment request. refund_available is true only for a paid purchase none of whose doors was ever opened (D12). Requires the locker.shop capability (403 APP_CAPABILITY_DISABLED otherwise) and the operator's shop switch. Needs the locker.shop capability on the token.
query PlatformAppLockerShopOrder($order_id: Int!) {
platformAppLockerShopOrder(order_id: $order_id)
}
Variables
{
"order_id": 9130
}
Response example
{
"result": true,
"data": {
"id": 9130,
"status": "paid",
"paid": true,
"created_at": "2026-09-27T14:02:11-04:00",
"paid_at": "2026-09-27T14:03:40-04:00",
"total": 7.7,
"currency": "CAD",
"store": {
"id": 31,
"name": "Corner Shop"
},
"cabinet": {
"id": 312,
"code": "CAB-A",
"location_id": 700
},
"items_total": 2,
"remaining": 2,
"pickup_due_at": "2026-09-28 14:03:40",
"can_open": true,
"pickup_code": "K7Q2M9",
"items": [
{
"id": 5001,
"name": "Cola",
"status": "sold",
"compartment_number": "3",
"pickup_number": 3
},
{
"id": 5002,
"name": "Cola",
"status": "sold",
"compartment_number": "4",
"pickup_number": 4
}
],
"payment": null,
"refund_available": true
}
}
platformAppLockerShopOpen
mutation
REST twin: POST /app/v1/locker-shop/orders/{id}/open
Opens the next unopened cell of a paid purchase — one door per call, the next only once the cabinet has seen the previous one closed. The buyer's own press, standing at the cabinet; never on the payment. On failure refund_available says whether the D12 refund may be asked for. Requires the locker.shop capability (403 APP_CAPABILITY_DISABLED otherwise) and the operator's shop switch. Needs the locker.shop capability on the token.
mutation PlatformAppLockerShopOpen($order_id: Int!) {
platformAppLockerShopOpen(order_id: $order_id)
}
Variables
{
"order_id": 9130
}
Response example
{
"result": true,
"message": "Pickup successful",
"door_opened": true,
"refund_available": false,
"data": {
"id": 9130,
"status": "paid",
"paid": true,
"created_at": "2026-09-27T14:02:11-04:00",
"paid_at": "2026-09-27T14:03:40-04:00",
"total": 7.7,
"currency": "CAD",
"store": {
"id": 31,
"name": "Corner Shop"
},
"cabinet": {
"id": 312,
"code": "CAB-A",
"location_id": 700
},
"items_total": 2,
"remaining": 1,
"pickup_due_at": "2026-09-28 14:03:40",
"can_open": false,
"pickup_code": "K7Q2M9",
"items": [
{
"id": 5001,
"name": "Cola",
"status": "sold",
"compartment_number": "3",
"pickup_number": 3
},
{
"id": 5002,
"name": "Cola",
"status": "sold",
"compartment_number": "4",
"pickup_number": 4
}
],
"payment": null,
"refund_available": true
}
}
platformAppLockerShopIssueAccessCode
mutation
REST twin: POST /app/v1/locker-shop/access-codes
For the owner of a store, or a sub-account with access to it: a code that opens the merchant menu on one cabinet the store is bound to, valid for the operator's configured hours. Shown once. Requires the locker.shop capability (403 APP_CAPABILITY_DISABLED otherwise) and the operator's shop switch. Needs the locker.shop capability on the token.
mutation PlatformAppLockerShopIssueAccessCode($store_id: Int!, $shelf_id: Int!, $label: String) {
platformAppLockerShopIssueAccessCode(store_id: $store_id, shelf_id: $shelf_id, label: $label)
}
Variables
{
"store_id": 31,
"shelf_id": 312,
"label": "Morning restock"
}
Response example
{
"result": true,
"data": {
"id": 77,
"code": "H4M2Q9",
"store": {
"id": 31,
"name": "Corner Shop"
},
"cabinet": {
"id": 312,
"code": "CAB-A"
},
"expires_at": "2026-09-27T18:02:11-04:00"
}
}
platformAppLockerShopRefundRequest
mutation
REST twin: POST /app/v1/locker-shop/orders/{id}/refund-request
The one automatic refund (D12): granted only when the purchase is paid and none of its cells was ever opened since payment. The money goes back the way it came and the goods go back on sale. Requires the locker.shop capability (403 APP_CAPABILITY_DISABLED otherwise) and the operator's shop switch. Needs the locker.shop capability on the token.
mutation PlatformAppLockerShopRefundRequest($order_id: Int!) {
platformAppLockerShopRefundRequest(order_id: $order_id)
}
Variables
{
"order_id": 9130
}
Response example
{
"result": true,
"data": {
"id": 9130,
"status": "refunded",
"paid": true,
"created_at": "2026-09-27T14:02:11-04:00",
"paid_at": "2026-09-27T14:03:40-04:00",
"total": 7.7,
"currency": "CAD",
"store": {
"id": 31,
"name": "Corner Shop"
},
"cabinet": {
"id": 312,
"code": "CAB-A",
"location_id": 700
},
"items_total": 2,
"remaining": 0,
"pickup_due_at": "2026-09-28 14:03:40",
"can_open": false,
"pickup_code": "K7Q2M9",
"items": [
{
"id": 5001,
"name": "Cola",
"status": "sold",
"compartment_number": "3",
"pickup_number": 3
},
{
"id": 5002,
"name": "Cola",
"status": "sold",
"compartment_number": "4",
"pickup_number": 4
}
],
"payment": null,
"refund_available": false,
"message": "The payment was refunded and the goods are back on sale."
}
}
Group buy
platformAppGroupBuy
query
REST twin: GET /app/v1/group-buys/{code}
Signed-in customer of the customer app or MiBox. The campaign a buyer opens: campaign (title, cutoff_at and expected_delivery_at on the store's clock, orderable, home_fee {mode fixed | store_areas | free, fee, free_over}, store, web_url), items [{id, name, group_price, regular_price}], pickup choices [{key point:{id} | leader:{group id}, name, address, notes, leader}], home (home delivery offered), leader (the leader of a marked link, D24: that leader's point comes first), address_suggestions, currency, tax_rate. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Added: share — next to a campaign's web_url and a group's share_url, how the store lets buyers share that link, null while it offers no channel or there is no link: {url, title, channels, links}, the same shape and channels as the product answer's share. Needs the group_buy capability on the token.
query PlatformAppGroupBuy($code: String!, $leader: String, $locale: String) {
platformAppGroupBuy(code: $code, leader: $leader, locale: $locale)
}
Variables
{
"code": "FRUITBOX26"
}
Response example
{
"result": true,
"data": {
"campaign": {
"code": "FRUITBOX26",
"kind": "campaign",
"title": "Fruit box",
"description": null,
"status": "published",
"orderable": true,
"opens_at": "2026-11-03T08:00:00-05:00",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null,
"takes_leaders": true,
"allow_pickup": true,
"allow_home_delivery": true,
"home_fee": {
"mode": "fixed",
"fee": 7,
"free_over": 30
},
"store": {
"id": 1,
"name": "Fresh Market",
"slug": "fresh-market",
"timezone": "America/Toronto"
},
"web_url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26"
}
}
},
"items": [
{
"id": 1,
"name": "Apples",
"group_price": 5,
"regular_price": 6
}
],
"pickup": [
{
"key": "leader:1",
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
{
"key": "point:1",
"name": "Library",
"address": "12 King St, Toronto, M5H 2N2",
"notes": "Sat 10-12",
"leader": false
}
],
"home": true,
"leader": {
"mark": "ANNPORCH01",
"name": "Ann's porch"
},
"address_suggestions": false,
"currency": "CAD",
"tax_rate": 13,
"guest_verification_channel": null
}
}
platformAppGroupBuyAddressDetails
query
REST twin: GET /app/v1/group-buys/{code}/address/details
Signed-in customer of the customer app or MiBox. The fields of a chosen suggestion (address null when it cannot be read). 404 GROUP_BUY_NO_HOME_DELIVERY when the campaign has no home delivery. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
query PlatformAppGroupBuyAddressDetails($code: String!, $place_id: String!, $session: String, $locale: String) {
platformAppGroupBuyAddressDetails(code: $code, place_id: $place_id, session: $session, locale: $locale)
}
Variables
{
"code": "FRUITBOX26",
"place_id": "ChIJ3Elm"
}
Response example
{
"result": true,
"data": {
"address": {
"address_1": "3 Elm St",
"city": "Toronto",
"province": "ON",
"postcode": "M4M 1A1"
}
}
}
platformAppGroupBuyAddressSuggestions
query
REST twin: GET /app/v1/group-buys/{code}/address/suggestions
Signed-in customer of the customer app or MiBox. Suggestions while the buyer types a home address, through the Client's map key (empty without one; data.address_suggestions on the campaign says whether to ask). 404 GROUP_BUY_NO_HOME_DELIVERY when the campaign has no home delivery. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
query PlatformAppGroupBuyAddressSuggestions($code: String!, $q: String!, $session: String, $locale: String) {
platformAppGroupBuyAddressSuggestions(code: $code, q: $q, session: $session, locale: $locale)
}
Variables
{
"code": "FRUITBOX26",
"q": "3 Elm"
}
Response example
{
"result": true,
"data": {
"suggestions": [
{
"place_id": "ChIJ3Elm",
"text": "3 Elm St, Toronto, ON"
}
]
}
}
platformAppGroupBuyQuote
mutation
REST twin: POST /app/v1/group-buys/{code}/quote
Signed-in customer of the customer app or MiBox. Prices an order without placing it: group prices × quantities, tax at the store rate, the home delivery fee (D25). Always 200: data.ok false lists errors (codes campaign_closed, item_unavailable, no_items, receive_unavailable, receive_required, address_required) and messages. lines = {campaign item id: quantity}; receive = point:{id} | leader:{group id} | home; leader = the mark of the link; address (home) = {address_1, address_2, city, province, postcode, delivery_instruction}. Coordinates are looked up by the server, never taken from the app. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. The app answer also carries currency (the Client's currency code the amounts are in), next to the web quote's fields. Needs the group_buy capability on the token.
mutation PlatformAppGroupBuyQuote($code: String!, $lines: Json, $receive: String, $address: Json, $locale: String) {
platformAppGroupBuyQuote(code: $code, lines: $lines, receive: $receive, address: $address, locale: $locale)
}
Variables
{
"code": "FRUITBOX26",
"lines": {
"1": 2
},
"receive": "home",
"address": {
"address_1": "3 Elm St",
"city": "Toronto",
"postcode": "M4M 1A1",
"delivery_instruction": "Side door"
}
}
Response example
{
"result": true,
"data": {
"ok": true,
"errors": [],
"messages": [],
"units": 2,
"subtotal": 10,
"delivery_fee": 0,
"tax": 1.3,
"total": 11.3,
"currency": "CAD"
}
}
platformAppGroupBuyPlaceOrder
mutation
REST twin: POST /app/v1/group-buys/{code}/order
Signed-in customer of the customer app or MiBox. Places the order with the account's name, e-mail and phone (and its linked identity) and starts a card hold for the total (D5): 201 with data.order and data.payment {client_secret, publishable_key, merchant_country, merchant_display_name, capture: manual}. Present the Stripe PaymentSheet with the client secret (a manual-capture PaymentIntent: the card is held, charged only if the group forms), then call confirm-payment. Same input as quote, plus note (max 500). 422 GROUP_BUY_ORDER_REFUSED with errors / messages (quote codes plus payment_unavailable, payment_failed). data.order: token, number (store order number), campaign {code, title, status, cutoff_at, expected_delivery_at}, store {id, name}, hold_status (pending | authorized | captured | voided | failed) with hold_label and hold_message, status (the store order: pending_payment until the group forms, then paid / completed / cancelled / refunded), delivery (pickup | home), units, total, currency, place (pickup: name, address, notes, leader) or null, shipment_status, collected_at, collected_via, created_at, web_url; and in detail subtotal, delivery_fee, tax, lines [{name, qty, unit_price, total}], address, at_locker, locker {location, compartment, pickup_code} once a bag waits in a locker cell, can_confirm_received. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
mutation PlatformAppGroupBuyPlaceOrder($code: String!, $lines: Json, $receive: String, $leader: String, $address: Json, $note: String, $locale: String) {
platformAppGroupBuyPlaceOrder(code: $code, lines: $lines, receive: $receive, leader: $leader, address: $address, note: $note, locale: $locale)
}
Variables
{
"code": "FRUITBOX26",
"lines": {
"1": 2
},
"receive": "point:1",
"leader": "ANNPORCH01",
"note": "Ring the bell"
}
Response example
{
"result": true,
"data": {
"order": {
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"number": 1,
"campaign": {
"code": "FRUITBOX26",
"title": "Fruit box",
"status": "published",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null
},
"store": {
"id": 1,
"name": "Fresh Market"
},
"hold_status": "pending",
"hold_label": "Waiting for the card",
"hold_message": "The card hold is being confirmed. This page updates by itself.",
"status": "pending_payment",
"delivery": "pickup",
"units": 2,
"total": 11.3,
"currency": "CAD",
"place": {
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
"shipment_status": null,
"collected_at": null,
"collected_via": null,
"created_at": "2026-11-04T12:00:00-05:00",
"web_url": "https://shop.example.com/portal/group-buy/orders/Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"actions": {
"can_pay": true
},
"subtotal": 10,
"delivery_fee": 0,
"tax": 1.3,
"lines": [
{
"name": "Apples",
"qty": 2,
"unit_price": 5,
"total": 10
}
],
"address": {
"address_1": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1"
},
"at_locker": false,
"locker": null,
"can_confirm_received": false
},
"payment": {
"client_secret": "pi_secret_1",
"publishable_key": "pk_test",
"merchant_country": "CA",
"merchant_display_name": "Fresh Market",
"capture": "manual"
}
}
}
platformAppGroupBuyConfirmPayment
mutation
REST twin: POST /app/v1/group-buy-orders/{token}/confirm-payment
Signed-in customer of the customer app or MiBox. After the payment sheet: reads the hold from Stripe and records it — authorized (the order counts, its stock is taken) or failed / voided (the order is cancelled). Safe to call again: a hold already recorded is left as it is. Answers the order. 404 GROUP_BUY_ORDER_NOT_FOUND for a token of another Client. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
mutation PlatformAppGroupBuyConfirmPayment($token: String!, $locale: String) {
platformAppGroupBuyConfirmPayment(token: $token, locale: $locale)
}
Variables
{
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk"
}
Response example
{
"result": true,
"data": {
"order": {
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"number": 1,
"campaign": {
"code": "FRUITBOX26",
"title": "Fruit box",
"status": "published",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null
},
"store": {
"id": 1,
"name": "Fresh Market"
},
"hold_status": "authorized",
"hold_label": "Amount held",
"hold_message": "The amount is held on your card. It is charged after orders close at 2026-11-07 20:00 if the group goes ahead, and released otherwise.",
"status": "pending_payment",
"delivery": "pickup",
"units": 2,
"total": 11.3,
"currency": "CAD",
"place": {
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
"shipment_status": null,
"collected_at": null,
"collected_via": null,
"created_at": "2026-11-04T12:00:00-05:00",
"web_url": "https://shop.example.com/portal/group-buy/orders/Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"actions": {
"can_pay": false
},
"subtotal": 10,
"delivery_fee": 0,
"tax": 1.3,
"lines": [
{
"name": "Apples",
"qty": 2,
"unit_price": 5,
"total": 10
}
],
"address": {
"address_1": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1"
},
"at_locker": false,
"locker": null,
"can_confirm_received": false
}
}
}
platformAppGroupBuyOrders
query
REST twin: GET /app/v1/group-buy-orders
Signed-in customer of the customer app or MiBox. The account's group buy orders with this Client, newest first (a hold the card never completed is left out). data.items as in the order detail without the detail fields; data.pagination {current_page, per_page, has_more}. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
query PlatformAppGroupBuyOrders($page: Int, $per_page: Int, $locale: String) {
platformAppGroupBuyOrders(page: $page, per_page: $per_page, locale: $locale)
}
Variables
{}
Response example
{
"result": true,
"data": {
"items": [
{
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"number": 1,
"campaign": {
"code": "FRUITBOX26",
"title": "Fruit box",
"status": "published",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null
},
"store": {
"id": 1,
"name": "Fresh Market"
},
"hold_status": "authorized",
"hold_label": "Amount held",
"hold_message": "The amount is held on your card. It is charged after orders close at 2026-11-07 20:00 if the group goes ahead, and released otherwise.",
"status": "pending_payment",
"delivery": "pickup",
"units": 2,
"total": 11.3,
"currency": "CAD",
"place": {
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
"shipment_status": "delivered",
"collected_at": null,
"collected_via": null,
"created_at": "2026-11-04T12:00:00-05:00",
"web_url": "https://shop.example.com/portal/group-buy/orders/Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"actions": {
"can_pay": false
}
}
],
"pagination": {
"current_page": 1,
"per_page": 20,
"has_more": false
}
}
}
platformAppGroupBuyOrder
query
REST twin: GET /app/v1/group-buy-orders/{token}
Signed-in customer of the customer app or MiBox. One order by its token; a hold still pending is read from Stripe first (as the web order page does). data.order: token, number (store order number), campaign {code, title, status, cutoff_at, expected_delivery_at}, store {id, name}, hold_status (pending | authorized | captured | voided | failed) with hold_label and hold_message, status (the store order: pending_payment until the group forms, then paid / completed / cancelled / refunded), delivery (pickup | home), units, total, currency, place (pickup: name, address, notes, leader) or null, shipment_status, collected_at, collected_via, created_at, web_url; and in detail subtotal, delivery_fee, tax, lines [{name, qty, unit_price, total}], address, at_locker, locker {location, compartment, pickup_code} once a bag waits in a locker cell, can_confirm_received. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
query PlatformAppGroupBuyOrder($token: String!, $locale: String) {
platformAppGroupBuyOrder(token: $token, locale: $locale)
}
Variables
{
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk"
}
Response example
{
"result": true,
"data": {
"order": {
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"number": 1,
"campaign": {
"code": "FRUITBOX26",
"title": "Fruit box",
"status": "published",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null
},
"store": {
"id": 1,
"name": "Fresh Market"
},
"hold_status": "authorized",
"hold_label": "Amount held",
"hold_message": "The amount is held on your card. It is charged after orders close at 2026-11-07 20:00 if the group goes ahead, and released otherwise.",
"status": "pending_payment",
"delivery": "pickup",
"units": 2,
"total": 11.3,
"currency": "CAD",
"place": {
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
"shipment_status": "delivered",
"collected_at": null,
"collected_via": null,
"created_at": "2026-11-04T12:00:00-05:00",
"web_url": "https://shop.example.com/portal/group-buy/orders/Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"actions": {
"can_pay": false
},
"subtotal": 10,
"delivery_fee": 0,
"tax": 1.3,
"lines": [
{
"name": "Apples",
"qty": 2,
"unit_price": 5,
"total": 10
}
],
"address": {
"address_1": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1"
},
"at_locker": false,
"locker": null,
"can_confirm_received": true
}
}
}
platformAppGroupBuyOrderPay
mutation
REST twin: POST /app/v1/group-buy-orders/{token}/pay
Signed-in customer of the customer app or MiBox. Resume the card hold of an order whose buyer closed the payment sheet (hold_status pending). While Stripe still lets the order's PaymentIntent be confirmed (requires_payment_method, requires_confirmation, requires_action) its client secret is answered again (reused_intent true); otherwise (cancelled or unreadable) a new manual-capture hold is started with the store's Stripe account, as placing the order does, and replaces the old one on the order (reused_intent false). data: order (as the order call) and payment {client_secret, publishable_key, merchant_country, merchant_display_name, capture: manual}, the same block as placing the order; present the PaymentSheet, then call confirm-payment. Allowed when order.actions.can_pay is true: the hold is pending, the campaign is still published and before its cutoff, and the order's group is still open (a hold started before the cutoff outlasts the merchant's decision window, GROUP-BUY-DESIGN D5 / D18). 409 GROUP_BUY_NOT_AWAITING_HOLD otherwise. 409 GROUP_BUY_ALREADY_HELD with data.order when Stripe shows the card already held (or being held): the hold is recorded as confirm-payment would, and no second hold is started. 422 PAYMENT_ERROR when no hold could be started. 404 GROUP_BUY_ORDER_NOT_FOUND for a token of another Client. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
mutation PlatformAppGroupBuyOrderPay($token: String!, $locale: String) {
platformAppGroupBuyOrderPay(token: $token, locale: $locale)
}
Variables
{
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk"
}
Response example
{
"result": true,
"data": {
"order": {
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"number": 1,
"campaign": {
"code": "FRUITBOX26",
"title": "Fruit box",
"status": "published",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null
},
"store": {
"id": 1,
"name": "Fresh Market"
},
"hold_status": "pending",
"hold_label": "Waiting for the card",
"hold_message": "Complete the card payment to join the group.",
"status": "pending_payment",
"delivery": "pickup",
"units": 2,
"total": 11.3,
"currency": "CAD",
"place": {
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
"shipment_status": null,
"collected_at": null,
"collected_via": null,
"created_at": "2026-11-04T12:00:00-05:00",
"web_url": "https://shop.example.com/portal/group-buy/orders/Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"actions": {
"can_pay": true
},
"subtotal": 10,
"delivery_fee": 0,
"tax": 1.3,
"lines": [
{
"name": "Apples",
"qty": 2,
"unit_price": 5,
"total": 10
}
],
"address": {
"address_1": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1"
},
"at_locker": false,
"locker": null,
"can_confirm_received": false
},
"payment": {
"client_secret": "pi_3Q_secret_abc",
"publishable_key": "pk_live_123",
"merchant_country": "CA",
"merchant_display_name": "Fresh Market",
"capture": "manual"
},
"reused_intent": true
}
}
platformAppGroupBuyOrderReceived
mutation
REST twin: POST /app/v1/group-buy-orders/{token}/received
Signed-in customer of the customer app or MiBox. The buyer says the goods arrived (D27): allowed once they are on their way or at the pickup point (can_confirm_received), never for a bag in a locker cell (collected with the pickup code). Answers the order. 422 GROUP_BUY_NOT_RECEIVABLE otherwise. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
mutation PlatformAppGroupBuyOrderReceived($token: String!, $locale: String) {
platformAppGroupBuyOrderReceived(token: $token, locale: $locale)
}
Variables
{
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk"
}
Response example
{
"result": true,
"data": {
"order": {
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"number": 1,
"campaign": {
"code": "FRUITBOX26",
"title": "Fruit box",
"status": "published",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null
},
"store": {
"id": 1,
"name": "Fresh Market"
},
"hold_status": "authorized",
"hold_label": "Amount held",
"hold_message": "The amount is held on your card. It is charged after orders close at 2026-11-07 20:00 if the group goes ahead, and released otherwise.",
"status": "pending_payment",
"delivery": "pickup",
"units": 2,
"total": 11.3,
"currency": "CAD",
"place": {
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
"shipment_status": "delivered",
"collected_at": "2026-11-04T12:00:00-05:00",
"collected_via": "leader",
"created_at": "2026-11-04T12:00:00-05:00",
"web_url": "https://shop.example.com/portal/group-buy/orders/Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"actions": {
"can_pay": false
},
"subtotal": 10,
"delivery_fee": 0,
"tax": 1.3,
"lines": [
{
"name": "Apples",
"qty": 2,
"unit_price": 5,
"total": 10
}
],
"address": {
"address_1": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1"
},
"at_locker": false,
"locker": null,
"can_confirm_received": false
}
}
}
platformAppGroupBuyLead
query
REST twin: GET /app/v1/group-buys/{code}/lead
Signed-in customer of the customer app or MiBox. For a published campaign that takes leaders, or a self-pick plan: campaign, leader_policy (any | approved), leader (the customer's application with status pending | approved | rejected | disabled and pickup details) or null, may_lead, can_apply (false once disabled), group {id, mark, share_url} once opened — share_url is the campaign link on the Client's white-label host with ?leader={mark} —, self_pick {items, earliest_cutoff, latest_cutoff, timezone} for a plan the leader may start a campaign from, commission_rates [{id, name, group_price, commission_mode percentage | fixed, commission_value}] (D10). Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. currency: the Client's currency code, which the amounts (group prices, commission) are in. Added: share — next to a campaign's web_url and a group's share_url, how the store lets buyers share that link, null while it offers no channel or there is no link: {url, title, channels, links}, the same shape and channels as the product answer's share. Needs the group_buy capability on the token.
query PlatformAppGroupBuyLead($code: String!, $locale: String) {
platformAppGroupBuyLead(code: $code, locale: $locale)
}
Variables
{
"code": "FRUITBOX26"
}
Response example
{
"result": true,
"data": {
"campaign": {
"code": "FRUITBOX26",
"kind": "campaign",
"title": "Fruit box",
"description": null,
"status": "published",
"orderable": true,
"opens_at": "2026-11-03T08:00:00-05:00",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null,
"takes_leaders": true,
"allow_pickup": true,
"allow_home_delivery": true,
"home_fee": {
"mode": "fixed",
"fee": 7,
"free_over": 30
},
"store": {
"id": 1,
"name": "Fresh Market",
"slug": "fresh-market",
"timezone": "America/Toronto"
},
"web_url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26"
}
}
},
"leader_policy": "any",
"leader": null,
"may_lead": false,
"can_apply": true,
"group": null,
"self_pick": null,
"currency": "CAD",
"commission_rates": [
{
"id": 1,
"name": "Apples",
"group_price": 5,
"commission_mode": "percentage",
"commission_value": 10
}
]
}
}
platformAppGroupBuyLeadApply
mutation
REST twin: POST /app/v1/group-buys/{code}/lead
Signed-in customer of the customer app or MiBox. Applies to the campaign's store with the address buyers collect from, or changes the details of a pending or approved application; a rejected applicant applies again. Fields pickup_name (required, max 120), address (required), address_2, city, province, postcode (required), phone, pickup_notes. 422 VALIDATION_FAILED with errors per field (also for a disabled leader: errors.status). Answers the lead view. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. currency: the Client's currency code, which the amounts (group prices, commission) are in. Added: share — next to a campaign's web_url and a group's share_url, how the store lets buyers share that link, null while it offers no channel or there is no link: {url, title, channels, links}, the same shape and channels as the product answer's share. Needs the group_buy capability on the token.
mutation PlatformAppGroupBuyLeadApply($code: String!, $pickup_name: String!, $address: String!, $city: String, $postcode: String!, $phone: String, $pickup_notes: String, $locale: String) {
platformAppGroupBuyLeadApply(code: $code, pickup_name: $pickup_name, address: $address, city: $city, postcode: $postcode, phone: $phone, pickup_notes: $pickup_notes, locale: $locale)
}
Variables
{
"code": "FRUITBOX26",
"pickup_name": "Ann's porch",
"address": "9 Oak Ave",
"city": "Toronto",
"postcode": "M6G 1A1",
"phone": "4165550111",
"pickup_notes": "Evenings"
}
Response example
{
"result": true,
"data": {
"campaign": {
"code": "FRUITBOX26",
"kind": "campaign",
"title": "Fruit box",
"description": null,
"status": "published",
"orderable": true,
"opens_at": "2026-11-03T08:00:00-05:00",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null,
"takes_leaders": true,
"allow_pickup": true,
"allow_home_delivery": true,
"home_fee": {
"mode": "fixed",
"fee": 7,
"free_over": 30
},
"store": {
"id": 1,
"name": "Fresh Market",
"slug": "fresh-market",
"timezone": "America/Toronto"
},
"web_url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26"
}
}
},
"leader_policy": "any",
"leader": {
"status": "pending",
"pickup_name": "Ann's porch",
"address": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1",
"phone": "4165550111",
"pickup_notes": "Evenings",
"decision_note": null
},
"may_lead": true,
"can_apply": true,
"group": null,
"self_pick": null,
"currency": "CAD",
"commission_rates": [
{
"id": 1,
"name": "Apples",
"group_price": 5,
"commission_mode": "percentage",
"commission_value": 10
}
]
}
}
platformAppGroupBuyLeadOpen
mutation
REST twin: POST /app/v1/group-buys/{code}/lead/open
Signed-in customer of the customer app or MiBox. Opens the customer's group in the campaign (once; again answers the same group) with its leader mark; share data.group.share_url. 403 GROUP_BUY_LEADER_NOT_ALLOWED when the customer may not lead (approved leaders only and not approved yet, disabled, or a self-pick plan). Answers the lead view. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. currency: the Client's currency code, which the amounts (group prices, commission) are in. Added: share — next to a campaign's web_url and a group's share_url, how the store lets buyers share that link, null while it offers no channel or there is no link: {url, title, channels, links}, the same shape and channels as the product answer's share. Needs the group_buy capability on the token.
mutation PlatformAppGroupBuyLeadOpen($code: String!, $locale: String) {
platformAppGroupBuyLeadOpen(code: $code, locale: $locale)
}
Variables
{
"code": "FRUITBOX26"
}
Response example
{
"result": true,
"data": {
"campaign": {
"code": "FRUITBOX26",
"kind": "campaign",
"title": "Fruit box",
"description": null,
"status": "published",
"orderable": true,
"opens_at": "2026-11-03T08:00:00-05:00",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null,
"takes_leaders": true,
"allow_pickup": true,
"allow_home_delivery": true,
"home_fee": {
"mode": "fixed",
"fee": 7,
"free_over": 30
},
"store": {
"id": 1,
"name": "Fresh Market",
"slug": "fresh-market",
"timezone": "America/Toronto"
},
"web_url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26"
}
}
},
"leader_policy": "any",
"leader": {
"status": "pending",
"pickup_name": "Ann's porch",
"address": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1",
"phone": "4165550111",
"pickup_notes": "Evenings",
"decision_note": null
},
"may_lead": true,
"can_apply": true,
"group": {
"id": 1,
"mark": "ANNPORCH01",
"share_url": "https://shop.example.com/portal/group-buy/FRUITBOX26?leader=ANNPORCH01",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26?leader=ANNPORCH01",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26%3Fleader%3DANNPORCH01"
}
}
},
"self_pick": null,
"currency": "CAD",
"commission_rates": [
{
"id": 1,
"name": "Apples",
"group_price": 5,
"commission_mode": "percentage",
"commission_value": 10
}
]
}
}
platformAppGroupBuyLeadStart
mutation
REST twin: POST /app/v1/group-buys/{code}/lead/start
Signed-in customer of the customer app or MiBox. D31: on a self-pick plan the leader may use, starts the leader's own campaign: item_ids (the plan's items), cutoff_at and expected_delivery_at as Y-m-d\TH:i on the store's clock (within self_pick.earliest_cutoff … latest_cutoff), optional title. Open to buyers at once (D32) with the leader's group opened; 201 with the new campaign's lead view. 422 VALIDATION_FAILED, 403 GROUP_BUY_LEADER_NOT_ALLOWED, 404 GROUP_BUY_NOT_FOUND for a campaign that is not a plan. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. currency: the Client's currency code, which the amounts (group prices, commission) are in. Added: share — next to a campaign's web_url and a group's share_url, how the store lets buyers share that link, null while it offers no channel or there is no link: {url, title, channels, links}, the same shape and channels as the product answer's share. Needs the group_buy capability on the token.
mutation PlatformAppGroupBuyLeadStart($code: String!, $item_ids: [Int]!, $cutoff_at: String!, $expected_delivery_at: String, $title: String, $locale: String) {
platformAppGroupBuyLeadStart(code: $code, item_ids: $item_ids, cutoff_at: $cutoff_at, expected_delivery_at: $expected_delivery_at, title: $title, locale: $locale)
}
Variables
{
"code": "FRUITBOX26",
"item_ids": [
1
],
"cutoff_at": "2026-11-06T20:00",
"expected_delivery_at": "2026-11-08T10:00",
"title": "Weekly fruit"
}
Response example
{
"result": true,
"data": {
"campaign": {
"code": "FRUITBOX26",
"kind": "leader",
"title": "Fruit box",
"description": null,
"status": "published",
"orderable": true,
"opens_at": "2026-11-03T08:00:00-05:00",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null,
"takes_leaders": true,
"allow_pickup": true,
"allow_home_delivery": true,
"home_fee": {
"mode": "fixed",
"fee": 7,
"free_over": 30
},
"store": {
"id": 1,
"name": "Fresh Market",
"slug": "fresh-market",
"timezone": "America/Toronto"
},
"web_url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26"
}
}
},
"leader_policy": "any",
"leader": {
"status": "pending",
"pickup_name": "Ann's porch",
"address": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1",
"phone": "4165550111",
"pickup_notes": "Evenings",
"decision_note": null
},
"may_lead": true,
"can_apply": true,
"group": {
"id": 1,
"mark": "ANNPORCH01",
"share_url": "https://shop.example.com/portal/group-buy/FRUITBOX26?leader=ANNPORCH01",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26?leader=ANNPORCH01",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26%3Fleader%3DANNPORCH01"
}
}
},
"self_pick": null,
"currency": "CAD",
"commission_rates": [
{
"id": 1,
"name": "Apples",
"group_price": 5,
"commission_mode": "percentage",
"commission_value": 10
}
]
}
}
platformAppGroupBuyLeading
query
REST twin: GET /app/v1/group-buy-leading
Signed-in customer of the customer app or MiBox. The groups the customer leads at this Client, newest first: id, mark, status, campaign, orders and units that count (held or charged), share_url while the campaign is published; templates = the self-pick plans the customer may start a campaign from. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Group status values: open (taking orders until the campaign closes), formed (going ahead; its holds are captured), short (short of a threshold, waiting for the merchant's decision), cancelled (did not go ahead; its holds are released); status_label is its translated name, as the store console shows it. currency: the Client's currency code, which the amounts (group prices, commission) are in. Added: share — next to a campaign's web_url and a group's share_url, how the store lets buyers share that link, null while it offers no channel or there is no link: {url, title, channels, links}, the same shape and channels as the product answer's share. Needs the group_buy capability on the token.
query PlatformAppGroupBuyLeading($locale: String) {
platformAppGroupBuyLeading(locale: $locale)
}
Variables
{}
Response example
{
"result": true,
"data": {
"groups": [
{
"id": 1,
"mark": "ANNPORCH01",
"status": "open",
"status_label": "Open",
"campaign": {
"code": "FRUITBOX26",
"kind": "campaign",
"title": "Fruit box",
"description": null,
"status": "published",
"orderable": true,
"opens_at": "2026-11-03T08:00:00-05:00",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null,
"takes_leaders": true,
"allow_pickup": true,
"allow_home_delivery": true,
"home_fee": {
"mode": "fixed",
"fee": 7,
"free_over": 30
},
"store": {
"id": 1,
"name": "Fresh Market",
"slug": "fresh-market",
"timezone": "America/Toronto"
},
"web_url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26"
}
}
},
"orders": 1,
"units": 2,
"share_url": "https://shop.example.com/portal/group-buy/FRUITBOX26?leader=ANNPORCH01",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26?leader=ANNPORCH01",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26%3Fleader%3DANNPORCH01"
}
}
}
],
"currency": "CAD",
"templates": []
}
}
platformAppGroupBuyLeadingGroup
query
REST twin: GET /app/v1/group-buy-leading/{groupId}
Signed-in customer of the customer app or MiBox. One of the customer's groups: group {id, mark, status, pickup_name, share_url}, campaign, households [{id, number, name, delivery, units, hold_status, lines, shipment_status, collected_at, collected_via, can_mark_collected, commission}], totals {orders, units, commission}, commission_rates. commission = what the leader earns on the order now (D10: per product, a percentage of what the buyer paid or a fixed amount per unit, refunds deducted); it is booked when the group is charged. 404 GROUP_BUY_GROUP_NOT_FOUND for a group the customer does not lead. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Group status values: open (taking orders until the campaign closes), formed (going ahead; its holds are captured), short (short of a threshold, waiting for the merchant's decision), cancelled (did not go ahead; its holds are released); status_label is its translated name, as the store console shows it. Each household also has hold_label: the translated name of hold_status, the wording of the buyer's order page (hold_status pending, authorized, captured, voided, failed). currency: the Client's currency code, which the amounts (group prices, commission) are in. Added: share — next to a campaign's web_url and a group's share_url, how the store lets buyers share that link, null while it offers no channel or there is no link: {url, title, channels, links}, the same shape and channels as the product answer's share. Needs the group_buy capability on the token.
query PlatformAppGroupBuyLeadingGroup($group_id: Int!, $locale: String) {
platformAppGroupBuyLeadingGroup(group_id: $group_id, locale: $locale)
}
Variables
{
"group_id": 1
}
Response example
{
"result": true,
"data": {
"group": {
"id": 1,
"mark": "ANNPORCH01",
"status": "open",
"status_label": "Open",
"pickup_name": "Ann's porch",
"share_url": "https://shop.example.com/portal/group-buy/FRUITBOX26?leader=ANNPORCH01",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26?leader=ANNPORCH01",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26%3Fleader%3DANNPORCH01"
}
}
},
"campaign": {
"code": "FRUITBOX26",
"kind": "campaign",
"title": "Fruit box",
"description": null,
"status": "published",
"orderable": true,
"opens_at": "2026-11-03T08:00:00-05:00",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null,
"takes_leaders": true,
"allow_pickup": true,
"allow_home_delivery": true,
"home_fee": {
"mode": "fixed",
"fee": 7,
"free_over": 30
},
"store": {
"id": 1,
"name": "Fresh Market",
"slug": "fresh-market",
"timezone": "America/Toronto"
},
"web_url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26"
}
}
},
"households": [
{
"id": 1,
"number": 1,
"name": "Ann",
"delivery": "pickup",
"units": 2,
"hold_status": "authorized",
"hold_label": "Amount held",
"lines": [
{
"name": "Apples",
"qty": 2
}
],
"shipment_status": "delivered",
"collected_at": null,
"collected_via": null,
"can_mark_collected": true,
"commission": 1
}
],
"totals": {
"orders": 1,
"units": 2,
"commission": 1
},
"currency": "CAD",
"commission_rates": [
{
"id": 1,
"name": "Apples",
"group_price": 5,
"commission_mode": "percentage",
"commission_value": 10
}
]
}
}
platformAppGroupBuyLeadingCollected
mutation
REST twin: POST /app/v1/group-buy-leading/{groupId}/orders/{orderId}/collected
Signed-in customer of the customer app or MiBox. D27: the leader confirms a household collected its bag at the leader's pickup point, once the bags arrived (can_mark_collected). The order completes and the commission is approved (D19). Answers the group. 422 GROUP_BUY_NOT_COLLECTABLE (not arrived, or in a locker cell), 404 GROUP_BUY_ORDER_NOT_FOUND / GROUP_BUY_GROUP_NOT_FOUND. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Group status values: open (taking orders until the campaign closes), formed (going ahead; its holds are captured), short (short of a threshold, waiting for the merchant's decision), cancelled (did not go ahead; its holds are released); status_label is its translated name, as the store console shows it. Each household also has hold_label: the translated name of hold_status, the wording of the buyer's order page (hold_status pending, authorized, captured, voided, failed). currency: the Client's currency code, which the amounts (group prices, commission) are in. Added: share — next to a campaign's web_url and a group's share_url, how the store lets buyers share that link, null while it offers no channel or there is no link: {url, title, channels, links}, the same shape and channels as the product answer's share. Needs the group_buy capability on the token.
mutation PlatformAppGroupBuyLeadingCollected($group_id: Int!, $order_id: Int!, $locale: String) {
platformAppGroupBuyLeadingCollected(group_id: $group_id, order_id: $order_id, locale: $locale)
}
Variables
{
"group_id": 1,
"order_id": 1
}
Response example
{
"result": true,
"data": {
"group": {
"id": 1,
"mark": "ANNPORCH01",
"status": "open",
"status_label": "Open",
"pickup_name": "Ann's porch",
"share_url": "https://shop.example.com/portal/group-buy/FRUITBOX26?leader=ANNPORCH01",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26?leader=ANNPORCH01",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26%3Fleader%3DANNPORCH01"
}
}
},
"campaign": {
"code": "FRUITBOX26",
"kind": "campaign",
"title": "Fruit box",
"description": null,
"status": "published",
"orderable": true,
"opens_at": "2026-11-03T08:00:00-05:00",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null,
"takes_leaders": true,
"allow_pickup": true,
"allow_home_delivery": true,
"home_fee": {
"mode": "fixed",
"fee": 7,
"free_over": 30
},
"store": {
"id": 1,
"name": "Fresh Market",
"slug": "fresh-market",
"timezone": "America/Toronto"
},
"web_url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26"
}
}
},
"households": [
{
"id": 1,
"number": 1,
"name": "Ann",
"delivery": "pickup",
"units": 2,
"hold_status": "authorized",
"hold_label": "Amount held",
"lines": [
{
"name": "Apples",
"qty": 2
}
],
"shipment_status": "delivered",
"collected_at": "2026-11-04T12:00:00-05:00",
"collected_via": "leader",
"can_mark_collected": false,
"commission": 1
}
],
"totals": {
"orders": 1,
"units": 2,
"commission": 1
},
"currency": "CAD",
"commission_rates": [
{
"id": 1,
"name": "Apples",
"group_price": 5,
"commission_mode": "percentage",
"commission_value": 10
}
]
}
}
Public
platformAppBootstrap
query
REST twin: GET /app/v1/clients/{clientCode}/bootstrap
Returns public branding, effective capability flags, service contracts and mobile version policy. settings.home.quick_actions lists the home screen shortcut ids in the Client's order, without the ones it hid. locales lists the language codes the app offers (en, chs, cht, de, es, fr, it, nl, pt, sr, hu, pl, sk, cs), the same for every client of an app. For the SuperRoute customer app (app_code customer), settings.web.portal_enabled says whether the client runs the web portal, and settings has no home section. Additive: client.country (ISO 3166 alpha-2, CA when unset) and client.currency (CAD when unset), the client's defaults for forms and prices.
query PlatformAppBootstrap($client_code: String!, $platform: String, $app_version: String) {
platformAppBootstrap(client_code: $client_code, platform: $platform, app_version: $app_version)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"platform": "ios",
"app_version": "1.0.0"
}
Response example
{
"result": true,
"data": {
"app": {
"code": "mibox",
"name": "MiBox",
"type": "customer",
"schema_version": 1
},
"configuration_version": 3,
"capabilities": {
"auth.google": true,
"wallet.balance": true,
"locker.pickup": true
}
}
}
platformAppGuestTrack
query
REST twin: POST /app/v1/clients/{clientCode}/guest/track
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. Needs the tracking capability on the token.
query PlatformAppGuestTrack($client_code: String!, $identifier: String!, $answer: String, $locale: String) {
platformAppGuestTrack(client_code: $client_code, identifier: $identifier, answer: $answer, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"identifier": "R8K2Q7"
}
Response example
{
"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
}
}
platformAppGuestStoreOrderLockerOpen
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/store-orders/{code}/locker-open
Open the next door of a guest's paid locker pickup. Guests: send the order token as X-Guest-Order-Token. Errors: 404 ORDER_NOT_FOUND. 409 NOT_A_LOCKER_ORDER, 404 CABINET_NOT_FOUND, 422/409 with code of the refusal (door not opened). Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md). Needs the locker.remote_open capability on the token.
mutation PlatformAppGuestStoreOrderLockerOpen($client_code: String!, $locale: String, $code: String!, $order_token: String) {
platformAppGuestStoreOrderLockerOpen(client_code: $client_code, locale: $locale, code: $code, order_token: $order_token)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"locale": "en",
"code": "EQ0MS2",
"order_token": "3f0c6c1b2b8e4d0a9a1e6f5d4c3b2a19087f6e5d4c3b2a1908f7e6d5c4b3a291"
}
Response example
{
"result": true,
"message": "Door 3 opened: Cola",
"door_opened": true,
"refund_available": false,
"data": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
platformAppRegistrationClients
query
REST twin: GET /app/v1/registration/clients
Public and unauthenticated, exactly like the REST route: it is the first call an app makes for a person with no account, and answers nothing account-specific. Only Clients whose assignment is effective, whose customer role is enabled and whose auth.registration capability is on are listed. When count is 1 the app can skip the picker and open the registration form directly.
query PlatformAppRegistrationClients {
platformAppRegistrationClients
}
Variables
{}
Response example
{
"result": true,
"data": {
"app": {
"code": "mibox",
"name": "MiBox"
},
"count": 2,
"items": [
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"display_name": "MiBox Toronto",
"logo_url": "https://cdn.example.com/mibox-to.png",
"primary_color": "#146C94",
"support_email": "support@example.com",
"privacy_url": "https://example.com/privacy",
"terms_url": "https://example.com/terms",
"requires_approval": true,
"phone_verification_required": true,
"sms_configured": true,
"default_phone_region": "CA",
"authentication": {
"google": true,
"google_server_client_id": "1234567890-abc.apps.googleusercontent.com",
"apple": true
}
},
{
"client_code": "b8k2m5q9w1e4r7t0y3u6i9op",
"display_name": "MiBox Vancouver",
"logo_url": null,
"primary_color": "#146C94",
"support_email": null,
"privacy_url": null,
"terms_url": null,
"requires_approval": false,
"phone_verification_required": false,
"sms_configured": false,
"default_phone_region": "CA",
"authentication": {
"google": false,
"google_server_client_id": "1234567890-abc.apps.googleusercontent.com",
"apple": false
}
}
]
}
}
platformAppRequestRegistrationCode
mutation
REST twin: POST /app/v1/clients/{clientCode}/registration/phone/request
Creates a five-minute App- and Client-scoped challenge and sends a six-digit code through the Client's configured SMS provider. Needs the auth.registration capability on the token.
mutation PlatformAppRequestRegistrationCode($client_code: String!, $phone: String!, $locale: String) {
platformAppRequestRegistrationCode(client_code: $client_code, phone: $phone, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"phone": "+14165550100",
"locale": "en"
}
Response example
{
"result": true,
"challenge_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"phone_e164": "+14****0100",
"expires_in": 300,
"resend_after": 60
}
platformAppRequestRegistrationEmailCode
mutation
REST twin: POST /app/v1/clients/{clientCode}/registration/email/request
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. Needs the auth.registration capability on the token.
mutation PlatformAppRequestRegistrationEmailCode($client_code: String!, $email: String!, $locale: String) {
platformAppRequestRegistrationEmailCode(client_code: $client_code, email: $email, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"email": "jane.doe@example.com",
"locale": "en"
}
Response example
{
"result": true,
"message": "A verification email was sent.",
"challenge_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"email": "j*******@example.com",
"expires_in": 600,
"resend_after": 60
}
platformAppRegisterCustomer
mutation
REST twin: POST /app/v1/clients/{clientCode}/registration/complete
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. Needs the auth.registration capability on the token.
mutation PlatformAppRegisterCustomer($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(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
{
"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"
}
Response example
{
"result": true,
"phone_verified": true,
"requires_approval": false,
"access_token": "passport-access-token",
"token_type": "Bearer",
"linked_providers": []
}
platformAppWithdrawAccountClosure
mutation
REST twin: POST /app/v1/account-closure/withdraw
Unauthenticated by necessity: an open closure request suspends the account and revokes its tokens, so requiring a session would make the request impossible to take back. The one-shot withdrawal_token is the credential — returned once by the closure request, again in the notice email link, and again in the 403 ACCOUNT_CLOSURE_PENDING body when a correct password is refused at sign-in. Withdrawing restores the account to exactly the status it held before the request.
mutation PlatformAppWithdrawAccountClosure($withdrawal_token: String!) {
platformAppWithdrawAccountClosure(withdrawal_token: $withdrawal_token)
}
Variables
{
"withdrawal_token": "one-shot-withdrawal-token"
}
Response example
{
"result": true,
"message": "The account closure request has been withdrawn.",
"data": {
"id": 42,
"status": "cancelled",
"requested_at": "2026-09-05T10:00:00+00:00",
"cancelled_at": "2026-09-05T12:00:00+00:00",
"completed_at": null,
"handled_at": null,
"reason": "Moving away",
"decision_reason": null,
"requires_review": true
}
}
platformAppGuestGroupBuy
query
REST twin: GET /app/v1/clients/{clientCode}/guest/group-buys/{code}
Public: no sign-in token is read. Same answer as the signed-in call, with guest_verification_channel (email | phone): the contact a guest verifies before ordering. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Added: share — next to a campaign's web_url and a group's share_url, how the store lets buyers share that link, null while it offers no channel or there is no link: {url, title, channels, links}, the same shape and channels as the product answer's share. Needs the group_buy capability on the token.
query PlatformAppGuestGroupBuy($client_code: String!, $code: String!, $leader: String, $locale: String) {
platformAppGuestGroupBuy(client_code: $client_code, code: $code, leader: $leader, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"code": "FRUITBOX26"
}
Response example
{
"result": true,
"data": {
"campaign": {
"code": "FRUITBOX26",
"kind": "campaign",
"title": "Fruit box",
"description": null,
"status": "published",
"orderable": true,
"opens_at": "2026-11-03T08:00:00-05:00",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null,
"takes_leaders": true,
"allow_pickup": true,
"allow_home_delivery": true,
"home_fee": {
"mode": "fixed",
"fee": 7,
"free_over": 30
},
"store": {
"id": 1,
"name": "Fresh Market",
"slug": "fresh-market",
"timezone": "America/Toronto"
},
"web_url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"share": {
"url": "https://shop.example.com/portal/group-buy/FRUITBOX26",
"title": "Fruit box",
"channels": [
"system",
"wechat",
"whatsapp",
"facebook",
"x",
"email",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Fruit%20box%20https%3A%2F%2Fshop.example.com%2Fportal%2Fgroup-buy%2FFRUITBOX26"
}
}
},
"items": [
{
"id": 1,
"name": "Apples",
"group_price": 5,
"regular_price": 6
}
],
"pickup": [
{
"key": "leader:1",
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
{
"key": "point:1",
"name": "Library",
"address": "12 King St, Toronto, M5H 2N2",
"notes": "Sat 10-12",
"leader": false
}
],
"home": true,
"leader": null,
"address_suggestions": false,
"currency": "CAD",
"tax_rate": 13,
"guest_verification_channel": "email"
}
}
platformAppGuestGroupBuyAddressDetails
query
REST twin: GET /app/v1/clients/{clientCode}/guest/group-buys/{code}/address/details
Public: no sign-in token is read. As the signed-in call. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
query PlatformAppGuestGroupBuyAddressDetails($client_code: String!, $code: String!, $place_id: String!, $session: String, $locale: String) {
platformAppGuestGroupBuyAddressDetails(client_code: $client_code, code: $code, place_id: $place_id, session: $session, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"code": "FRUITBOX26",
"place_id": "ChIJ3Elm"
}
Response example
{
"result": true,
"data": {
"address": {
"address_1": "3 Elm St",
"city": "Toronto",
"province": "ON",
"postcode": "M4M 1A1"
}
}
}
platformAppGuestGroupBuyAddressSuggestions
query
REST twin: GET /app/v1/clients/{clientCode}/guest/group-buys/{code}/address/suggestions
Public: no sign-in token is read. As the signed-in call. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
query PlatformAppGuestGroupBuyAddressSuggestions($client_code: String!, $code: String!, $q: String!, $session: String, $locale: String) {
platformAppGuestGroupBuyAddressSuggestions(client_code: $client_code, code: $code, q: $q, session: $session, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"code": "FRUITBOX26",
"q": "3 Elm"
}
Response example
{
"result": true,
"data": {
"suggestions": [
{
"place_id": "ChIJ3Elm",
"text": "3 Elm St, Toronto, ON"
}
]
}
}
platformAppGuestGroupBuyConfirmPayment
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/group-buy-orders/{token}/confirm-payment
Public: no sign-in token is read. As the signed-in call. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
mutation PlatformAppGuestGroupBuyConfirmPayment($client_code: String!, $token: String!, $locale: String) {
platformAppGuestGroupBuyConfirmPayment(client_code: $client_code, token: $token, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk"
}
Response example
{
"result": true,
"data": {
"order": {
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"number": 1,
"campaign": {
"code": "FRUITBOX26",
"title": "Fruit box",
"status": "published",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null
},
"store": {
"id": 1,
"name": "Fresh Market"
},
"hold_status": "authorized",
"hold_label": "Amount held",
"hold_message": "The amount is held on your card. It is charged after orders close at 2026-11-07 20:00 if the group goes ahead, and released otherwise.",
"status": "pending_payment",
"delivery": "pickup",
"units": 2,
"total": 11.3,
"currency": "CAD",
"place": {
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
"shipment_status": null,
"collected_at": null,
"collected_via": null,
"created_at": "2026-11-04T12:00:00-05:00",
"web_url": "https://shop.example.com/portal/group-buy/orders/Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"actions": {
"can_pay": false
},
"subtotal": 10,
"delivery_fee": 0,
"tax": 1.3,
"lines": [
{
"name": "Apples",
"qty": 2,
"unit_price": 5,
"total": 10
}
],
"address": {
"address_1": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1"
},
"at_locker": false,
"locker": null,
"can_confirm_received": false
}
}
}
platformAppGuestGroupBuyOrder
query
REST twin: GET /app/v1/clients/{clientCode}/guest/group-buy-orders/{token}
Public: no sign-in token is read. As the signed-in call. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
query PlatformAppGuestGroupBuyOrder($client_code: String!, $token: String!, $locale: String) {
platformAppGuestGroupBuyOrder(client_code: $client_code, token: $token, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk"
}
Response example
{
"result": true,
"data": {
"order": {
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"number": 1,
"campaign": {
"code": "FRUITBOX26",
"title": "Fruit box",
"status": "published",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null
},
"store": {
"id": 1,
"name": "Fresh Market"
},
"hold_status": "authorized",
"hold_label": "Amount held",
"hold_message": "The amount is held on your card. It is charged after orders close at 2026-11-07 20:00 if the group goes ahead, and released otherwise.",
"status": "pending_payment",
"delivery": "pickup",
"units": 2,
"total": 11.3,
"currency": "CAD",
"place": {
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
"shipment_status": "delivered",
"collected_at": null,
"collected_via": null,
"created_at": "2026-11-04T12:00:00-05:00",
"web_url": "https://shop.example.com/portal/group-buy/orders/Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"actions": {
"can_pay": false
},
"subtotal": 10,
"delivery_fee": 0,
"tax": 1.3,
"lines": [
{
"name": "Apples",
"qty": 2,
"unit_price": 5,
"total": 10
}
],
"address": {
"address_1": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1"
},
"at_locker": false,
"locker": null,
"can_confirm_received": true
}
}
}
platformAppGuestGroupBuyOrderPay
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/group-buy-orders/{token}/pay
Public: no sign-in token is read; the order token is the credential, as for the other guest group buy order calls. As the signed-in call. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
mutation PlatformAppGuestGroupBuyOrderPay($client_code: String!, $token: String!, $locale: String) {
platformAppGuestGroupBuyOrderPay(client_code: $client_code, token: $token, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk"
}
Response example
{
"result": true,
"data": {
"order": {
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"number": 1,
"campaign": {
"code": "FRUITBOX26",
"title": "Fruit box",
"status": "published",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null
},
"store": {
"id": 1,
"name": "Fresh Market"
},
"hold_status": "pending",
"hold_label": "Waiting for the card",
"hold_message": "Complete the card payment to join the group.",
"status": "pending_payment",
"delivery": "pickup",
"units": 2,
"total": 11.3,
"currency": "CAD",
"place": {
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
"shipment_status": null,
"collected_at": null,
"collected_via": null,
"created_at": "2026-11-04T12:00:00-05:00",
"web_url": "https://shop.example.com/portal/group-buy/orders/Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"actions": {
"can_pay": true
},
"subtotal": 10,
"delivery_fee": 0,
"tax": 1.3,
"lines": [
{
"name": "Apples",
"qty": 2,
"unit_price": 5,
"total": 10
}
],
"address": {
"address_1": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1"
},
"at_locker": false,
"locker": null,
"can_confirm_received": false
},
"payment": {
"client_secret": "pi_3Q_secret_abc",
"publishable_key": "pk_live_123",
"merchant_country": "CA",
"merchant_display_name": "Fresh Market",
"capture": "manual"
},
"reused_intent": true
}
}
platformAppGuestGroupBuyOrderReceived
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/group-buy-orders/{token}/received
Public: no sign-in token is read. As the signed-in call. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
mutation PlatformAppGuestGroupBuyOrderReceived($client_code: String!, $token: String!, $locale: String) {
platformAppGuestGroupBuyOrderReceived(client_code: $client_code, token: $token, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk"
}
Response example
{
"result": true,
"data": {
"order": {
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"number": 1,
"campaign": {
"code": "FRUITBOX26",
"title": "Fruit box",
"status": "published",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null
},
"store": {
"id": 1,
"name": "Fresh Market"
},
"hold_status": "authorized",
"hold_label": "Amount held",
"hold_message": "The amount is held on your card. It is charged after orders close at 2026-11-07 20:00 if the group goes ahead, and released otherwise.",
"status": "pending_payment",
"delivery": "pickup",
"units": 2,
"total": 11.3,
"currency": "CAD",
"place": {
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
"shipment_status": "delivered",
"collected_at": "2026-11-04T12:00:00-05:00",
"collected_via": "leader",
"created_at": "2026-11-04T12:00:00-05:00",
"web_url": "https://shop.example.com/portal/group-buy/orders/Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"actions": {
"can_pay": false
},
"subtotal": 10,
"delivery_fee": 0,
"tax": 1.3,
"lines": [
{
"name": "Apples",
"qty": 2,
"unit_price": 5,
"total": 10
}
],
"address": {
"address_1": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1"
},
"at_locker": false,
"locker": null,
"can_confirm_received": false
}
}
}
platformAppGuestGroupBuyOrders
query
REST twin: GET /app/v1/clients/{clientCode}/guest/group-buy-orders
Public: no sign-in token is read. The orders of the tokens the device kept (X-Guest-Order-Token, comma-separated, up to 50), newest first; unknown tokens and other Clients' orders are left out. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
query PlatformAppGuestGroupBuyOrders($client_code: String!, $order_tokens: [String]!, $locale: String) {
platformAppGuestGroupBuyOrders(client_code: $client_code, order_tokens: $order_tokens, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"order_tokens": [
"Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk"
]
}
Response example
{
"result": true,
"data": {
"items": [
{
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"number": 1,
"campaign": {
"code": "FRUITBOX26",
"title": "Fruit box",
"status": "published",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null
},
"store": {
"id": 1,
"name": "Fresh Market"
},
"hold_status": "authorized",
"hold_label": "Amount held",
"hold_message": "The amount is held on your card. It is charged after orders close at 2026-11-07 20:00 if the group goes ahead, and released otherwise.",
"status": "pending_payment",
"delivery": "pickup",
"units": 2,
"total": 11.3,
"currency": "CAD",
"place": {
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
"shipment_status": "delivered",
"collected_at": null,
"collected_via": null,
"created_at": "2026-11-04T12:00:00-05:00",
"web_url": "https://shop.example.com/portal/group-buy/orders/Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"actions": {
"can_pay": false
}
}
],
"pagination": {
"current_page": 1,
"per_page": 20,
"has_more": false
}
}
}
platformAppGuestGroupBuyPlaceOrder
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/group-buys/{code}/order
Public: no sign-in token is read. As the signed-in order, with the verified contact and name (required, max 120): 422 GROUP_BUY_VERIFY_FIRST without a valid contact token, GROUP_BUY_ORDER_REFUSED with errors [name_required] without a name. Keep data.order.token on the device: it is the only way back to the order (no push for guests). Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
mutation PlatformAppGuestGroupBuyPlaceOrder($client_code: String!, $code: String!, $contact_token: String!, $lines: Json, $receive: String, $leader: String, $address: Json, $note: String, $name: String, $locale: String) {
platformAppGuestGroupBuyPlaceOrder(client_code: $client_code, code: $code, contact_token: $contact_token, lines: $lines, receive: $receive, leader: $leader, address: $address, note: $note, name: $name, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"code": "FRUITBOX26",
"contact_token": "eyJpdiI6Ik1hY0...",
"lines": {
"1": 2
},
"receive": "point:1",
"leader": "ANNPORCH01",
"note": "Ring the bell",
"name": "Gina"
}
Response example
{
"result": true,
"data": {
"order": {
"token": "Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"number": 1,
"campaign": {
"code": "FRUITBOX26",
"title": "Fruit box",
"status": "published",
"cutoff_at": "2026-11-07T20:00:00-05:00",
"expected_delivery_at": null
},
"store": {
"id": 1,
"name": "Fresh Market"
},
"hold_status": "pending",
"hold_label": "Waiting for the card",
"hold_message": "The card hold is being confirmed. This page updates by itself.",
"status": "pending_payment",
"delivery": "pickup",
"units": 2,
"total": 11.3,
"currency": "CAD",
"place": {
"name": "Ann's porch",
"address": "9 Oak Ave, Toronto, M6G 1A1",
"notes": "Evenings",
"leader": true
},
"shipment_status": null,
"collected_at": null,
"collected_via": null,
"created_at": "2026-11-04T12:00:00-05:00",
"web_url": "https://shop.example.com/portal/group-buy/orders/Q7mVx2LpR9tK4wNc8bHs3dFj6gYz1aEu5iOo0rTy2uWq4ePk",
"actions": {
"can_pay": true
},
"subtotal": 10,
"delivery_fee": 0,
"tax": 1.3,
"lines": [
{
"name": "Apples",
"qty": 2,
"unit_price": 5,
"total": 10
}
],
"address": {
"address_1": "9 Oak Ave",
"address_2": null,
"city": "Toronto",
"province": null,
"postcode": "M6G 1A1"
},
"at_locker": false,
"locker": null,
"can_confirm_received": false
},
"payment": {
"client_secret": "pi_secret_1",
"publishable_key": "pk_test",
"merchant_country": "CA",
"merchant_display_name": "Fresh Market",
"capture": "manual"
}
}
}
platformAppGuestGroupBuyQuote
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/group-buys/{code}/quote
Public: no sign-in token is read. As the signed-in quote. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. The app answer also carries currency (the Client's currency code the amounts are in), next to the web quote's fields. Needs the group_buy capability on the token.
mutation PlatformAppGuestGroupBuyQuote($client_code: String!, $code: String!, $lines: Json, $receive: String, $address: Json, $locale: String) {
platformAppGuestGroupBuyQuote(client_code: $client_code, code: $code, lines: $lines, receive: $receive, address: $address, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"code": "FRUITBOX26",
"lines": {
"1": 2
},
"receive": "home",
"address": {
"address_1": "3 Elm St",
"city": "Toronto",
"postcode": "M4M 1A1",
"delivery_instruction": "Side door"
}
}
Response example
{
"result": true,
"data": {
"ok": true,
"errors": [],
"messages": [],
"units": 2,
"subtotal": 10,
"delivery_fee": 0,
"tax": 1.3,
"total": 11.3,
"currency": "CAD"
}
}
platformAppGuestGroupBuyVerifyComplete
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/group-buys/{code}/verify/complete
Public: no sign-in token is read. Answers contact_token (90 days), sent as X-Guest-Contact-Token when ordering. A contact token from guest/verify/complete of the same app and Client works too. GraphQL names the code argument verification_code. 422 INVALID_VERIFICATION_CODE, INVALID_VERIFICATION_CHALLENGE, VERIFICATION_ATTEMPTS_EXCEEDED. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
mutation PlatformAppGuestGroupBuyVerifyComplete($client_code: String!, $code: String!, $challenge_id: String!, $verification_code: String!, $locale: String) {
platformAppGuestGroupBuyVerifyComplete(client_code: $client_code, code: $code, challenge_id: $challenge_id, verification_code: $verification_code, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"code": "FRUITBOX26",
"challenge_id": "Qm9vZ2llV29vZ2ll...",
"verification_code": "123456"
}
Response example
{
"result": true,
"contact_token": "eyJpdiI6Ik1hY0...",
"channel": "email",
"contact": "g****@example.com",
"expires_in": 7776000
}
platformAppGuestGroupBuyVerifyRequest
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/group-buys/{code}/verify/request
Public: no sign-in token is read. Sends a six-digit code to the e-mail address or phone number (the Client's guest verification channel), as guest shipping's verify/request does — the answer is the verification's own body (challenge_id, channel, masked contact, expires_in, resend_after). Group buy verifies guests whether or not the Client offers guest shipping. 422 INVALID_EMAIL / INVALID_PHONE_NUMBER, 429 GUEST_VERIFICATION_RATE_LIMITED, 503 SMS_SERVICE_NOT_CONFIGURED. Available when the calling app's own group_buy capability is on and the Client has a white-label host (404 GROUP_BUY_UNAVAILABLE otherwise), and the campaign's store has group buy on (404 GROUP_BUY_NOT_FOUND). GraphQL answers the REST body. Needs the group_buy capability on the token.
mutation PlatformAppGuestGroupBuyVerifyRequest($client_code: String!, $code: String!, $contact: String!, $locale: String) {
platformAppGuestGroupBuyVerifyRequest(client_code: $client_code, code: $code, contact: $contact, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"code": "FRUITBOX26",
"contact": "gina@example.com"
}
Response example
{
"result": true,
"message": "A verification code was sent.",
"challenge_id": "Qm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2ll",
"channel": "email",
"contact": "g****@example.com",
"expires_in": 600,
"resend_after": 60
}
platformAppGuestShipment
query
REST twin: GET /app/v1/clients/{clientCode}/guest/shipments/{guestOrderId}
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. Needs the guest.shipping capability on the token.
query PlatformAppGuestShipment($client_code: String!, $id: Int!, $order_token: String, $contact_token: String, $locale: String) {
platformAppGuestShipment(client_code: $client_code, id: $id, order_token: $order_token, contact_token: $contact_token, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"id": 42,
"order_token": "k3J9xQ2mV8pL5tR1wZ7cN4bH6dF0gS2aY9eU3iO8"
}
Response example
{
"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
REST twin: POST /app/v1/clients/{clientCode}/guest/shipments/{guestOrderId}/cancel
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. Needs the guest.shipping capability on the token.
mutation PlatformAppGuestShipmentCancel($client_code: String!, $id: Int!, $order_token: String, $contact_token: String, $locale: String) {
platformAppGuestShipmentCancel(client_code: $client_code, id: $id, order_token: $order_token, contact_token: $contact_token, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"id": 42,
"order_token": "k3J9xQ2mV8pL5tR1wZ7cN4bH6dF0gS2aY9eU3iO8"
}
Response example
{
"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
REST twin: GET /app/v1/clients/{clientCode}/guest/shipments/{guestOrderId}/label
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. Needs the guest.shipping capability on the token.
query PlatformAppGuestShipmentLabel($client_code: String!, $id: Int!, $order_token: String, $contact_token: String, $locale: String) {
platformAppGuestShipmentLabel(client_code: $client_code, id: $id, order_token: $order_token, contact_token: $contact_token, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"id": 42,
"order_token": "k3J9xQ2mV8pL5tR1wZ7cN4bH6dF0gS2aY9eU3iO8"
}
Response example
{
"result": true,
"format": "pdf",
"tracking_numbers": [
"1Z999AA10123456784"
],
"pdf_data": "JVBERi0xLjQKJ..."
}
platformAppGuestShipmentRate
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/shipments/rate
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. Needs the guest.shipping capability on the token.
mutation PlatformAppGuestShipmentRate($client_code: String!, $input: Json, $locale: String) {
platformAppGuestShipmentRate(client_code: $client_code, input: $input, locale: $locale)
}
Variables
{
"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
}
]
}
}
Response example
{
"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
REST twin: POST /app/v1/clients/{clientCode}/guest/shipments
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). Needs the guest.shipping capability on the token.
mutation PlatformAppGuestShipmentSubmit($client_code: String!, $contact_token: String!, $input: Json, $locale: String) {
platformAppGuestShipmentSubmit(client_code: $client_code, contact_token: $contact_token, input: $input, locale: $locale)
}
Variables
{
"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..."
}
}
Response example
{
"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
REST twin: GET /app/v1/clients/{clientCode}/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. Needs the guest.shipping capability on the token.
query PlatformAppGuestShipments($client_code: String!, $contact_token: String!, $locale: String) {
platformAppGuestShipments(client_code: $client_code, contact_token: $contact_token, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"contact_token": "eyJpdiI6IkNvbnRhY3RUb2tlbiIs..."
}
Response example
{
"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
}
]
}
platformAppGuestStorefront
query
REST twin: GET /app/v1/clients/{clientCode}/guest/storefront
Public, no sign-in: a guest of the client behind client_code (A6). Which home cards the app shows (A3, A4): `mall` (null when the platform gave the client no mall or the client has no marketplace) with `operations_enabled` (false = the mall is its store directory only), `stores_available` (listed stores exist), `group_buy_available` (the app's group_buy capability is on and a group buy can be ordered now). Never 404 for a known client: a client without the marketplace answers all off. Same body as REST.
query PlatformAppGuestStorefront($client_code: String!, $locale: String) {
platformAppGuestStorefront(client_code: $client_code, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab"
}
Response example
{
"result": true,
"data": {
"mall": {
"enabled": true,
"operations_enabled": true,
"name": "Client",
"web_url": "https://shop.example.com/mall"
},
"stores_available": true,
"group_buy_available": true,
"currency": "CAD"
}
}
platformAppGuestStore
query
REST twin: GET /app/v1/clients/{clientCode}/guest/stores/{slug}
Public, no sign-in: a guest of the client behind client_code (A6). A listed store of the client (also one that is not in the mall): profile, whether it takes orders now (`accepting_orders`, `intake_blockers`), the order types it can take with ASAP and the time slots (store clock, `timezone`), categories, the menu as `sections` (category order; products of no active category last, `label` null) with each item's price, stock state and option groups (each group and value with `image_url`, null without an image), the featured row and banners (unfiltered view only), its group buys and the 10 newest approved reviews. `q`, `cat` (with its sub-categories) and `sort` filter the menu like the web. `table` enters the dine-in context: `dine_in.table_code` is set and only dine_in is offered; an unknown table, or a store without dine-in, answers 404 TABLE_NOT_FOUND. Without `table`, dine_in is never listed. Same body as REST. Every product card carries currency: the currency code its prices are in (the store's Client's). Added (docs/STORE-STYLES-DESIGN.md P2): design {style simple|multi_page — the style this answer is drawn in (a table always gets simple), published_version (0 = never published; the store then looks as before), nav_categories[{id, name}], top_blocks — in the simple style, the blocks above the menu on the unfiltered view of a store that published a design; banners, announcement and featured stay in the answer as before}. In the multi-page style the app opens /home, /categories/{categoryId} and the product page's page_blocks. Each block is {id, type, settings, data}. type is one of banner, announcement, category_entry, product_grid, image_text, video, coupons, group_buys, reviews, category_header, product_list (category pages), buy_box, details, specs, product_reviews, related (product pages). settings holds only how the block is drawn (columns, layout, height, tone, display, image_position, autoplay, page_size, show_*); data holds what it shows, resolved in the answer language with absolute URLs: banner {slides[{image_url, link}]}, announcement {text}, category_entry {title, categories[{id, name, image_url}]}, product_grid and related {title, products[product card]}, image_text {image_url, title, body, link, button_text}, video {title, kind embed|file, url}, coupons {title, coupons[{id, name, description, code, ends_on}]}, group_buys {title, group_buys[]}, reviews {title, reviews[{rating, comment, reviewer, merchant_reply, created_at}]}, category_header {name, image_url, description}, product_list {items[product card], pagination, subcategories[{id, name}], filters{q, sort, sub, in_stock}}, details {title, body, image_urls[]}, specs {title, fields[{label, value}]}, product_reviews {title, reviews[{rating, comment, created_at}]}, buy_box {} (the product answer itself). A block with nothing to show is left out; unknown types should be skipped.
query PlatformAppGuestStore($client_code: String!, $slug: String!, $q: String, $sort: String, $cat: Int, $table: String, $locale: String) {
platformAppGuestStore(client_code: $client_code, slug: $slug, q: $q, sort: $sort, cat: $cat, table: $table, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"slug": "pizza-place-1"
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place",
"description": "",
"store_type": "restaurant",
"store_type_label": "Restaurant",
"tags": [
"pizza",
"halal"
],
"logo_url": "https://shop.example.com/storage/stores/31/logo.png",
"cover_url": "https://shop.example.com/storage/stores/31/cover.jpg",
"rating_avg": 4.6,
"rating_count": 38,
"address": "12 King St W",
"city": "Toronto",
"phone": "",
"min_order_amount": 15,
"prep_time_minutes": 20,
"timezone": "America/Toronto",
"announcement": "",
"social_links": {
"instagram": "https://instagram.com/pizzaplace"
},
"food": true,
"guest_orders": true,
"currency": "CAD",
"web_url": "https://shop.example.com/s/pizza-place-1"
},
"accepting_orders": true,
"intake_blockers": [],
"order_types": [
{
"type": "collection",
"label": "Pick-up",
"accepting": true,
"asap": true,
"days": [
{
"date": "2026-10-14",
"slots": [
"15:30",
"15:45",
"16:00"
]
}
]
}
],
"dine_in": null,
"banners": [
{
"image_url": "https://shop.example.com/storage/stores/31/banner.jpg",
"link": "https://shop.example.com/s/pizza-place-1/i/1"
}
],
"categories": [
{
"id": 1,
"parent_id": null,
"name": "Pizzas"
}
],
"sections": [
{
"category_id": 1,
"label": "Pizzas",
"items": [
{
"id": 1,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Margherita",
"description": "Tomato, mozzarella, basil",
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg",
"price": 12,
"list_price": 14,
"compare_at_price": 14,
"on_sale": true,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [
{
"code": "gluten",
"label": "Gluten"
},
{
"code": "milk",
"label": "Milk"
}
],
"affiliate": false,
"external_url": null,
"option_groups": [
{
"id": 1,
"label": "Size",
"type": "radio",
"required": true,
"min": 0,
"max": null,
"image_url": null,
"values": [
{
"id": 1,
"label": "12 inch",
"delta": 0,
"default": false,
"image_url": null
},
{
"id": 2,
"label": "16 inch",
"delta": 4,
"default": false,
"image_url": null
}
]
}
]
}
]
},
{
"category_id": null,
"label": null,
"items": [
{
"id": 2,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Cola",
"description": "",
"image_url": null,
"price": 2.5,
"list_price": 2.5,
"compare_at_price": null,
"on_sale": false,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [],
"affiliate": false,
"external_url": null,
"option_groups": []
}
]
}
],
"featured": [
{
"id": 1,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Margherita",
"description": "Tomato, mozzarella, basil",
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg",
"price": 12,
"list_price": 14,
"compare_at_price": 14,
"on_sale": true,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [
{
"code": "gluten",
"label": "Gluten"
},
{
"code": "milk",
"label": "Milk"
}
],
"affiliate": false,
"external_url": null,
"option_groups": [
{
"id": 1,
"label": "Size",
"type": "radio",
"required": true,
"min": 0,
"max": null,
"image_url": null,
"values": [
{
"id": 1,
"label": "12 inch",
"delta": 0,
"default": false,
"image_url": null
},
{
"id": 2,
"label": "16 inch",
"delta": 4,
"default": false,
"image_url": null
}
]
}
]
}
],
"group_buys": [
{
"code": "PZBOX7",
"title": "Friday pizza box",
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"opens_at": "2026-10-14T14:00:00-04:00",
"cutoff_at": "2026-10-16T15:00:00-04:00",
"url": "https://shop.example.com/portal/group-buy/PZBOX7"
}
],
"reviews": [
{
"rating": 5,
"comment": "Great crust",
"reviewer": null,
"merchant_reply": "Thank you!",
"created_at": "2026-10-14T15:00:00-04:00"
}
],
"filters": {
"q": "",
"sort": "",
"cat": 0
},
"design": {
"style": "simple",
"published_version": 2,
"nav_categories": [
{
"id": 5,
"name": "Kitchen"
}
],
"top_blocks": [
{
"id": "b9a8b7c6d5",
"type": "announcement",
"settings": {
"tone": "accent"
},
"data": {
"text": "Closed on Monday"
}
}
]
}
}
}
platformAppGuestStoreHome
query
REST twin: GET /app/v1/clients/{clientCode}/guest/stores/{slug}/home
Public, no sign-in: a guest of the client behind client_code (A6). The home page of a store in the multi-page style (docs/STORE-STYLES-DESIGN.md P2): the store card, accepting_orders, design {style, published_version, nav_categories[{id, name}] — the top-level categories for the category bar} and blocks in the order the merchant published. A store in the simple style answers 404 STORE_PAGE_NOT_FOUND (its home is the store page); an unknown store 404 STORE_NOT_FOUND. Each block is {id, type, settings, data}. type is one of banner, announcement, category_entry, product_grid, image_text, video, coupons, group_buys, reviews, category_header, product_list (category pages), buy_box, details, specs, product_reviews, related (product pages). settings holds only how the block is drawn (columns, layout, height, tone, display, image_position, autoplay, page_size, show_*); data holds what it shows, resolved in the answer language with absolute URLs: banner {slides[{image_url, link}]}, announcement {text}, category_entry {title, categories[{id, name, image_url}]}, product_grid and related {title, products[product card]}, image_text {image_url, title, body, link, button_text}, video {title, kind embed|file, url}, coupons {title, coupons[{id, name, description, code, ends_on}]}, group_buys {title, group_buys[]}, reviews {title, reviews[{rating, comment, reviewer, merchant_reply, created_at}]}, category_header {name, image_url, description}, product_list {items[product card], pagination, subcategories[{id, name}], filters{q, sort, sub, in_stock}}, details {title, body, image_urls[]}, specs {title, fields[{label, value}]}, product_reviews {title, reviews[{rating, comment, created_at}]}, buy_box {} (the product answer itself). A block with nothing to show is left out; unknown types should be skipped. Same body as REST.
query PlatformAppGuestStoreHome($client_code: String!, $slug: String!, $locale: String) {
platformAppGuestStoreHome(client_code: $client_code, slug: $slug, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"slug": "home-goods-1"
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "home-goods-1",
"name": "Home Goods",
"description": "",
"store_type": "retail",
"store_type_label": "Retail Store",
"tags": [],
"logo_url": null,
"cover_url": null,
"rating_avg": 0,
"rating_count": 0,
"address": "1 Main St",
"city": "Toronto",
"currency": "CAD",
"web_url": "https://shop.example.com/s/home-goods-1"
},
"accepting_orders": true,
"design": {
"style": "multi_page",
"published_version": 3,
"nav_categories": [
{
"id": 5,
"name": "Kitchen"
},
{
"id": 7,
"name": "Garden"
}
]
},
"blocks": [
{
"id": "b1a2b3c4d5",
"type": "banner",
"settings": {
"height": "medium",
"autoplay": true
},
"data": {
"slides": [
{
"image_url": "https://shop.example.com/storage/merchant_stores/1/banner_1.jpg",
"link": "https://shop.example.com/s/home-goods-1/c/5"
}
]
}
},
{
"id": "b2b3c4d5e6",
"type": "category_entry",
"settings": {
"columns": 4,
"display": "image"
},
"data": {
"title": "Categories",
"categories": [
{
"id": 5,
"name": "Kitchen",
"image_url": null
},
{
"id": 7,
"name": "Garden",
"image_url": null
}
]
}
},
{
"id": "b3c4d5e6f7",
"type": "product_grid",
"settings": {
"limit": 8,
"columns": 4,
"layout": "grid"
},
"data": {
"title": "New arrivals",
"products": [
{
"id": 12,
"store": {
"id": 1,
"slug": "home-goods-1",
"name": "Home Goods"
},
"name": "Copper pan",
"description": "",
"image_url": "https://shop.example.com/storage/products/12/pan.jpg",
"price": 40,
"list_price": 40,
"compare_at_price": null,
"on_sale": false,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [],
"affiliate": false,
"external_url": null
}
]
}
},
{
"id": "b4d5e6f7a8",
"type": "image_text",
"settings": {
"image_position": "left"
},
"data": {
"image_url": "https://shop.example.com/storage/merchant_stores/1/banner_2.jpg",
"title": "Our story",
"body": "Made by hand since 1990",
"link": "",
"button_text": ""
}
},
{
"id": "b5e6f7a8b9",
"type": "video",
"settings": {
"autoplay": false
},
"data": {
"title": "",
"kind": "embed",
"url": "https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
}
}
]
}
}
platformAppGuestStoreCategory
query
REST twin: GET /app/v1/clients/{clientCode}/guest/stores/{slug}/categories/{categoryId}
Public, no sign-in: a guest of the client behind client_code (A6). A category page of a store in the multi-page style (docs/STORE-STYLES-DESIGN.md P2): store, category {id, parent_id, name}, accepting_orders, design (as the home page) and blocks — the category's own layout when it has one, else the store's; the product_list block holds the category's items and its sub-categories', paged and filtered by sort (price_asc, price_desc, name, newest), sub (a direct sub-category), in_stock and page. 404 STORE_PAGE_NOT_FOUND in the simple style, CATEGORY_NOT_FOUND for a category the store does not have (or has switched off). Each block is {id, type, settings, data}. type is one of banner, announcement, category_entry, product_grid, image_text, video, coupons, group_buys, reviews, category_header, product_list (category pages), buy_box, details, specs, product_reviews, related (product pages). settings holds only how the block is drawn (columns, layout, height, tone, display, image_position, autoplay, page_size, show_*); data holds what it shows, resolved in the answer language with absolute URLs: banner {slides[{image_url, link}]}, announcement {text}, category_entry {title, categories[{id, name, image_url}]}, product_grid and related {title, products[product card]}, image_text {image_url, title, body, link, button_text}, video {title, kind embed|file, url}, coupons {title, coupons[{id, name, description, code, ends_on}]}, group_buys {title, group_buys[]}, reviews {title, reviews[{rating, comment, reviewer, merchant_reply, created_at}]}, category_header {name, image_url, description}, product_list {items[product card], pagination, subcategories[{id, name}], filters{q, sort, sub, in_stock}}, details {title, body, image_urls[]}, specs {title, fields[{label, value}]}, product_reviews {title, reviews[{rating, comment, created_at}]}, buy_box {} (the product answer itself). A block with nothing to show is left out; unknown types should be skipped. Same body as REST.
query PlatformAppGuestStoreCategory($client_code: String!, $slug: String!, $category_id: Int!, $sort: String, $sub: Int, $in_stock: Boolean, $page: Int, $locale: String) {
platformAppGuestStoreCategory(client_code: $client_code, slug: $slug, category_id: $category_id, sort: $sort, sub: $sub, in_stock: $in_stock, page: $page, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"slug": "home-goods-1",
"category_id": 5
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "home-goods-1",
"name": "Home Goods"
},
"category": {
"id": 5,
"parent_id": null,
"name": "Kitchen"
},
"accepting_orders": true,
"design": {
"style": "multi_page",
"published_version": 3,
"nav_categories": [
{
"id": 5,
"name": "Kitchen"
},
{
"id": 7,
"name": "Garden"
}
]
},
"blocks": [
{
"id": "c1d2e3f4a5",
"type": "category_header",
"settings": {
"show_image": true,
"show_description": true
},
"data": {
"name": "Kitchen",
"image_url": null,
"description": "Pots, pans and knives"
}
},
{
"id": "c2d3e4f5a6",
"type": "product_list",
"settings": {
"page_size": 24,
"columns": 4,
"show_filters": true
},
"data": {
"items": [
{
"id": 12,
"store": {
"id": 1,
"slug": "home-goods-1",
"name": "Home Goods"
},
"name": "Copper pan",
"description": "",
"image_url": "https://shop.example.com/storage/products/12/pan.jpg",
"price": 40,
"list_price": 40,
"compare_at_price": null,
"on_sale": false,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [],
"affiliate": false,
"external_url": null
}
],
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 24,
"total": 1,
"has_more": false
},
"subcategories": [
{
"id": 6,
"name": "Knives"
}
],
"filters": {
"q": "",
"sort": "",
"sub": 0,
"in_stock": false
}
}
}
]
}
}
platformAppGuestStoreProduct
query
REST twin: GET /app/v1/clients/{clientCode}/guest/stores/{slug}/products/{productId}
Added (docs/SKU-STRUCTURES-DESIGN.md SX3, SX11): custom_fields [{key, label, value}] — the merchant's custom SKU fields shown to buyers, in the request's language — only when the product has any. Public, no sign-in: a guest of the client behind client_code (A6). One product on sale in the store: the card fields plus `sku`, all `images`, active `variants` with their storefront price, `option_groups` (each group and value with `image_url`, null without an image), `group_buy` (lowest group price while an orderable campaign carries it, else null), the 20 newest approved reviews and up to 6 related products of the same category. Same body as REST. Every product card carries currency: the currency code its prices are in (the store's Client's). Added (docs/STORE-STYLES-DESIGN.md P2): page_blocks — in the multi-page style, the product page's blocks in order (the product's own layout, else the store's; buy_box marks where this answer's purchase area goes); null in the simple style. Each block is {id, type, settings, data}. type is one of banner, announcement, category_entry, product_grid, image_text, video, coupons, group_buys, reviews, category_header, product_list (category pages), buy_box, details, specs, product_reviews, related (product pages). settings holds only how the block is drawn (columns, layout, height, tone, display, image_position, autoplay, page_size, show_*); data holds what it shows, resolved in the answer language with absolute URLs: banner {slides[{image_url, link}]}, announcement {text}, category_entry {title, categories[{id, name, image_url}]}, product_grid and related {title, products[product card]}, image_text {image_url, title, body, link, button_text}, video {title, kind embed|file, url}, coupons {title, coupons[{id, name, description, code, ends_on}]}, group_buys {title, group_buys[]}, reviews {title, reviews[{rating, comment, reviewer, merchant_reply, created_at}]}, category_header {name, image_url, description}, product_list {items[product card], pagination, subcategories[{id, name}], filters{q, sort, sub, in_stock}}, details {title, body, image_urls[]}, specs {title, fields[{label, value}]}, product_reviews {title, reviews[{rating, comment, created_at}]}, buy_box {} (the product answer itself). A block with nothing to show is left out; unknown types should be skipped. Added: share — how the store lets buyers share this product, null while it offers none: {url (the product's web page, on the client's white-label host when it has one), title, channels (the store's choice in display order, each one of system, wechat, whatsapp, facebook, x, telegram, line, linkedin, reddit, pinterest, threads, tumblr, viber, weibo, qq, sms, email, copy, qr; system = the phone's own share menu, wechat = WeChat, which has no share address: copy the url and open WeChat (weixin://) on a phone, show the url as a QR code to scan with WeChat elsewhere, copy = copy the url, qr = show the url as a QR code), links (channel => address to open, for the chosen network, sms and email channels)}. Skip a channel the app does not know. Show only the listed channels.
query PlatformAppGuestStoreProduct($client_code: String!, $slug: String!, $product_id: Int!, $locale: String) {
platformAppGuestStoreProduct(client_code: $client_code, slug: $slug, product_id: $product_id, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"slug": "pizza-place-1",
"product_id": 1
}
Response example
{
"result": true,
"data": {
"id": 1,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Margherita",
"description": "Tomato, mozzarella, basil",
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg",
"price": 12,
"list_price": 14,
"compare_at_price": 14,
"on_sale": true,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [
{
"code": "gluten",
"label": "Gluten"
},
{
"code": "milk",
"label": "Milk"
}
],
"affiliate": false,
"external_url": null,
"option_groups": [
{
"id": 1,
"label": "Size",
"type": "radio",
"required": true,
"min": 0,
"max": null,
"image_url": null,
"values": [
{
"id": 1,
"label": "12 inch",
"delta": 0,
"default": false,
"image_url": null
},
{
"id": 2,
"label": "16 inch",
"delta": 4,
"default": false,
"image_url": null
}
]
}
],
"sku": "MARGHERITA",
"images": [
"https://shop.example.com/storage/products/88/margherita.jpg"
],
"variants": [],
"accepting_orders": true,
"group_buy": {
"price": 10.5,
"code": "PZBOX7",
"title": "Friday pizza box",
"cutoff_at": "2026-10-16T15:00:00-04:00",
"url": "https://shop.example.com/portal/group-buy/PZBOX7"
},
"reviews": [
{
"rating": 5,
"comment": "Best in town",
"created_at": "2026-10-14T15:00:00-04:00"
}
],
"related": [],
"share": {
"url": "https://shop.example.com/s/pizza-place-1/i/1",
"title": "Margherita",
"channels": [
"system",
"whatsapp",
"facebook",
"copy",
"qr"
],
"links": {
"whatsapp": "https://wa.me/?text=Margherita%20https%3A%2F%2Fshop.example.com%2Fs%2Fpizza-place-1%2Fi%2F1",
"facebook": "https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fshop.example.com%2Fs%2Fpizza-place-1%2Fi%2F1"
}
},
"page_blocks": [
{
"id": "p1a2b3c4d5",
"type": "buy_box",
"settings": {
"show_thumbnails": true
},
"data": []
},
{
"id": "p2b3c4d5e6",
"type": "details",
"settings": [],
"data": {
"title": "Details",
"body": "Heavy copper pan",
"image_urls": []
}
},
{
"id": "p3c4d5e6f7",
"type": "related",
"settings": {
"limit": 6
},
"data": {
"title": "Related products",
"products": [
{
"id": 12,
"store": {
"id": 1,
"slug": "home-goods-1",
"name": "Home Goods"
},
"name": "Copper pan",
"description": "",
"image_url": "https://shop.example.com/storage/products/12/pan.jpg",
"price": 40,
"list_price": 40,
"compare_at_price": null,
"on_sale": false,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [],
"affiliate": false,
"external_url": null
}
]
}
}
]
}
}
platformAppGuestStoreCart
query
REST twin: GET /app/v1/clients/{clientCode}/guest/stores/{slug}/cart
Public, no sign-in: a guest of the client behind client_code (A6). The buyer's cart in this store (empty when there is none). Signed in: the linked identity's cart — the same cart as on the web — or, for an account without a linked identity, the account's own cart. Guest: the cart of guest_cart_token (REST: header X-Guest-Cart-Token); `guest_cart_token` echoes the token in use (null without one). Line prices are display prices; checkout prices again. Same body as REST.
query PlatformAppGuestStoreCart($client_code: String!, $slug: String!, $guest_cart_token: String, $locale: String) {
platformAppGuestStoreCart(client_code: $client_code, slug: $slug, guest_cart_token: $guest_cart_token, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"slug": "pizza-place-1",
"guest_cart_token": "3gldEoWgIUaixQRT2beBfqX7kg0CKDZtSrRXeeuj"
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"items": [
{
"id": 1,
"product_id": 1,
"product_variant_id": null,
"name": "Margherita",
"options_label": "16 inch",
"qty": 2,
"unit_price": 16,
"total": 32,
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg"
}
],
"subtotal": 32,
"count": 2,
"currency": "CAD",
"guest_cart_token": "3gldEoWgIUaixQRT2beBfqX7kg0CKDZtSrRXeeuj"
}
}
platformAppGuestStoreCartAdd
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/stores/{slug}/cart/items
Public, no sign-in: a guest of the client behind client_code (A6). Put a product in the cart with the web rules: the store takes orders now (only dine-in when `table` names a table), the product is on sale, in stock and inside its mealtimes, a product with variants names its `product_variant_id`, and `options` keep the item's option rules ({product_options.id: [store_option_value_id, ...]}, the ids from option_groups[].id and values[].id). An identical line (product, variant, options) grows. A guest's first write issues the guest cart token: keep it (body `guest_cart_token` and response header guest_cart_token (REST: header X-Guest-Cart-Token)) and send it with every later cart call. Same body as REST.
mutation PlatformAppGuestStoreCartAdd($client_code: String!, $slug: String!, $product_id: Int!, $product_variant_id: Int, $qty: Int!, $options: Json, $table: String, $guest_cart_token: String, $locale: String) {
platformAppGuestStoreCartAdd(client_code: $client_code, slug: $slug, product_id: $product_id, product_variant_id: $product_variant_id, qty: $qty, options: $options, table: $table, guest_cart_token: $guest_cart_token, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"slug": "pizza-place-1",
"product_id": 1,
"qty": 2,
"options": {
"1": [
2
]
},
"guest_cart_token": "3gldEoWgIUaixQRT2beBfqX7kg0CKDZtSrRXeeuj"
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"items": [
{
"id": 1,
"product_id": 1,
"product_variant_id": null,
"name": "Margherita",
"options_label": "16 inch",
"qty": 2,
"unit_price": 16,
"total": 32,
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg"
}
],
"subtotal": 32,
"count": 2,
"currency": "CAD",
"guest_cart_token": "3gldEoWgIUaixQRT2beBfqX7kg0CKDZtSrRXeeuj"
}
}
platformAppGuestStoreCartUpdate
mutation
REST twin: PATCH /app/v1/clients/{clientCode}/guest/stores/{slug}/cart/items/{itemId}
Public, no sign-in: a guest of the client behind client_code (A6). Set the quantity of one line (items[].id); 0 removes it. A line that is not in this buyer's cart changes nothing and the cart is answered. Same body as REST.
mutation PlatformAppGuestStoreCartUpdate($client_code: String!, $slug: String!, $item_id: Int!, $qty: Int!, $guest_cart_token: String, $locale: String) {
platformAppGuestStoreCartUpdate(client_code: $client_code, slug: $slug, item_id: $item_id, qty: $qty, guest_cart_token: $guest_cart_token, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"slug": "pizza-place-1",
"item_id": 1,
"qty": 3,
"guest_cart_token": "3gldEoWgIUaixQRT2beBfqX7kg0CKDZtSrRXeeuj"
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"items": [
{
"id": 1,
"product_id": 1,
"product_variant_id": null,
"name": "Margherita",
"options_label": "16 inch",
"qty": 2,
"unit_price": 16,
"total": 32,
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg"
}
],
"subtotal": 32,
"count": 2,
"currency": "CAD"
}
}
platformAppGuestStoreCartRemove
mutation
REST twin: DELETE /app/v1/clients/{clientCode}/guest/stores/{slug}/cart/items/{itemId}
Public, no sign-in: a guest of the client behind client_code (A6). Remove one line (items[].id). A line that is not in this buyer's cart changes nothing and the cart is answered. Same body as REST.
mutation PlatformAppGuestStoreCartRemove($client_code: String!, $slug: String!, $item_id: Int!, $guest_cart_token: String, $locale: String) {
platformAppGuestStoreCartRemove(client_code: $client_code, slug: $slug, item_id: $item_id, guest_cart_token: $guest_cart_token, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"slug": "pizza-place-1",
"item_id": 1,
"guest_cart_token": "3gldEoWgIUaixQRT2beBfqX7kg0CKDZtSrRXeeuj"
}
Response example
{
"result": true,
"data": {
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"items": [
{
"id": 1,
"product_id": 1,
"product_variant_id": null,
"name": "Margherita",
"options_label": "16 inch",
"qty": 2,
"unit_price": 16,
"total": 32,
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg"
}
],
"subtotal": 32,
"count": 2,
"currency": "CAD"
}
}
platformAppGuestStoreCheckoutQuote
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/stores/{slug}/checkout/quote
Price a guest's cart at a store; login_required says whether the store takes guest orders. Guests: X-Guest-Cart-Token names the cart. table_code puts the order at that table (dine-in only). Errors: 404 STORE_NOT_FOUND, 422 CART_EMPTY, 422 TABLE_NOT_FOUND, 422 VALIDATION_FAILED. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppGuestStoreCheckoutQuote($client_code: String!, $locale: String, $slug: String!, $cart_token: String, $input: Json) {
platformAppGuestStoreCheckoutQuote(client_code: $client_code, locale: $locale, slug: $slug, cart_token: $cart_token, input: $input)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"locale": "en",
"slug": "bistro",
"cart_token": "Yx8kLm2Qp9TzR4vB7nC1dF6gH3jK5wE0aS8uI2oP",
"input": {
"order_type": "delivery",
"postcode": "H2X 2Z2"
}
}
Response example
{
"result": true,
"data": {
"ok": true,
"errors": [],
"subtotal": 25,
"delivery_fee": 4,
"tip": 0,
"discount": 0,
"coupon_code": null,
"min_order": 0,
"total": 29,
"tax": 0,
"price_changed": false,
"currency": "CAD",
"order_type": "delivery",
"table_code": null,
"order_types": [
"delivery",
"collection",
"dine_in"
],
"payment_methods": [
"stripe"
],
"slots": {
"delivery": {
"asap": true,
"days": [
{
"date": "2026-10-14",
"slots": [
"13:00",
"13:30"
]
}
]
}
},
"locker_cabinets": [],
"tab": null,
"login_required": false
}
}
platformAppGuestStoreCheckoutPlace
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/stores/{slug}/checkout/place
Place a guest order where the store takes guest orders (or at a table); answers the order, the payment and the guest order token. Guests: X-Guest-Cart-Token names the cart; keep guest_order_token on the device and send it as X-Guest-Order-Token. payment.method stripe: present the PaymentSheet with client_secret / publishable_key / merchant_country / merchant_display_name, then call confirm-payment; offline and tab: nothing to pay in the app. Errors: 404 STORE_NOT_FOUND, 422 CART_EMPTY, 403 LOGIN_REQUIRED (guest where the store takes no guest orders), 422 ORDER_TYPE_UNAVAILABLE, 422 TABLE_REQUIRED, 422 TABLE_NOT_FOUND, 422 CHECKOUT_VALIDATION_FAILED with errors[] (price_changed, out_of_delivery_area, store_closed, item_unavailable, cabinet_required, invalid_time_slot, below_min_order, ...), 422 PAYMENT_METHOD_UNAVAILABLE, 422 PAYMENT_ERROR, 500 CHECKOUT_FAILED, 422 VALIDATION_FAILED. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md). CHECKOUT_VALIDATION_FAILED also carries reasons: an object {code: message} with the same codes as errors, each with the message the web checkout shows for it in the answer language (an unforeseen code gets the general message); errors stays the list of codes.
mutation PlatformAppGuestStoreCheckoutPlace($client_code: String!, $locale: String, $slug: String!, $cart_token: String, $input: Json) {
platformAppGuestStoreCheckoutPlace(client_code: $client_code, locale: $locale, slug: $slug, cart_token: $cart_token, input: $input)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"locale": "en",
"slug": "bistro",
"cart_token": "Yx8kLm2Qp9TzR4vB7nC1dF6gH3jK5wE0aS8uI2oP",
"input": {
"order_type": "delivery",
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com",
"address_1": "10 Rue Test",
"city": "Montreal",
"postcode": "H2X 2Z2"
}
}
Response example
{
"result": true,
"data": {
"order": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "pending_payment",
"status_label": "Pending payment",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": false,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": null,
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": true,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
},
"payment": {
"method": "stripe",
"client_secret": "pi_3Q_secret_abc",
"publishable_key": "pk_live_123",
"merchant_country": "CA",
"merchant_display_name": "Bistro",
"amount": 29,
"currency": "CAD"
},
"tab": null,
"guest_order_token": "3f0c6c1b2b8e4d0a9a1e6f5d4c3b2a19087f6e5d4c3b2a1908f7e6d5c4b3a291"
}
}
platformAppGuestStoreOrder
query
REST twin: GET /app/v1/clients/{clientCode}/guest/store-orders/{code}
One guest store order, with its order token. Guests: send the order token as X-Guest-Order-Token. Errors: 404 ORDER_NOT_FOUND. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md). items[].options is always a list of the option groups chosen for the line (empty without options): [{attachment_id: int|null, store_option_id: int|null, name: string, values: [{store_option_value_id: int|null, value: string, delta: number}]}] (delta = the price added per unit by that choice, already included in unit_price). locker (locker pickup orders, else null): {cabinet, address, pickup_code, pickup_due_at, remaining, items, door_open, can_open, refund_available}; locker.items is always a list of the order's cells: [{id: int, name: string, status: string (sold = waiting to be opened, opened = door open now, collected, expired, removed), compartment_number: string|null (the lock board's door number), pickup_number: int|null (the number shown on the door)}].
query PlatformAppGuestStoreOrder($client_code: String!, $locale: String, $code: String!, $order_token: String) {
platformAppGuestStoreOrder(client_code: $client_code, locale: $locale, code: $code, order_token: $order_token)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"locale": "en",
"code": "EQ0MS2",
"order_token": "3f0c6c1b2b8e4d0a9a1e6f5d4c3b2a19087f6e5d4c3b2a1908f7e6d5c4b3a291"
}
Response example
{
"result": true,
"data": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
platformAppGuestStoreOrderPay
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/store-orders/{code}/pay
Pay a guest order whose payment was left unfinished: the same payment object as place. Guests: send the order token as X-Guest-Order-Token. Keeps the order's PaymentIntent while Stripe still accepts it (reused_intent true), otherwise starts a new one as checkout does; present the PaymentSheet, then call confirm-payment. Only while actions.can_pay is true. Errors: 404 ORDER_NOT_FOUND, 409 NOT_AWAITING_PAYMENT (paid, cancelled, refunded, a tab round or paid at the counter), 409 ORDER_ALREADY_PAID (the intent had already succeeded: the order is settled now, data carries it), 422 PAYMENT_ERROR. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppGuestStoreOrderPay($client_code: String!, $locale: String, $code: String!, $order_token: String) {
platformAppGuestStoreOrderPay(client_code: $client_code, locale: $locale, code: $code, order_token: $order_token)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"locale": "en",
"code": "EQ0MS2",
"order_token": "3f0c6c1b2b8e4d0a9a1e6f5d4c3b2a19087f6e5d4c3b2a1908f7e6d5c4b3a291"
}
Response example
{
"result": true,
"data": {
"order": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "pending_payment",
"status_label": "Pending payment",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": false,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": null,
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": true,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
},
"payment": {
"method": "stripe",
"client_secret": "pi_3Q_secret_abc",
"publishable_key": "pk_live_123",
"merchant_country": "CA",
"merchant_display_name": "Bistro",
"amount": 29,
"currency": "CAD"
},
"reused_intent": true
}
}
platformAppGuestStoreOrderConfirmPayment
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/store-orders/{code}/confirm-payment
After the PaymentSheet, for a guest order: ask Stripe and settle the order once. Guests: send the order token as X-Guest-Order-Token. Errors: 404 ORDER_NOT_FOUND. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppGuestStoreOrderConfirmPayment($client_code: String!, $locale: String, $code: String!, $order_token: String) {
platformAppGuestStoreOrderConfirmPayment(client_code: $client_code, locale: $locale, code: $code, order_token: $order_token)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"locale": "en",
"code": "EQ0MS2",
"order_token": "3f0c6c1b2b8e4d0a9a1e6f5d4c3b2a19087f6e5d4c3b2a1908f7e6d5c4b3a291"
}
Response example
{
"result": true,
"data": {
"paid": true,
"order": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
}
platformAppGuestStoreOrderCancel
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/store-orders/{code}/cancel
Cancel a guest order before the store accepts it. Guests: send the order token as X-Guest-Order-Token. Errors: 404 ORDER_NOT_FOUND. 409 CANCEL_NOT_ALLOWED, 422 CANCEL_FAILED. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppGuestStoreOrderCancel($client_code: String!, $locale: String, $code: String!, $order_token: String) {
platformAppGuestStoreOrderCancel(client_code: $client_code, locale: $locale, code: $code, order_token: $order_token)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"locale": "en",
"code": "EQ0MS2",
"order_token": "3f0c6c1b2b8e4d0a9a1e6f5d4c3b2a19087f6e5d4c3b2a1908f7e6d5c4b3a291"
}
Response example
{
"result": true,
"message": "Your order has been cancelled and refunded.",
"data": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "refunded",
"status_label": "Refunded",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
platformAppGuestStoreOrderLockerRefund
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/store-orders/{code}/locker-refund
Refund a guest's locker pickup whose door never opened. Guests: send the order token as X-Guest-Order-Token. Errors: 404 ORDER_NOT_FOUND. 409 NOT_A_LOCKER_ORDER, 404 CABINET_NOT_FOUND, 409 REFUND_REFUSED. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppGuestStoreOrderLockerRefund($client_code: String!, $locale: String, $code: String!, $order_token: String) {
platformAppGuestStoreOrderLockerRefund(client_code: $client_code, locale: $locale, code: $code, order_token: $order_token)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"locale": "en",
"code": "EQ0MS2",
"order_token": "3f0c6c1b2b8e4d0a9a1e6f5d4c3b2a19087f6e5d4c3b2a1908f7e6d5c4b3a291"
}
Response example
{
"result": true,
"message": "Refund issued.",
"data": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
platformAppGuestStoreOrderReorder
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/store-orders/{code}/reorder
Put a guest order's lines back into the guest cart; answers the cart token. Guests: send the order token as X-Guest-Order-Token. Errors: 404 ORDER_NOT_FOUND. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppGuestStoreOrderReorder($client_code: String!, $locale: String, $code: String!, $order_token: String, $cart_token: String) {
platformAppGuestStoreOrderReorder(client_code: $client_code, locale: $locale, code: $code, order_token: $order_token, cart_token: $cart_token)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"locale": "en",
"code": "EQ0MS2",
"order_token": "3f0c6c1b2b8e4d0a9a1e6f5d4c3b2a19087f6e5d4c3b2a1908f7e6d5c4b3a291"
}
Response example
{
"result": true,
"message": "The items were added to your cart.",
"data": {
"added": 1,
"skipped": 0,
"store_slug": "bistro",
"cart_token": "Yx8kLm2Qp9TzR4vB7nC1dF6gH3jK5wE0aS8uI2oP"
}
}
platformAppGuestStoreOrderReturn
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/store-orders/{code}/return
Ask the store to take a guest's paid or completed order back. Guests: send the order token as X-Guest-Order-Token. Errors: 404 ORDER_NOT_FOUND. 409 RETURN_NOT_ALLOWED, 409 RETURN_ALREADY_OPEN, 422 VALIDATION_FAILED. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppGuestStoreOrderReturn($client_code: String!, $locale: String, $code: String!, $order_token: String, $reason: String) {
platformAppGuestStoreOrderReturn(client_code: $client_code, locale: $locale, code: $code, order_token: $order_token, reason: $reason)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"locale": "en",
"code": "EQ0MS2",
"order_token": "3f0c6c1b2b8e4d0a9a1e6f5d4c3b2a19087f6e5d4c3b2a1908f7e6d5c4b3a291"
}
Response example
{
"result": true,
"message": "Return requested.",
"data": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
platformAppGuestStoreOrderReview
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/store-orders/{code}/review/{productId}
Rate one product of a guest order. Guests: send the order token as X-Guest-Order-Token. Errors: 404 ORDER_NOT_FOUND. 404 PRODUCT_NOT_IN_ORDER, 422 VALIDATION_FAILED. Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
mutation PlatformAppGuestStoreOrderReview($client_code: String!, $locale: String, $code: String!, $order_token: String, $product_id: Int!, $rating: Int, $comment: String) {
platformAppGuestStoreOrderReview(client_code: $client_code, locale: $locale, code: $code, order_token: $order_token, product_id: $product_id, rating: $rating, comment: $comment)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"locale": "en",
"code": "EQ0MS2",
"order_token": "3f0c6c1b2b8e4d0a9a1e6f5d4c3b2a19087f6e5d4c3b2a1908f7e6d5c4b3a291",
"product_id": 41
}
Response example
{
"result": true,
"message": "Thanks for your review.",
"data": {
"code": "EQ0MS2",
"kind": "store_order",
"store": {
"id": 31,
"slug": "bistro",
"name": "Bistro",
"logo_url": null
},
"order_type": "delivery",
"status": "paid",
"status_label": "Paid",
"fulfillment_status": "new",
"fulfillment_label": "New",
"paid": true,
"total": 29,
"currency": "CAD",
"items_count": 2,
"table_code": null,
"created_at": "2026-10-14T12:00:00-04:00",
"channel": "app",
"requested_at": null,
"promised_at": "2026-10-14T12:35:00-04:00",
"paid_at": "2026-10-14T12:01:10-04:00",
"subtotal": 25,
"delivery_fee": 4,
"tax": 0,
"tip": 0,
"discount": 0,
"coupon_code": null,
"refunded_amount": 0,
"payment_method": "stripe",
"contact": {
"name": "Ada",
"telephone": "+15145550100",
"email": "ada@example.com"
},
"address": {
"address_1": "10 Rue Test",
"address_2": null,
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2X 2Z2",
"latitude": 45.51,
"longitude": -73.57
},
"delivery_instruction": null,
"note": null,
"items": [
{
"id": 1,
"product_id": 41,
"product_variant_id": null,
"name": "Burger",
"qty": 2,
"refunded_qty": 0,
"unit_price": 12.5,
"total": 25,
"options": [],
"review_rating": null
}
],
"events": [
{
"track": "status",
"from": null,
"to": "pending_payment",
"at": "2026-10-14T12:00:00-04:00"
},
{
"track": "status",
"from": "pending_payment",
"to": "paid",
"at": "2026-10-14T12:01:10-04:00"
}
],
"tracking_number": null,
"locker": null,
"actions": {
"can_pay": false,
"can_cancel": true,
"can_reorder": true,
"can_return": true,
"return_pending": false,
"can_review": true
},
"web_url": "https://shop.example.com/account/order/EQ0MS2"
}
}
platformAppGuestMallHome
query
REST twin: GET /app/v1/clients/{clientCode}/guest/mall/home
Public, no sign-in: a guest of the client behind client_code (A6). The blocks the client shows on the mall home, in the client's order: `banners`, `categories`, `group_buys`, `coupons`, `featured_stores`, `featured_products`, `stores` (first 12 of the directory). An empty block is left out; `group_buys` is left out when the app's group_buy capability is off. Without mall operations or without a mall: `directory_only: true` and no blocks — show mall/stores. 404 STOREFRONT_UNAVAILABLE when the client has no marketplace. Same body as REST. Every product card carries currency: the currency code its prices are in (the store's Client's).
query PlatformAppGuestMallHome($client_code: String!, $locale: String) {
platformAppGuestMallHome(client_code: $client_code, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab"
}
Response example
{
"result": true,
"data": {
"directory_only": false,
"blocks": [
{
"key": "categories",
"items": [
{
"id": 1,
"name": "Food",
"children": [
{
"id": 2,
"name": "Pizza"
}
]
}
]
},
{
"key": "group_buys",
"items": [
{
"code": "PZBOX7",
"title": "Friday pizza box",
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"opens_at": "2026-10-14T14:00:00-04:00",
"cutoff_at": "2026-10-16T15:00:00-04:00",
"url": "https://shop.example.com/portal/group-buy/PZBOX7"
}
]
},
{
"key": "coupons",
"items": [
{
"id": 1,
"name": "5 off your first order",
"description": "Orders over 30",
"code": "WELCOME5",
"ends_on": "2026-12-31"
}
]
},
{
"key": "featured_stores",
"items": [
{
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place",
"description": "",
"store_type": "restaurant",
"store_type_label": "Restaurant",
"tags": [
"pizza",
"halal"
],
"logo_url": "https://shop.example.com/storage/stores/31/logo.png",
"cover_url": "https://shop.example.com/storage/stores/31/cover.jpg",
"rating_avg": 4.6,
"rating_count": 38,
"address": "12 King St W",
"city": "Toronto"
}
]
},
{
"key": "featured_products",
"items": [
{
"id": 1,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Margherita",
"description": "Tomato, mozzarella, basil",
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg",
"price": 12,
"list_price": 14,
"compare_at_price": 14,
"on_sale": true,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [
{
"code": "gluten",
"label": "Gluten"
},
{
"code": "milk",
"label": "Milk"
}
],
"affiliate": false,
"external_url": null
}
]
},
{
"key": "stores",
"items": [
{
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place",
"description": "",
"store_type": "restaurant",
"store_type_label": "Restaurant",
"tags": [
"pizza",
"halal"
],
"logo_url": "https://shop.example.com/storage/stores/31/logo.png",
"cover_url": "https://shop.example.com/storage/stores/31/cover.jpg",
"rating_avg": 4.6,
"rating_count": 38,
"address": "12 King St W",
"city": "Toronto"
}
]
}
]
}
}
platformAppGuestMallStores
query
REST twin: GET /app/v1/clients/{clientCode}/guest/mall/stores
Public, no sign-in: a guest of the client behind client_code (A6). Every listed store in the mall (every listed store of the client when it has no mall), in the client's order then by rating; filters by store type, tag (case-insensitive) and words (name, names in other languages, tags); 24 a page. `facets` lists the types and tags with their store counts, computed over all stores. Same body as REST.
query PlatformAppGuestMallStores($client_code: String!, $type: String, $tag: String, $q: String, $page: Int, $locale: String) {
platformAppGuestMallStores(client_code: $client_code, type: $type, tag: $tag, q: $q, page: $page, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"tag": "pizza"
}
Response example
{
"result": true,
"data": {
"stores": [
{
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place",
"description": "",
"store_type": "restaurant",
"store_type_label": "Restaurant",
"tags": [
"pizza",
"halal"
],
"logo_url": "https://shop.example.com/storage/stores/31/logo.png",
"cover_url": "https://shop.example.com/storage/stores/31/cover.jpg",
"rating_avg": 4.6,
"rating_count": 38,
"address": "12 King St W",
"city": "Toronto"
}
],
"facets": {
"types": [
{
"type": "restaurant",
"label": "Restaurant",
"count": 1
}
],
"tags": [
{
"tag": "pizza",
"count": 1
},
{
"tag": "halal",
"count": 1
}
]
},
"filters": {
"type": "",
"tag": "pizza",
"q": ""
},
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 24,
"total": 1,
"has_more": false
}
}
}
platformAppGuestMallSearch
query
REST twin: GET /app/v1/clients/{clientCode}/guest/mall/search
Public, no sign-in: a guest of the client behind client_code (A6). Stores whose name, other-language names or tags contain the words, and listed products whose name contains them, grouped by store (at most 60 products). Empty words: empty lists. Needs a mall with operations: 404 MALL_UNAVAILABLE otherwise. Same body as REST. Every product card carries currency: the currency code its prices are in (the store's Client's).
query PlatformAppGuestMallSearch($client_code: String!, $q: String, $locale: String) {
platformAppGuestMallSearch(client_code: $client_code, q: $q, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"q": "marg"
}
Response example
{
"result": true,
"data": {
"q": "marg",
"stores": [],
"products_by_store": [
{
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place",
"description": "",
"store_type": "restaurant",
"store_type_label": "Restaurant",
"tags": [
"pizza",
"halal"
],
"logo_url": "https://shop.example.com/storage/stores/31/logo.png",
"cover_url": "https://shop.example.com/storage/stores/31/cover.jpg",
"rating_avg": 4.6,
"rating_count": 38,
"address": "12 King St W",
"city": "Toronto"
},
"products": [
{
"id": 1,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Margherita",
"description": "Tomato, mozzarella, basil",
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg",
"price": 12,
"list_price": 14,
"compare_at_price": 14,
"on_sale": true,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [
{
"code": "gluten",
"label": "Gluten"
},
{
"code": "milk",
"label": "Milk"
}
],
"affiliate": false,
"external_url": null
}
]
}
]
}
}
platformAppGuestMallCategory
query
REST twin: GET /app/v1/clients/{clientCode}/guest/mall/categories/{categoryId}
Public, no sign-in: a guest of the client behind client_code (A6). One active mall category (a top category includes its sub-categories) and one page of its products, 24 a page, each with its own store. Needs a mall with operations. Same body as REST. Every product card carries currency: the currency code its prices are in (the store's Client's).
query PlatformAppGuestMallCategory($client_code: String!, $category_id: Int!, $page: Int, $locale: String) {
platformAppGuestMallCategory(client_code: $client_code, category_id: $category_id, page: $page, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"category_id": 1
}
Response example
{
"result": true,
"data": {
"category": {
"id": 1,
"name": "Food",
"children": [
{
"id": 2,
"name": "Pizza"
}
],
"parent": null
},
"products": [
{
"id": 1,
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"name": "Margherita",
"description": "Tomato, mozzarella, basil",
"image_url": "https://shop.example.com/storage/products/88/margherita.jpg",
"price": 12,
"list_price": 14,
"compare_at_price": 14,
"on_sale": true,
"currency": "CAD",
"available": true,
"within_mealtimes": true,
"mealtimes": [],
"allergens": [
{
"code": "gluten",
"label": "Gluten"
},
{
"code": "milk",
"label": "Milk"
}
],
"affiliate": false,
"external_url": null
}
],
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 24,
"total": 1,
"has_more": false
}
}
}
platformAppGuestMallGroupBuys
query
REST twin: GET /app/v1/clients/{clientCode}/guest/mall/group-buys
Public, no sign-in: a guest of the client behind client_code (A6). The group buys buyers may order now, soonest cutoff first; each `url` opens its page on the client's white-label host. In a mall with operations `category` narrows them to those carrying a product of that mall category and `categories` is the tree to choose from; otherwise the group buys of every listed store and no categories. Empty when the app's group_buy capability is off. Same body as REST.
query PlatformAppGuestMallGroupBuys($client_code: String!, $category: Int, $locale: String) {
platformAppGuestMallGroupBuys(client_code: $client_code, category: $category, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab"
}
Response example
{
"result": true,
"data": {
"group_buys": [
{
"code": "PZBOX7",
"title": "Friday pizza box",
"store": {
"id": 1,
"slug": "pizza-place-1",
"name": "Pizza Place"
},
"opens_at": "2026-10-14T14:00:00-04:00",
"cutoff_at": "2026-10-16T15:00:00-04:00",
"url": "https://shop.example.com/portal/group-buy/PZBOX7"
}
],
"categories": [
{
"id": 1,
"name": "Food",
"children": [
{
"id": 2,
"name": "Pizza"
}
]
}
],
"category_id": 0
}
}
platformAppGuestLinkResolve
query
REST twin: GET /app/v1/clients/{clientCode}/guest/links/resolve
What a scanned link of the client opens in the app, without signing in. Accepts the client's white-label host and the platform host (mall by /m/{code}); kind is store | store_table | store_category (slug, category_id: a category page of the multi-page style, added with docs/STORE-STYLES-DESIGN.md P2) | product | mall (page home|stores|search|group_buy) | mall_category | group_buy (campaign_code, leader_code) | unknown. 404 APP_CONTEXT_NOT_FOUND for an unknown app/client (guest). Same envelope as REST (docs/APP-STOREFRONT-DESIGN.md).
query PlatformAppGuestLinkResolve($client_code: String!, $locale: String, $url: String!) {
platformAppGuestLinkResolve(client_code: $client_code, locale: $locale, url: $url)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"locale": "en",
"url": "https://shop.example.com/s/bistro/t/5"
}
Response example
{
"result": true,
"data": {
"kind": "store_table",
"slug": "bistro",
"table_code": "T5",
"campaign_code": null,
"leader_code": null,
"category_id": null,
"product_id": null,
"page": null
}
}
platformAppGuestClients
query
REST twin: GET /app/v1/guest/clients
Public. The Clients of an app that answer without an account, each with features: quotes, when the app's quotes.public capability is on for the Client and public quotes are switched on in its Customer Portal; shipping, when the Client takes guest shipments (guest.shipping), in which case guest_shipping lists the kinds offered (self_delivery, label) and the verification_channel (email or phone). Sorted by display name. An unknown app answers 404 APP_CONTEXT_NOT_FOUND.
query PlatformAppGuestClients {
platformAppGuestClients
}
Variables
{}
Response example
{
"result": true,
"data": {
"app": {
"code": "customer",
"name": "SuperRoute"
},
"count": 1,
"items": [
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"display_name": "Northstar Courier",
"logo_url": null,
"primary_color": "#E02020",
"features": [
"quotes"
]
}
]
}
}
platformAppGuestVerifyRequest
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/verify/request
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.
mutation PlatformAppGuestVerifyRequest($client_code: String!, $contact: String!, $locale: String) {
platformAppGuestVerifyRequest(client_code: $client_code, contact: $contact, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"contact": "guest@example.com"
}
Response example
{
"result": true,
"message": "A verification code was sent.",
"challenge_id": "Qm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2ll",
"channel": "email",
"contact": "g****@example.com",
"expires_in": 600,
"resend_after": 60
}
platformAppGuestVerifyComplete
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/verify/complete
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.
mutation PlatformAppGuestVerifyComplete($client_code: String!, $challenge_id: String!, $code: String!, $locale: String) {
platformAppGuestVerifyComplete(client_code: $client_code, challenge_id: $challenge_id, code: $code, locale: $locale)
}
Variables
{
"client_code": "n4x7q2g8w9r3k6m1p5t0z8ab",
"challenge_id": "Qm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2llQm9vZ2llV29vZ2ll",
"code": "482913"
}
Response example
{
"result": true,
"contact_token": "eyJpdiI6IkNvbnRhY3RUb2tlbiIs...",
"channel": "email",
"contact": "g****@example.com",
"expires_in": 1200
}
partnerAppSwidget
query
REST twin: GET /app/v1/clients/{clientCode}/swidget
The configuration of the sWidget widget the app key (X-App-Key) belongs to: type, options, branding and language; same answer as the REST route. `country` is the client's own country (ISO 3166-1 alpha-2). A widget's key runs only its own type's operations, from its own website origins.
query ($client_code: String!) {
partnerAppSwidget(client_code: $client_code)
}
Variables
{
"client_code": "k3m9q2"
}
Response example
{
"result": true,
"data": {
"widget": {
"id": "k2m9q7x4b1c8",
"type": "order",
"name": "Ship with us",
"locale": null,
"options": {
"order_mode": "sign_in",
"service_code": "STD",
"sign_in": {
"redirect_uri": "https://open.superroute.ca/app/oauth/swidget/callback"
}
}
},
"branding": {
"display_name": "Acme Delivery",
"primary_color": "#E02020",
"logo_url": "https://acme.example/logo.png"
},
"country": "CA",
"app_code": "p_swidget",
"client_code": "k3m9q2"
}
}
platformAppSwidget
query
REST twin: GET /app/v1/clients/{clientCode}/swidgets/{widgetId}
A widget the Client configured in the back office, loaded by its id from an app the Client ticked on it (docs/SWIDGET-SDK-DESIGN.md WS7, WS8). The answer has the shape of the sWidget configuration (`widget`, `branding`, `country`, `app_code`, `client_code`) plus `config_version` (1), `channel` (`app`) and `unavailable`: the capabilities this app's assignment lacks for the widget's kind (WS9; every later call is an ordinary call of the app under its own capabilities). Branding is the widget's own, else this app's branding at the Client, else the Client's name. A sign-in order widget carries no redirect URI: in an app, signing in is the app's own sign-in. Errors: 404 `widget_not_found` (unknown id or another Client's widget), 403 `widget_not_enabled_for_app` (this app is not ticked on the widget), 410 `widget_revoked`. The app's own key (`X-App-Key`); a widget's key is refused here (403 `widget_call_not_allowed`).
query ($client_code: String!, $widget_id: String!) {
platformAppSwidget(client_code: $client_code, widget_id: $widget_id)
}
Variables
{
"client_code": "k3m9q2",
"widget_id": "k2m9q7x4b1c8"
}
Response example
{
"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": "mibox",
"client_code": "k3m9q2",
"config_version": 1,
"channel": "app",
"unavailable": []
}
}
partnerAppGuestQuoteSchema
query
REST twin: GET /app/v1/clients/{clientCode}/guest/quotes/schema
The quote form a client offers without signing in, called with the app key; same answer as the REST route and platformAppPublicQuoteSchema. Needs quotes.public.
query ($client_code: String!) {
partnerAppGuestQuoteSchema(client_code: $client_code)
}
Variables
{
"client_code": "k3m9q2"
}
Response example
{
"result": true,
"schema": {
"business_name": "Acme Delivery",
"currency": "CAD",
"valid_days": 7,
"country": "CA",
"units": {
"weight": {
"default": 2,
"options": {
"1": "weight_g",
"2": "weight_kg",
"3": "weight_oz",
"4": "weight_lb"
}
},
"dimension": {
"default": 2,
"options": {
"1": "dimension_mm",
"2": "dimension_cm",
"3": "dimension_m",
"4": "dimension_inch"
}
}
},
"lines": {
"delivery": {
"line": "delivery",
"max_package_count": 5,
"origins": [],
"custom_origin": true
}
}
}
}
partnerAppGuestQuote
mutation
REST twin: POST /app/v1/clients/{clientCode}/guest/quotes/{line}
A quote on one line for a visitor who has not signed in, called with the app key; same answer as the REST route. Needs quotes.public.
mutation ($client_code: String!, $line: String!, $input: Json) {
partnerAppGuestQuote(client_code: $client_code, line: $line, input: $input)
}
Variables
{
"client_code": "k3m9q2",
"line": "delivery",
"input": {
"to_postcode": "M5V 2T6",
"weight_unit": 2,
"dimension_unit": 2,
"packages": [
{
"weight": 2,
"qty": 1
}
]
}
}
Response example
{
"result": true,
"quote": {
"covered": true,
"line": "delivery",
"currency": "CAD",
"pre_tax": 12.5,
"tax": 1.63,
"total": 14.13,
"breakdown": [],
"not_covered_reason": null,
"token": "q8w2…",
"valid_until": "2026-11-05T10:00:00-05:00"
}
}
partnerAppGuestPickupPoints
query
REST twin: GET /app/v1/clients/{clientCode}/guest/pickup-points
The client's smart locker locations and pickup point counters a visitor may choose, called with the app key; same answer as the REST route. Read only.
query ($client_code: String!, $latitude: Float, $longitude: Float, $search: String, $page: Int, $per_page: Int) {
partnerAppGuestPickupPoints(client_code: $client_code, latitude: $latitude, longitude: $longitude, search: $search, page: $page, per_page: $per_page)
}
Variables
{
"client_code": "k3m9q2",
"latitude": 43.6453,
"longitude": -79.3806
}
Response example
{
"result": true,
"data": {
"items": [
{
"id": "L12",
"kind": "locker",
"location_id": 12,
"name": "Union Station lockers",
"address": "65 Front St W",
"city": "Toronto",
"province": "ON",
"postcode": "M5J 1E6",
"country": "CA",
"latitude": 43.6453,
"longitude": -79.3806,
"distance_km": 0.8,
"status": "online",
"services": {
"pickup": true,
"return": true,
"dropoff": true
}
},
{
"id": "C40",
"kind": "counter",
"location_id": 40,
"name": "Corner shop",
"address": "12 King St W",
"city": "Toronto",
"province": "ON",
"postcode": "M5H 1A1",
"country": "CA",
"latitude": 43.6487,
"longitude": -79.3787,
"distance_km": 1.1,
"status": "online",
"services": {
"pickup": true,
"return": false,
"dropoff": false
},
"has_room": true
}
],
"pagination": {
"current_page": 1,
"per_page": 20,
"total": 2,
"last_page": 1,
"has_more": false
}
}
}
Customer orders
customerSavedFormAnswers
query
REST twin: GET /app/v1/saved-form-answers
Every active form the client bound to an order type with remember_answers on, with its fields (same shape as form_bindings[].fields in the order-form config bundles) and the answers this customer saved (an empty object when none).
query CustomerSavedFormAnswers {
customerSavedFormAnswers
}
Variables
{}
Response example
{
"result": true,
"data": [
{
"form_id": 3,
"form_code": "contact_info",
"form_name": "Contact Info",
"fields": [
{
"code": "wechat",
"name": "WeChat ID / WhatsApp ID",
"type": "text",
"required": true,
"display_name": {
"en": "WeChat ID / WhatsApp ID",
"chs": "微信ID 或 WhatsApp ID"
},
"options": [],
"default_value": "",
"sort_order": 0
}
],
"answers": {
"wechat": "wx_alice"
}
}
]
}
customerSaveFormAnswers
mutation
REST twin: PUT /app/v1/saved-form-answers/{formId}
Replaces the saved answers for the form. Only the form's own field codes are kept, as trimmed strings or lists of strings (at most 1000 characters each); blanks are dropped, and answers that end up empty remove the saved entry. A form that does not allow it answers {result: false, message: 'Form not found.'}.
mutation CustomerSaveFormAnswers($formId: Int!, $answers: Json!) {
customerSaveFormAnswers(formId: $formId, answers: $answers)
}
Variables
{
"formId": 3,
"answers": {
"wechat": "wx_alice"
}
}
Response example
{
"result": true,
"data": {
"form_id": 3,
"form_code": "contact_info",
"form_name": "Contact Info",
"fields": [
{
"code": "wechat",
"name": "WeChat ID / WhatsApp ID",
"type": "text",
"required": true,
"display_name": {
"en": "WeChat ID / WhatsApp ID",
"chs": "微信ID 或 WhatsApp ID"
},
"options": [],
"default_value": "",
"sort_order": 0
}
],
"answers": {
"wechat": "wx_alice"
}
}
}
customerDeleteSavedFormAnswers
mutation
REST twin: DELETE /app/v1/saved-form-answers/{formId}
Removes the saved answers for the form. A form that does not allow saved answers answers {result: false, message: 'Form not found.'}.
mutation CustomerDeleteSavedFormAnswers($formId: Int!) {
customerDeleteSavedFormAnswers(formId: $formId)
}
Variables
{
"formId": 3
}
Response example
{
"result": true
}
customerStorageOrderConfig
query
REST twin: GET /app/v1/storage-orders/config
Get the configuration bundle needed to render the customer create-storage-order form Needs orders.storage on the token.
query CustomerStorageOrderConfig {
customerStorageOrderConfig
}
Variables
{}
Response example
{
"result": true,
"data": {
"warehouses": [
{
"id": 5,
"name": "Toronto Hub",
"address": "10 Main St",
"city": "Toronto",
"province": "ON",
"postcode": "M5V 2T6"
}
],
"packagings": [
{
"id": 1,
"name": "Small Box",
"display_names": {
"en": "Small Box"
},
"type": "Box",
"length": 30,
"width": 20,
"height": 15,
"dimension_unit": 2
}
],
"products": [
{
"id": 7,
"name": "Bubble Wrap Roll",
"display_names": {
"en": "Bubble Wrap Roll"
},
"price": "5.99",
"type": "Supplies",
"image": null
}
],
"pickup_timeframes": [
{
"start": "09:00",
"end": "12:00"
}
],
"dimension_units": {
"1": {
"name": "dimension_mm",
"accuracy": 0
},
"2": {
"name": "dimension_cm",
"accuracy": 1
},
"3": {
"name": "dimension_m",
"accuracy": 3
},
"4": {
"name": "dimension_inch",
"accuracy": 3
}
},
"weight_units": {
"1": {
"name": "weight_g",
"accuracy": 0
},
"2": {
"name": "weight_kg",
"accuracy": 3
},
"3": {
"name": "weight_oz",
"accuracy": 1
},
"4": {
"name": "weight_lb",
"accuracy": 3
}
},
"default_dimension_unit": 2,
"default_weight_unit": 2,
"allow_custom_package": false,
"surcharges": [],
"form_bindings": [],
"saved_form_answers": []
}
}
customerStorageOrders
query
REST twin: GET /app/v1/storage-orders
Paginated list of the authenticated customer's storage orders Needs orders.storage on the token.
query CustomerStorageOrders($id: String, $package_id: String, $warehouse_id: Int, $status: String, $store_from: String, $store_to: String, $created_at_from: String, $created_at_to: String, $quick_search: String, $sort_by: String, $per_page: Int, $page: Int) {
customerStorageOrders(id: $id, package_id: $package_id, warehouse_id: $warehouse_id, status: $status, store_from: $store_from, store_to: $store_to, created_at_from: $created_at_from, created_at_to: $created_at_to, quick_search: $quick_search, sort_by: $sort_by, per_page: $per_page, page: $page)
}
Variables
{
"id": "example",
"package_id": "example",
"warehouse_id": 1,
"status": "example",
"store_from": "2026-01-01",
"store_to": "2026-01-01",
"created_at_from": "2026-01-01",
"created_at_to": "2026-01-01",
"quick_search": "example",
"sort_by": "example",
"per_page": 1,
"page": 1
}
Response example
{
"result": true,
"data": {
"current_page": 1,
"per_page": 20,
"total": 1,
"data": [
{
"id": 12,
"status": "pending payment",
"warehouse": {
"id": 5,
"name": "Toronto Hub"
},
"store_from": "2026-04-01",
"store_to": "2026-05-01",
"need_pickup": true,
"need_delivery": false,
"package_count": 3,
"total_price": 120,
"currency": "CAD",
"paid_amount": 0,
"created_at": "2026-03-28 10:12:00"
}
]
}
}
customerStorageOrderShow
query
REST twin: GET /app/v1/storage-orders/{id}
Get the detail of one storage order owned by the authenticated customer. can_edit / can_cancel say whether the order may be edited or cancelled in its current status; each package's received flag marks packages the warehouse has already received, which an edit keeps and must not resend. Needs orders.storage on the token.
query CustomerStorageOrderShow($id: Int!) {
customerStorageOrderShow(id: $id) {
result
message
data {
id
status
can_edit
can_cancel
store_from
store_to
warehouse {
id
name
}
warehouse_id
need_pickup
need_delivery
pickup_address
pickup_date
pickup_start_time
pickup_end_time
pickup_channel_id
delivery_address
delivery_channel_id
notes
form_data
items {
id
description
value
weight
weight_unit
length
width
height
dimension_unit
packaging_id
packaging {
id
name
}
quantity
received
}
packages {
id
description
value
weight
weight_unit
length
width
height
dimension_unit
packaging_id
packaging {
id
name
}
quantity
received
}
products {
product_id
name
display_names
quantity
}
surcharges {
id
surcharge_id
name
amount
quantity
}
estimate_price_response
total_price
final_total
currency
coupon_code
promotion_id
promotion_discount
paid_amount
created_at
}
}
}
Variables
{
"id": 1
}
Response example
{
"result": true,
"data": {
"id": 12,
"status": "pending payment",
"can_edit": true,
"can_cancel": true,
"store_from": "2026-04-01",
"store_to": "2026-05-01",
"warehouse": {
"id": 5,
"name": "Toronto Hub"
},
"need_pickup": true,
"need_delivery": false,
"notes": null,
"packages": [
{
"id": 901,
"description": "Books",
"value": "0",
"weight": "12",
"weight_unit": 2,
"length": "60",
"width": "40",
"height": "40",
"dimension_unit": 2,
"packaging_id": null,
"packaging": null,
"quantity": 1,
"received": false
}
],
"created_at": "2026-03-28 10:12:00"
}
}
customerMovingOrderConfig
query
REST twin: GET /app/v1/moving-orders/config
Get the configuration bundle needed to render the customer create-moving-order form Needs orders.moving on the token.
query CustomerMovingOrderConfig {
customerMovingOrderConfig
}
Variables
{}
Response example
{
"result": true,
"data": {
"moving_packages": [
{
"id": 1,
"display_name": {
"en": "Studio",
"chs": "单间"
},
"description": {
"en": "Studio-sized moves"
},
"is_default": true,
"display_order": 1,
"pricing_type": 2,
"base_price": null,
"hourly_rate": 120,
"min_hours": 2,
"min_charge": 240,
"prepay_ratio": 30
}
],
"packagings": [],
"products": [],
"moving_timeframes": [
{
"start": "08:00",
"end": "11:00"
}
],
"dimension_units": {
"1": {
"name": "dimension_mm",
"accuracy": 0
},
"2": {
"name": "dimension_cm",
"accuracy": 1
}
},
"weight_units": {
"1": {
"name": "weight_g",
"accuracy": 0
},
"2": {
"name": "weight_kg",
"accuracy": 3
}
},
"default_dimension_unit": 2,
"default_weight_unit": 2,
"surcharges": [],
"form_bindings": []
}
}
customerMovingOrders
query
REST twin: GET /app/v1/moving-orders
Paginated list of the authenticated customer's moving orders Needs orders.moving on the token.
query CustomerMovingOrders($status: String, $moving_date_from: String, $moving_date_to: String, $created_at_from: String, $created_at_to: String, $sort_by: String, $per_page: Int, $page: Int) {
customerMovingOrders(status: $status, moving_date_from: $moving_date_from, moving_date_to: $moving_date_to, created_at_from: $created_at_from, created_at_to: $created_at_to, sort_by: $sort_by, per_page: $per_page, page: $page)
}
Variables
{
"status": "example",
"moving_date_from": "2026-01-01",
"moving_date_to": "2026-01-01",
"created_at_from": "2026-01-01",
"created_at_to": "2026-01-01",
"sort_by": "example",
"per_page": 1,
"page": 1
}
Response example
{
"result": true,
"data": {
"current_page": 1,
"per_page": 20,
"total": 1,
"data": [
{
"id": 7,
"status": "pending payment",
"moving_date": "2026-04-20",
"moving_start_time": "09:00",
"moving_end_time": "12:00",
"moving_package": {
"id": 1,
"display_name": "Studio"
},
"origin_city": "Toronto",
"destination_city": "Montreal",
"item_count": 10,
"total_price": 540,
"currency": "CAD",
"paid_amount": 0,
"created_at": "2026-04-10 08:00:00"
}
]
}
}
customerMovingOrderShow
query
REST twin: GET /app/v1/moving-orders/{id}
Get the detail of one moving order owned by the authenticated customer. Additive: data.can_edit (the customer may still change it: their own order while pending payment) and data.can_cancel (pending payment, confirmed or scheduled), the rules the update and cancel endpoints enforce, as storage orders already answer. Needs orders.moving on the token.
query CustomerMovingOrderShow($id: Int!) {
customerMovingOrderShow(id: $id) {
result
message
data {
id
status
moving_date
moving_start_time
moving_end_time
origin_address
destination_address
moving_package {
id
display_name
pricing_type
}
moving_time_slot_id
moving_time_slot {
id
label
start_time
end_time
}
estimated_hours
items {
id
description
quantity
value
weight
weight_unit
length
width
height
dimension_unit
packaging {
id
name
}
}
products {
product_id
name
display_names
quantity
}
surcharges {
id
surcharge_id
name
amount
quantity
}
notes
estimate_price_response
price_breakdown
coupon_code
promotion_id
promotion_discount
final_total
paid_amount
total_price
currency
created_at
}
}
}
Variables
{
"id": 1
}
Response example
{
"result": true,
"data": {
"id": 7,
"status": "pending payment",
"moving_date": "2026-04-20",
"origin_address": {
"city": "Toronto"
},
"destination_address": {
"city": "Montreal"
},
"items": [],
"products": [],
"surcharges": [],
"total_price": 540,
"currency": "CAD",
"paid_amount": 0,
"created_at": "2026-04-10 08:00:00"
}
}
customerShippingOrderServices
query
REST twin: GET /app/v1/shipping-orders/services
List shipping services available to the authenticated customer Needs orders.shipping on the token.
query CustomerShippingOrderServices {
customerShippingOrderServices
}
Variables
{}
Response example
{
"result": true,
"data": {
"services": [
{
"id": 12,
"service_code": "intl_express",
"name": {
"en": "International Express",
"chs": "国际快递"
},
"offer_pickup": true,
"allow_warehouse_delivery": true,
"support_multi_package": true,
"allow_special_requirements": false,
"allow_purchase_supplies": true,
"send_confirmation_email": true,
"warehouses": [
{
"id": 5,
"name": "Toronto Hub"
}
]
}
]
}
}
customerShippingOrderServiceConfig
query
REST twin: GET /app/v1/shipping-orders/services/{serviceCode}/config
Get the per-service configuration bundle for a shipping service the customer has access to Needs orders.shipping on the token.
query CustomerShippingOrderServiceConfig($serviceCode: String!) {
customerShippingOrderServiceConfig(serviceCode: $serviceCode)
}
Variables
{
"serviceCode": "example"
}
Response example
{
"result": true,
"data": {
"service": {
"id": 12,
"service_code": "intl_express",
"name": {
"en": "International Express"
},
"offer_pickup": true,
"allow_warehouse_delivery": true,
"support_multi_package": true,
"allow_special_requirements": false,
"allow_purchase_supplies": true,
"send_confirmation_email": true
},
"warehouses": [
{
"id": 5,
"name": "Toronto Hub",
"address": "10 Main St",
"address_1": "10 Main St",
"address_2": null,
"city": "Toronto",
"province": "ON",
"country": "CA",
"postcode": "M5V 2T6",
"telephone": "4165551234"
}
],
"packagings": [],
"products": [],
"surcharges": [],
"dimension_units": {
"1": {
"name": "dimension_mm",
"accuracy": 0
},
"2": {
"name": "dimension_cm",
"accuracy": 1
}
},
"weight_units": {
"1": {
"name": "weight_g",
"accuracy": 0
},
"2": {
"name": "weight_kg",
"accuracy": 3
}
},
"default_dimension_unit": 2,
"default_weight_unit": 2,
"form_bindings": [],
"saved_form_answers": []
}
}
customerShippingOrders
query
REST twin: GET /app/v1/shipping-orders/services/{serviceCode}/orders
Paginated list of the authenticated customer's shipping orders under one service Needs orders.shipping on the token.
query CustomerShippingOrders($serviceCode: String!, $status: Int, $id: String, $created_at_from: String, $created_at_to: String, $sort_by: String, $per_page: Int, $page: Int) {
customerShippingOrders(serviceCode: $serviceCode, status: $status, id: $id, created_at_from: $created_at_from, created_at_to: $created_at_to, sort_by: $sort_by, per_page: $per_page, page: $page)
}
Variables
{
"serviceCode": "example",
"status": 1,
"id": "example",
"created_at_from": "2026-01-01",
"created_at_to": "2026-01-01",
"sort_by": "example",
"per_page": 1,
"page": 1
}
Response example
{
"result": true,
"data": {
"service": {
"id": 12,
"service_code": "intl_express",
"name": {
"en": "International Express"
}
},
"orders": {
"current_page": 1,
"per_page": 20,
"total": 1,
"data": [
{
"id": 100,
"status": 0,
"status_name": "Pending",
"tracking_number": "TRK-12345",
"reference_number": "REF-789",
"origin_type": "warehouse",
"warehouse_id": 5,
"delivery_city": "New York",
"delivery_country": "US",
"package_count": 2,
"total_weight": 3.5,
"total_price": 80,
"currency": "CAD",
"needs_quote": false,
"scheduled_date": "2026-04-15",
"created_at": "2026-04-10 08:00:00"
}
]
}
}
}
customerShippingOrderShow
query
REST twin: GET /app/v1/shipping-orders/{id}
Get the detail of one shipping order owned by the authenticated customer, under a specific service Needs orders.shipping on the token.
query CustomerShippingOrderShow($id: Int!) {
customerShippingOrderShow(id: $id) {
result
message
data {
id
status
status_name
can_edit
can_cancel
tracking_number
reference_number
shipping_service {
id
service_code
name
volumetric_factor
}
origin_type
warehouse_id
pickup_address
delivery_address
supplies_delivery_address
scheduled_date
time_window
package_count
total_weight
total_volume
packages {
id
description
tracking_number
weight
weight_unit
length
width
height
dimension_unit
quantity
value
notes
packaging {
id
name
}
}
products {
product_id
name
display_names
quantity
}
special_requirements {
id
description
completed
}
price_breakdown
total_price
promotion_id
coupon_code
promotion_discount
notes
created_at
}
}
}
Variables
{
"id": 1
}
Response example
{
"result": true,
"data": {
"id": 100,
"status": 0,
"tracking_number": "TRK-12345",
"reference_number": "REF-789",
"origin_type": "warehouse",
"warehouse_id": 5,
"delivery_address": {
"city": "New York"
},
"packages": [],
"products": [],
"special_requirements": [],
"total_price": 80,
"created_at": "2026-04-10 08:00:00"
}
}
uniorderRate
mutation
REST twin: POST /app/v1/uniorder/rate
A price quote on a complete address at both ends, answered as one list of rates to choose from. A customer (or sub-account) caller is quoted only the services open to it: self delivery needs its place-order permission, the client's delivery service and the requested type's own switch (D, P or P2P); a label service needs its place-order permission and the client's label service; signed in through a platform app, that app's orders.delivery or orders.label capability must also be on. Access: these single-order uniorder operations (uniorderRate, uniorderCreate, uniorder, uniorderLabel, uniorderPurchaseLabel, uniorderTracking, uniorderCancel) require the account's API permission, except for a customer signed in through a platform app session whose app has orders.delivery or orders.label on; that session needs no API permission. The batch operations always require it. A service it may not order is left out of rates and errors (labels are then not asked of the carriers); when neither is open the answer is HTTP 403 with result false and code SERVICE_NOT_AVAILABLE. Clients, employees and integrations are unaffected. The sender's and the recipient's street line, city, province, country and postcode are all required; only from_address_2 and to_address_2 are optional. For type P (pickup) the sender is the pickup stop and the recipient is where the parcel goes. Nothing is created or booked. v1 and v2 are unchanged. Every rate has a `type` and a `rate_id`: - `self_delivery`: delivery by the business, priced on the caller's (or the named customer's) plan. At most one per quote. How the business carries it is not part of the answer. - `label_service`: one per service of each label carrier account, only when quote_labels is true. `rate_id` is an opaque token naming the choice (self_delivery: channel and customer; label_service: account, carrier and service) and the quoted price. It is valid for 30 minutes and only for the caller it was issued to; order at a rate by passing it back. Whatever could not be quoted is listed in `errors` with its type; an address outside the delivery area is an error of type self_delivery with code OUT_OF_DELIVERY_AREA. `result` is true when at least one rate was found. A client or employee may quote for one of its customers (customer_id or customer_code) so the price follows that customer's plan; a delivery window (time_window_start / time_window_end) is taken into account; channel_id must be one of the business's own channels. Taxes are worked out from the provinces given. Each rate carries carrier_name and service_name: for label_service the carrier and its service; for self_delivery the brand the order would print under (else the business's name) and the channel's display name (null without a channel). Needs orders.delivery or orders.label on the token.
mutation OrdersRateV3($from_address: String!, $from_address_2: String, $from_city: String!, $from_province: String!, $from_country: String!, $from_postcode: String!, $to_address: String!, $to_address_2: String, $to_city: String!, $to_province: String!, $to_country: String!, $to_postcode: String!, $packages: [Json]!, $customer_id: Int, $customer_code: String, $time_window_start: String, $time_window_end: String, $quote_labels: Boolean, $shipping_methods: [Int], $carriers: [String], $services: [String], $from_name: String, $from_telephone: String, $to_name: String, $to_telephone: String, $signature_option: Int, $channel_id: String, $fulfillment_center_id: String, $type: String) {
uniorderRate(from_address: $from_address, from_address_2: $from_address_2, from_city: $from_city, from_province: $from_province, from_country: $from_country, from_postcode: $from_postcode, to_address: $to_address, to_address_2: $to_address_2, to_city: $to_city, to_province: $to_province, to_country: $to_country, to_postcode: $to_postcode, packages: $packages, customer_id: $customer_id, customer_code: $customer_code, time_window_start: $time_window_start, time_window_end: $time_window_end, quote_labels: $quote_labels, shipping_methods: $shipping_methods, carriers: $carriers, services: $services, from_name: $from_name, from_telephone: $from_telephone, to_name: $to_name, to_telephone: $to_telephone, signature_option: $signature_option, channel_id: $channel_id, fulfillment_center_id: $fulfillment_center_id, type: $type)
}
Variables
{
"from_address": "1010 Rue Sainte-Catherine O",
"from_city": "Montréal",
"from_province": "QC",
"from_country": "CA",
"from_postcode": "H3B 1G1",
"to_address": "6841 Rue Saint-Denis",
"to_city": "Montréal",
"to_province": "QC",
"to_country": "CA",
"to_postcode": "H2S 2S3",
"packages": [
{
"weight": 2.5,
"weight_unit": 2,
"length": 30,
"width": 20,
"height": 15,
"dimension_unit": 2
}
],
"signature_option": 1,
"type": "D"
}
Response example
{
"result": true,
"rates": [
{
"rate_id": "eyJpdiI6Ik1rT2Z...",
"type": "self_delivery",
"channel_id": null,
"carrier_name": "Swift Courier",
"service_name": null,
"shipping_price": "14.60",
"currency": "CAD",
"price_details": {
"shipping_fee": 12.92,
"sub_total": "12.92",
"tax_details": [
{
"tax_number_id": 2,
"tax_name": "HST",
"tax_rate": "13.00",
"tax": "1.68"
}
],
"tax_zone_id": 3
},
"warning": null
},
{
"rate_id": "eyJpdiI6IlpxR0...",
"type": "label_service",
"shipping_price": "15.18",
"currency": "CAD",
"price_details": [],
"shipping_method_id": 71,
"shipping_method_name": "Canada Post",
"carrier_name": "canadapost",
"carrier_id": "CPTEST",
"service_code": "canadapost_expedited_parcel",
"service_name": "CANADAPOST EXPEDITED PARCEL",
"transit_days": 2
},
{
"rate_id": "eyJpdiI6IlpxR0...",
"type": "label_service",
"shipping_price": "18.40",
"currency": "CAD",
"price_details": [],
"shipping_method_id": 72,
"shipping_method_name": "UPS",
"carrier_name": "ups",
"carrier_id": "UPS1",
"service_code": "ups_standard",
"service_name": "UPS STANDARD",
"transit_days": 3
}
],
"errors": []
}
uniorderCreate
mutation
REST twin: POST /app/v1/uniorder
Creates an order at a rate returned by POST /v1/uniorder/rate. The rate_id alone decides the service; the same rules as POST /v1/uniorder/rate decide, again at create time, whether a customer (or sub-account) caller may order that service; when it may not, the answer is HTTP 403 with result false and code SERVICE_NOT_AVAILABLE and nothing is created. Otherwise the body carries the shipment in the quote's shape (from_*, to_*, packages) and the order's own details. - `self_delivery`: a delivery order on the quote's customer and channel, created as POST /v1/client/orderCreate would create it (needs the place-order permission). For type D the recipient is the order's stop. The sender is kept as its pickup address only with need_pick_up 1; without it, from_* prices the quote only and the order starts from the account's default shipping-from address, which GET /v1/uniorder/{orderId} returns as from. For type P the sender is the stop. - `label_service`: a label order on the quote's account, then the label is bought at the carrier service the quote named. The service is priced again for the order, so shipping_price may differ from quoted_price. When the label cannot be bought, the order is kept and the answer is HTTP 200 with result false, code LABEL_PURCHASE_FAILED and the order id; do not submit it again — buy the label with POST /v1/uniorder/{orderId}/label. A rate_id that is malformed, expired (30 minutes) or was issued to another caller is refused with RATE_ID_INVALID and nothing is created. Send an Idempotency-Key header to make retries safe. Needs orders.delivery or orders.label on the token.
mutation OrdersCreateV3($rate_id: String!, $from_address: String!, $from_address_2: String, $from_city: String!, $from_province: String!, $from_country: String!, $from_postcode: String!, $from_name: String, $from_company_name: String, $from_telephone: String, $from_email: String, $from_buzz_code: String, $to_address: String!, $to_address_2: String, $to_city: String!, $to_province: String!, $to_country: String!, $to_postcode: String!, $to_name: String!, $to_company_name: String, $to_telephone: String, $to_email: String, $to_buzz_code: String, $packages: [Json]!, $type: String, $ref: String, $order_code: String, $note: String, $quote_token: String, $notification_language: String, $signature_option: Int, $insurance_option: Int, $auto_deduplication: Int, $shipping_from: String, $shipping_from_code: String, $order_batch: String, $pickup_instruction: String, $delivery_instruction: String, $need_pick_up: Int, $allow_dropoff: Int, $pin_verification_required: Int, $self_pickup: Int, $strict_duplicate_check: Int, $service_time: Int, $pickup_service_time: Int, $pickup_date: String, $schedule_date: String, $time_window_start: String, $time_window_end: String, $pickup_time_window_start: String, $pickup_time_window_end: String, $tip_amount: Float, $skills: String, $surcharges: [Json], $fulfillment_center_code: String, $external_order_id: String, $export_reason: Int, $package_type: String) {
uniorderCreate(rate_id: $rate_id, from_address: $from_address, from_address_2: $from_address_2, from_city: $from_city, from_province: $from_province, from_country: $from_country, from_postcode: $from_postcode, from_name: $from_name, from_company_name: $from_company_name, from_telephone: $from_telephone, from_email: $from_email, from_buzz_code: $from_buzz_code, to_address: $to_address, to_address_2: $to_address_2, to_city: $to_city, to_province: $to_province, to_country: $to_country, to_postcode: $to_postcode, to_name: $to_name, to_company_name: $to_company_name, to_telephone: $to_telephone, to_email: $to_email, to_buzz_code: $to_buzz_code, packages: $packages, type: $type, ref: $ref, order_code: $order_code, note: $note, quote_token: $quote_token, notification_language: $notification_language, signature_option: $signature_option, insurance_option: $insurance_option, auto_deduplication: $auto_deduplication, shipping_from: $shipping_from, shipping_from_code: $shipping_from_code, order_batch: $order_batch, pickup_instruction: $pickup_instruction, delivery_instruction: $delivery_instruction, need_pick_up: $need_pick_up, allow_dropoff: $allow_dropoff, pin_verification_required: $pin_verification_required, self_pickup: $self_pickup, strict_duplicate_check: $strict_duplicate_check, service_time: $service_time, pickup_service_time: $pickup_service_time, pickup_date: $pickup_date, schedule_date: $schedule_date, time_window_start: $time_window_start, time_window_end: $time_window_end, pickup_time_window_start: $pickup_time_window_start, pickup_time_window_end: $pickup_time_window_end, tip_amount: $tip_amount, skills: $skills, surcharges: $surcharges, fulfillment_center_code: $fulfillment_center_code, external_order_id: $external_order_id, export_reason: $export_reason, package_type: $package_type)
}
Variables
{
"rate_id": "eyJpdiI6Ik1rT2Z...",
"from_address": "1010 Rue Sainte-Catherine O",
"from_address_2": "Suite 400",
"from_city": "Montréal",
"from_province": "QC",
"from_country": "CA",
"from_postcode": "H3B 1G1",
"from_name": "Sender Co",
"from_telephone": "5145550100",
"to_address": "6841 Rue Saint-Denis",
"to_address_2": "Apt 2",
"to_city": "Montréal",
"to_province": "QC",
"to_country": "CA",
"to_postcode": "H2S 2S3",
"to_name": "Jane Doe",
"to_telephone": "5145550199",
"to_email": "jane@example.com",
"packages": [
{
"weight": 2.5,
"weight_unit": 2,
"length": 30,
"width": 20,
"height": 15,
"dimension_unit": 2,
"ref": "PKG-1"
}
],
"type": "D"
}
Response example
{
"result": true,
"type": "self_delivery",
"id": 123456,
"ref": "ORDER-1001",
"tracking_numbers": [
"SR26092800001"
],
"packages": [
{
"id": 987,
"ref": "PKG-1",
"tracking_number": "SR26092800001",
"external_tracking_number": ""
}
],
"shipping_price": "14.60",
"currency": "CAD",
"price_details": {
"shipping_fee": 12.92,
"sub_total": "12.92",
"tax_details": [
{
"tax_number_id": 2,
"tax_name": "HST",
"tax_rate": "13.00",
"tax": "1.68"
}
],
"tax_zone_id": 3
},
"quoted_price": "14.60",
"warning": null
}
uniorderPurchaseLabel
mutation
REST twin: POST /app/v1/uniorder/{orderId}/label
Buys the label of a label order created by POST /v1/uniorder whose label was not bought (LABEL_PURCHASE_FAILED). For a customer (or sub-account) caller the label service must still be open, by the same rules as POST /v1/uniorder; otherwise result false, code SERVICE_NOT_AVAILABLE. The service is the one chosen when the order was created; the order is priced again for it, so shipping_price may differ from quoted_price. Pass rate_id (a label_service rate from POST /v1/uniorder/rate for the same account) to buy at that service instead. A label already bought is returned as it is and is not bought again. The answer has the same shape as POST /v1/uniorder for a label_service rate; a purchase that fails again answers HTTP 200 with result false and code LABEL_PURCHASE_FAILED. Needs orders.delivery or orders.label on the token.
mutation OrdersPurchaseLabelV3($order_id: Int!, $rate_id: String) {
uniorderPurchaseLabel(order_id: $order_id, rate_id: $rate_id)
}
Variables
{
"order_id": 123458
}
Response example
{
"result": true,
"type": "label_service",
"id": 123457,
"ref": "ORDER-1002",
"tracking_numbers": [
"SR26092800002"
],
"shipping_price": "18.40",
"currency": "CAD",
"quoted_price": "18.40",
"label": {
"shipping_method_id": 72,
"carrier_name": "ups",
"carrier_id": "UPS1",
"service_code": "ups_standard",
"service_name": "UPS STANDARD",
"main_tracking_number": "1Z999AA10123456784",
"carrier_tracking_numbers": [
"1Z999AA10123456784"
],
"label_status": "ready",
"shipping_label": "JVBERi0xLjQK..."
}
}
uniorderCancel
mutation
REST twin: POST /app/v1/uniorder/{orderId}/cancel
Cancels an order created by POST /v1/uniorder: a self delivery or a label order of the caller. A label is voided with its carrier first; a label the carrier does not void leaves the order unchanged (LABEL_CANCEL_FAILED). A delivery is withdrawn wherever it is being carried first; when that is refused the order is unchanged (ORDER_CANCEL_REFUSED). An order already cancelled answers success with already_cancelled true, so a retry is safe. Orders of other families (storage, shipping, moving, ...) are not found here. A refusal fires order.cancel_failed when the account has subscribed to it. Needs orders.delivery or orders.label on the token.
mutation UniorderCancel($order_id: Int!) {
uniorderCancel(order_id: $order_id)
}
Variables
{
"order_id": 123456
}
Response example
{
"result": true,
"type": "self_delivery",
"id": 123456,
"already_cancelled": false,
"message": "The order has been cancelled."
}
uniorder
query
REST twin: GET /app/v1/uniorder/{orderId}
One self delivery or label order of the caller. `status` is one of: pending, in_transit, out_for_pickup, out_for_delivery, ready_for_self_pickup, delivered, exception, cancelled (self delivery); label_pending, label_purchased, cancelled (label order — where the parcel is, GET /v1/uniorder/{orderId}/tracking says). A self delivery reads the same however it is carried. A label order carries a `label` block: account, carrier, service, the carrier's tracking numbers and `label_status` (not_purchased, pending, ready, failed). Additive: `status_group` (pending_payment, active, completed, exception, cancelled) and `status_label` (the status name in the request language), the same reading as platformAppOrders. Needs orders.delivery or orders.label on the token.
query Uniorder($order_id: Int!) {
uniorder(order_id: $order_id)
}
Variables
{
"order_id": 123456
}
Response example
{
"result": true,
"type": "self_delivery",
"id": 123456,
"ref": "ORDER-1001",
"order_type": "D",
"status": "in_transit",
"created_at": "2026-09-28 10:00:00",
"scheduled_date": "2026-09-29",
"time_window_start": null,
"time_window_end": null,
"from": {
"name": "Sender Co",
"company_name": null,
"telephone": "5145550100",
"email": null,
"address": "1010 Rue Sainte-Catherine O",
"address_2": "Suite 400",
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H3B1G1"
},
"to": {
"name": "Jane Recipient",
"company_name": null,
"telephone": "5145550199",
"email": "jane@example.com",
"address": "6841 Rue Saint-Denis",
"address_2": "Apt 2",
"city": "Montreal",
"province": "QC",
"country": "CA",
"postcode": "H2S2S3"
},
"packages": [
{
"id": 987,
"ref": "PKG-1",
"tracking_number": "SR26092800001",
"external_tracking_number": "",
"weight": "2.5",
"weight_unit": 2,
"length": "30",
"width": "20",
"height": "15",
"dimension_unit": 2
}
],
"shipping_price": "14.60",
"currency": "CAD",
"price_details": {
"shipping_fee": 12.92,
"sub_total": "12.92",
"tax_details": [
{
"tax_name": "QST",
"tax_rate": "9.975",
"tax": "1.29"
}
]
}
}
uniorderLabel
query
REST twin: GET /app/v1/uniorder/{orderId}/label
The label PDF of the order, base64 in `pdf_data`. A self delivery gives the business's own label (`hide_sender_address` / `hide_receiver_address` = 1 leave an address off). A label order gives the carrier label it bought; a label is never bought here. A label not bought yet answers 409 LABEL_PURCHASE_FAILED — buy it with POST /v1/uniorder/{orderId}/label. A label bought whose file the carrier has not produced yet answers 200 with result false and `label_status` pending (or failed). Needs orders.delivery or orders.label on the token.
query UniorderLabel($order_id: Int!, $hide_sender_address: Int, $hide_receiver_address: Int) {
uniorderLabel(order_id: $order_id, hide_sender_address: $hide_sender_address, hide_receiver_address: $hide_receiver_address)
}
Variables
{
"order_id": 123456
}
Response example
{
"result": true,
"type": "self_delivery",
"id": 123456,
"format": "pdf",
"label_code": "MTL-01",
"tracking_numbers": [
"SR26092800001"
],
"pdf_data": "JVBERi0xLjQK..."
}
uniorderTracking
query
REST twin: GET /app/v1/uniorder/{orderId}/tracking
The order's timeline, newest first: each event's `code` (OTEP status), `description`, `location` and time; proofs of delivery with a signed URL. A self delivery's timeline reads the same however it is carried and every event has `source` shipper. A label order adds a `carrier` block (name, tracking number, tracking URL, status) and its carrier events have `source` carrier. Needs orders.delivery or orders.label on the token.
query UniorderTracking($order_id: Int!) {
uniorderTracking(order_id: $order_id)
}
Variables
{
"order_id": 123456
}
Response example
{
"result": true,
"type": "self_delivery",
"id": 123456,
"status": "in_transit",
"tracking_numbers": [
"SR26092800001"
],
"events": [
{
"code": "IN_TRANSIT",
"description": "Your shipment is in transit.",
"location": "Montreal,QC",
"time": "2026-09-29 09:12:00",
"time_zone": "America/Toronto",
"gmt_offset": "-04:00",
"timestamp": 1790687520,
"source": "shipper"
}
],
"proofs": []
}
customerShippingOrderEstimatePrice
mutation
REST twin: POST /app/v1/shipping-orders/services/{serviceCode}/estimate-price
Estimate the price of a shipping order under a service, with the promotions that apply; writes nothing. Needs the orders.shipping capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.shipping.
mutation CustomerShippingOrderEstimatePrice($service_code: String!, $input: Json) {
customerShippingOrderEstimatePrice(service_code: $service_code, input: $input)
}
Variables
{
"service_code": "express",
"input": {
"origin_type": "warehouse",
"warehouse_id": 7,
"delivery_postcode": "V6B1A1",
"delivery_country": "CA",
"packages": [
{
"weight": 2.5,
"length": 30,
"width": 20,
"height": 15,
"weight_unit": 2,
"dimension_unit": 2,
"quantity": 1
}
]
}
}
Response example
{
"result": true,
"data": {
"needs_manual_quote": false,
"manual_quote_reasons": [],
"shipping_fee": 18.5,
"shipping_fee_total": 20.35,
"fuel_surcharge": 1.85,
"handling_fee": 0,
"shipping_fee_error": null,
"pickup_fee": null,
"pickup_fee_error": null,
"surcharges": [],
"surcharges_total": 0,
"sub_total": 20.35,
"tax": {
"total_tax": 2.65
},
"total": 23,
"currency": "CAD",
"all_fees_calculated": true,
"fee_items": [],
"has_items_needing_quote": false,
"unpriced_items": [],
"refused": false,
"refusal_message": null
}
}
customerShippingOrderCreate
mutation
REST twin: POST /app/v1/shipping-orders/services/{serviceCode}/orders
Create a shipping order under a service; an optional quote_token marks the public quote it came from as converted. Needs the orders.shipping capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.shipping.
mutation CustomerShippingOrderCreate($service_code: String!, $input: Json, $idempotency_key: String) {
customerShippingOrderCreate(service_code: $service_code, input: $input, idempotency_key: $idempotency_key)
}
Variables
{
"service_code": "express",
"input": [],
"idempotency_key": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
Response example
{
"result": true
}
customerShippingOrderUpdate
mutation
REST twin: PUT /app/v1/shipping-orders/{id}
Edit a pending shipping order owned by the customer. Needs the orders.shipping capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.shipping.
mutation CustomerShippingOrderUpdate($id: Int!, $input: Json) {
customerShippingOrderUpdate(id: $id, input: $input)
}
Variables
{
"id": 123,
"input": {
"origin_type": "warehouse",
"warehouse_id": 7,
"delivery_name": "John Smith",
"delivery_telephone": "6045550100",
"delivery_address_1": "123 Main St",
"delivery_postcode": "V6B1A1",
"delivery_city": "Vancouver",
"delivery_province": "BC",
"delivery_country": "CA",
"package": [
{
"weight": 3,
"length": 30,
"width": 20,
"height": 15,
"weight_unit": 2,
"dimension_unit": 2,
"quantity": 1
}
]
}
}
Response example
{
"result": true,
"data": {
"id": 5501,
"status": 0,
"total_price": 23,
"price_breakdown": {
"sub_total": 20.35,
"tax_amount": 2.65,
"total": 23,
"currency": "CAD"
},
"promotion_id": null,
"coupon_code": null,
"promotion_discount": null
}
}
customerShippingOrderCancel
mutation
REST twin: POST /app/v1/shipping-orders/{id}/cancel
Cancel a shipping order while it is pending or confirmed; what was paid goes back to the customer's credit. Needs the orders.shipping capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.shipping.
mutation CustomerShippingOrderCancel($id: Int!) {
customerShippingOrderCancel(id: $id)
}
Variables
{
"id": 123
}
Response example
{
"result": true,
"data": {
"id": 5501,
"status": 4,
"refund_amount": 23
},
"message": "Order cancelled. $23.00 refunded to your credit."
}
customerShippingOrderPaymentInfo
query
REST twin: GET /app/v1/shipping-orders/{id}/payment-info
What is paid and what is left to pay on a shipping order. Needs the orders.shipping capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.shipping.
query CustomerShippingOrderPaymentInfo($id: Int!) {
customerShippingOrderPaymentInfo(id: $id)
}
Variables
{
"id": 123
}
Response example
{
"result": true,
"data": {
"order_id": 5501,
"currency": "CAD",
"total_price": "23.00",
"paid_amount": "0.00",
"remaining_balance": "23.00",
"user_balance": "50.00",
"has_sufficient_balance": true,
"shortfall": 0,
"allow_online_payment": 1,
"payment_options": [
{
"type": "remaining_balance",
"label": "Pay Remaining Balance",
"amount": 23,
"description": "Pay the remaining balance to complete payment"
}
]
}
}
customerShippingOrderPay
mutation
REST twin: POST /app/v1/shipping-orders/{id}/pay
Pay the remaining balance of a shipping order from the account balance. Needs the orders.shipping capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.shipping.
mutation CustomerShippingOrderPay($id: Int!, $input: Json) {
customerShippingOrderPay(id: $id, input: $input)
}
Variables
{
"id": 123,
"input": []
}
Response example
{
"result": true,
"message": "Payment of $23.00 processed successfully.",
"data": {
"order_id": 5501,
"status": 1,
"amount_paid": "23.00"
}
}
customerStorageOrderCalculatePrice
mutation
REST twin: POST /app/v1/storage-orders/calculate-price
Estimate the price of a storage order; writes nothing. Needs the orders.storage capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.storage.
mutation CustomerStorageOrderCalculatePrice($input: Json) {
customerStorageOrderCalculatePrice(input: $input)
}
Variables
{
"input": {
"warehouse_id": 7,
"start_date": "2026-10-01",
"end_date": "2027-01-01",
"items": [
{
"packaging_id": 3,
"qty": 2,
"length": 60,
"width": 40,
"height": 40,
"dimension_unit": 2,
"weight": 12,
"weight_unit": 2
}
],
"need_pickup": true,
"pickup_postcode": "M5V2H1",
"pickup_country": "CA",
"pickup_channel_id": 4
}
}
Response example
{
"success": true,
"price": {
"shipping_price": 245.6,
"total_price": "245.60",
"currency": "CAD",
"storage": {
"result": true,
"shipping_price": 180,
"currency": "CAD"
},
"pickup": {
"result": true,
"shipping_price": 65.6,
"currency": "CAD"
}
}
}
customerStorageOrderCreate
mutation
REST twin: POST /app/v1/storage-orders
Create a storage order; an optional quote_token marks the public quote it came from as converted. Needs the orders.storage capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.storage.
mutation CustomerStorageOrderCreate($input: Json, $idempotency_key: String) {
customerStorageOrderCreate(input: $input, idempotency_key: $idempotency_key)
}
Variables
{
"input": [],
"idempotency_key": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
Response example
{
"result": true
}
customerStorageOrderPaymentInfo
query
REST twin: GET /app/v1/storage-orders/{id}/payment-info
What is paid and what is left to pay on a storage order. Needs the orders.storage capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.storage.
query CustomerStorageOrderPaymentInfo($id: Int!) {
customerStorageOrderPaymentInfo(id: $id)
}
Variables
{
"id": 123
}
Response example
{
"success": true,
"result": true,
"data": {
"order_id": 1024,
"currency": "CAD",
"total_price": "245.60",
"original_price": "245.60",
"paid_amount": "0.00",
"remaining_balance": "245.60",
"amount_due": "245.60",
"user_balance": "300.00",
"has_sufficient_balance": true,
"shortfall": 0,
"allow_online_payment": 1,
"has_pickup_service": true,
"minimum_payment": "65.60",
"has_previous_payment": false,
"payment_options": [
{
"type": "minimum_payment",
"label": "Pay Minimum Amount",
"amount": 65.6,
"description": "Pay the minimum amount to confirm your order"
},
{
"type": "custom",
"label": "Custom Amount",
"amount": null,
"min_amount": 65.6,
"max_amount": 245.6,
"description": "Enter a custom amount between minimum and total"
},
{
"type": "full_balance",
"label": "Pay Full Balance",
"amount": 245.6,
"description": "Pay the full amount"
}
],
"promotion": null
}
}
customerStorageOrderPay
mutation
REST twin: POST /app/v1/storage-orders/{id}/pay
Pay a storage order. Needs the orders.storage capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.storage.
mutation CustomerStorageOrderPay($id: Int!, $input: Json) {
customerStorageOrderPay(id: $id, input: $input)
}
Variables
{
"id": 123,
"input": {
"payment_type": "custom",
"custom_amount": 100
}
}
Response example
{
"success": true,
"result": true,
"message": "Partial payment of 100.00 processed successfully. Remaining balance: 145.60",
"new_balance": 200,
"paid_amount": 100,
"charge_amount": 100,
"is_fully_paid": false
}
customerStorageOrderCancel
mutation
REST twin: POST /app/v1/storage-orders/{id}/cancel
Cancel a storage order in a cancellable status; its pickup, delivery and supplies orders are cancelled and what was paid goes back to credit. Needs the orders.storage capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.storage.
mutation CustomerStorageOrderCancel($id: Int!) {
customerStorageOrderCancel(id: $id)
}
Variables
{
"id": 123
}
Response example
{
"result": true,
"message": "Storage order cancelled",
"data": {
"id": 1024,
"status": "cancelled"
}
}
customerMovingOrderCalculatePrice
mutation
REST twin: POST /app/v1/moving-orders/calculate-price
Estimate the price of a moving order; writes nothing. Needs the orders.moving capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.moving.
mutation CustomerMovingOrderCalculatePrice($input: Json) {
customerMovingOrderCalculatePrice(input: $input)
}
Variables
{
"input": []
}
Response example
{
"result": true
}
customerMovingOrderCreate
mutation
REST twin: POST /app/v1/moving-orders
Create a moving order. Needs the orders.moving capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.moving.
mutation CustomerMovingOrderCreate($input: Json, $idempotency_key: String) {
customerMovingOrderCreate(input: $input, idempotency_key: $idempotency_key)
}
Variables
{
"input": [],
"idempotency_key": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
Response example
{
"result": true
}
customerMovingOrderPaymentInfo
query
REST twin: GET /app/v1/moving-orders/{id}/payment-info
What is paid and what is left to pay on a moving order. Needs the orders.moving capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.moving.
query CustomerMovingOrderPaymentInfo($id: Int!) {
customerMovingOrderPaymentInfo(id: $id)
}
Variables
{
"id": 123
}
Response example
{
"result": true
}
customerMovingOrderPay
mutation
REST twin: POST /app/v1/moving-orders/{id}/pay
Pay a moving order. Needs the orders.moving capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.moving.
mutation CustomerMovingOrderPay($id: Int!, $input: Json) {
customerMovingOrderPay(id: $id, input: $input)
}
Variables
{
"id": 123,
"input": []
}
Response example
{
"result": true
}
customerMovingOrderCancel
mutation
REST twin: POST /app/v1/moving-orders/{id}/cancel
Cancel a moving order. Needs the orders.moving capability; the customer's own service switch applies as on the REST route. On the partner surface the token must carry orders.moving.
mutation CustomerMovingOrderCancel($id: Int!) {
customerMovingOrderCancel(id: $id)
}
Variables
{
"id": 123
}
Response example
{
"result": true
}
customerStorageOrderUpdate
mutation
REST twin: PUT /app/v1/storage-orders/{id}
Edit a storage order owned by the customer in a customer-editable status; the warehouse and status stay as they are and the price is recalculated. On the partner surface the token must carry orders.storage.
mutation CustomerStorageOrderUpdate($id: Int!, $input: Json) {
customerStorageOrderUpdate(id: $id, input: $input)
}
Variables
{
"id": 123,
"input": {
"store_from": "2026-10-01",
"store_to": "2027-02-01",
"items": [
{
"description": "Books",
"packaging_id": 3,
"length": 60,
"width": 40,
"height": 40,
"dimension_unit": 2,
"weight": 12,
"weight_unit": 2
}
],
"need_pickup": false,
"need_delivery": false,
"notes": "Fragile"
}
}
Response example
{
"result": true,
"message": "Storage order updated",
"data": {
"id": 1024
}
}
customerMovingOrderUpdate
mutation
REST twin: PUT /app/v1/moving-orders/{id}
Edit a moving order owned by the customer while it is pending payment; the price is recalculated. On the partner surface the token must carry orders.moving.
mutation CustomerMovingOrderUpdate($id: Int!, $input: Json) {
customerMovingOrderUpdate(id: $id, input: $input)
}
Variables
{
"id": 123,
"input": {
"moving_date": "2026-11-01",
"origin_address_1": "1 Main St",
"origin_city": "Vancouver",
"origin_province": "BC",
"origin_country": "CA",
"origin_postcode": "V6B1A1",
"destination_address_1": "2 Side St",
"destination_city": "Burnaby",
"destination_province": "BC",
"destination_country": "CA",
"destination_postcode": "V5K0A1"
}
}
Response example
{
"success": true,
"message": "Moving order updated successfully",
"redirect_url": "https://admin.superroute.ca/movingorder/123",
"result": true
}
Driver
driverRoutes
query
REST twin: GET /app/v1/driver/routes
Get routes for one driver. Each route's total and per-status count are this driver's orders on that route, not every order on the route. Recurring pickups (additive, after every existing key): recurring_pickup_route tells a route generated from a recurring pickup template; allow_adhoc_pickups and allow_deliveries are its two switches (true for every other route, and before the recurring pickup migrations). Driver apps only; needs driver.routes on the token.
query DriverRoutes($isToday: Int, $isUpcoming: Int, $isProcessing: Int, $isHistory: Int, $page: Int, $per_page: Int, $sort: String, $id: Int) {
driverRoutes(isToday: $isToday, isUpcoming: $isUpcoming, isProcessing: $isProcessing, isHistory: $isHistory, page: $page, per_page: $per_page, sort: $sort, id: $id)
}
Variables
{
"isToday": 1,
"isUpcoming": 1,
"isProcessing": 1,
"isHistory": 1,
"page": 1,
"per_page": 1,
"sort": "example",
"id": 1
}
Response example
{
"data": [
{
"id": 323,
"route_type": 2,
"name": "20210220171743",
"date": "2021-02-20",
"operation_mode": 0,
"allow_rescheduled_by_same_driver": 1,
"allow_hands_off_order_by_driver": 0,
"allow_reset_order_status_by_driver": 0,
"allow_add_order_by_driver": 0,
"allow_add_manual_order_by_driver": 0,
"driver_alias": "XXX",
"vehicle_type": "1 (1: Car, 2: Truck, 3: Motorcycle, 4: Bicycle 5:Pedestrian)",
"distance": 2321231,
"geometry": "XXX",
"total": 1,
"count": {
"already_planned": 1
},
"recurring_pickup_route": false,
"allow_adhoc_pickups": true,
"allow_deliveries": true
},
{
"id": 12,
"name": "20200511",
"date": "2020-05-11",
"operation_mode": 0,
"allow_rescheduled_by_same_driver": 1,
"allow_hands_off_order_by_driver": 0,
"allow_reset_order_status_by_driver": 0,
"allow_add_order_by_driver": 0,
"allow_add_manual_order_by_driver": 0,
"driver_alias": "XXX",
"distance": 43242321,
"geometry": "XXX",
"total": 11,
"count": {
"already_planned": 11
},
"recurring_pickup_route": false,
"allow_adhoc_pickups": true,
"allow_deliveries": true
}
]
}
driverRouteBoards
query
REST twin: GET /app/v1/driver/boards
Boards of the driver's own pool that are scheduled, open, filled or closed, dated today or later, with seat counts and the driver's own claims. Same envelope as REST. Seat-mode boards additionally carry capacity, quota_taken, my_pooled and settled. Rows also carry my_bidding, bid_count and bid_visibility (bid mode) and currency. Driver apps only; needs driver.board on the token.
query DriverRouteBoards {
driverRouteBoards
}
Variables
{}
Response example
{
"result": true,
"message": "Successful",
"data": [
{
"id": 12,
"title": "Route Board 2026-03-04",
"date": "2026-03-04",
"mode": "route",
"status": "open",
"opens_at": "2026-03-03T18:00:00+00:00",
"closes_at": "2026-03-04T05:00:00+00:00",
"is_open": true,
"seats": 8,
"open_seats": 3,
"standby_limit": 2,
"withdraw_policy": "until_cutoff",
"withdraw_cutoff_hours": 12,
"my_primary": 1,
"my_standby": 0,
"my_pooled": 0,
"capacity": null,
"quota_taken": null,
"settled": false
},
{
"id": 13,
"title": "Saturday pool",
"date": "2026-03-07",
"mode": "seat",
"status": "open",
"opens_at": "2026-03-06T18:00:00+00:00",
"closes_at": "2026-03-07T05:00:00+00:00",
"is_open": true,
"seats": 6,
"open_seats": 6,
"standby_limit": 1,
"withdraw_policy": "allowed",
"withdraw_cutoff_hours": null,
"my_primary": 0,
"my_standby": 0,
"my_pooled": 1,
"capacity": 12,
"quota_taken": 7,
"settled": false
}
]
}
driverRouteBoard
query
REST twin: GET /app/v1/driver/boards/{id}
Every seat with its queue length, the driver's own claim, whether the driver can claim now and the blocked_reason if not. A board of another pool answers a not-found envelope. On a seat-mode board data.board.quota describes the pool (capacity / taken / remaining / my_claim / claimable / blocked_reason) and every seat is a read-only preview (claimable false, blocked_reason "seat_mode"). On a bid-mode board every seat row carries a bid object {visibility, currency, reference_price, ceiling_price, instant_price, bid_count, lowest (open only), my_bid, my_claim_id, my_position (open only), allow_rebid, min_bid_step, can_bid, blocked_reason} and claimable=false / blocked_reason=bid_mode; the board carries bid_visibility, award_method, currency, settled, closes_at. Use bidRouteBoardSeat to bid. Driver apps only; needs driver.board on the token.
query DriverRouteBoard($id: Int!) {
driverRouteBoard(id: $id)
}
Variables
{
"id": 12
}
Response example
{
"result": true,
"message": "Successful",
"data": {
"board": {
"id": 12,
"title": "Route Board 2026-03-04",
"date": "2026-03-04",
"mode": "route",
"status": "open",
"is_open": true,
"opens_at": "2026-03-03T18:00:00+00:00",
"closes_at": "2026-03-04T05:00:00+00:00",
"standby_limit": 2,
"withdraw_policy": "until_cutoff",
"withdraw_cutoff_hours": 12,
"max_primary_per_driver": 1,
"my_primary": 0,
"quota": null
},
"seats": [
{
"seat_id": 41,
"route_id": 9001,
"status": "open",
"snapshot": {
"route_name": "East-3",
"date": "2026-03-04",
"work_from": "07:00",
"work_end": "15:00",
"vehicle_type": "Van",
"distance": 68,
"stops": 42,
"start": "Scarborough",
"end": "Markham"
},
"primary_taken": false,
"standby_taken": 0,
"standby_limit": 2,
"next_rank": 1,
"my_claim": null,
"claimable": true,
"blocked_reason": null
},
{
"seat_id": 42,
"route_id": 9002,
"status": "filled",
"snapshot": {
"route_name": "East-5",
"date": "2026-03-04",
"work_from": "07:30",
"work_end": "15:30",
"vehicle_type": "Van",
"distance": 55,
"stops": 38,
"start": "Scarborough",
"end": "Markham"
},
"primary_taken": true,
"standby_taken": 1,
"standby_limit": 2,
"next_rank": 3,
"my_claim": null,
"claimable": true,
"blocked_reason": null
},
{
"seat_id": 43,
"route_id": 9003,
"status": "open",
"snapshot": {
"route_name": "North-1",
"date": "2026-03-04",
"work_from": "06:30",
"work_end": "14:30",
"vehicle_type": "Van",
"distance": 68,
"stops": 42,
"start": "Scarborough",
"end": "Markham"
},
"primary_taken": false,
"standby_taken": 0,
"standby_limit": 2,
"next_rank": 1,
"my_claim": null,
"claimable": false,
"blocked_reason": "time_conflict"
}
]
}
}
driverCancelRoute
mutation
REST twin: POST /app/v1/driver/routes/cancel
Cancel a route as the assigned driver Driver apps only; needs driver.route_edit on the token.
mutation DriverCancelRoute($routeId: Int!) {
driverCancelRoute(routeId: $routeId)
}
Variables
{
"routeId": 1
}
driverStartDelivery
mutation
REST twin: POST /app/v1/driver/startDelivery
start Delivery By route id. A route of up to 200 stops (START_DELIVERY_SYNC_ORDERS) starts wholly before the answer. On a larger route the first 200 stops in stop order start before the answer and the rest start right after it in the background, a chunk at a time, with the same tracking events, notifications and stock-outs, normally within seconds and at most about a minute later. A stop the driver delivers, fails or picks up before its chunk starts is left as it is, and once the run is ended the remaining stops are not started; the answer is unchanged. With the load gate (clients with warehouse management) the start is refused while an outbound order on the run is still being picked, naming the outbound numbers, as the strict start refusal is. Driver apps only; needs driver.routes on the token.
mutation DriverStartDelivery($routeId: Int, $gps_tracking_id: Int) {
driverStartDelivery(routeId: $routeId, gps_tracking_id: $gps_tracking_id)
}
Variables
{
"routeId": 1,
"gps_tracking_id": 1
}
Response example
{
"message": "Success"
}
driverEndDelivery
mutation
REST twin: POST /app/v1/driver/endDelivery
end Delivery By route id Driver apps only; needs driver.routes on the token.
mutation DriverEndDelivery($routeId: Int, $gps_tracking_id: Int) {
driverEndDelivery(routeId: $routeId, gps_tracking_id: $gps_tracking_id)
}
Variables
{
"routeId": 1,
"gps_tracking_id": 1
}
Response example
{
"message": "Success"
}
driverRoutesOrders
query
REST twin: GET /app/v1/driver/routes/{routeId}/orders
Get orders on route for one driver Recurring pickups (additive, after every existing key): is_recurring_pickup marks a mandatory recurring pickup stop; can_hand_off is false for such a stop and otherwise follows the route's allow_hands_off_order_by_driver (the hand-off endpoints refuse a recurring stop with recurring_pickups.skip_instead either way). Stops the warehouse took back while open are listed as failed stops (no statusType, or statusType=failed) with stop_outcome_code=returned_to_warehouse and stop_outcome_text (additive). Recipient privacy (client setting, off by default): once the driver has ended the route, or the client's chosen number of days after the route date has passed, name, company_name, email and telephone are empty strings; every key and the address stay. Driver apps only; needs driver.routes on the token.
query DriverRoutesOrders($routeId: Int, $ids: String, $isHideSuccess: Int, $statusType: String) {
driverRoutesOrders(routeId: $routeId, ids: $ids, isHideSuccess: $isHideSuccess, statusType: $statusType)
}
Variables
{
"routeId": 1,
"ids": "example",
"isHideSuccess": 1,
"statusType": "example"
}
Response example
[
{
"id": 299545,
"type": "P",
"order_type": "P2P",
"is_p2p": 1,
"is_locked": 0,
"scan_count": 0,
"sorting_scan_count": 1,
"ref": "T1",
"carrier_name": "DEMO",
"business_name": "DEMO",
"address_1": "DMEO",
"address_2": "",
"address_type": "",
"signature_option": 0,
"city": "Saint-Jerome",
"province": "QC",
"country": "Canada",
"postcode": "J5L0E1",
"buzz_code": "",
"latitude": "45.797655",
"longitude": "-74.049801",
"name": "y",
"email": null,
"telephone": null,
"tracking_number": "SR904766214130",
"external_tracking_number": "",
"note": "",
"driver_alias": "pd1",
"stop_number": 1,
"driver_id": 343,
"leg_number": "L-1",
"status": "Already Planned",
"packages": 1,
"notification_language": "en",
"return_reason_id": null,
"orders_status_id": 5,
"current_route_id": 3964,
"allow_dropoff": 1,
"driverNotes": [],
"files": {
"photo_urls": [],
"signature_urls": []
},
"is_finished": 0,
"is_postponed": 0,
"full_address": "DEMO,Montreal,QC,H4E3R3",
"operation_mode": 0,
"is_multiple": 1,
"total_orders": 2,
"current_order": 2,
"is_recurring_pickup": false,
"can_hand_off": false
},
{
"id": 299548,
"type": "P",
"order_type": "P",
"is_p2p": 0,
"is_locked": 0,
"ref": "",
"carrier_name": "DEMO",
"business_name": "DEMO",
"address_1": "DEMO",
"address_2": "",
"city": "Saint-Jerome",
"province": "QC",
"country": "Canada",
"postcode": "J5L0E1",
"buzz_code": "",
"latitude": "45.797655",
"longitude": "-74.049801",
"name": "X",
"email": "",
"telephone": "",
"tracking_number": "SR270017412103",
"external_tracking_number": "",
"note": "",
"driver_alias": "pd1",
"stop_number": 2,
"driver_id": 343,
"leg_number": "L-1",
"status": "Already Planned",
"packages": 1,
"notification_language": "en",
"return_reason_id": null,
"orders_status_id": 5,
"current_route_id": 3964,
"allow_dropoff": 1,
"driverNotes": [],
"files": {
"photo_urls": [],
"signature_urls": []
},
"is_finished": 0,
"is_postponed": 0,
"full_address": "DEMO,Saint-Jerome,QC,J5L0E1",
"operation_mode": 0,
"is_multiple": 1,
"total_orders": 3,
"current_order": 1,
"is_recurring_pickup": false,
"can_hand_off": false
},
{
"id": 299546,
"type": "D",
"order_type": "D",
"is_p2p": 0,
"is_locked": 0,
"ref": "1231",
"carrier_name": "DEMO",
"business_name": "DEMO",
"address_1": "DEMO",
"address_2": "",
"city": "Saint-Jerome",
"province": "QC",
"country": "Canada",
"postcode": "J5L0E1",
"buzz_code": "",
"latitude": "45.797655",
"longitude": "-74.049801",
"name": "",
"email": "",
"telephone": "",
"tracking_number": "SR662910478770",
"external_tracking_number": "",
"note": "",
"driver_alias": "pd1",
"stop_number": 3,
"driver_id": 343,
"leg_number": "L-1",
"status": "Already Planned",
"packages": 1,
"notification_language": "en",
"return_reason_id": null,
"orders_status_id": 5,
"current_route_id": 3964,
"allow_dropoff": 1,
"driverNotes": [],
"files": {
"photo_urls": [],
"signature_urls": []
},
"is_finished": 0,
"is_postponed": 0,
"full_address": "DEMO,Saint-Jerome,QC,J5L0E1",
"operation_mode": 0,
"is_multiple": 1,
"total_orders": 3,
"current_order": 2,
"is_recurring_pickup": false,
"can_hand_off": false
},
{
"id": 299547,
"type": "P",
"order_type": "D",
"is_p2p": 0,
"is_locked": 0,
"ref": "123",
"carrier_name": "DEMO",
"business_name": "DEMO",
"address_1": "DEMO",
"address_2": "",
"city": "Montreal",
"province": "QC",
"country": "Canada",
"postcode": "H4E3R3",
"buzz_code": "",
"latitude": "45.452116",
"longitude": "-73.591895",
"name": "X",
"email": null,
"telephone": null,
"tracking_number": "SR329762240553",
"external_tracking_number": "",
"note": "",
"driver_alias": "pd1",
"stop_number": 4,
"driver_id": 343,
"leg_number": "L-1",
"status": "Already Planned",
"packages": 1,
"notification_language": "en",
"return_reason_id": null,
"orders_status_id": 5,
"current_route_id": 3964,
"allow_dropoff": 1,
"driverNotes": [],
"files": {
"photo_urls": [],
"signature_urls": []
},
"is_finished": 0,
"is_postponed": 0,
"full_address": "DEMO,Saint-Jerome,QC,J5L0E1",
"operation_mode": 0,
"is_multiple": 1,
"total_orders": 3,
"current_order": 3,
"is_recurring_pickup": false,
"can_hand_off": false
},
{
"id": 299545,
"type": "D",
"order_type": "P2P",
"is_p2p": 1,
"is_locked": 1,
"ref": "T1",
"carrier_name": "DEMO",
"business_name": "DEMO",
"address_1": "DEMO",
"address_2": "",
"city": "Montreal",
"province": "QC",
"country": "Canada",
"postcode": "H4E3R3",
"buzz_code": "",
"latitude": "45.452116",
"longitude": "-73.591895",
"name": "x",
"email": "",
"telephone": "",
"tracking_number": "SR904766214130",
"external_tracking_number": "",
"note": "",
"driver_alias": "pd1",
"stop_number": 5,
"driver_id": 343,
"leg_number": "L-1",
"status": "Already Planned",
"packages": 1,
"notification_language": "en",
"return_reason_id": null,
"orders_status_id": 5,
"current_route_id": 3964,
"allow_dropoff": 1,
"driverNotes": [],
"files": {
"photo_urls": [],
"signature_urls": []
},
"is_finished": 0,
"is_postponed": 0,
"full_address": "DEMO,Montreal,QC,H4E3R3",
"operation_mode": 0,
"is_multiple": 1,
"total_orders": 2,
"current_order": 2,
"is_recurring_pickup": false,
"can_hand_off": false
}
]
driverRoutesOrdersRemove
mutation
REST twin: POST /app/v1/driver/routes/{routeId}/orders/{orderId}/remove
Remove order from route by orderId. A stop the driver already delivered or failed cannot be handed off: the request answers 400 with error_code `hand_off_finished_stop` and nothing is changed. Every hand-off is recorded in the order's operation history as `driver handed off order`, with the stop number it left. Driver apps only; needs driver.route_edit on the token.
mutation DriverRoutesOrdersRemove($routeId: Int, $orderId: Int) {
driverRoutesOrdersRemove(routeId: $routeId, orderId: $orderId)
}
Variables
{
"routeId": 1,
"orderId": 1
}
Response example
{
"result": true,
"message": "Successful",
"route_id": 123,
"deleted_order_id": 102223
}
driverRoutesGetStopNumber
query
REST twin: GET /app/v1/driver/routes/{routeId}/get-stop-number/{ref}
Get order stop number by ref/orderid/trackingnumber. An order on hold is not loaded: the scan answers result 0 with code `order_on_hold` and a message for the driver, and the scan is not counted. An intercepted order (status 48) is refused the same way with code `order_intercepted`. Driver apps only; needs driver.scan on the token.
query DriverRoutesGetStopNumber($routeId: Int, $ref: String) {
driverRoutesGetStopNumber(routeId: $routeId, ref: $ref)
}
Variables
{
"routeId": 1,
"ref": "example"
}
Response example
{
"result": 1,
"stopnumber": "driver001-1",
"stop_number": 1,
"order/tracking/operationevents_id": 18458,
"packages": 1,
"ref": "test-03",
"tracking_number": "SR585479158593",
"store_grid": "",
"orders_scanned": 1,
"orders_unscanned": 49,
"orders_total": 50,
"route_id": 1024
}
driverRoutesAddOrder
mutation
REST twin: POST /app/v1/driver/routes/{routeId}/add-order/{ref}
Add order to route by ref/orderid/trackingnumber. An order the same driver handed off this route goes back to the stop number it left, when that stop is still free; any other order is added after the driver's last stop. An order on hold or cancelled is refused with code `order_on_hold` or `order_cancelled`; nothing is added. Driver apps only; needs driver.route_edit on the token.
mutation DriverRoutesAddOrder($routeId: Int, $ref: String, $optimize: Int) {
driverRoutesAddOrder(routeId: $routeId, ref: $ref, optimize: $optimize)
}
Variables
{
"routeId": 1,
"ref": "example",
"optimize": 1
}
Response example
{
"message": "Order added to route successfully",
"order_id": 18458,
"ref": "test-03",
"name": "test-03"
}
driverRoutesBatchAddOrdersByDriver
mutation
REST twin: POST /app/v1/driver/routes/{routeId}/batch-add-orders-by-driver
Batch Add Orders to Route by Driver. An order the same driver handed off this route goes back to the stop number it left, when that stop is still free; any other order is added after the driver's last stop. An order on hold or cancelled is refused with code `order_on_hold` or `order_cancelled` and `refused_order_ids`; nothing is added. Driver apps only; needs driver.route_edit on the token.
mutation DriverRoutesBatchAddOrdersByDriver($routeId: Int, $order_ids: [Int]) {
driverRoutesBatchAddOrdersByDriver(routeId: $routeId, order_ids: $order_ids)
}
Variables
{
"routeId": 1,
"order_ids": [
1
]
}
driverRoutesPoints
query
REST twin: GET /app/v1/driver/routes/{routeId}/points
Get points on route for one driver. The JSON also carries `breaks` (additive): the driver breaks planned on the run, each with start and end (Y-m-d H:i:s, the route's local time), minutes and after_stop_number (null = before the first stop); an empty array when the run has none. It also carries `reload_stops` (additive, phase P3; also the reload stops sRoute 2T 1.0.20 plans itself, in the same shape): the reloads at a depot planned between the trips of the run, each with start, end, minutes, after_stop_number, trip, addressbook_id, name, latitude and longitude; an empty array when the run has none. A break taken at one of its places (sRoute 2T 1.0.21) also carries addressbook_id, name, latitude and longitude. It also carries `delivery_alternatives` (additive): the stops of the run delivered at one of the order's alternative delivery locations, each with order_id, stop_number, alternative_id, addressbook_id, name, address, latitude, longitude, time_window_start and time_window_end; an empty array when there are none. Recurring pickups (additive, after every existing key): recurring_pickup_route tells a route generated from a recurring pickup template; allow_adhoc_pickups and allow_deliveries are its two switches (true for every other route, and before the recurring pickup migrations). It also carries `shifts` (additive, after every existing key): the shifts the driver's run is split into, each with shift (1 = the first), start and end (HH:MM), start_address and end_address (addressbook_id, name, latitude, longitude; null = the run's own start or end address), planned_start and planned_end (Y-m-d H:i:s once planned; null before planning, and planned_start is null for the first shift) and starts_after_stop_number / ends_after_stop_number; an empty array when the run is one shift. Driver apps only; needs driver.routes on the token.
query DriverRoutesPoints($routeId: Int) {
driverRoutesPoints(routeId: $routeId)
}
Variables
{
"routeId": 1
}
Response example
[
{
"startPoint": {
"id": 2,
"name": "Home",
"unique_identifier": "home",
"business_id": 2,
"description": "",
"postcode": "H9S2H9",
"street_number": "40",
"street_name": "BOUL Des Sources",
"apt": "D",
"city": "Pointe-claire",
"province": "Quebec",
"country": "Canada",
"lat": "45.4484317",
"lng": "-73.7799527",
"telephone": "5144444444",
"territory": 1,
"is_point": 1,
"status": 1,
"created_at": "2020-05-10 17:21:43.00",
"updated_at": "2020-05-10 17:21:43.00",
"territory_info": {
"id": 1,
"name": "Great Montreal Area",
"center": "[24.886,-70.268]",
"coordinate_array": "",
"business_id": 0,
"status": 1
}
},
"endPoint": {
"id": 2,
"name": "Home",
"unique_identifier": "home",
"business_id": 2,
"description": "",
"postcode": "H9S2H9",
"street_number": "40",
"street_name": "BOUL Des Sources",
"apt": "D",
"city": "Pointe-claire",
"province": "Quebec",
"country": "Canada",
"lat": "45.4484317",
"lng": "-73.7799527",
"telephone": "5144444444",
"territory": 1,
"is_point": 1,
"status": 1,
"created_at": "2020-05-10 17:21:43.00",
"updated_at": "2020-05-10 17:21:43.00",
"territory_info": {
"id": 1,
"name": "Great Montreal Area",
"center": "[24.886,-70.268]",
"coordinate_array": "",
"business_id": 0,
"status": 1
}
},
"breaks": [
{
"start": "2026-09-14 12:10:00",
"end": "2026-09-14 12:40:00",
"minutes": 30,
"after_stop_number": 7
}
],
"reload_stops": [
{
"start": "2026-09-14 13:05:00",
"end": "2026-09-14 13:25:00",
"minutes": 20,
"after_stop_number": 12,
"trip": 1,
"addressbook_id": 12,
"name": "Main warehouse",
"latitude": 45.5017,
"longitude": -73.5673
}
],
"delivery_alternatives": [
{
"order_id": 1234,
"stop_number": 4,
"alternative_id": 88,
"addressbook_id": null,
"name": null,
"address": "100 Rue Sainte-Catherine, Montreal",
"latitude": 45.5075,
"longitude": -73.5605,
"time_window_start": "2026-09-14 13:00:00",
"time_window_end": "2026-09-14 15:00:00"
}
],
"recurring_pickup_route": false,
"allow_adhoc_pickups": true,
"allow_deliveries": true,
"shifts": [
{
"shift": 1,
"start": "08:00",
"end": "12:00",
"start_address": null,
"end_address": {
"addressbook_id": 12,
"name": "Main warehouse",
"latitude": 45.5017,
"longitude": -73.5673
},
"planned_start": null,
"planned_end": "2026-09-14 11:50:00",
"starts_after_stop_number": null,
"ends_after_stop_number": 7
},
{
"shift": 2,
"start": "13:00",
"end": "17:00",
"start_address": {
"addressbook_id": 12,
"name": "Main warehouse",
"latitude": 45.5017,
"longitude": -73.5673
},
"end_address": null,
"planned_start": "2026-09-14 13:00:00",
"planned_end": null,
"starts_after_stop_number": 7,
"ends_after_stop_number": null
}
]
}
]
driverOrdersStatus
query
REST twin: GET /app/v1/driver/orders-status
Get order status list Driver apps only; needs driver.delivery on the token.
query DriverOrdersStatus {
driverOrdersStatus
}
Variables
{}
Response example
{
"S": "Successful",
"NR": "Need Rescheduled",
"RL": "Reschedule Later"
}
driverRoutesValidateOrder
mutation
REST twin: POST /app/v1/driver/routes/validate-order
Validate an order ref against an order id Driver apps only; needs driver.scan on the token.
mutation DriverRoutesValidateOrder($order_id: Int, $ref: String) {
driverRoutesValidateOrder(order_id: $order_id, ref: $ref)
}
Variables
{
"order_id": 1,
"ref": "example"
}
driverAddGpsTackingRecord
mutation
REST twin: POST /app/v1/driver/addGpsTackingRecord
Add Gps Tracking Record Driver apps only; needs driver.location on the token.
mutation DriverAddGpsTackingRecord($route_id: Int, $order_id: Int, $latitude: String, $longitude: String) {
driverAddGpsTackingRecord(route_id: $route_id, order_id: $order_id, latitude: $latitude, longitude: $longitude)
}
Variables
{
"route_id": 1,
"order_id": 1,
"latitude": "example",
"longitude": "example"
}
Response example
{
"message": "Success"
}
driverOrders
query
REST twin: GET /app/v1/driver/orders/{orderId}
The authenticated driver's own orders Driver apps only; needs driver.delivery on the token.
query DriverOrders($orderId: Int, $id: Int, $status: Int, $unassigned: Int, $page: Int, $pageSize: Int) {
driverOrders(orderId: $orderId, id: $id, status: $status, unassigned: $unassigned, page: $page, pageSize: $pageSize)
}
Variables
{
"orderId": 1,
"id": 1,
"status": 1,
"unassigned": 1,
"page": 1,
"pageSize": 1
}
Response example
{
"id": 10410,
"ref": "84040085",
"address_1": "2300 ainsley crescent",
"address_2": "",
"city": "Mont-royal",
"province": "QC",
"country": "Canada",
"postcode": "H3P2S7",
"buzz_code": "",
"latitude": "45.5261811",
"longitude": "-73.6496515",
"name": "Customer 172",
"email": "172@sr.com",
"telephone": "5144309193",
"tracking_number": null,
"note": "Note 172",
"driver_alias": "driver001",
"stop_number": 1,
"status": "Successful",
"packages": 1,
"notification_language": "",
"return_reason_id": 1,
"files": {
"signature_urls": [
{
"id": 1555,
"url": "https://admin.superroute.ca/storage/2020-08-17/10410_S_In1mB3gI2ntwsKg.jpeg",
"operation_event_id": null,
"gps_tracking_id": null
}
],
"photo_urls": [
{
"id": 1528,
"url": "https://admin.superroute.ca/storage/2020-08-16/10410_P_pNQhAWt8d1Ow3NQ.jpeg",
"operation_event_id": null,
"gps_tracking_id": null
}
]
},
"driverNotes": [],
"full_address": "22 boul des sources, pointe claire, h9s2h9",
"allow_dropoff": 1,
"could_edit_items": true,
"related_order_type": 9,
"related_order_id": 501,
"related_order_items": {
"related_order_type": 9,
"related_order_id": 501,
"related_order_name": "shipping_order",
"status": 1,
"grouped_items": [
{
"packaging_id": null,
"packaging_name": null,
"description": "Box",
"length": 30,
"width": 20,
"height": 15,
"dimension_unit": 0,
"weight": 5,
"weight_unit": 0,
"value": null,
"qty": 2
}
],
"raw_items": [],
"products": [],
"summary": {
"total_item_count": 2,
"total_group_count": 1,
"total_grouped_quantity": 2,
"total_product_quantity": 0
}
}
}
driverOrderPriceDetail
query
REST twin: GET /app/v1/driver/orders/{orderId}/price-detail
Get unified price detail for a driver order Driver apps only; needs driver.delivery on the token.
query DriverOrderPriceDetail($orderId: Int) {
driverOrderPriceDetail(orderId: $orderId)
}
Variables
{
"orderId": 1
}
Response example
{
"success": true,
"order_id": 1001,
"price_type": "storage_order",
"currency": "CAD",
"price_details": [
{
"label": "Storage",
"amount": 50
},
{
"label": "Pickup",
"amount": 15
},
{
"label": "Surcharges",
"amount": 20
}
],
"surcharge_items": [
{
"name": "Stairs",
"amount": 10,
"quantity": 2,
"calculated_amount": 20
}
],
"promotion_discount": 5,
"total_price": 80,
"paid_amount": 60,
"tax": {
"total_tax": 6.5,
"tax_details": [
{
"tax_name": "GST",
"tax_rate": 5,
"tax_amount": 3.25
},
{
"tax_name": "PST",
"tax_rate": 7,
"tax_amount": 3.25
}
]
}
}
driverOrdersNotesCreate
mutation
REST twin: POST /app/v1/driver/orders-notes/create
Create a note on one of the driver's orders Driver apps only; needs driver.delivery on the token.
mutation DriverOrdersNotesCreate($orderId: Int, $notes: String) {
driverOrdersNotesCreate(orderId: $orderId, notes: $notes)
}
Variables
{
"orderId": 1,
"notes": "example"
}
Response example
{
"orderId": 12708,
"message": "Success"
}
driverOrdersNotes
query
REST twin: GET /app/v1/driver/orders-notes/{orderId}
Get order notes Driver apps only; needs driver.delivery on the token.
query DriverOrdersNotes($orderId: Int) {
driverOrdersNotes(orderId: $orderId)
}
Variables
{
"orderId": 1
}
Response example
[
{
"id": 78,
"notes": "adsafdssa",
"note_time": "2020-10-01 06:35:32",
"created_by": "9397-2958 Quebec inc."
}
]
driverOrderFiles
query
REST twin: GET /app/v1/driver/orders-files/{orderId}
Get driver order files by order ID Driver apps only; needs driver.delivery on the token.
query DriverOrderFiles($orderId: Int) {
driverOrderFiles(orderId: $orderId)
}
Variables
{
"orderId": 1
}
driverUploadPictures
mutation
REST twin: POST /app/v1/driver/upload-pictures
Upload order picture Driver apps only; needs driver.delivery on the token.
mutation DriverUploadPictures($orderId: Int, $type: String, $image: String, $imageUrl: String, $gps_tracking_id: Int, $operation_event_id: Int, $tracking_event_id: Int, $note: String) {
driverUploadPictures(orderId: $orderId, type: $type, image: $image, imageUrl: $imageUrl, gps_tracking_id: $gps_tracking_id, operation_event_id: $operation_event_id, tracking_event_id: $tracking_event_id, note: $note)
}
Variables
{
"orderId": 1,
"type": "example",
"image": "example",
"imageUrl": "example",
"gps_tracking_id": 1,
"operation_event_id": 1,
"tracking_event_id": 1,
"note": "example"
}
Response example
{
"id": 2323,
"orderId": 12708,
"url": "https://admin.superroute.ca/storage/2020-08-09/12708_P_JklHTuSn5IiQmkX.jpeg",
"gps_tracking_id": null,
"operation_event_id": 456789,
"tracking_event_id": 123456,
"message": "Success"
}
driverDeletePictures
mutation
REST twin: POST /app/v1/driver/delete-pictures
Delete order picture by orderfile Id Driver apps only; needs driver.delivery on the token.
mutation DriverDeletePictures($id: Int) {
driverDeletePictures(id: $id)
}
Variables
{
"id": 1
}
Response example
{
"message": "Success"
}
driverUpdateDeliver
mutation
REST twin: POST /app/v1/driver/update-deliver
Update delivery status for an order. An order that is intercepted (48), on hold (14) or cancelled (12) is refused for every status code with `code` order_intercepted / order_on_hold / order_cancelled, unless `captured_at` is earlier than the moment it was stopped (an offline delivery uploaded later). Driver apps only; needs driver.delivery on the token.
mutation DriverUpdateDeliver($route_id: Int, $order_id: Int, $latitude: Float, $longitude: Float, $status_code: String, $photo_url1: String, $photo_url2: String, $photo_urls: [String], $signature_url: String, $signature_note: String, $return_reason_id: Int, $package_ids: [Int], $delivery_pin: String, $captured_at: String) {
driverUpdateDeliver(route_id: $route_id, order_id: $order_id, latitude: $latitude, longitude: $longitude, status_code: $status_code, photo_url1: $photo_url1, photo_url2: $photo_url2, photo_urls: $photo_urls, signature_url: $signature_url, signature_note: $signature_note, return_reason_id: $return_reason_id, package_ids: $package_ids, delivery_pin: $delivery_pin, captured_at: $captured_at)
}
Variables
{
"route_id": 1,
"order_id": 1,
"latitude": 1.5,
"longitude": 1.5,
"status_code": "example",
"photo_url1": "example",
"photo_url2": "example",
"photo_urls": [
"example"
],
"signature_url": "example",
"signature_note": "example",
"return_reason_id": 1,
"package_ids": [
1
],
"delivery_pin": "example",
"captured_at": "2026-01-01"
}
driverBatchUpdateDeliver
mutation
REST twin: POST /app/v1/driver/batch-update-deliver
Batch update delivery status for multiple orders. Such orders (intercepted, on hold, cancelled) are listed in failed_orders with a `code` (order_intercepted / order_on_hold / order_cancelled); the REST batch endpoint refuses the whole batch with 409 instead. Driver apps only; needs driver.delivery on the token.
mutation DriverBatchUpdateDeliver($route_id: Int, $order_ids: [Int], $latitude: Float, $longitude: Float, $status_code: String, $photo_urls: [String], $signature_url: String, $signature_note: String, $return_reason_id: Int, $captured_at: String) {
driverBatchUpdateDeliver(route_id: $route_id, order_ids: $order_ids, latitude: $latitude, longitude: $longitude, status_code: $status_code, photo_urls: $photo_urls, signature_url: $signature_url, signature_note: $signature_note, return_reason_id: $return_reason_id, captured_at: $captured_at)
}
Variables
{
"route_id": 1,
"order_ids": [
1
],
"latitude": 1.5,
"longitude": 1.5,
"status_code": "example",
"photo_urls": [
"example"
],
"signature_url": "example",
"signature_note": "example",
"return_reason_id": 1,
"captured_at": "2026-01-01"
}
driverSmsTemplates
query
REST twin: GET /app/v1/driver/sms/templates
Get driver SMS templates. Lists the business's templates marked Viewable to Driver, regardless of Active — Active only controls automatic sending and is not a driver-app visibility switch. Optional order_id narrows the list to templates whose order-type restriction includes that order's type (templates with no restriction always match); an order outside the driver's business is Not Found. Driver apps only; needs driver.sms on the token.
query DriverSmsTemplates($type: String, $order_id: Int) {
driverSmsTemplates(type: $type, order_id: $order_id)
}
Variables
{
"type": "example",
"order_id": 1
}
driverSmsSuccessContent
query
REST twin: GET /app/v1/driver/sms/success-content/order/{id}
Get SMS success content by order ID. Resolved from the business's Delivered Success template whether or not it is Active (Active only controls automatic sending); falls back to the built-in default wording when the business has none. Recipient privacy (client setting, off by default): for an order whose route the driver has ended, or whose route date is past the client's chosen number of days, the answer is the existing empty one (content '' with 404). A driver gets the text only for an order they are delivering or created; any other order answers 404 with the not-found message. Driver apps only; needs driver.sms on the token.
query DriverSmsSuccessContent($id: Int) {
driverSmsSuccessContent(id: $id)
}
Variables
{
"id": 1
}
driverDuty
query
REST twin: GET /app/v1/driver/duty
On-duty state, feature flags and pending auto-assign offers for the calling driver (Dispatch Options → driver_duty). Driver apps only; needs driver.duty on the token.
query DriverDuty {
driverDuty
}
Variables
{}
Response example
{
"result": true,
"feature_enabled": true,
"on_duty": true,
"on_duty_changed_at": "2026-08-17 08:00:00",
"require_accept": true,
"response_timeout_minutes": 5,
"pending_offers": [
{
"offer_id": 31,
"subject_type": "order",
"subject_id": 555,
"order_id": 555,
"order_ref": "SR100000555",
"order_type": "D",
"address": "12 Main St, Montreal",
"offered_at": "2026-08-17 10:00:00",
"expires_at": "2026-08-17 10:05:00"
}
]
}
driverDutyUpdate
mutation
REST twin: POST /app/v1/driver/duty
Sets the calling driver on or off duty. Fails when the business did not enable driver duty. Driver apps only; needs driver.duty on the token.
mutation DriverDutyUpdate($on_duty: Boolean!) {
driverDutyUpdate(on_duty: $on_duty)
}
Variables
{
"on_duty": false
}
Response example
{
"result": true,
"feature_enabled": true,
"on_duty": true,
"on_duty_changed_at": "2026-08-17 08:00:00",
"require_accept": true,
"response_timeout_minutes": 5,
"pending_offers": [
{
"offer_id": 31,
"subject_type": "order",
"subject_id": 555,
"order_id": 555,
"order_ref": "SR100000555",
"order_type": "D",
"address": "12 Main St, Montreal",
"offered_at": "2026-08-17 10:00:00",
"expires_at": "2026-08-17 10:05:00"
}
]
}
driverAssignmentRespond
mutation
REST twin: POST /app/v1/driver/orders/{orderId}/accept
Accept (action=accepted) or reject (action=rejected, optional reason) an order assigned to the calling driver. Rejected auto-assigned work is released and re-queued or held per the business setting. Driver apps only; needs driver.duty on the token.
mutation DriverAssignmentRespond($order_id: Int!, $action: String!, $reason: String) {
driverAssignmentRespond(order_id: $order_id, action: $action, reason: $reason)
}
Variables
{
"order_id": 555,
"action": "rejected",
"reason": "Too far away"
}
Response example
{
"result": true,
"action": "rejected",
"released": true
}
driverEarnings
query
REST twin: GET /app/v1/driver/earnings
Delivered orders, tips and a payroll-rule estimate for the calling driver (opt-in family delivery_tips). Driver apps only; needs driver.earnings on the token.
query DriverEarnings($period: String, $from: String, $to: String) {
driverEarnings(period: $period, from: $from, to: $to)
}
Variables
{
"period": "today",
"from": null,
"to": null
}
Response example
{
"result": true,
"feature_enabled": true,
"period": "today",
"from": "2026-08-17 00:00:00",
"to": "2026-08-17 23:59:59",
"delivered_orders": 12,
"tips_total": 34.5,
"tips_pass_through_pct": 100,
"tips_driver_share": 34.5,
"estimated_pay": 118.5,
"estimated_pay_items": [
{
"type": "per_delivery",
"name": "Per Delivery",
"rate": 7,
"quantity": 12,
"subtotal": 84
},
{
"type": "tip_pass_through",
"name": "Tips pass-through (%)",
"rate": 100,
"quantity": 34.5,
"subtotal": 34.5
}
],
"currency": "CAD",
"has_payroll_rule": true
}
driverOrderCashCollected
mutation
REST twin: POST /app/v1/driver/orders/{orderId}/cash-collected
Cash-collected settlement (opt-in family payroll_v2 with cash_settlement). Payroll nets the cash out of the pay (payment_after_cash). Driver apps only; needs driver.cash on the token.
mutation DriverOrderCashCollected($order_id: Int!, $amount: Float) {
driverOrderCashCollected(order_id: $order_id, amount: $amount)
}
Variables
{
"order_id": 555,
"amount": 42.5
}
Response example
{
"result": true,
"order_id": 555,
"cash_collected": 42.5
}
driverPodRequirements
query
REST twin: GET /app/v1/driver/pod-requirements
Pickup photo and geofence requirements for the calling driver's business (opt-in family pod_extras). age_verification_required, item_check_required and geofence_allow_override are always false (not offered); the keys stay for compatibility. Driver apps only; needs driver.delivery on the token.
query DriverPodRequirements {
driverPodRequirements
}
Variables
{}
Response example
{
"result": true,
"enabled": true,
"age_verification_required": false,
"pickup_photo_required": true,
"item_check_required": false,
"geofence_m": 150,
"geofence_allow_override": false
}
driverSmsFailContent
query
REST twin: GET /app/v1/driver/sms/fail-content/order/{id}
Get SMS fail content by order ID. Resolved from the business's Delivered Fail template whether or not it is Active (Active only controls automatic sending); falls back to the built-in default wording when the business has none. Recipient privacy (client setting, off by default): for an order whose route the driver has ended, or whose route date is past the client's chosen number of days, the answer is the existing empty one (content '' with 404). A driver gets the text only for an order they are delivering or created; any other order answers 404 with the not-found message. Driver apps only; needs driver.sms on the token.
query DriverSmsFailContent($id: Int) {
driverSmsFailContent(id: $id)
}
Variables
{
"id": 1
}
driverSmsTemplateContent
query
REST twin: GET /app/v1/driver/sms/template/{templateId}/order/{id}
Get SMS content by template ID and order ID. Recipient privacy (client setting, off by default): for an order whose route the driver has ended, or whose route date is past the client's chosen number of days, the answer is content '' with 404. A driver gets the text only for an order they are delivering or created; any other order answers 404 with the not-found message. Driver apps only; needs driver.sms on the token.
query DriverSmsTemplateContent($templateId: Int, $id: Int) {
driverSmsTemplateContent(templateId: $templateId, id: $id)
}
Variables
{
"templateId": 1,
"id": 1
}
driverProfile
query
REST twin: GET /app/v1/driver/profile
The authenticated driver profile Driver apps only; needs driver.routes on the token.
query DriverProfile {
driverProfile {
id
business_id
users_info_id
name
email
language
alias
telephone
address
address_2
city
province
country
postcode
business_name
vehicle_type
fuel_type
capacity
territory_id
start_location_id
end_location_id
gps_tracking
gps_device_type
gps_identifier
status
skills
}
}
Variables
{}
Response example
[
{
"data": {
"id": 1,
"name": "driver003_1",
"email": "driver003@example.com",
"vehicle_type": "1 (1: Car, 2: Truck, 3: Motorcycle, 4: Bicycle 5:Pedestrian)",
"address": "",
"address_2": "",
"city": "",
"province": "",
"country": "Canada",
"postcode": "",
"telephone": "5140000003",
"tracking_number_prefix": null,
"alias": "alias0003_1",
"users_info_id": 3,
"business_id": 2,
"capacity": null,
"business_name": "Client Example",
"language": "en",
"territory_id": 1,
"start_location_id": null,
"end_location_id": null,
"gps_tracking": 0,
"gps_device_type": "1",
"gps_identifier": "",
"status": 1
}
}
]
driverCreateRoute
mutation
REST twin: POST /app/v1/driver/create-route
Create a new route by driver Driver apps only; needs driver.route_edit on the token.
mutation DriverCreateRoute($name: String, $service_time: Int, $pickup_time: Int) {
driverCreateRoute(name: $name, service_time: $service_time, pickup_time: $pickup_time)
}
Variables
{
"name": "example",
"service_time": 1,
"pickup_time": 1
}
driverOptimizeRoute
mutation
REST twin: POST /app/v1/driver/optimize-route
optimize route Driver apps only; needs driver.route_edit on the token.
mutation DriverOptimizeRoute($async: Boolean, $routeId: Int, $driverAddresses: Json) {
driverOptimizeRoute(async: $async, routeId: $routeId, driverAddresses: $driverAddresses)
}
Variables
{
"async": true,
"routeId": 1,
"driverAddresses": {
"key": "value"
}
}
Response example
{
"result": true,
"message": "Optimize job is running in background"
}
driverKioskReceivingPackages
query
REST twin: GET /app/v1/driver/kiosk-receiving-packages
Get kiosk receiving area package count for a kiosk pickup order on a route Driver apps only; needs driver.kiosk on the token.
query DriverKioskReceivingPackages($route_id: Int!, $order_id: Int!) {
driverKioskReceivingPackages(route_id: $route_id, order_id: $order_id)
}
Variables
{
"route_id": 1,
"order_id": 1
}
Response example
{
"result": true,
"location_id": 42,
"location_name": "Kiosk Downtown",
"receiving_package_count": 15
}
driverKioskReceivePackage
mutation
REST twin: POST /app/v1/driver/kiosk-receive-package
Scan a code at a kiosk location and receive an awaiting-processing package (return, express drop-off or storage drop-off) from the receiving area onto driver vehicle inventory Driver apps only; needs driver.kiosk on the token.
mutation DriverKioskReceivePackage($route_id: Int!, $order_id: Int!, $scan_code: String!) {
driverKioskReceivePackage(route_id: $route_id, order_id: $order_id, scan_code: $scan_code)
}
Variables
{
"route_id": 1,
"order_id": 1,
"scan_code": "example"
}
Response example
{
"result": true,
"message": "Package received successfully",
"inventory_id": 50,
"inventory_type": 4,
"return_request": {
"id": 10,
"return_code": "ABC123",
"reference_number": "REF-001",
"status": 5,
"original_order_id": 200
}
}
driverAvailability
query
REST twin: GET /app/v1/driver/availability
The signed-in driver's availability record — every driver, not only Instant riders. `online` is the switch; `heartbeat_stale` is true when the app has not checked in for 20 minutes; `active_orders` counts in-flight instant orders. Driver apps only; needs driver.availability on the token.
query DriverAvailability {
driverAvailability
}
Variables
{}
Response example
{
"result": true,
"message": "Online",
"data": {
"driver_id": 12,
"online": true,
"task_online": true,
"accepting_offers": true,
"max_concurrent": null,
"vehicle_type": "bike",
"last_heartbeat_at": "2026-08-26T14:03:11+00:00",
"heartbeat_stale": false,
"position": {
"lat": 45.5019,
"lng": -73.5674,
"source": "app",
"recorded_at": "2026-08-26T14:03:11+00:00"
},
"active_orders": 1,
"online_changed_at": "2026-08-26T13:00:00+00:00"
}
}
driverAvailabilitySet
mutation
REST twin: POST /app/v1/driver/availability
Send `online` to flip the switch (also written to drivers.task_online for legacy readers). Without `online` the call is a heartbeat that refreshes `last_heartbeat_at` and applies any preference sent. `lat`/`lng` must be sent together. Driver apps only; needs driver.availability on the token.
mutation DriverAvailabilitySet($online: Boolean, $accepting_offers: Boolean, $max_concurrent: Int, $vehicle_type: String, $lat: Float, $lng: Float) {
driverAvailabilitySet(online: $online, accepting_offers: $accepting_offers, max_concurrent: $max_concurrent, vehicle_type: $vehicle_type, lat: $lat, lng: $lng)
}
Variables
{
"online": true,
"accepting_offers": true,
"max_concurrent": 3,
"vehicle_type": "bike",
"lat": 45.5019,
"lng": -73.5674
}
Response example
{
"result": true,
"message": "Online",
"data": {
"driver_id": 12,
"online": true,
"task_online": true,
"accepting_offers": true,
"max_concurrent": null,
"vehicle_type": "bike",
"last_heartbeat_at": "2026-08-26T14:03:11+00:00",
"heartbeat_stale": false,
"position": {
"lat": 45.5019,
"lng": -73.5674,
"source": "app",
"recorded_at": "2026-08-26T14:03:11+00:00"
},
"active_orders": 1,
"online_changed_at": "2026-08-26T13:00:00+00:00"
}
}
driverPoolOrders
query
REST twin: GET /app/v1/driver/pool/orders
Orders a dispatcher published to the driver pool that the signed-in driver may claim (M3). Driver apps only; needs driver.pool on the token.
query DriverPoolOrders {
driverPoolOrders
}
Variables
{}
Response example
{
"result": true,
"message": "Successful",
"data": [
{
"order_id": 7001,
"ref": "ORD-7001",
"type": "D",
"status_id": 2,
"name": "Ann Lee",
"address": "2 Elm St, Toronto M5V 1A1",
"latitude": 43.6452,
"longitude": -79.3806,
"packages": 1,
"time_window_start": null,
"time_window_end": null,
"distance_km": 2.4,
"published_at": "2026-08-26T14:00:00+00:00"
}
],
"meta": {
"total": 1,
"enabled": true
}
}
driverPoolClaim
mutation
REST twin: POST /app/v1/driver/pool/orders/{orderId}/claim
Atomic claim + append as the next stop of the driver's route for today (M3). Driver apps only; needs driver.pool on the token.
mutation DriverPoolClaim($order_id: Int!, $route_id: Int) {
driverPoolClaim(order_id: $order_id, route_id: $route_id)
}
Variables
{
"order_id": 7001,
"route_id": null
}
Response example
{
"result": true,
"message": "Claimed",
"data": {
"result": "ok",
"order_id": 7001,
"route_id": 23565,
"stop_number": 4
}
}
driverOrderChat
query
REST twin: GET /app/v1/driver/orders/{orderId}/chat
Chat thread of one of the calling driver's assigned orders: only messages delivered to the driver (opt-in family order_chat with driver_chat). Orders not assigned to the caller answer 404. Driver apps only; needs driver.delivery on the token.
query DriverOrderChat($order_id: Int!, $since_id: Int) {
driverOrderChat(order_id: $order_id, since_id: $since_id)
}
Variables
{
"order_id": 5551,
"since_id": null
}
Response example
{
"result": true,
"feature_enabled": true,
"conversation": {
"id": 12,
"order_id": 5551,
"status": "open",
"locked": false,
"last_message_at": "2026-09-01 14:05:00",
"unread": {
"dispatcher": 0,
"driver": 1,
"customer": 0
},
"can_send": true,
"driver_chat": true,
"customer_chat": false,
"driver_to_customer": false,
"allow_attachments": true,
"quick_replies": [
"On my way",
"Running late"
]
},
"order": {
"id": 5551,
"ref": "ORD-5551",
"tracking_number": "SR123456",
"status_id": 16,
"driver_id": 11,
"driver_alias": "Alex"
},
"messages": [
{
"id": 102,
"conversation_id": 12,
"sender_type": "dispatcher",
"sender_user_id": 700,
"sender_name": "Ops Team",
"body": "Please call the customer before arriving",
"attachment_url": null,
"delivered_to": [
"driver"
],
"created_at": "2026-09-01 14:05:00",
"read_at_dispatcher": "2026-09-01 14:05:00",
"read_at_driver": null,
"read_at_customer": null
}
],
"poll_interval": 15
}
driverOrderChatSend
mutation
REST twin: POST /app/v1/driver/orders/{orderId}/chat
Posts a driver message into the thread of one of the caller's assigned orders (opt-in family order_chat with driver_chat). Reaches the dispatcher, and the customer too when driver_to_customer is on. `mark_read` also clears the driver unread counter (REST: POST /v1/driver/orders/{orderId}/chat/read). Driver apps only; needs driver.delivery on the token.
mutation DriverOrderChatSend($order_id: Int!, $body: String, $attachment_base64: String, $mark_read: Boolean) {
driverOrderChatSend(order_id: $order_id, body: $body, attachment_base64: $attachment_base64, mark_read: $mark_read)
}
Variables
{
"order_id": 5551,
"body": "On my way",
"attachment_base64": null,
"mark_read": true
}
Response example
{
"result": true,
"feature_enabled": true,
"message": {
"id": 103,
"conversation_id": 12,
"sender_type": "driver",
"sender_user_id": 901,
"sender_name": "Alex",
"body": "On my way",
"attachment_url": null,
"delivered_to": [
"dispatcher"
],
"created_at": "2026-09-01 14:06:00",
"read_at_dispatcher": null,
"read_at_driver": "2026-09-01 14:06:00",
"read_at_customer": null
},
"conversation": {
"id": 12,
"order_id": 5551,
"status": "open",
"locked": false,
"last_message_at": "2026-09-01 14:06:00",
"unread": {
"dispatcher": 1,
"driver": 0,
"customer": 0
}
}
}
driverOrderChatUnread
query
REST twin: GET /app/v1/driver/chat/unread
Per-order unread chat message counters for the calling driver's assigned orders (opt-in family order_chat with driver_chat). Answers {result:true, feature_enabled:false, items:[]} while off. Driver apps only; needs driver.delivery on the token.
query DriverOrderChatUnread {
driverOrderChatUnread
}
Variables
{}
Response example
{
"result": true,
"feature_enabled": true,
"items": [
{
"order_id": 5551,
"order_ref": "ORD-5551",
"unread": 1,
"last_message_at": "2026-09-01 14:05:00"
}
],
"unread_total": 1
}
driverRoutesOrdersBatchremove
mutation
REST twin: POST /app/v1/driver/routes/{routeId}/orders/batchremove
Batch remove orders from a route. A stop the driver already delivered or failed cannot be handed off: the request answers 400 with error_code `hand_off_finished_stop` and `refused_order_ids`; no order of the batch is handed off and nothing is changed. Every hand-off is recorded in the order's operation history as `driver handed off order`, with the stop number it left. Driver apps only; needs driver.route_edit on the token.
mutation DriverRoutesOrdersBatchremove($routeId: Int, $order_ids: [Int]) {
driverRoutesOrdersBatchremove(routeId: $routeId, order_ids: $order_ids)
}
Variables
{
"routeId": 1,
"order_ids": [
1
]
}
partnerAppDriverRegister
mutation
REST twin: POST /app/v1/clients/{clientCode}/driver/register
Sign up a new driver at the client named by client_code, with the restricted app key (X-App-Key); same rules and answer as the REST route.
mutation ($client_code: String!, $name: String, $alias: String, $email: String, $password: String, $telephone: String, $language: String) {
partnerAppDriverRegister(client_code: $client_code, name: $name, alias: $alias, email: $email, password: $password, telephone: $telephone, language: $language)
}
Variables
{
"client_code": "k3m9q2",
"name": "Dee Driver",
"alias": "dee",
"email": "dee@example.com",
"password": "a-strong-password",
"telephone": "+1 416 555 0100",
"language": "en"
}
Response example
{
"result": true,
"id": 790,
"business_id": "1704",
"Email": "join34@gmail.com",
"name": "John",
"status": 0,
"language": "en",
"alias": "driver01",
"telephone": "5555555555",
"capacity": null,
"start_location_id": null,
"end_location_id": null,
"territory_id": null,
"skills": []
}
returnreasons
query
REST twin: GET /app/v1/driver/return-reasons/{order_id?}
The reasons a driver may give when a stop fails (return_reason_id of update-deliver), in the driver's language and the business's order; with order_id only those valid for that order's direction. Driver apps only; needs driver.delivery on the token.
query DriverReturnReasons($order_id: Int) {
returnreasons(order_id: $order_id) {
id
reason
for_delivery
for_pickup
}
}
Variables
{
"order_id": 299545
}
Response example
[
{
"id": 3,
"reason": "Recipient not home",
"business_id": 2,
"status": 1,
"sort_number": 10,
"receive_status": 0,
"visible_to_public": 1,
"for_delivery": 1,
"for_pickup": 0,
"created_at": "2026-01-10 09:00:00"
}
]