---
title: "Get Route by ID"
method: GET
path: "/routes/{id}"
tags: ["Routes"]
---

# Get Route by ID

`GET /routes/{id}`

## Request

- Base URL: `https://api.smartroutes.io/v2`
- URL: `https://api.smartroutes.io/v2/routes/{id}`
- Auth: none declared

Fetch a single route in your depot with everything on it: the vehicle and driver, planned and actual times and distances, and its `stops` in visiting order. Each stop includes:
- its orders, with their line items and current status
- live progress once the route is under way: actual arrival and completion, failure reason, check-ins and driver phone calls
- proof of delivery (signature and photo URLs) once the driver has recorded it

The route's start and end points are not listed as stops. `start_time` is the departure from the start point and `end_time` is the arrival at the end point. If your depot uses partial orders, each stop lists only the line items delivered on that stop.

Any route in your depot can be fetched by id, including a route that is no longer part of its plan's current optimisation. `plan_id` links the route back to its plan. To discover route ids, use `GET /routes`. To get all of a plan's current routes in one call, use `GET /plans/{id}`.

## Path parameters

- `id` number, required

## Response `200`

Successful response

- RouteResponse
  - `route` Route, required: A route belonging to a plan.
    - `id` integer
    - `plan_id` integer, nullable
    - `name` string
    - `dispatched` boolean
    - `is_locked` boolean: Whether the route is marked as locked. Set with `PUT /routes/{id}`. A stored flag only: this API does not enforce it, so a locked route can still be reversed, dispatched and have orders added or removed.
    - `date` string, date
    - `start_time` string: HH:mm:ss
    - `end_time` string: HH:mm:ss
    - `started_ts` string, date-time, nullable
    - `completed_ts` string, date-time, nullable
    - `total_time` number
    - `travel_time` number
    - `planned_distance` number
    - `actual_distance` number, nullable
    - `vehicle` VehicleRef: Trimmed vehicle reference embedded in Route responses.
      - `vehicle_id` integer
      - `name` string
      - `driver` string, nullable
    - `stops` Stop[]
      - `id` integer
      - `route_id` integer
      - `address` string
      - `postcode` string
      - `phone` string, nullable
      - `lat` number
      - `lng` number
      - `planned_distance` number
      - `arrival_date` string, date
      - `departure_date` string, date
      - `estimated_arrival_time` string: HH:mm or HH:mm:ss
      - `estimated_departure_time` string: HH:mm or HH:mm:ss
      - `actual_completed_ts` string, date-time, nullable
      - `actual_completed_lat` number, nullable
      - `actual_completed_lng` number, nullable
      - `delivery_notes` string, nullable
      - `driver_notes` string, nullable
      - `actual_arrival_time` string, nullable
      - `actual_duration` number, nullable
      - `signature` string, uri, nullable
      - `signature_lat` number, nullable
      - `signature_lng` number, nullable
      - `signed_by` string, nullable
      - `photos` string[], nullable: URLs to proof-of-delivery photos for this stop. A photo appears here as soon as it is uploaded and synced, which may be before compression has finished — following the URL of a photo that is still compressing returns `202 Accepted` with a `Retry-After` header (see the photo endpoint), so be prepared to retry. `null` when there are none.
      - `tracking_link` string, uri, nullable
      - `vehicle_assignment` object, nullable
        - `vehicle_id` integer
      - `directions` object[]
        - `instruction` string
      - `driver_questionnaire_submissions` DriverQuestionnaireSubmission[]
        - `id` integer
        - `driver_questionnaire` object
          - `type` string
          - `title` string
      - `failed_reason` object, nullable
        - `reason` string
      - `tags` string[]
      - `available_days` integer[]
      - `emails` object[], nullable
        - `email` string
      - `driver_phone_calls` object[]: Calls the driver placed to this stop's contact number from the driver app, oldest first. `[]` when there are none. **These are call *attempts*, not call records.** The app hands a `tel:` URL to the device's dialler and the OS takes over, so SmartRoutes never observes the call itself. There is deliberately no status, no duration and no answered/unanswered flag — that information does not exist. Inbound calls, and calls placed outside the driver app, are not recorded at all. `initiated_at` is the driver device's own clock at the moment the call was placed; `recorded_at` is when our servers received it. Drivers work offline, so these can differ by hours, and a call can arrive with an `initiated_at` well before the `recorded_at` of calls you have already seen. Poll on `recorded_at`, not `initiated_at`, if you are looking for new activity. Absence is not proof no call was made: on iOS, a call placed with no GPS fix available is not recorded.
        - `initiated_at` string, date-time, nullable: When the driver placed the call, from the device clock. `null` if the app did not record a usable timestamp.
        - `recorded_at` string, date-time, nullable: When our servers received the record. Later than `initiated_at` — by hours if the driver was working offline.
        - `lat` number, nullable: Driver's latitude when the call was placed. `null` when no GPS fix was available.
        - `lng` number, nullable: Driver's longitude when the call was placed. `null` when no GPS fix was available.
      - `orders` object[]
        - `id` integer
        - `order_number` string
        - `custom_fields` CustomField[], nullable
          - `id` integer
          - `name` string
          - `value` unknown
        - `line_items` LineItem[]
          - `id` integer: Server-set line item id. Ignored on input.
          - `product_name` string
          - `product_code` string
          - `product_quantity` number, float
          - `quantity_outstanding` number, float, nullable: Quantity still to be fulfilled on this line item — left to deliver on a `delivery` order, left to collect on a `pickup` order. Equal to `product_quantity` before anything is fulfilled, a value in between after a partial fulfillment, `0` when exactly the ordered quantity has been fulfilled, and **negative** when more than the ordered quantity was fulfilled. Subtract it from `product_quantity` for the amount the driver has fulfilled. **Read-only:** drivers record fulfillment in the SmartRoutes mobile app; it cannot be set through this API. Notes: `null` — not `0` — when the line item is not tracked, so treat null as unknown rather than as fully fulfilled; an over-fulfillment is reported as recorded rather than clamped; stays at `product_quantity` on `shipment` orders, which are dual-leg and not yet tracked. A value still equal to `product_quantity` does not distinguish an untouched line item from one a driver attended and fulfilled nothing on — read `status` for that (`UNDELIVERED` means attended).
          - `capacities` Capacity[]
            - `id` integer
            - `type` string
            - `capacity` number, nullable
          - `status` 'BOOKED' | 'BOOKING_DECLINED' | 'CANCEL' | 'COMPLETED' | 'DECLINED' | 'DELIVERED' | 'LOADED' | 'NOT RECEIVED' | 'ON HOLD' | 'OPEN' | 'PARTIALLY_DELIVERED' | 'READY_FOR_COLLECTION' | 'RETURNED' | 'ROUTED' | 'UNDELIVERED' | 'WAITING_CONFIRMATION': Per-line-item status. `PARTIALLY_DELIVERED` appears when a line item has been partly fulfilled — partly collected on a `pickup` order or partly delivered on a `delivery` order; how much is left to fulfil is in `quantity_outstanding`. There is no separate picked-up status: completed and partial pickups both use the `DELIVERED` family. `UNDELIVERED` covers both "attended, nothing fulfilled" (in which case `quantity_outstanding` still equals `product_quantity`) and a failed visit. The other `PARTIALLY_*` values are aggregates and apply only at the order level (see `OrderStatus`). Historical rows may still carry legacy values (`CANCEL`, `COMPLETED`, `DECLINED`, `NOT RECEIVED`, `ON HOLD`, `RETURNED`); these are listed for backward compatibility but are no longer written by current SR code.
          - `date` string, date, nullable
          - `custom_fields` CustomField[]
            - `id` integer
            - `name` string
            - `value` unknown
        - `type` string
    - `driver_questionnaire_submissions` DriverQuestionnaireSubmission[]
      - `id` integer
      - `driver_questionnaire` object
        - `type` string
        - `title` string
    - `created` string, date-time
    - `updated` string, date-time

## Other responses

- `403`: Forbidden. Returned for rate-limit exhaustion (`name = FORBIDDEN`, with `details.retry_after_seconds` and `Retry-After` header) and permission gate failures (`name = PERMISSION`). Inspect `name` to disambiguate. Send `x-sr-api-features: rate-limit-429` to receive `429 RATE_LIMIT_EXCEEDED` for the rate-limit case instead, leaving `403` to mean permission failures only — see [API Features](#section/Getting-started/API-Features).
- `404`: The requested resource was not found.
- `429`: Rate limit exceeded (`name = RATE_LIMIT_EXCEEDED`). **Only returned to callers that opt in** with the `x-sr-api-features: rate-limit-429` request header; without it the same condition returns `403` with `name = FORBIDDEN` and an otherwise identical body. See [API Features](#section/Getting-started/API-Features).
- `500`: An unexpected server error occurred. The body carries only `name` and `description`: internal details are never returned.

---

[API](https://skmtc.dev/smartroutes/apis/smartroutes-api.md) · [All operations](https://skmtc.dev/smartroutes/apis/smartroutes-api/llms.txt) · [OpenAPI document](https://skmtc.dev/smartroutes/apis/smartroutes-api/revisions/a7ea507afac7?raw)
