View Categories

Launching soon on the Shopify App StoreAbout Delivery RoutingJoin the waitlist

GraphQL API reference: Delivery Routing

8 min read

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. Every id argument also takes the bare number, and legacyResourceId gives it.
  • Days are YYYY-MM-DD in the store’s time zone and times HH:MM. location takes 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_required error with data.feature and data.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_webhooks and 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.

OperationArgumentsWhat it does
routesStops
query · secret key
returns RoutesStopConnection!
filter: RoutesStopFilterInput
first: Int
after: String
last: Int
before: 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.
routesStop
query · secret key
returns RoutesStop
id: ID!One stop with its history (events), or null.
routesStopCreate
mutation · secret key
returns RoutesStopPayload!
input: RoutesStopInput!Add a stop by hand (source MANUAL). name and address.address1 are required.
routesStopUpdate
mutation · 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.
routesStopDelete
mutation · 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

FieldTypeNotes
dateStringYYYY-MM-DD
status[RoutesStopStatus!]One or several; CANCELLED stops come only when you ask for them.
locationIDA Shopify location: gid://shopify/Location/801 or 801.
sourceRoutesStopSource
qStringAn order name or a postcode.
needsDayBooleantrue: only stops that still need a day.
unroutedBooleantrue: only stops that aren’t on a route.

RoutesStopInput

FieldTypeNotes
nameStringWho the stop is for. Required when you add a stop.
addressRoutesAddressInputaddress1 is required when you add a stop.
dateStringYYYY-MM-DD; null puts the stop back to Needs a day.
fromStringHH:MM, with to.
toString
kindRoutesStopKindDELIVERY (default) or COLLECTION.
locationIdIDThe depot it leaves from (a Shopify location id or gid).
phoneString
emailString
instructionsStringFor the driver (gate code, floor).
noteStringFor your team.
unitsIntThe load: units, kilograms and cubic metres.
kgFloat
m3Float
serviceMinIntMinutes spent at the stop.
latFloatA map pin, when the address alone isn’t enough (with lng).
lngFloat

RoutesAddressInput

FieldTypeNotes
companyString
address1String
address2String
cityString
provinceString
zipString
countryString
countryCodeString
EnumValues
RoutesStopStatusNEEDS_DAY
UNPLANNED
PLANNED
SENT
ON_THE_WAY
ARRIVED
DONE
FAILED
CANCELLED
RoutesStopSourceORDER
RENTAL
MANUAL
RoutesStopKindDELIVERY
COLLECTION
TypeFields
RoutesStopid: 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!]!
RoutesStopOrderid: ID, gid: String, name: String
RoutesAddresscompany: String, address1: String, address2: String, city: String, province: String, zip: String, country: String, countryCode: String
RoutesStopItemorder: String, line: String, name: String, qty: Int, kg: Float
RoutesStopRouteid: Int!, name: String!, status: RoutesRouteStatus!
RoutesVehicleRefkey: String!, name: String!, colour: String!
RoutesEtaeta: String, from: String, to: String
RoutesPodsignedBy: String, note: String, photos: [RoutesPodPhoto!]!, signatureUrl: String, at: String, lat: Float, lng: Float
RoutesPodPhotourl: String!, takenAt: String
RoutesStopEventat: String!, actor: String!, action: String!, data: JSON
RoutesStopPayloadstop: RoutesStop, userErrors: [RentalUserError!]!
RoutesStopDeletePayloaddeletedId: ID, stop: RoutesStop, userErrors: [RentalUserError!]!
EnumValues
RoutesRouteStatusDRAFT
SENT
STARTED
ENDED

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.

OperationArgumentsWhat it does
routesRoutes
query · secret key
returns [RoutesRoute!]!
date: String
location: String
status: [RoutesRouteStatus!]
A day’s routes (default: today) with their stops in order.
routesRoute
query · secret key
returns RoutesRoute
id: ID!One route with its stops, or null.
routesDayPlan
mutation · secret key
returns RoutesDayPlanPayload!
date: String!
location: String
locks: 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.
routesPlanAccept
mutation · 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.
routesSend
mutation · 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.
routesReport
query · secret key
returns RoutesReport!
from: String
to: String
location: 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 } }
TypeFields
RoutesRouteid: 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!]!
RoutesDriverRefid: Int!, name: String!
RoutesRouteStopstopId: Int!, position: Int!, status: RoutesStopStatus!, eta: String, from: String, to: String
RoutesDayPlanPayloadplan: RoutesPlan, userErrors: [RentalUserError!]!
RoutesPlan
A 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!
RoutesPlanRoutevehicleKey: String!, km: Float!, driveMin: Int!, stops: [RoutesPlanStop!]!
RoutesPlanStopstopId: Int!, eta: String, from: String, to: String
RoutesPlanUnassignedstopId: Int!, reason: String!, message: String!
RoutesPlanTotalsvehicles: Int!, placed: Int!, km: Float!, driveMin: Int!
RoutesPlanAcceptPayloadok: Boolean, planId: ID, date: String, locationGid: String, version: String, routes: [RoutesRoute!], userErrors: [RentalUserError!]!
RoutesSendPayloadok: Boolean, date: String, locationGid: String, sent: Int, version: String, routes: [RoutesRoute!], userErrors: [RentalUserError!]!
RoutesReportfrom: String!, to: String!, days: Int!, rows: [RoutesReportRow!]!, total: RoutesReportRow!
RoutesReportRowdate: String!, vehicle: String!, stops: Int!, done: Int!, failed: Int!, km: Float!, drivingMin: Int!, onRoadMin: Int!, stopsPerHour: Float!, onTimePct: Int

Vehicles #

OperationArgumentsWhat it does
routesVehicles
query · secret key
returns [RoutesVehicle!]!
activeOnly: Boolean = falseThe vehicles, in the order you keep them. activeOnly: true leaves out the inactive ones.
routesVehicle
query · secret key
returns RoutesVehicle
id: ID!One vehicle, or null.
routesVehicleCreate
mutation · secret key
returns RoutesVehiclePayload!
input: RoutesVehicleInput!Add a vehicle. Refused with vehicle_limit at your plan’s number of vehicles.
routesVehicleUpdate
mutation · secret key
returns RoutesVehiclePayload!
id: ID!
input: RoutesVehicleInput!
Change a vehicle; fields you don’t send keep their values.
routesVehicleDelete
mutation · 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

FieldTypeNotes
nameStringRequired when you add a vehicle.
colourStringA hex colour for the map, #2563eb.
locationIdIDThe depot it starts from.
unitsIntWhat it can carry: units, kilograms, cubic metres. Leave one out for no limit.
kgFloat
m3Float
features[String!]Tags the planner matches with stops.
shiftFromStringWorking hours, HH:MM (with shiftTo).
shiftToString
activeBooleanfalse keeps it out of planning.
TypeFields
RoutesVehicleid: 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!
RoutesVehiclePayloadvehicle: RoutesVehicle, userErrors: [RentalUserError!]!
RoutesDeletePayloaddeletedId: ID, userErrors: [RentalUserError!]!

Drivers, areas, depots, settings and slots #

Read only: these are changed in the app.

OperationArgumentsWhat it does
routesDrivers
query · secret key
returns [RoutesDriver!]!
–The drivers: name, vehicle and whether they are active. Never an email, a phone number or a sign-in link.
routesAreas
query · secret key
returns [RoutesArea!]!
–The delivery areas (read only).
routesLocations
query · secret key
returns [RoutesLocation!]!
–The depots: the Shopify locations the app has read (read only).
routesSettings
query · secret key
returns RoutesSettings!
–The app’s settings (read only; map keys are never included).
routesSlots
query · 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 }
}
TypeFields
RoutesDriverid: ID!, legacyResourceId: Int!, name: String!, vehicleKey: String, active: Boolean!
RoutesAreaid: 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!
RoutesLocationlocationGid: String!, name: String!, address: JSON, zip: String, countryCode: String, lat: Float, lng: Float, active: Boolean!, deleted: Boolean!
RoutesSettingsversion: Int!, settings: JSON!
RoutesSlotfrom: 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.

OperationArgumentsWhat it does
routesWebhookSubscriptions
query · secret key
returns [RoutesWebhookSubscription!]!
–Your webhooks. The secret is never shown again.
routesWebhookSubscriptionCreate
mutation · 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.
routesWebhookSubscriptionDelete
mutation · 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

FieldTypeNotes
topicRoutesWebhookTopic!
callbackUrlString!https only; not a private network.
EnumValues
RoutesWebhookTopicSTOP_STATUS_CHANGED
ROUTES_SENT
PLAN_ACCEPTED
TypeFields
RoutesWebhookSubscriptionid: ID!, legacyResourceId: Int!, topic: RoutesWebhookTopic!, url: String!, active: Boolean!, createdAt: String, lastStatus: Int, lastAt: String
RoutesWebhookSubscriptionPayloadwebhookSubscription: RoutesWebhookSubscription, secret: String, userErrors: [RentalUserError!]!