SmartRoutes API

Download OpenAPI specification:Download

Getting started

Introduction

The SmartRoutes API gives your software programmatic access to the SmartRoutes platform. You can manage the full delivery lifecycle from your own systems. Push orders and customers in from your ERP, e-commerce or WMS. Keep your fleet, capacities and zones in sync. Run route optimisation and dispatch plans to drivers. Then pull back what happened on the road: order statuses, proof of delivery photos and signatures, and driver questionnaire responses. The API also handles customer notifications and booking availability checks. Webhooks notify your systems in real time when orders change status, plans are dispatched or routes are completed. It's a REST API over HTTPS that uses JSON and authenticates with an access key, which you generate in SmartRoutes under Settings > Integrations.

Base URL

Every endpoint path in this reference is relative to this base URL:

https://api.smartroutes.io/v2

So GET /orders in this reference means GET https://api.smartroutes.io/v2/orders.

The documentation host is not the API host. These docs are published at smartroutes.io/api, which serves this page and the OpenAPI spec only. Requests sent there do not reach the API — send them to the base URL above.

Authentication

To use the API, you will need to generate an API Access Key for your SmartRoutes account. To generate, login to your SmartRoutes account and navigate to Settings > Integrations > SmartRoutes Open API and click 'Generate API Key'. For more details on SmartRoutes integrations, click here.

All requests expect the API Access Key to be provided in the x-access-key header:

x-access-key: YOUR_ACCESS_KEY

Two optional request headers are also recognised: x-sr-api-features, accepted on every endpoint (see API Features), and idempotency-key, honoured on the POST endpoints listed under Idempotency.

Webhook deliveries from SmartRoutes to your receiver are authenticated differently — see Webhooks.

Rate Limits

Every endpoint has a per-minute limit, and most also have a short burst limit. Requests are rejected as soon as either limit is hit.

Most endpoints fall into one of these tiers:

Tier Rate limit Description
Standard 60 / min (max 5 / sec) Single-resource reads and writes (e.g. GET /orders/{id}, PUT /orders/{id})
Listing 30 / min (max 5 / sec) Paginated lists (e.g. /customers, /plans, /routes)
Reference 60 / min (max 1 / sec) Reference data (e.g. capacities, custom fields, vehicles, zone groups)
Bulk 30 / min (max 1 per 2 sec) Bulk creates and heavy deletes
Upload 15 / min (max 1 / sec) File uploads (e.g. POST /orders/{id}/attachments)

The following endpoints have a stricter limit of 5 / min (max 2 per 15 sec):

  • Plan optimisation: POST /plans/optimise/orders, POST /plans/optimise/orders-for-ids
  • Booking availability: POST /booking/availability, POST /booking/available-dates, POST /booking/available-slots
  • Route reversal: POST /routes/{id}/reverse

The two dispatch endpoints have the Standard per-minute allowance with a tighter burst, 60 / min (max 2 per 2 sec), because each call blocks for the full dispatch (up to two minutes):

  • POST /routes/{id}/dispatch
  • POST /plans/{id}/dispatch

When a limit is hit, the API returns HTTP 403 with a Retry-After header and details.retry_after_seconds in the body — both give the seconds until the limit resets.

Prefer 429? 403 is the status this API has always returned for rate limiting, and it stays the default so existing integrations keep working. Send x-sr-api-features: rate-limit-429 and a tripped limit answers 429 Too Many Requests with name: "RATE_LIMIT_EXCEEDED" instead. Nothing else about the response changes — Retry-After and details.retry_after_seconds are already sent on the 403. See API Features.

We may temporarily reduce these limits to protect platform stability. We aim to keep such reductions brief and rare. However, your application should be built to handle rate limits gracefully.

API Features

Some improvements to this API are not wire-compatible with what it already returns, so switching them on for everyone would break integrations built against the current behaviour. Those improvements are offered as opt-in feature tokens: list the ones you want on the x-sr-api-features request header and they apply to that request.

x-sr-api-features: rate-limit-429

The header takes a comma-separated list. Tokens are case-insensitive and order does not matter. Tokens this API does not recognise are ignored, not rejected — so a single header value is safe to send across environments that support different token sets.

Every response echoes the tokens that were actually applied:

x-sr-api-features-applied: rate-limit-429

The echo header is absent when nothing was applied. Check it to confirm an opt-in took effect — a mistyped token is silently ignored, and this header is how you see that.

Token Effect
rate-limit-429 Rate-limit exhaustion returns 429 with name: "RATE_LIMIT_EXCEEDED" in place of 403 with name: "FORBIDDEN". The Retry-After header, details.retry_after_seconds, and description are identical either way. Note that 403 is also returned for permission failures (name: "PERMISSION"), which this token does not affect.

Opting in is per request, so you can adopt a token on one worker or one endpoint before rolling it out. A token is never renamed or redefined while it exists.

Every token has an end date

A feature token is a migration aid, not a permanent setting. Each one exists so you can adopt a fix on your own schedule during a notice period; when that period ends the fix becomes the default for everyone and the token stops mattering.

You do not have to track this in a changelog. While a token is in its notice period, the legacy response — the one you get by not sending the token — carries the schedule:

Sunset: Wed, 01 Mar 2028 00:00:00 GMT
Deprecation: @1818720000
Link: <https://api.smartroutes.io/v2/docs#api-features>; rel="deprecation"

Sunset (RFC 8594) is the date the default changes. Deprecation (RFC 9745) is the date the old behaviour becomes deprecated — it may be in the future, meaning advance notice. Responses to callers who have already opted in carry neither, so these headers are a live signal that you still have migrating to do.

After the sunset date the token keeps being accepted and simply does nothing, because it now describes the default. There is no second migration and no flag day for anyone who adopted early — leaving the header in place forever is safe.

Token Default changes on
rate-limit-429 2028-03-01 — from this date a tripped rate limit returns 429 for all callers.

Idempotency

State-changing POST endpoints accept an optional idempotency-key request header. The mechanism is Stripe-style: send the same key with a retry of the same request and the server returns the original response without re-executing the underlying operation.

Header: idempotency-key: <opaque string, 1–255 characters>. The key can be any client-chosen identifier — a UUID is recommended. Keys are scoped per (api-key, endpoint), so the same key on two different endpoints is treated as two distinct operations.

Retention: 24 hours from the time the original request was received.

Behaviour:

  • Replay (same key, same body, completed) — server returns the original status code and response body. The response header idempotency-replay: true indicates the response is a cached replay. The underlying operation is not re-executed. Replays do not consume rate-limit or data-row quota.
  • Body mismatch (same key, different body) — server returns 422 IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_BODY.
  • In-flight (same key, original request still processing) — server returns 409 IDEMPOTENCY_REQUEST_IN_PROGRESS with a Retry-After header. Retry after the period given.
  • 5xx server error — the cached reservation is released. A retry with the same key is treated as a fresh request and can re-execute. 4xx client errors are cached: replays return the same 4xx response.
  • Invalid key (empty, or longer than 255 chars) — server returns 400 IDEMPOTENCY_INVALID_KEY.

Supporting endpoints (Phase 1):

  • POST /orders
  • POST /plans/optimise/orders
  • POST /plans/optimise/orders-for-ids
  • POST /customers/
  • POST /vehicles/
  • POST /notifications/schedule/orders

Other POST endpoints ignore the header. Multipart uploads (POST /orders/{id}/attachments) are excluded from idempotency in this phase.

Paging

Usage

Make an initial request to the API endpoint with the desired query parameters. The API response includes the requested data and a Link header for the next page. Use the URL provided in the Link header to make subsequent requests for the next page.

Example:

Link: <https://api.smartroutes.io/v2/orders?page_info=PAGE_INFO_STRING&limit=100>; rel=next

Query parameters

  • limit — specifies the maximum number of items per page. Limited to a maximum of 100. Example: limit=50
  • updated_at_min — filters results based on the minimum update timestamp, in the format YYYY-MM-DD hh:mm:ss or YYYY-MM-DDTHH:mm:ss.SSSZ. Example: updated_at_min=2023-01-01 12:00:00 or updated_at_min=2023-01-01T12:00:00.000Z
  • status — filters results based on the order status. Example: status=Pending

Errors

Errors are classified into specific types, each identified by a unique name and accompanied by an appropriate HTTP statusCode. The error responses include a brief description of the error, and additional details that provide more context about the specific issue.

name
required
string
Enum: "INVALID_INPUT" "RESOURCE_NOT_FOUND" "UNAUTHORIZED" "FORBIDDEN" "PERMISSION" "RATE_LIMIT_EXCEEDED" "GONE" "ALREADY_DISPATCHED" "SERVICE_UNAVAILABLE" "INTERNAL_SERVER_ERROR" "INTERNAL_RESOURCE_MISSING" "ERROR_WARNING" "LATENCY_WARNING" "VALIDATION_REQUEST" "GEOCODING_FAILED" "REDIS_UNAVAILABLE" "SEND_ERROR" "IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_BODY" "IDEMPOTENCY_REQUEST_IN_PROGRESS" "IDEMPOTENCY_INVALID_KEY"

Machine-readable error name. Stable string identifier; safe to switch on programmatically.

statusCode
required
integer

HTTP status code matching the response status. Legacy camelCase property name; kept for backwards compatibility.

description
required
string

Human-readable description of the error. For developers; not intended for end-user display.

required
object

Additional structured details about the error. Always an object — never null. May be empty ({}). Validation errors populate this with a validationErrors array of Zod issues.

{
  • "name": "INVALID_INPUT",
  • "statusCode": 400,
  • "description": "Customers Validation Error",
  • "details": {
    }
}

Error types

RESOURCE_NOT_FOUND

  • statusCode: 404
  • description: The requested resource could not be found.

INVALID_INPUT

  • statusCode: 400
  • description: The submitted input data is invalid.
  • details: An object { validationErrors: An array of Zod errors specifying validation issues in the submitted data. } containing additional details about the error. For processing validation errors, refer to the Zod documentation.

GEOCODING_FAILED

  • statusCode: 400
  • description: Geocoding operation failed.
  • details: An object containing additional details about the error.

RATE_LIMIT_EXCEEDED

  • statusCode: 429
  • description: You've reached your limits for this request.
  • details: An object containing retry_after_seconds — the seconds until the limit resets, matching the Retry-After header.
  • Returned only to callers that send x-sr-api-features: rate-limit-429; otherwise the same condition is reported as FORBIDDEN with statusCode 403. See API Features.

Webhooks

Configure a webhook receiver URL in your depot settings and SmartRoutes will deliver event notifications as they happen. Eight event types are emitted, each documented below with its payload schema and an example delivery:

Event Type ID Fires when
orders.status 2 An order's status changes (line-item or order-level transition)
plans.created 4 A Plan row is persisted (after POST /plans/optimise/orders or /orders-for-ids). Fires before optimisation completes.
plans.dispatched 5 A Plan is dispatched (routes published to drivers).
routes.edited 6 A dispatched route is edited (re-ordering stops, etc.)
routes.deleted 7 One or more dispatched routes are deleted (typically as part of solution replacement)
routes.completed 8 A dispatched route is completed by the driver.
plans.deleted 9 A Plan row is destroyed
orders.on_delivery 10 A stop enters its on-delivery ETA window on a dispatched route (the arriving-soon signal).

Delivery envelope

Each event is delivered as an HTTP POST to the receiver URL configured in your depot settings. The request body is always a JSON object of shape:

{ "type": <event type string>, "data": <payload object for that event type> }

The data field is the payload object for a single event — NOT an array. Bulk operations (e.g. order-status changes from a route delete) fan out into one POST per event, each with its own data object.

Delivery headers

Outgoing requests carry Content-Type: application/json and an HTTP Basic Authorization header derived from the username/password you supplied when configuring the webhook. No HMAC signature header is sent; treat the basic-auth credentials as the integrity check and rotate them if compromised.

Timeout and retries

Each delivery has a server-side request timeout of 3 seconds. On failure (HTTP error, timeout, or connection error) the delivery is retried up to 3 times total; after the third failure the task is marked permanently ERROR and not retried again. Your receiver should respond 2xx quickly — slow receivers will be treated as failures.

Machine-readable schemas

The delivery envelope and per-event payloads are also defined under #/components/schemas/: WebhookDelivery, WebhookOrdersStatusPayload, WebhookPlanIdPayload, WebhookRouteIdPayload, WebhookOnDeliveryPayload. Reference these from your own integration's tests / contract tools to catch shape drift early.

orders.status Webhook

Fires when an order's status changes (line-item or order-level transition).

Type ID: 2.

Request Body schema: application/json
required
type
required
string
Value: "orders.status"

Event type — always orders.status for this event.

required
object (WebhookOrdersStatusPayload)

Payload of the orders.status event, sent whenever an order's status changes.

Responses

Request samples

Content type
application/json
{
  • "type": "orders.status",
  • "data": {
    }
}

plans.created Webhook

Fires when a Plan row is persisted — after POST /plans/optimise/orders or POST /plans/optimise/orders-for-ids.

It fires before optimisation completes, so a delivery does not mean the optimisation finished — poll GET /plans/{id} for the outcome. See Optimise Orders.

Type ID: 4.

Request Body schema: application/json
required
type
required
string
Value: "plans.created"

Event type — always plans.created for this event.

required
object (WebhookPlanIdPayload)

Payload for plans.created / plans.dispatched / plans.deleted. Carries only the plan id.

Responses

Request samples

Content type
application/json
{
  • "type": "plans.created",
  • "data": {
    }
}

plans.dispatched Webhook

Fires when a Plan is dispatched (routes published to drivers).

Type ID: 5.

Request Body schema: application/json
required
type
required
string
Value: "plans.dispatched"

Event type — always plans.dispatched for this event.

required
object (WebhookPlanIdPayload)

Payload for plans.created / plans.dispatched / plans.deleted. Carries only the plan id.

Responses

Request samples

Content type
application/json
{
  • "type": "plans.dispatched",
  • "data": {
    }
}

routes.edited Webhook

Fires when a dispatched route is edited (re-ordering stops, etc.).

Type ID: 6.

Request Body schema: application/json
required
type
required
string
Value: "routes.edited"

Event type — always routes.edited for this event.

required
object (WebhookRouteIdPayload)

Payload for routes.edited / routes.deleted / routes.completed. Carries only the route id.

Responses

Request samples

Content type
application/json
{
  • "type": "routes.edited",
  • "data": {
    }
}

routes.deleted Webhook

Fires when one or more dispatched routes are deleted (typically as part of solution replacement), once per deleted route.

Type ID: 7.

Request Body schema: application/json
required
type
required
string
Value: "routes.deleted"

Event type — always routes.deleted for this event.

required
object (WebhookRouteIdPayload)

Payload for routes.edited / routes.deleted / routes.completed. Carries only the route id.

Responses

Request samples

Content type
application/json
{
  • "type": "routes.deleted",
  • "data": {
    }
}

routes.completed Webhook

Fires when a dispatched route is completed by the driver.

Type ID: 8.

Request Body schema: application/json
required
type
required
string
Value: "routes.completed"

Event type — always routes.completed for this event.

required
object (WebhookRouteIdPayload)

Payload for routes.edited / routes.deleted / routes.completed. Carries only the route id.

Responses

Request samples

Content type
application/json
{
  • "type": "routes.completed",
  • "data": {
    }
}

plans.deleted Webhook

Fires when a Plan row is destroyed.

Type ID: 9.

Request Body schema: application/json
required
type
required
string
Value: "plans.deleted"

Event type — always plans.deleted for this event.

required
object (WebhookPlanIdPayload)

Payload for plans.created / plans.dispatched / plans.deleted. Carries only the plan id.

Responses

Request samples

Content type
application/json
{
  • "type": "plans.deleted",
  • "data": {
    }
}

orders.on_delivery Webhook

Fires when a stop enters its on-delivery ETA window on a dispatched route (the arriving-soon signal). Rides on the depot's on-delivery customer notifications — fires once per stop, and only for stops that carry contact details.

Type ID: 10.

Request Body schema: application/json
required
type
required
string
Value: "orders.on_delivery"

Event type — always orders.on_delivery for this event.

required
object (WebhookOnDeliveryPayload)

orders.on_delivery payload. Emitted when an order's stop enters the on-delivery (ETA-threshold) window on a dispatched route — the arriving-soon signal. Rides on the depot's on-delivery customer notifications, so it fires once per stop and only for stops that carry contact details (phone/email + a notification template).

Responses

Request samples

Content type
application/json
{
  • "type": "orders.on_delivery",
  • "data": {
    }
}

Booking

Check Booking Availability

Checks whether one more order could be fitted in on a given date before you accept the booking. Nothing is created or reserved, and the response is just {"booking": {"available": true|false}}.

Availability is worked out by running the routing engine on the spot. It takes every order already scheduled for date in this depot, adds the order you describe, and plans the whole day from scratch on either the vehicles you list in vehicles or, if you leave that out, all active vehicles. available is true only if every stop, both the existing ones and the new one, fits into a route. Your existing plans and routes for that day are not used. So a day that is already overbooked returns false even when the new order itself would fit.

  • The order's location comes from delivery_* fields for delivery orders and from pickup_* fields otherwise. If you don't send both lat and lng, the address and postcode are geocoded first. If geocoding fails you get 400 GEOCODING_FAILED.
  • Capacity ids that don't belong to the depot are ignored, and so are vehicle ids that don't belong to it. Look up valid capacity ids with GET /capacities.
  • If any vehicle used for the check has no shift on that weekday, the request fails with 400 INVALID_INPUT.

The call waits for the routing engine to finish, so it is much slower than a normal read. It also has a stricter rate limit of 5 / min (see Rate Limits), so call it when a customer is booking, not in bulk.

Request Body schema: application/json
date
required
string <date>

Requested date for order collection/delivery.

required
object

Location and other routing information for booking availability check.

vehicles
Array of arrays

Array of vehicles to attempt to route the order with. If no vehicles provided, active vehicles will be used.

Responses

Request samples

Content type
application/json
[
  • {
    }
]

Response samples

Content type
application/json
{
  • "booking": {
    }
}

Capacities

Get All Capacities

Lists the capacity types (for example pallets, weight or volume) set up for the depot your API key belongs to. Use it to look up the capacity ids that orders, customers, vehicles and POST /booking/availability accept in their capacities arrays.

Each entry has id, type (the display name), default (the default amount for that capacity type), created and updated. Deleted capacity types are left out. The list covers this depot only and is returned in full, with no paging.

Responses

Response samples

Content type
application/json
{
  • "capacities": [
    ]
}

Custom Fields

Get All Custom Fields

Lists the custom fields set up for the depot your API key belongs to. Use it to look up the ids and names you send in custom_fields on POST /orders, PUT /orders/{id}, POST /customers and PUT /customers/{id}.

Each entry has id, name, created and updated. Deleted fields are left out. The list covers this depot only and is returned in full, with no paging. When you create orders, a custom field that matches neither an id nor a name here is silently skipped, so check against this list if values seem to go missing.

Responses

Response samples

Content type
application/json
{
  • "custom_fields": [
    ]
}

Customers

Get Customers Page

Lists the customers in your depot, most recently updated first. Each item carries the customer's core details (id, account, name, address, lat/lng, phone, email, duration, notes, created, updated). Capacities, skills, tags, time windows and custom fields are not included; fetch them per customer with GET /customers/{id}.

Results are paged. limit sets the page size (1–100, default 100), and the Link header holds the URL of the next page. See Paging. updated_at_min (YYYY-MM-DD hh:mm:ss or YYYY-MM-DDTHH:mm:ss.SSSZ) returns only customers updated after that time. A limit outside 1–100 falls back to 100, and an updated_at_min in any other format is ignored rather than rejected. A malformed page_info returns 400 INVALID_INPUT.

query Parameters
page_info
string

Information about the page for pagination. Generated by the OpenAPI

updated_at_min
string <date-time>

Minimum updated date and time for filtering.

limit
integer
Default: 100

Maximum number of customers to retrieve per page.

Responses

Response samples

Content type
application/json
{
  • "customers": [
    ]
}

Bulk Create or Update Customers

Creates or updates up to 100 customers in your depot in one request, matching on account. If a customer with that account already exists in your depot, it is updated: the fields you send overwrite the stored values, and the fields you leave out are kept. For capacities, skills, tags, time_windows and custom_fields, sending the array replaces the customer's whole set (an empty array clears it). Leaving the array out keeps the existing set.

  • Each customer needs an address, or both lat and lng. An address sent without coordinates is geocoded. If geocoding fails, the customer is still saved, just without coordinates.
  • capacities are matched by id (see GET /capacities), skills and tags by name (ignoring case), and custom_fields by id or name (see GET /custom-fields). Entries that match nothing in your depot or organisation are dropped without an error.
  • If any customer in the array fails validation, nothing is saved and the response is 400 INVALID_INPUT. A depot can hold at most 300,000 customers. A request that would go over that limit is also rejected with 400 INVALID_INPUT.

The response lists the id and account of each customer, in request order. This endpoint supports Idempotency, but a replayed response has the original status and an empty body, so recover the IDs with GET /customers. Because customers are matched on account, sending the same batch again updates the same customers rather than creating duplicates.

header Parameters
idempotency-key
string [ 1 .. 255 ] characters

Optional idempotency key for safe retries. See Idempotency for behaviour. 1–255 characters; UUID recommended.

Request Body schema: application/json
required
Array (<= 100 items)
name
string <= 100 characters

Full name of the customer

account
required
string <= 100 characters

Unique account reference (required)

address
string <= 255 characters
postcode
string <= 20 characters
phone
string <= 30 characters
email
string <email>
lat
number

Latitude coordinate

lng
number

Longitude coordinate

duration
number [ 1 .. 9999 ]

Visit duration in minutes

notes
string <= 40000 characters
Array of objects

List of predefined capacity types by ID

skills
Array of strings

List of skill names (must already exist)

tags
Array of strings

List of tag names (must already exist)

Array of objects <= 2 items

Up to two delivery/pickup time windows per customer

Array of objects

Custom field values. Use id or name to identify each custom field.

Responses

Request samples

Content type
application/json
[
  • {
    }
]

Response samples

Content type
application/json
{
  • "customers": [
    ],
  • "errors": [ ]
}

Get Customer by ID

Returns one customer from your depot by its SmartRoutes id, nested under a customer key. The response includes the customer's details plus its capacities, skills, tags, time windows and custom fields. If you only have the account reference, find the customer with GET /customers first. An id that doesn't exist in your depot returns 404 RESOURCE_NOT_FOUND.

path Parameters
id
required
string

ID of the customer to retrieve.

Responses

Response samples

Content type
application/json
{
  • "id": 123,
  • "name": "John Doe",
  • "account": "NO123",
  • "address": "123 Main St 12345",
  • "postcode": "12345",
  • "lat": 12.345,
  • "lng": -67.89,
  • "phone": "+1234567890",
  • "email": "john@doe.com",
  • "duration": 30,
  • "notes": "Leave at the doorstep",
  • "created": "2022-04-29T16:12:08.000Z",
  • "updated": "2022-09-29T10:11:06.000Z",
  • "capacities": [
    ],
  • "time_windows": [
    ],
  • "skills": [
    ],
  • "tags": [
    ],
  • "custom_fields": [
    ]
}

Update Customer by ID

Updates one customer in your depot. This is a partial update: only the fields you send change. For capacities, skills, tags, time_windows and custom_fields, sending the array replaces the customer's whole set (an empty array clears it), and leaving it out keeps the existing set. Capacities, skills and tags that don't exist in your depot or organisation are dropped without an error. Custom fields are matched by id only.

  • Every request must include either address or both lat and lng, even when you are only changing other fields.
  • Changing address without sending coordinates re-geocodes the customer. If geocoding fails, the update is still saved and the failure is reported as a GEOCODING_FAILED entry in the response's errors array.
  • Setting account to a value another customer in your depot already uses returns 400 INVALID_INPUT.

The response holds the updated customer under customer (the same shape as GET /customers/{id}) along with an errors array.

path Parameters
id
required
string

ID of the customer to update.

Request Body schema: application/json
name
string

Name of the customer.

account
string

Account number of the customer.

address
string

Address of the customer.

postcode
string

Postcode for the customer address.

lat
number

Latitude of the customer location.

lng
number

Longitude of the customer location.

phone
string

Contact number of the customer.

email
string <email>

Email of the customer.

duration
number

Duration for customer interaction in minutes.

notes
string

Notes for customer interaction.

Array of objects

List of time windows for the customer.

skills
Array of strings

List of required skills for the customer.

tags
Array of strings

List of tags for the customer

Array of objects

List of capacities for the customer.

Array of objects

List custom fields for the customer

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "account": "string",
  • "address": "string",
  • "postcode": "string",
  • "lat": 0,
  • "lng": 0,
  • "phone": "string",
  • "email": "user@example.com",
  • "duration": 0,
  • "notes": "string",
  • "time_windows": [
    ],
  • "skills": [
    ],
  • "tags": [
    ],
  • "capacities": [
    ],
  • "custom_fields": [
    ]
}

Response samples

Content type
application/json
{
  • "id": 123,
  • "name": "John Doe",
  • "account": "NO123",
  • "address": "123 Main St 12345",
  • "postcode": "12345",
  • "lat": 12.345,
  • "lng": -67.89,
  • "phone": "+1234567890",
  • "email": "john@doe.com",
  • "duration": 30,
  • "notes": "Leave at the doorstep",
  • "capacities": [
    ],
  • "time_windows": [
    ],
  • "skills": [
    ],
  • "tags": [
    ],
  • "custom_fields": [
    ]
}

Driver Questionnaires

Get a driver questionnaire submission by ID

Retrieve a driver questionnaire submission and its answers by submission ID.

Each answer carries an images array of absolute URLs pointing at GET /driver-questionnaire-images/{id}. Only images the driver app has finished uploading are listed. Images are compressed asynchronously after upload, so a URL taken from this response may answer 202 Accepted with a Retry-After header instead of image data if it is followed before compression finishes — apply the same retry logic described on that endpoint.

path Parameters
id
required
integer

Driver Questionnaire Submission ID

Responses

Response samples

Content type
application/json
{
  • "driver_questionnaire_submission": {
    }
}

Get Driver Questionnaire Image

Retrieve a driver questionnaire image (photo or signature) by its ID. The IDs come from the images arrays on GET /driver-questionnaire-submission/{id}. By default returns the raw image binary. Use the encoding query parameter to request a Base64-encoded JSON response instead.

Images are compressed asynchronously after the driver uploads them. If the image exists but compression has not finished, this endpoint returns 202 Accepted with a Retry-After header and the image's model (id and is_compressed: false) as the JSON body, instead of image data — retry after the interval given. Image data is returned as WebP; the Content-Type header (or content_type in the Base64 response) states the format.

path Parameters
id
required
integer

The ID of the image to retrieve.

query Parameters
encoding
string
Value: "base64"

Set to base64 to receive the image as a Base64-encoded string in a JSON response instead of raw binary.

Responses

Response samples

Content type
No sample

Notification Tasks

Get Notification Tasks Page

Lists the SMS and email notifications scheduled for the depot your API key belongs to, with their recipient, rendered message, scheduled send time and status. Use it to check what was queued by POST /notifications/schedule/orders or by your notification automations, and whether it has been sent.

Results are paged, most recently updated first. Use limit (up to 100) and follow the Link header for the next page (see Paging). Filters:

  • updated_at_min returns only tasks updated at or after the given time.
  • status matches the task status exactly, for example Scheduled or Cancelled.
  • type is SMS or EMAIL (case-insensitive). Any other value returns 400 INVALID_INPUT.

Each task links to the order or visit it was created for, and the other one is null. The template gives the id of the template that was used (see GET /notifications/templates).

query Parameters
page_info
string

Pagination cursor generated by the API. Pass this value to retrieve the next page of results.

limit
integer
Default: 100

Maximum number of notification tasks to retrieve per page. Max 100.

updated_at_min
string <date-time>

Filter tasks modified at or after this datetime. Format: YYYY-MM-DD hh:mm:ss or YYYY-MM-DDTHH:mm:ss.SSSZ.

status
string

Filter notification tasks by status.

type
string
Enum: "SMS" "EMAIL"

Filter notification tasks by type. Accepted values: SMS, EMAIL (case-insensitive).

Responses

Response samples

Content type
application/json
{
  • "notification_tasks": [
    ]
}

Notifications

Get Notification Templates

Lists the SMS and email notification templates set up for the depot your API key belongs to. Use it to look up the template ids you pass in templates to POST /notifications/schedule/orders.

The response is a plain JSON array, not wrapped in a key. Each entry has id, type (SMS or EMAIL) and name. Deleted templates are left out, and the list is returned in full, with no paging. Templates are created and edited in the SmartRoutes web app, not through the API.

Responses

Response samples

Content type
application/json
[
  • {
    },
  • {
    }
]

Schedule Order Notifications

Schedules SMS and/or email notifications to the customers on a set of orders, using up to two of your notification templates (see GET /notifications/templates). One notification is created for each order and template pair. It is sent at send_time_utc, or straight away if you leave that out.

  • The recipient is the order's contact phone number or email. If the order has none, the linked customer's phone number or email is used.
  • Each SMS uses one credit per 160 characters of the rendered message, and the credits are deducted when the notification is scheduled. If the depot has no SMS credits left, SMS templates are skipped without being reported in errors.
  • Order and template ids that don't belong to this depot are ignored. If none of the orders are found, the response is 404 RESOURCE_NOT_FOUND.
  • Nothing checks for duplicates. Calling again for the same orders schedules the notifications again.

The request can partly succeed. The response is 200 with notifications listing the order_id of each notification created, and errors listing each order and template pair that was skipped and why (for example, no phone number, or an invalid email). Check errors even when the status is 200. To see the scheduled notifications, use GET /notification-tasks. Supports the idempotency-key header, so a retry won't schedule twice (see Idempotency).

header Parameters
idempotency-key
string [ 1 .. 255 ] characters

Optional idempotency key for safe retries. See Idempotency for behaviour. 1–255 characters; UUID recommended.

Request Body schema: application/json
orders
required
Array of arrays

Array of order IDs to create notifications for

templates
required
Array of arrays

Array of template IDs to generate notifications for

send_time_utc
string

UTC Timestamp at which to send the notifications, defaults to current time if not provided.

Responses

Request samples

Content type
application/json
{
  • "orders": [
    ],
  • "templates": [
    ],
  • "send_time_utc": "2024-12-25 12:00:00"
}

Response samples

Content type
application/json
{
  • "notifications": [
    ],
  • "errors": [
    ]
}

Order Statuses

Get All Order Statuses

Every order status your organisation can use: the SmartRoutes defaults that every organisation has, plus any custom statuses your organisation has added.

Order statuses are organisation-wide — the same list is returned for every depot in your organisation, and it is the same list your users see in the SmartRoutes web app.

Aggregate PARTIALLY_* statuses are excluded: SmartRoutes derives those from line-item states and they are never chosen directly.

Responses

Response samples

Content type
application/json
{
  • "order_statuses": [
    ]
}

Add an Order Status

Add a status to your organisation's catalogue. The status becomes available to every depot in your organisation and appears in the SmartRoutes web app.

The name is stored as you send it — surrounding whitespace is not trimmed. The response carries the name exactly as it will appear on orders; use that value when removing it later.

Adding a name your organisation already has, or one of the SmartRoutes defaults, returns 400.

Request Body schema: application/json
required
status
required
string [ 1 .. 255 ] characters

The status name. Cannot be empty or whitespace only.

Responses

Request samples

Content type
application/json
{
  • "status": "Packed"
}

Response samples

Content type
application/json
{
  • "order_status": {
    }
}

Remove an Order Status

Remove a custom status from your organisation's catalogue.

This only removes the status from your organisation's list. Orders already carrying the status keep it — their status is not rewritten, and GET /orders will still return it.

Removing a SmartRoutes default returns 400. A name your organisation has not added returns 404.

path Parameters
status
required
string [ 1 .. 255 ] characters
Example: Packed

Name of the status to remove, exactly as returned by GET /order-statuses, URL-encoded — for example Awaiting%20Parts for Awaiting Parts. Surrounding whitespace is part of the name and must be encoded too.

Responses

Response samples

Content type
application/json
{
  • "deleted": {
    }
}

Orders

Bulk Create or Update Orders

Create or update up to 100 orders in your depot in one request. Orders are matched on order_number. A new number creates an order. A number that already exists in your depot updates that order instead of creating a duplicate, and restores it if it was deleted. Send each order complete, because some fields you leave out are reset rather than kept. To change only some fields of an existing order, use PUT /orders/{id}.

The whole batch is checked before anything is saved, and any problem rejects all of it with 400 INVALID_INPUT. This includes:

  • more than 100 orders, or the same order_number twice in one request
  • an order with no address for its type (shipment needs both)
  • a skill, vehicle or third party that doesn't exist
  • an order_exchange.reference that isn't shared by exactly two orders

If your depot doesn't allow orders without customers, any order without a customer is rejected with 403 PERMISSION. Custom fields, capacities and tags that don't exist in your depot are skipped silently, not rejected.

Addresses sent without coordinates are geocoded. A geocoding failure doesn't fail the request. The order is still saved, and the failure is listed in errors with name GEOCODING_FAILED and the order's order_number. The response gives the id and order_number of every order you sent. Fetch any of them in full with GET /orders/{id}. New orders start as OPEN, or as your depot's configured starting status. Existing orders keep their current status.

Supports Idempotency: retrying with the same idempotency-key returns the original response without processing the orders again.

header Parameters
idempotency-key
string [ 1 .. 255 ] characters

Optional idempotency key for safe retries. See Idempotency for behaviour. 1–255 characters; UUID recommended.

Request Body schema: application/json
Array
order_number
required
string

Order number.

id (object) or account (object)

Optional. When omitted, the order is created without a customer association (customer_id will be null). Requires the 'Orders Without Customers' depot setting to be enabled.

type
required
string
Enum: "delivery" "pickup" "shipment"

Type of the order (delivery, pickup, or shipment).

priority
string

Priority of the order (Accepts 'P1', 'P2', 'P3').

delivery_contact_name
string

Name of the contact person.

delivery_contact_number
string

Contact number of the person.

delivery_contact_email
string <email>

Email of the contact person.

delivery_address
string

Delivery address.

delivery_country_code
string

Two letter country code, e.g. 'IE'

delivery_postcode
string

Postcode for delivery address.

delivery_lat
number

Latitude of the delivery location.

delivery_lng
number

Longitude of the delivery location.

delivery_duration
number

Duration for order delivery in minutes.

delivery_notes
string

Notes for delivery instructions.

Array of objects

List of time windows for order delivery.

delivery_date
string <date>

Date for order delivery.

delivery_available_days
Array of strings
Items Enum: "MONDAY" "TUESDAY" "WEDNESDAY" "THURSDAY" "FRIDAY" "SATURDAY" "SUNDAY"

List of available delivery days for the order.

pickup_address
string

Address for order pickup.

pickup_country_code
string

Two letter country code, e.g. 'IE'

pickup_postcode
string

Postcode for pickup address.

pickup_duration
number

Duration for order pickup in minutes.

pickup_lat
number

Latitude of the pickup location.

pickup_lng
number

Longitude of the pickup location.

pickup_notes
string

Notes for pickup instructions.

Array of objects

List of time windows for order pickup.

pickup_contact_name
string

Name of the contact person for pickup.

pickup_contact_number
string

Contact number of the person for pickup.

pickup_contact_email
string <email>

Email of the contact person for pickup.

pickup_available_days
Array of strings
Items Enum: "MONDAY" "TUESDAY" "WEDNESDAY" "THURSDAY" "FRIDAY" "SATURDAY" "SUNDAY"

List of available pickup days for the order.

object

Optional. Links this order to its partner as an exchange — a drop and a collect at one location served by the same vehicle in one consecutive visit. Provide the SAME reference on exactly two orders in the same request; the server pairs them and mints an internal exchange_id. A pair cannot be formed via single-order update.

parts
number

Number of parts in the order.

object
Array of objects
object

Third party object

order_value
number or null <double>

Total monetary value of the order, to 2 decimal places, at most 10,000,000. Settable only on an order with no valued line items — if any line item has a value, from product_unit_price or product_value, whether sent in this request or already stored, the value is derived from the lines and a non-null order_value returns 400. Omit to keep what is stored; null clears a manual value (and is accepted, with no effect, on a derived one); 0 means a genuinely zero-value order. When sending back an order read from GET /orders/{id}, remove order_value if its value_source is DERIVED — echoing a derived value returns 400. Orders are valued in the currency set on your organisation.

Array of objects

List of line items in the order.

skills
Array of strings

List of required skills for the order.

Array of objects

List of custom fields for the order.

Array of objects

List of capacities for the order. Either id or type is required.

tags
Array of strings

List of tag names (must already exist)

Responses

Request samples

Content type
application/json
[
  • {
    }
]

Response samples

Content type
application/json
{
  • "orders": [
    ],
  • "errors": [
    ]
}

Get Orders Page

List the orders in your depot, most recently updated first. Each order is a summary with these fields:

  • id, order_number, type and current status
  • customer (id and account)
  • created and updated
  • the stops it is planned on, with route, delivery outcome and proof of delivery
  • line_items, as ids only

Fetch an order with GET /orders/{id} for its full details.

Filters can be combined, and each one narrows the results:

  • status: the order's current status, case-insensitive. An unrecognised status returns 400 INVALID_INPUT.
  • date: orders scheduled for that day, as YYYY-MM-DD. Any other format returns 400 INVALID_INPUT.
  • updated_at_min: orders updated at or after this time. Use it to poll for changes.
  • customer_id and order_number: exact match.

Results are paged. limit sets the page size, up to 100 (the default). The Link header holds the URL of the next page. Request that URL as it is, because it already carries your filters. When the Link header has no rel=next URL, you have reached the last page. See Paging.

query Parameters
page_info
string

Information about the page for pagination. Generated by the OpenAPI

status
string

Status of the orders to filter.

customer_id
integer

Filter orders by the customer they belong to.

order_number
string

Filter orders by a specific order number

updated_at_min
string <date-time>

Minimum updated date and time for filtering.

date
string <date>
Example: date=2026-08-24

Return only orders scheduled for this date — when the order is due, not when it was created or updated. Use updated_at_min for those.

limit
integer
Default: 100

Maximum number of orders to retrieve per page.

Responses

Response samples

Content type
application/json
{
  • "orders": [
    ]
}

Get Order by ID

Fetch one order from your depot with all of its details. The response wraps the order in order. It includes:

  • addresses, contacts, dates and time windows
  • line items, each with its own status, quantities, capacities and custom fields
  • order-level capacities, custom fields, skills, tags, emails and attachments
  • order value fields, if order values are enabled for your depot
  • stops: every stop the order is planned on, with its route, delivery outcome and proof of delivery (photos, signatures, questionnaire submissions)

id is the SmartRoutes order ID returned by POST /orders and GET /orders. It is not your order number. To find an order by its order number, use GET /orders?order_number=.... If the order isn't in your depot or has been deleted, you get 404 RESOURCE_NOT_FOUND.

path Parameters
id
required
string

ID of the order to retrieve.

Responses

Response samples

Content type
application/json
{
  • "order_number": "ABC123",
  • "priority": "P1",
  • "customer_id": 101,
  • "delivery_contact_name": "John Doe",
  • "delivery_contact_number": "+1234567890",
  • "delivery_contact_email": "john.doe@example.com",
  • "delivery_address": "123 Main St 12345",
  • "delivery_lat": 12.345,
  • "delivery_lng": -67.89,
  • "delivery_notes": "Leave at the doorstep",
  • "delivery_date": "2023-01-01",
  • "delivery_duration": 30,
  • "delivery_available_days": [
    ],
  • "pickup_address": "456 Oak Ave 67890",
  • "pickup_lat": 12.678,
  • "pickup_lng": -67.432,
  • "pickup_notes": "Collect from reception",
  • "pickup_contact_name": "Jane Smith",
  • "pickup_contact_number": "+0987654321",
  • "pickup_contact_email": "jane.smith@example.com",
  • "pickup_available_days": [
    ],
  • "pickup_duration": 15,
  • "status": "OPEN",
  • "type": "delivery",
  • "parts": 2,
  • "created": "2022-04-29T16:12:08.000Z",
  • "updated": "2022-09-29T10:11:06.000Z",
  • "vehicle_assignment": {
    },
  • "emails": [
    ],
  • "customer": {
    },
  • "third_party": {
    },
  • "tags": [
    ],
  • "order_value": 27.75,
  • "currency": "EUR",
  • "value_source": "DERIVED",
  • "line_items": [
    ],
  • "capacities": [
    ],
  • "custom_fields": [
    ],
  • "time_windows": [
    ],
  • "skills": [
    ],
  • "stops": [
    ],
  • "attachments": [
    ]
}

Update Order by ID

Change some of the details of an existing order in your depot. Only the fields you send are changed. Everything else keeps its stored value. The response is {order, errors}, where order is the updated order in the same shape as GET /orders/{id}.

List fields replace the stored list instead of merging with it:

  • Sending line_items, custom_fields, capacities, tags, skills or emails replaces the order's whole list. An empty array clears it.
  • Line items are matched to existing ones by product_code. Stored line items that are missing from the list are removed.
  • Sending any time-window field (delivery_time_windows, pickup_time_windows or time_windows) replaces all of the order's time windows, both delivery and pickup.

If you change the delivery or pickup address without sending new coordinates, the new address is geocoded. A geocoding failure doesn't fail the update. The change is saved, and the failure is returned in errors with name GEOCODING_FAILED.

You get 400 INVALID_INPUT if:

  • the new order_number already belongs to another order in your depot
  • a skill, vehicle or third party doesn't exist
  • a type change would leave the order without the address its new type needs

If the order isn't in your depot, you get 404 RESOURCE_NOT_FOUND. The order's customer can't be changed here. order_exchange can only change the reference of an existing exchange pair. Pairs are created with POST /orders.

path Parameters
id
required
string

ID of the order to update.

Request Body schema: application/json
Schema not provided

Responses

Request samples

Content type
application/json
{
  • "order_number": "ABC123",
  • "priority": "P1",
  • "customer_id": 101,
  • "delivery_contact_name": "John Doe",
  • "delivery_contact_number": "+1234567890",
  • "delivery_contact_email": "john.doe@example.com",
  • "delivery_address": "123 Main St 12345",
  • "delivery_lat": 12.345,
  • "delivery_lng": -67.89,
  • "delivery_notes": "Leave at the doorstep",
  • "delivery_time_windows": [
    ],
  • "delivery_duration": 30,
  • "delivery_date": "2023-01-01",
  • "delivery_available_days": [
    ],
  • "type": "delivery",
  • "parts": 2,
  • "vehicle_assignment": {
    },
  • "emails": [
    ],
  • "updated": "2023-01-04T08:45:00Z",
  • "third_party": {
    },
  • "tags": [
    ],
  • "line_items": [
    ],
  • "capacities": [
    ],
  • "custom_fields": [
    ],
  • "skills": [
    ]
}

Response samples

Content type
application/json
{
  • "order": {
    },
  • "errors": [ ]
}

Add Attachment to Order by ID

Upload a single attachment file to the order. Only one file can be uploaded per request. Supported file types: pdf, png, jpg, jpeg. Maximum file size: 10MB. Maximum of 2 attachments are allowed per order overall.

path Parameters
id
required
string

The ID of the order to attach the file to.

Request Body schema: multipart/form-data
required
attachment
required
string <binary>

Upload a single file (allowed types: pdf, png, jpg, jpeg; max size 10MB).

Responses

Response samples

Content type
application/json
{
  • "id": 58,
  • "order_id": "451020",
  • "depot_id": 578,
  • "name": "SmartRoutes-Orders1.pdf",
  • "mime": "application/pdf",
  • "ext": "pdf",
  • "size": 473892,
  • "is_compressed": false,
  • "modified": "2025-09-01T14:02:02.922Z",
  • "created": "2025-09-01T14:02:02.922Z"
}

Delete Orders by Order Number

Delete an order from your depot using your own order number instead of the SmartRoutes id. The match is exact. URL-encode order numbers that contain characters such as /. The response gives the number of orders and line items deleted.

Deleting an order also deletes:

  • its line items, custom fields, capacities, time windows and other details
  • its links to any route stops and plans it was on
  • proof of delivery photos and signatures recorded against the order

If the order is one half of an exchange, its partner order is deleted too and counted in orders_count.

If no order in your depot has this number, the request still succeeds, with orders_count: 0. It does not return 404. If you later send the same order number to POST /orders, the order is restored under its original id.

path Parameters
order_number
required
string

Order number to delete orders.

Responses

Response samples

Content type
application/json
{
  • "deleted": {
    }
}

Plans

Dispatch a Plan

Dispatch every undispatched route on a plan. Each route is flagged as dispatched, its orders move to ROUTED, customer tracking portals are minted, superseded notification tasks are cancelled (with SMS credit refunded), planned-start notifications are scheduled, and each route is pushed to its assigned driver's app. Once every route on the plan is dispatched, the plan is marked dispatched and a plans.dispatched webhook is published.

As with GET and DELETE /plans/{id}, the id may be a plan id or the id of that plan's active solution. A solution id that is not the plan's active solution is rejected with 400.

Routes already dispatched individually are skipped and the rest are dispatched — routes in the response lists only the routes this call dispatched. A plan still being optimised is rejected with 400.

Dispatch is synchronous and can take up to two minutes: the response is returned only once every side effect has run (order statuses, customer tracking portals, notification scheduling and SMS credit refunds, driver push, external-system export). A 200 means the dispatch is live — there is nothing to poll.

The request takes no body. Dispatch is as-planned: no vehicle, driver or date override, and no re-optimisation.

Dispatch is not idempotent and not reversible. A repeat call answers 409 ALREADY_DISPATCHED, and there is no endpoint to undispatch. If the call times out (503), re-post it: a 409 means the first attempt succeeded.

path Parameters
id
required
integer
Example: 9001

ID of the plan to dispatch. The id of the plan's active solution is also accepted.

Responses

Response samples

Content type
application/json
{
  • "plan": {
    }
}

Optimise Orders

Async optimisation. Returns 200 immediately with a Plan id and an optimisation job id. The optimisation runs in the background; poll GET /plans/{id} to check progress.

Initial response: { "optimisation": { "id": "<uuid>", "in_progress": true }, "plan": { "id": <integer>, "dispatched": false } }

Polling — GET /plans/{id}:

  • Completed: plan.routes is non-empty (or plan.unserved is non-empty if no routes are viable). Either signals the optimisation has finished.
  • Still running: plan.routes and plan.unserved are both empty.
  • Failed: there is no terminal failure signal on the Plan resource today. A failed job leaves the Plan in the same "empty arrays" state as a still-running job. Implement a client-side hard timeout — recommended 10 minutes — after which the job should be considered abandoned.

Recommended polling cadence:

  • First minute: every 2 seconds.
  • After 1 minute: every 10 seconds, exponential backoff up to 30 seconds.
  • Hard timeout: 10 minutes total.

Webhook coupling: the plans.created webhook fires immediately after the Plan is persisted — before the optimisation job has completed. A webhook delivery does not imply the optimisation finished. Consumers receiving plans.created should still poll GET /plans/{id} to learn the outcome.

Concurrency limits per depot (queue, not timeout): STANDARD 100, LONG 100, DIST 7, BOOKING_CHECK 1. No documented per-job timeout; typical jobs complete in 5–30 seconds.

Idempotency: Supports the idempotency-key request header for safe retries. See the Idempotency section in the API description.

header Parameters
idempotency-key
string [ 1 .. 255 ] characters

Optional idempotency key for safe retries. See Idempotency for behaviour. 1–255 characters; UUID recommended.

Request Body schema: application/json
delivery_date
required
string <date>

The delivery date YYYY-MM-DD for optimizing orders.

object
required
object
Array of objects or null
zone_group_id
integer

The ID of the zone group for optimization.

Responses

Request samples

Content type
application/json
{
  • "delivery_date": "2023-12-31",
  • "settings": {
    },
  • "zone_group_id": 123
}

Response samples

Content type
application/json
{
  • "optimisation": {
    },
  • "plan": {
    }
}

Optimise Orders For IDs

Async optimisation for a specific set of order IDs. Same lifecycle and polling semantics as POST /plans/optimise/orders — see that endpoint's description for completion detection, polling cadence, and the failure-signal gap. Supports the idempotency-key request header for safe retries.

header Parameters
idempotency-key
string [ 1 .. 255 ] characters

Optional idempotency key for safe retries. See Idempotency for behaviour. 1–255 characters; UUID recommended.

Request Body schema: application/json
required
Array of objects
object
required
object
Array of objects or null
zone_group_id
integer

The ID of the zone group for optimization.

Responses

Request samples

Content type
application/json
{
  • "orders": [
    ],
  • "settings": {
    },
  • "zone_group_id": 123
}

Response samples

Content type
application/json
{
  • "optimisation": {
    },
  • "plan": {
    }
}

Get Optimisation Job by ID

Check whether an optimisation started with POST /plans/optimise/orders or POST /plans/optimise/orders-for-ids is still running. Pass the optimisation.id (a UUID) that the call returned. The response has the same shape as that call's response: optimisation.in_progress, plus the plan the job writes into. plan.dispatched becomes true once the plan is dispatched, for example by auto_dispatch.

in_progress: false means the job has stopped, not that it succeeded. A failed optimisation also reports false, and this endpoint does not tell you which outcome you got. Fetch GET /plans/{id} with plan.id to see the result, and use the polling cadence and hard timeout described under POST /plans/optimise/orders. An optimisation id that does not exist, or that belongs to another depot, answers 404 RESOURCE_NOT_FOUND.

path Parameters
id
required
string

ID of the current optimisation to retrieve.

Responses

Response samples

Content type
application/json
{
  • "optimisation": {
    },
  • "plan": {
    }
}

Get Plan by ID

Fetch a plan in your depot with its full routes and its unserved stops (the stops the optimiser could not fit on any route). Each route includes its vehicle, timings and stops in visiting order. Each stop includes its orders and line items, live progress and proof of delivery, in the same shape as GET /routes/{id}. A plan's routes are the routes from its current optimisation, plus any route dispatched from an earlier optimisation of the same plan.

While the optimisation started by POST /plans/optimise/orders is still running on a new plan, routes and unserved are both empty arrays. See that endpoint for polling guidance and its limits on failure signals, or poll GET /plans/optimise/{id}. To list plans without their stops, use GET /plans.

path Parameters
id
required
number

ID of the plan to retrieve.

Responses

Response samples

Content type
application/json
{
  • "plan": {
    }
}

Delete Plan by ID

Permanently delete a plan from your depot, along with every route and stop on it. This includes routes from earlier optimisations of the plan and routes that have already been dispatched. Deleting a dispatched plan is allowed, and there is no undo.

What happens to related data:

  • The orders themselves are not deleted.
  • Orders on dispatched routes move back to OPEN so they can be planned again, and an orders.status webhook is published for each one. Orders on routes that were never dispatched keep their current status.
  • Scheduled customer notifications for the removed stops are cancelled, and the SMS credit reserved for them is refunded.
  • A routes.deleted webhook is published for each dispatched route removed, and a plans.deleted webhook is published for the plan.

A plan that does not exist, or that belongs to another depot, answers 404 RESOURCE_NOT_FOUND.

path Parameters
id
required
number

ID of the plan to delete.

Responses

Response samples

Content type
application/json
{
  • "deleted": {
    }
}

Get Plans Page

Page through the plans in your depot, most recently updated first. Each plan carries its dispatched flag, total_time, created and updated timestamps, and a summary of each of its routes: the routes from its current optimisation, plus any route dispatched from an earlier optimisation of the same plan. Route summaries give a stop count, not the stops themselves. To get stops, orders and proof of delivery, call GET /plans/{id} or GET /routes/{id}.

Results are paged with the Link header and limit (1–100, default 100). See Paging. Keep following the rel=next URL until a response has none. updated_at_min returns only plans whose own updated timestamp is at or after the given time, and the rel=next URL carries it forward. A malformed page_info is rejected with 400 INVALID_INPUT. An updated_at_min in an unrecognised format, or a limit outside 1–100, is ignored rather than rejected.

A plan whose optimisation has not produced a result yet is listed with only id, total_time: 0 and an empty routes array. Deleted plans drop out of the list; the plans.deleted webhook tells you when a plan is deleted.

query Parameters
page_info
string

Information about the page for pagination. Generated by the OpenAPI

updated_at_min
string <date-time>

Minimum updated date and time for filtering.

limit
integer
Default: 100

Maximum number of plans to retrieve per page.

Responses

Response samples

Content type
application/json
{
  • "plans": [
    ]
}

Proof of Delivery

Get Proof of Delivery Photo

Retrieve the compressed proof-of-delivery photo by its ID. By default returns the raw image binary. Use the encoding query parameter to request a Base64-encoded JSON response instead.

Photos are compressed asynchronously after the driver uploads them. If the photo exists but compression has not finished, this endpoint returns 202 Accepted with a Retry-After header and the photo's model (id and is_compressed: false) as the JSON body, instead of image data — retry after the interval given. The photos arrays on order and plan responses list every synced photo as soon as it exists, including photos that are still compressing, so a URL taken from one of those responses may itself return a 202 if followed before compression finishes — apply the same retry logic.

path Parameters
id
required
integer

The ID of the photo to retrieve.

query Parameters
encoding
string
Value: "base64"

Set to base64 to receive the image as a Base64-encoded string in a JSON response instead of raw binary.

Responses

Response samples

Content type
No sample

Get Proof of Delivery Signature

Retrieve a proof-of-delivery signature by its ID. By default returns the raw image binary. Use the encoding query parameter to request a Base64-encoded JSON response instead.

path Parameters
id
required
integer

The ID of the signature to retrieve.

query Parameters
encoding
string
Value: "base64"

Set to base64 to receive the image as a Base64-encoded string in a JSON response instead of raw binary.

Responses

Response samples

Content type
No sample

Routes

Dispatch a Route

Dispatch a single optimised route. The route is flagged as dispatched, its orders move to ROUTED, customer tracking portals are minted, superseded notification tasks are cancelled (with SMS credit refunded), planned-start notifications are scheduled, and the route is pushed to the assigned driver's app.

The route must have a vehicle assigned — a route whose vehicle was unassigned after planning is rejected with 400. If this route is the last undispatched route on its plan, the plan itself becomes dispatched and a plans.dispatched webhook is published.

Dispatch is synchronous and can take up to two minutes: the response is returned only once every side effect has run (order statuses, customer tracking portals, notification scheduling and SMS credit refunds, driver push, external-system export). A 200 means the dispatch is live — there is nothing to poll.

The request takes no body. Dispatch is as-planned: no vehicle, driver or date override, and no re-optimisation.

Dispatch is not idempotent and not reversible. A repeat call answers 409 ALREADY_DISPATCHED, and there is no endpoint to undispatch. If the call times out (503), re-post it: a 409 means the first attempt succeeded.

path Parameters
id
required
integer
Example: 55123

ID of the route to dispatch.

Responses

Response samples

Content type
application/json
{
  • "route": {
    }
}

Remove Orders from a Route

Bulk-remove orders from a single route. The orders drop back to unrouted (their status returns to OPEN); they are not moved to another route. This is a partial-success bulk operation returning { orders, errors } (the same envelope as POST /orders): one un-removable order never fails the rest of the batch.

The route id is given in the path (/routes/{id}/remove-orders) and scopes the whole call — every order must be on that route. Each order is identified by id (depot-scoped, preferred) and/or order_number — at least one is required per entry; if both are given they must resolve to the same order. An order that is not on the given route is reported as NOT_ON_ROUTE.

orders and errors are NOT mutually exclusive. orders lists every order that resolved to a real record, each with the stops it was taken off ({ id, route: { id } }) — an empty stops array means nothing was removed from it. errors is a parallel list of problems; an order that exists but could not be removed (e.g. it is on a dispatched route, or not on the given route) appears in BOTH orders (with empty stops) and errors. An order reference that does not resolve at all (ORDER_NOT_FOUND) appears only in errors.

Each errors entry is a { name, description, details } envelope where name is the failure reason and details carries at least the id / order_number. Failure names include ORDER_NOT_FOUND, ID_NUMBER_MISMATCH, NOT_ON_ROUTE, ROUTE_DISPATCHED, ORDER_COMPLETED, STOP_REBUILD_FAILED, STALE_VISIT and REMOVAL_FAILED. By default orders on dispatched (live) routes are reported as ROUTE_DISPATCHED; set settings.allow_dispatched to true to edit live routes.

Set settings.reoptimize to true to re-sequence each affected route after the removal. This runs synchronously — the request blocks until each affected route has been solved and applied, so it can take seconds to minutes. The removal is applied first and the affected route is then re-sequenced. A reoptimised order is returned in orders with the stops it was taken off, the same as a normal removal. If the removal itself fails the order is reported with REMOVAL_FAILED (as in a non-reoptimise call); if only the re-sequencing step fails after the order was already removed, the order still counts as removed (returned in orders) and the re-sequencing failure is not surfaced per order.

Multi-order (consolidated) stops are handled per stop: if every order on the stop is being removed the whole stop is deleted, otherwise the stop is kept and rebuilt with just the remaining orders (the removed order drops to OPEN, the others are unaffected). In the rare case the remaining orders cannot be re-consolidated into a single stop, that order is reported with STOP_REBUILD_FAILED and the stop is left untouched.

path Parameters
id
required
integer
Example: 4567

Route id every order must belong to; scopes the whole call.

Request Body schema: application/json
required
required
Array of objects [ 1 .. 10 ] items

1..10 order references to remove.

object

Responses

Request samples

Content type
application/json
{
  • "orders": [
    ],
  • "settings": {
    }
}

Response samples

Content type
application/json
{
  • "orders": [
    ],
  • "errors": [
    ]
}

Add Orders to a Route

Bulk-add existing unrouted orders to a single route, without re-running a full plan optimisation. The orders must already exist — this endpoint does not create them. This is a partial-success bulk operation returning { orders, errors } (the same envelope as POST /orders and POST /routes/{id}/remove-orders): one un-addable order never fails the rest of the batch.

The route id is given in the path (/routes/{id}/add-orders) and scopes the whole call. Each order is identified by id (depot-scoped, preferred) and/or order_number — at least one is required per entry; if both are given they must resolve to the same order.

Placement. By default each order is APPENDED to the end of the route, as a new stop before the end stop, in the order given in the request. Nothing else on the route moves and no solve runs, so the call is fast and predictable. Two consequences: appending does NOT check feasibility (vehicle capacity, driver skills and time windows are not validated — the stop is placed because you asked for it), and a route can end up with two stops at the same address, because an added order is always a new stop and is never merged into a stop the route already has there. Callers who need a feasible, sensibly-sequenced route use settings.reoptimize. There is no per-order position field in this version.

Re-optimising. Set settings.reoptimize to true to re-solve the whole route so the new stops are placed by the solver rather than appended. This runs synchronously — the request blocks until the route has been solved and applied, so it can take seconds to minutes. It also re-sequences stops you never mentioned, and the solver may decline to place an order it cannot serve, which is reported as NOT_PLACED for that order. A route whose existing stops plus the requested additions exceed 120 is reported as REOPTIMIZE_LIMIT and no solve is attempted.

orders and errors are NOT mutually exclusive. orders lists every order that resolved to a real record, each with the stops it was added to ({ id, route: { id } }) — an empty stops array means it was not added. errors is a parallel list of problems; an order that exists but could not be added appears in BOTH orders (with empty stops) and errors. An order reference that does not resolve at all (ORDER_NOT_FOUND) appears only in errors. A response never reports an add that is not backed by a real stop: what landed is confirmed against the route after the change is applied.

Each errors entry is a { name, description, details } envelope where name is the failure reason and details carries at least the id / order_number. Failure names are ORDER_NOT_FOUND, ID_NUMBER_MISMATCH, ROUTE_DISPATCHED, ALREADY_ROUTED, PREVIOUSLY_ON_ROUTE, ORDER_COMPLETED, ORDER_TYPE_UNSUPPORTED, STOP_BUILD_FAILED, NOT_PLACED, REOPTIMIZE_LIMIT and ADD_FAILED. An order already on an active route is reported as ALREADY_ROUTED and is never moved implicitly — remove it from that route first, then add it to the one you want. Orders that have already been delivered or marked undelivered (in full or in part) are reported as ORDER_COMPLETED. Shipment orders (a pickup and delivery pair) and exchange-linked orders are not supported and are reported as ORDER_TYPE_UNSUPPORTED.

Only orders new to the route can be added. An order that has been on this route before — added and then removed again — is reported as PREVIOUSLY_ON_ROUTE and is not re-added. This holds on both the append and the reoptimize path, and it applies to the route's whole history, not just its current stops. To get a removed order back onto the same route, re-run a plan optimisation (POST /plans/optimise/orders-for-ids) rather than adding it; to move an order between routes, remove it from the first and add it to the second, which is a different route and so unaffected.

By default adding to a dispatched (live) route is refused and every order is reported as ROUTE_DISPATCHED; set settings.allow_dispatched to true to edit live routes. Adding a stop to a route a driver is already running changes their live workload, so it is opt-in per call: when allowed, the customer is notified for the new stop, the driver's app is pushed the updated route, and the change is written to the route audit log.

path Parameters
id
required
integer
Example: 4567

Route id to add the orders to; scopes the whole call.

Request Body schema: application/json
required
required
Array of objects [ 1 .. 10 ] items

1..10 order references to add. Appended to the route in this order.

object

Responses

Request samples

Content type
application/json
{
  • "orders": [
    ],
  • "settings": {
    }
}

Response samples

Content type
application/json
{
  • "orders": [
    ],
  • "errors": [
    ]
}

Get Route by ID

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
required
number

ID of the route to retrieve.

Responses

Response samples

Content type
application/json
{
  • "id": 12345,
  • "plan_id": 67,
  • "name": "Route 1",
  • "dispatched": true,
  • "is_locked": false,
  • "date": "2023-12-31",
  • "start_time": "08:00",
  • "started_ts": "2024-07-09T09:00:00.000Z",
  • "end_time": "16:00",
  • "completed_ts": "2024-07-09T17:18:40.000Z",
  • "total_time": 480,
  • "travel_time": 300,
  • "planned_distance": 50,
  • "actual_distance": 45,
  • "vehicle": {
    },
  • "stops": [
    ],
  • "created": "2024-06-19T08:56:43.000Z",
  • "updated": "2024-06-19T08:56:43.000Z",
  • "driver_questionnaire_submissions": [
    ]
}

Update Route by ID

Update a route based on ID. This is a partial update: send only the fields to change, and omitted fields are left as they are. is_locked is currently the only editable field.

is_locked is a stored flag only. This API does not enforce it: a locked route can still be reversed, dispatched and have orders added or removed.

Returns the updated route in the same shape as GET /routes/{id}.

path Parameters
id
required
integer
Example: 12345

ID of the route to update.

Request Body schema: application/json
required
non-empty
is_locked
boolean

Mark the route as locked (true) or unlocked (false).

Responses

Request samples

Content type
application/json
{
  • "is_locked": true
}

Response samples

Content type
application/json
{
  • "route": {
    }
}

Reverse Route by ID

Reverse the order in which a route visits its stops, for example when the driver would rather run the route from the far end. The route's start and end points stay where they are, and breaks keep their place in the sequence. Every other stop is reversed, and the route's planned times and distances are recalculated for the new order. The response is the updated route in the same shape as GET /routes/{id}.

Reversing deletes every stop and creates it again, so stop ids change. Re-read the route rather than reusing stop ids from before the call. For the same reason, reverse a route before the driver starts it, not once stops are under way or completed. Reversing a dispatched route publishes a routes.edited webhook. is_locked is not enforced (see PUT /routes/{id}).

A route with fewer than two stops (not counting its start, end and breaks) is rejected with 400 INVALID_INPUT. The call is not idempotent: calling it a second time puts the stops back in their original order. A 503 SERVICE_UNAVAILABLE does not prove the reverse failed, so check GET /routes/{id} before you retry.

path Parameters
id
required
number

ID of the route to reverse.

Responses

Response samples

Content type
application/json
{
  • "id": 12345,
  • "plan_id": 67,
  • "name": "Route 1",
  • "date": "2023-12-31",
  • "start_time": "08:00",
  • "started_ts": "2024-07-09T09:00:00.000Z",
  • "end_time": "16:00",
  • "completed_ts": "2024-07-09T17:18:40.000Z",
  • "total_time": 480,
  • "travel_time": 300,
  • "planned_distance": 50,
  • "actual_distance": 45,
  • "vehicle": {
    },
  • "stops": [
    ]
}

Get Routes Page

Page through the routes in your depot, most recently updated first. Each entry is a route summary: name, plan_id, date, the dispatched and is_locked flags, stop count, planned and actual times and distances, and timestamps. The stops themselves are not included; call GET /routes/{id} for stops, orders and proof of delivery.

The list covers every route in your depot across all plans, not only each plan's current routes. A route left behind when a plan was optimised again can still appear. To see the routes currently on a plan, use GET /plans/{id}. Deleted routes drop out of the list, and the routes.deleted webhook tells you about deleted dispatched routes.

Results are paged with the Link header and limit (1–100, default 100). See Paging. Keep following the rel=next URL until a response has none. updated_at_min returns only routes whose updated timestamp is at or after the given time, and the rel=next URL carries it forward. A malformed page_info is rejected with 400 INVALID_INPUT. An updated_at_min in an unrecognised format, or a limit outside 1–100, is ignored rather than rejected.

query Parameters
page_info
string

Information about the page for pagination. Generated by the OpenAPI

updated_at_min
string <date-time>

Minimum updated date and time for filtering.

limit
integer
Default: 100

Maximum number of routes to retrieve per page.

Responses

Response samples

Content type
application/json
{
  • "routes": [
    ]
}

Third Parties

Get All Third Parties

Lists the third parties, such as subcontracted carriers, that orders can be assigned to. Use it to look up the id you send as third_party.id on POST /orders and PUT /orders/{id}.

The list covers your whole organisation, not just the depot your API key belongs to, because an order can be assigned to any third party in the organisation. Each entry has id and name, under a third_parties key. Deleted third parties are left out, and the list is returned in full, with no paging.

Responses

Response samples

Content type
application/json
{
  • "third-parties": [
    ]
}

Vehicles

Add Vehicle

Adds a vehicle to your depot's fleet so it can be used when building plans. The new vehicle is returned under vehicle, in the same shape as GET /vehicles/{id}.

  • name (up to 30 characters), availability and start_location are required. start_location and end_location accept depot or other. With other, you must send the matching *_lat and *_lng. Addresses are stored as given and are not geocoded.
  • With availability: shift, shift_start must be before shift_end (24-hour HH:mm). With break: true, break_start, break_end and break_duration are all required, and break_duration (in minutes) must be shorter than the break window.
  • capacities are matched by id against your depot's capacity types (see GET /capacities). Unknown IDs are dropped without an error. Any skills name not yet in your organisation is created. Names match exactly and are case-sensitive.
  • active defaults to true.

Each organisation has a vehicle limit across all its depots. Adding a vehicle beyond that limit returns 400 INVALID_INPUT. This endpoint supports Idempotency.

header Parameters
idempotency-key
string [ 1 .. 255 ] characters

Optional idempotency key for safe retries. See Idempotency for behaviour. 1–255 characters; UUID recommended.

Request Body schema: application/json
name
string

Name of the vehicle.

availability
required
string
Enum: "shift" "full_day" "always"

Availability status of the vehicle.

shift_start
string or null <time>

Start time of the vehicle's shift (if availability is 'shift').

shift_end
string or null <time>

End time of the vehicle's shift (if availability is 'shift').

start_location
required
string
Enum: "depot" "other" "app-location"

Location type where the vehicle starts.

start_address
string or null

Address where the vehicle starts (if location is 'other').

start_lng
number or null

Longitude of the starting location (if location is 'other').

start_lat
number or null

Latitude of the starting location (if location is 'other').

end_location
required
string
Enum: "depot" "other" "none"

Location type where the vehicle ends.

end_address
string or null

Address where the vehicle ends (if location is 'other').

end_lng
number or null

Longitude of the ending location (if location is 'other').

end_lat
number or null

Latitude of the ending location (if location is 'other').

break
boolean

Flag indicating whether the vehicle has a break.

break_start
string or null <time>

Start time of the vehicle's break (if it has a break).

break_end
string or null <time>

End time of the vehicle's break (if it has a break).

break_duration
number or null

Duration of the vehicle's break in minutes (if it has a break).

active
boolean

Flag indicating whether the vehicle is active.

skills
Array of strings

Array of skills associated with the vehicle.

Array of objects

Array of capacities associated with the vehicle.

Responses

Request samples

Content type
application/json
{
  • "name": "Vehicle Name",
  • "availability": "shift",
  • "shift_start": "08:00",
  • "shift_end": "17:30",
  • "start_location": "depot",
  • "end_location": "none",
  • "break": false,
  • "capacities": [
    ]
}

Response samples

Content type
application/json
{
  • "vehicle": {
    }
}

Get All Vehicles

Returns every vehicle in your depot under a vehicles array, inactive vehicles included. The list isn't paged and has no filters. Each vehicle has the same shape as GET /vehicles/{id}: name, active, capacities, skills, availability and shift times, start and end locations, and break settings. Each vehicle also has a shift array with its shift schedule and breaks, as set up in the SmartRoutes web app. Deleted vehicles are not returned.

Responses

Response samples

Content type
application/json
{
  • "vehicles": [
    ]
}

Update Vehicle by ID

Updates one vehicle in your depot. This is a partial update: only the fields you send change. The same rules as POST /vehicles apply to the fields you do send. For example, switching start_location to other requires start_lat and start_lng in the same request, and setting break: true requires the break times and duration. Sending capacities or skills replaces the vehicle's whole set (an empty array clears it), and leaving them out keeps the existing set.

Changes to availability, shift times and break are applied to the vehicle's default shift. If the vehicle has a per-day or multi-shift schedule set up in the SmartRoutes web app, that schedule is left unchanged. The updated vehicle is returned under vehicle. An id that doesn't belong to a vehicle in your depot returns 400 INVALID_INPUT.

path Parameters
id
required
number

ID of the vehicle to update.

Request Body schema: application/json
name
string

Name of the vehicle.

availability
required
string
Enum: "shift" "full_day" "always"

Availability status of the vehicle.

shift_start
string or null <time>

Start time of the vehicle's shift (if availability is 'shift').

shift_end
string or null <time>

End time of the vehicle's shift (if availability is 'shift').

start_location
required
string
Enum: "depot" "other" "app-location"

Location type where the vehicle starts.

start_address
string or null

Address where the vehicle starts (if location is 'other').

start_lng
number or null

Longitude of the starting location (if location is 'other').

start_lat
number or null

Latitude of the starting location (if location is 'other').

end_location
required
string
Enum: "depot" "other" "none"

Location type where the vehicle ends.

end_address
string or null

Address where the vehicle ends (if location is 'other').

end_lng
number or null

Longitude of the ending location (if location is 'other').

end_lat
number or null

Latitude of the ending location (if location is 'other').

break
boolean

Flag indicating whether the vehicle has a break.

break_start
string or null <time>

Start time of the vehicle's break (if it has a break).

break_end
string or null <time>

End time of the vehicle's break (if it has a break).

break_duration
number or null

Duration of the vehicle's break in minutes (if it has a break).

active
boolean

Flag indicating whether the vehicle is active.

skills
Array of strings

Array of skills associated with the vehicle.

Array of objects

Array of capacities associated with the vehicle.

Responses

Request samples

Content type
application/json
{
  • "name": "Vehicle Name",
  • "availability": "shift",
  • "shift_start": "08:00",
  • "shift_end": "17:30",
  • "start_location": "depot",
  • "end_location": "none",
  • "break": false,
  • "capacities": [
    ]
}

Response samples

Content type
application/json
{
  • "vehicle": {
    }
}

Get Vehicle by ID

Returns one vehicle from your depot, nested under a vehicle key. The response includes the vehicle's capacities, skills, availability, start and end locations, break settings, and a shift array with its shift schedule and breaks. shift_start/shift_end appear only when availability is shift. Start or end address and coordinates appear only when that location is other. Break times appear only when break is true. An id that doesn't belong to a vehicle in your depot, including a deleted one, returns 400 INVALID_INPUT.

path Parameters
id
required
number

ID of the vehicle to retrieve.

Responses

Response samples

Content type
application/json
{
  • "vehicle": {
    }
}

Delete Vehicle by ID

Removes a vehicle from your depot's fleet. Afterwards, the vehicle is no longer returned by GET /vehicles and can't be fetched or updated. Its capacities and skills are removed. Any assignments of this vehicle to orders, customers or plan visits are also removed, so those orders and customers are no longer tied to it. The response is {"deleted": {"vehicles_count": 1}}.

path Parameters
id
required
number

ID of the vehicle to delete.

Responses

Response samples

Content type
application/json
{
  • "deleted": {
    }
}

Zone Groups

Get Zone Groups

Lists the zone groups set up for the depot your API key belongs to. A zone group is a named set of geographic zones. Use it to look up the id you pass as zone_group_id to POST /plans/optimise/orders and POST /plans/optimise/orders-for-ids, or to POST /zone-groups/{id}/bounds/contains to find which zone a location falls in.

Each entry has id, name, created and updated. The zones inside each group are not included. Deleted zone groups are left out, and the list is returned in full, with no paging.

Responses

Response samples

Content type
application/json
{
  • "zone_groups": [
    ]
}

Get Zone for Location

Finds which zone of a zone group contains a location, for example to work out a delivery area before you create an order. Pass the zone group id in the path. You can get it from GET /zone-groups.

Send location.lat and location.lng if you have them. If either is missing, the address, postcode and country are geocoded first. If the location can't be geocoded, you get 400 INVALID_INPUT.

  • If the point is inside a zone, the response is {"zone": {"id", "name", "zone_group": {"id", "name"}}}.
  • If the point is outside every zone in the group, the response is 200 with {"zone": null}.
  • If zones overlap, only one matching zone is returned.
  • If the zone group doesn't exist in this depot, or has no zones, the response is 404 RESOURCE_NOT_FOUND.
path Parameters
id
required
number

ID of the zone group.

Request Body schema: application/json
required
object

Location information for zone. If no lat, lng is provided, address or postcode is required.

Responses

Request samples

Content type
application/json
{
  • "location": {
    }
}

Response samples

Content type
application/json
{
  • "zone": {
    }
}