The GraphQL operations of SI Delivery Routing. They use the same address, keys and rules as all our apps: see GraphQL API reference: Rentals & Bookings for how to send a request, and the API overview for keys, limits and errors. The same operations are in REST: REST API reference: Delivery Routing, which also describes the webhook deliveries.
Needs the SI Delivery Routing app. It works on a store without Rentals & Bookings. On a store without Delivery Routing, its operations are refused before anything runs, with errors[].extensions.code: "app_not_installed" and data.app: "routes". Every operation needs a secret key; mutations need Allow changes (write). Make the key in Delivery Routing under Settings › API; a key made in any of our apps works too.
- Ids are global:
gid://sirentals/RoutesStop/42,RoutesRoute,RoutesVehicle,RoutesDriver,RoutesWebhook. Everyidargument also takes the bare number, andlegacyResourceIdgives it. - Days are
YYYY-MM-DDin the store’s time zone and timesHH:MM.locationtakes a Shopify location id or gid; without it, the store’s first depot is used. - Planning a day and accepting a plan need the Regular plan, the report needs Pro: below it the answer is a
plan_requirederror withdata.featureanddata.required_plan. - Mutations answer
userErrors { field message code data }instead of an error when the change is refused:invalid,not_movable,not_deletable,vehicle_limit,vehicle_in_use,not_ready,version_conflict,url_not_allowed,too_many_webhooksand so on. The REST errors table explains each.
curl -s "https://YOUR-API-ADDRESS/graphql/2026-10" -H "Authorization: Bearer sirb_your_secret_key" -H "Content-Type: application/json" \
-d '{"query": "{ routesStops(filter: {date: \"2026-11-20\"}, first: 50) { totalCount nodes { id status orderName addressLine } } }"}'
Stops #
A stop is one delivery or collection at one address on one day, made from an order, a rental booking or added by hand. Statuses: NEEDS_DAY (no delivery day yet), UNPLANNED (a day, no saved route), PLANNED (on a saved route), SENT, ON_THE_WAY, ARRIVED, DONE, FAILED and CANCELLED.
| Operation | Arguments | What it does |
|---|---|---|
routesStopsquery · secret key returns RoutesStopConnection! | filter: RoutesStopFilterInputfirst: Intafter: Stringlast: Intbefore: String | Stops by day, status, depot, source or a search on the order name or postcode. Stops without a day come first, then by day and slot. Cancelled stops only with status CANCELLED. |
routesStopquery · secret key returns RoutesStop | id: ID! | One stop with its history (events), or null. |
routesStopCreatemutation · secret key returns RoutesStopPayload! | input: RoutesStopInput! | Add a stop by hand (source MANUAL). name and address.address1 are required. |
routesStopUpdatemutation · secret key returns RoutesStopPayload! | id: ID!input: RoutesStopInput! | Change a stop’s day and slot, instructions, note or map pin; for a stop added by hand, also its name, contact, address and load. Only the fields you send change. |
routesStopDeletemutation · secret key returns RoutesStopDeletePayload! | id: ID! | Delete a stop added by hand: it becomes CANCELLED and keeps its history. |
query DayStops {
routesStops(filter: {date: "2026-11-20", status: [SENT, DONE, FAILED]}, first: 50) {
totalCount
pageInfo { hasNextPage endCursor }
nodes {
id legacyResourceId status kind source date from to orderName addressLine
route { id name status }
vehicle { key name }
eta { eta from to }
}
}
}
mutation AddStop {
routesStopCreate(input: {name: "Jordan Lee", date: "2026-11-20", from: "08:00", to: "12:00",
address: {address1: "18 Elm Street", city: "Columbus", zip: "43215", countryCode: "US"}}) {
stop { id status date }
userErrors { field code message } # without a name: field ["input", "name"], code "invalid", "Who is the stop for?"
}
}
RoutesStopFilterInput
| Field | Type | Notes |
|---|---|---|
date | String | YYYY-MM-DD |
status | [RoutesStopStatus!] | One or several; CANCELLED stops come only when you ask for them. |
location | ID | A Shopify location: gid://shopify/Location/801 or 801. |
source | RoutesStopSource | |
q | String | An order name or a postcode. |
needsDay | Boolean | true: only stops that still need a day. |
unrouted | Boolean | true: only stops that aren’t on a route. |
RoutesStopInput
| Field | Type | Notes |
|---|---|---|
name | String | Who the stop is for. Required when you add a stop. |
address | RoutesAddressInput | address1 is required when you add a stop. |
date | String | YYYY-MM-DD; null puts the stop back to Needs a day. |
from | String | HH:MM, with to. |
to | String | |
kind | RoutesStopKind | DELIVERY (default) or COLLECTION. |
locationId | ID | The depot it leaves from (a Shopify location id or gid). |
phone | String | |
email | String | |
instructions | String | For the driver (gate code, floor). |
note | String | For your team. |
units | Int | The load: units, kilograms and cubic metres. |
kg | Float | |
m3 | Float | |
serviceMin | Int | Minutes spent at the stop. |
lat | Float | A map pin, when the address alone isn’t enough (with lng). |
lng | Float |
RoutesAddressInput
| Field | Type | Notes |
|---|---|---|
company | String | |
address1 | String | |
address2 | String | |
city | String | |
province | String | |
zip | String | |
country | String | |
countryCode | String |
| Enum | Values |
|---|---|
RoutesStopStatus | NEEDS_DAYUNPLANNEDPLANNEDSENTON_THE_WAYARRIVEDDONEFAILEDCANCELLED |
RoutesStopSource | ORDERRENTALMANUAL |
RoutesStopKind | DELIVERYCOLLECTION |
| Type | Fields |
|---|---|
RoutesStop | id: ID!, legacyResourceId: Int!, source: RoutesStopSource!, kind: RoutesStopKind!, status: RoutesStopStatus!, date: String, from: String, to: String, daySource: String, orderId: ID, orderGid: String, orderName: String, orders: [RoutesStopOrder!]!, locationGid: String, areaId: Int, name: String, address: RoutesAddress!, addressLine: String!, phone: String, email: String, instructions: String, note: String, zip: String, lat: Float, lng: Float, geo: String, needsPin: Boolean!, units: Int!, kg: Float!, m3: Float!, serviceMin: Int!, addedByHand: Boolean!, items: [RoutesStopItem!]!, route: RoutesStopRoute, vehicle: RoutesVehicleRef, eta: RoutesEta, pod: RoutesPod, events: [RoutesStopEvent!]! |
RoutesStopOrder | id: ID, gid: String, name: String |
RoutesAddress | company: String, address1: String, address2: String, city: String, province: String, zip: String, country: String, countryCode: String |
RoutesStopItem | order: String, line: String, name: String, qty: Int, kg: Float |
RoutesStopRoute | id: Int!, name: String!, status: RoutesRouteStatus! |
RoutesVehicleRef | key: String!, name: String!, colour: String! |
RoutesEta | eta: String, from: String, to: String |
RoutesPod | signedBy: String, note: String, photos: [RoutesPodPhoto!]!, signatureUrl: String, at: String, lat: Float, lng: Float |
RoutesPodPhoto | url: String!, takenAt: String |
RoutesStopEvent | at: String!, actor: String!, action: String!, data: JSON |
RoutesStopPayload | stop: RoutesStop, userErrors: [RentalUserError!]! |
RoutesStopDeletePayload | deletedId: ID, stop: RoutesStop, userErrors: [RentalUserError!]! |
| Enum | Values |
|---|---|
RoutesRouteStatus | DRAFTSENTSTARTEDENDED |
Routes and the day’s plan #
A route is one vehicle’s run on one day. Plan the day with routesDayPlan, save the plan with routesPlanAccept, then send it with routesSend: the same steps as Plan the day and Send to drivers in the app.
| Operation | Arguments | What it does |
|---|---|---|
routesRoutesquery · secret key returns [RoutesRoute!]! | date: Stringlocation: Stringstatus: [RoutesRouteStatus!] | A day’s routes (default: today) with their stops in order. |
routesRoutequery · secret key returns RoutesRoute | id: ID! | One route with its stops, or null. |
routesDayPlanmutation · secret key returns RoutesDayPlanPayload! | date: String!location: Stringlocks: JSON | Plan a day: the planner shares the stops among the vehicles. Nothing changes until you accept the plan. locks fixes stops: {"<stop id>": {"vehicle": "v1", "position": 1, "time": "09:00"}}. Regular plan and up. |
routesPlanAcceptmutation · secret key returns RoutesPlanAcceptPayload! | id: ID!version: String | Save a plan as the day’s routes. Refused with version_conflict when the day changed since it was planned. Regular plan and up. |
routesSendmutation · secret key returns RoutesSendPayload! | date: String!location: String | Send the day’s saved routes to the drivers. not_ready when there is nothing to send. |
routesReportquery · secret key returns RoutesReport! | from: Stringto: Stringlocation: String | The route report per day and vehicle for up to 62 days (default: the last 7). Pro plan and up. |
query Day {
routesRoutes(date: "2026-11-20") {
id name status km driveMin
vehicle { key name }
driver { id name }
stops { stopId position status eta from to }
}
}
mutation PlanAndAccept {
routesDayPlan(date: "2026-11-20") {
plan { id version totals { vehicles placed km driveMin } unassigned { stopId reason message } }
userErrors { code message } # not_ready: "There are no stops to plan on this day."
}
}
# then: routesPlanAccept(id: "<plan id>") { routes { id name } userErrors { code message } }
# and: routesSend(date: "2026-11-20") { sent userErrors { code message } }
| Type | Fields |
|---|---|
RoutesRoute | id: ID!, legacyResourceId: Int!, date: String!, locationGid: String!, name: String!, status: RoutesRouteStatus!, version: Int!, position: Int!, vehicle: RoutesVehicleRef, driver: RoutesDriverRef, km: Float, driveMin: Int, sentAt: String, stops: [RoutesRouteStop!]! |
RoutesDriverRef | id: Int!, name: String! |
RoutesRouteStop | stopId: Int!, position: Int!, status: RoutesStopStatus!, eta: String, from: String, to: String |
RoutesDayPlanPayload | plan: RoutesPlan, userErrors: [RentalUserError!]! |
RoutesPlanA proposal for a day. version is the day’s version when it was planned. | id: ID!, legacyResourceId: Int!, date: String!, locationGid: String!, version: String!, travel: String!, plannerNote: String, routes: [RoutesPlanRoute!]!, unassigned: [RoutesPlanUnassigned!]!, totals: RoutesPlanTotals! |
RoutesPlanRoute | vehicleKey: String!, km: Float!, driveMin: Int!, stops: [RoutesPlanStop!]! |
RoutesPlanStop | stopId: Int!, eta: String, from: String, to: String |
RoutesPlanUnassigned | stopId: Int!, reason: String!, message: String! |
RoutesPlanTotals | vehicles: Int!, placed: Int!, km: Float!, driveMin: Int! |
RoutesPlanAcceptPayload | ok: Boolean, planId: ID, date: String, locationGid: String, version: String, routes: [RoutesRoute!], userErrors: [RentalUserError!]! |
RoutesSendPayload | ok: Boolean, date: String, locationGid: String, sent: Int, version: String, routes: [RoutesRoute!], userErrors: [RentalUserError!]! |
RoutesReport | from: String!, to: String!, days: Int!, rows: [RoutesReportRow!]!, total: RoutesReportRow! |
RoutesReportRow | date: String!, vehicle: String!, stops: Int!, done: Int!, failed: Int!, km: Float!, drivingMin: Int!, onRoadMin: Int!, stopsPerHour: Float!, onTimePct: Int |
Vehicles #
| Operation | Arguments | What it does |
|---|---|---|
routesVehiclesquery · secret key returns [RoutesVehicle!]! | activeOnly: Boolean = false | The vehicles, in the order you keep them. activeOnly: true leaves out the inactive ones. |
routesVehiclequery · secret key returns RoutesVehicle | id: ID! | One vehicle, or null. |
routesVehicleCreatemutation · secret key returns RoutesVehiclePayload! | input: RoutesVehicleInput! | Add a vehicle. Refused with vehicle_limit at your plan’s number of vehicles. |
routesVehicleUpdatemutation · secret key returns RoutesVehiclePayload! | id: ID!input: RoutesVehicleInput! | Change a vehicle; fields you don’t send keep their values. |
routesVehicleDeletemutation · secret key returns RoutesDeletePayload! | id: ID! | Delete a vehicle. Refused with vehicle_in_use while it is on a route that isn’t finished. |
mutation AddVehicle {
routesVehicleCreate(input: {name: "Van 3", colour: "#7c3aed", units: 8, kg: 900, shiftFrom: "08:00", shiftTo: "17:00"}) {
vehicle { id key name }
userErrors { field code message } # vehicle_limit at your plan's number of vehicles
}
}
RoutesVehicleInput
| Field | Type | Notes |
|---|---|---|
name | String | Required when you add a vehicle. |
colour | String | A hex colour for the map, #2563eb. |
locationId | ID | The depot it starts from. |
units | Int | What it can carry: units, kilograms, cubic metres. Leave one out for no limit. |
kg | Float | |
m3 | Float | |
features | [String!] | Tags the planner matches with stops. |
shiftFrom | String | Working hours, HH:MM (with shiftTo). |
shiftTo | String | |
active | Boolean | false keeps it out of planning. |
| Type | Fields |
|---|---|
RoutesVehicle | id: ID!, legacyResourceId: Int!, key: String!, name: String!, colour: String, locationGid: String, units: Int, kg: Float, m3: Float, features: [String!]!, shiftFrom: String, shiftTo: String, active: Boolean! |
RoutesVehiclePayload | vehicle: RoutesVehicle, userErrors: [RentalUserError!]! |
RoutesDeletePayload | deletedId: ID, userErrors: [RentalUserError!]! |
Drivers, areas, depots, settings and slots #
Read only: these are changed in the app.
| Operation | Arguments | What it does |
|---|---|---|
routesDriversquery · secret key returns [RoutesDriver!]! | – | The drivers: name, vehicle and whether they are active. Never an email, a phone number or a sign-in link. |
routesAreasquery · secret key returns [RoutesArea!]! | – | The delivery areas (read only). |
routesLocationsquery · secret key returns [RoutesLocation!]! | – | The depots: the Shopify locations the app has read (read only). |
routesSettingsquery · secret key returns RoutesSettings! | – | The app’s settings (read only; map keys are never included). |
routesSlotsquery · secret key returns [RoutesSlot!]! | date: String!zip: String | The time slots offered on a day: the store’s, or the delivery area’s of a postcode. |
query Reference {
routesDrivers { id legacyResourceId name vehicleKey active }
routesSlots(date: "2026-11-20", zip: "43215") { from to }
routesLocations { locationGid name zip active }
}
| Type | Fields |
|---|---|
RoutesDriver | id: ID!, legacyResourceId: Int!, name: String!, vehicleKey: String, active: Boolean! |
RoutesArea | id: Int!, name: String!, publicName: String, kind: String!, postcodes: [String!]!, radiusKm: Float, lat: Float, lng: Float, locationGid: String, days: JSON, windowList: JSON, dayCap: Int, feeHint: String, active: Boolean!, position: Int! |
RoutesLocation | locationGid: String!, name: String!, address: JSON, zip: String, countryCode: String, lat: Float, lng: Float, active: Boolean!, deleted: Boolean! |
RoutesSettings | version: Int!, settings: JSON! |
RoutesSlot | from: String!, to: String! |
Webhooks #
Up to 10 per store. Topics: STOP_STATUS_CHANGED, ROUTES_SENT, PLAN_ACCEPTED. The body, headers, signature and retries are described in the REST reference.
| Operation | Arguments | What it does |
|---|---|---|
routesWebhookSubscriptionsquery · secret key returns [RoutesWebhookSubscription!]! | – | Your webhooks. The secret is never shown again. |
routesWebhookSubscriptionCreatemutation · secret key returns RoutesWebhookSubscriptionPayload! | input: RoutesWebhookSubscriptionInput! | Send a signed JSON POST to your https address when the topic happens. The secret that signs the deliveries is in this answer only. |
routesWebhookSubscriptionDeletemutation · secret key returns RoutesDeletePayload! | id: ID! | Delete a webhook. |
mutation Hook {
routesWebhookSubscriptionCreate(input: {topic: STOP_STATUS_CHANGED, callbackUrl: "https://example.com/hooks/deliveries"}) {
webhookSubscription { id topic url active }
secret # shown in this answer only: store it now
userErrors { field code message } # url_not_allowed for an address that isn't https
}
}
# routesWebhookSubscriptionDelete(id: "gid://sirentals/RoutesWebhook/1") { deletedId userErrors { code message } }
RoutesWebhookSubscriptionInput
| Field | Type | Notes |
|---|---|---|
topic | RoutesWebhookTopic! | |
callbackUrl | String! | https only; not a private network. |
| Enum | Values |
|---|---|
RoutesWebhookTopic | STOP_STATUS_CHANGEDROUTES_SENTPLAN_ACCEPTED |
| Type | Fields |
|---|---|
RoutesWebhookSubscription | id: ID!, legacyResourceId: Int!, topic: RoutesWebhookTopic!, url: String!, active: Boolean!, createdAt: String, lastStatus: Int, lastAt: String |
RoutesWebhookSubscriptionPayload | webhookSubscription: RoutesWebhookSubscription, secret: String, userErrors: [RentalUserError!]! |
