API Documentation
Everything you need to integrate your delivery platform with RouteMate. Import jobs, optimize routes, and track deliveries via our REST API.
Contents
Getting Started
The RouteMate Integration API allows you to programmatically import delivery jobs, have routes automatically optimized, and query job status — all from your existing systems (WMS, TMS, e-commerce platforms, or custom software).
Base URL
https://api.routemate.app/v1How It Works
- Create an Integration — In the RouteMate web app, go to Integrations and click Create Integration. You'll receive a client_id and client_secret.
- Get an Access Token — Exchange your credentials for a short-lived bearer token (valid 1 hour).
- Import a Job — POST your delivery stops. RouteMate will geocode addresses, optimize the route order, and assign the job to a driver.
- Query Status — Check the progress of your job at any time to see which stops have been completed.
Requirements
- A RouteMate account on the Team or Enterprise plan
- An organization created in the RouteMate web app
- An active integration with client credentials
Client Credentials Flow
The API uses OAuth2 client credentials. Your secret is never stored — only a SHA-256 hash is kept on our servers.
Security Model
- Client secrets are hashed with SHA-256 and never stored in plaintext
- Access tokens are opaque (not JWTs), valid for 1 hour
- Tokens are single-use and scoped to your integration
- All requests must be made over HTTPS
- Include the token in every request: Authorization: Bearer <token>
Important: Your client_secret is shown only once when you create the integration. Store it securely (for example in environment variables or a secrets manager). If lost, you must regenerate it from the RouteMate dashboard.
Get Access Token
/v1/integration-tokenRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
| client_id | string | Yes | Your integration's client ID (starts with rm_ci_) |
| client_secret | string | Yes | Your integration's client secret (starts with rm_cs_) |
Example Request
curl -X POST https://api.routemate.app/v1/integration-token \
-H "Content-Type: application/json" \
-d '{
"client_id": "rm_ci_abc123...",
"client_secret": "rm_cs_xyz789..."
}'Success Response 200
{
"access_token": "rm_at_e5988dd1b91a4ff3827c5d2ed416ccbe37ae227a",
"token_type": "Bearer",
"expires_in": 3600
}Error Responses
400Missing client_id or client_secret
401Invalid credentials (wrong secret or unknown client_id)
403Integration has been disabled
Import Job & Stops
/v1/integration-importRequest Headers
| Field | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Yes | Bearer <access_token> |
| Content-Type | string | Yes | application/json |
| Idempotency-Key | string | No | Unique key to prevent duplicate imports (valid for 24 hours) |
Request Body — Job Fields
| Field | Type | Required | Description |
|---|---|---|---|
| external_job_id | string | Yes | Your unique identifier for this job (must be unique per integration) |
| title | string | Yes | Job title (e.g., "Morning Deliveries - Zone A") |
| driver_email | string | Yes | Email of the driver to assign. If the driver has a RouteMate account, they'll see the route in their app. If not, an invitation is created. |
| scheduled_date | string | No | Scheduled date in ISO format (YYYY-MM-DD) |
| timezone | string | No | IANA timezone (e.g., "Australia/Brisbane", "America/New_York") |
| metadata | object | No | Custom key-value data attached to the job |
| stops | array | Yes | Array of stop objects (see Stop Fields below) |
Example Request
curl -X POST https://api.routemate.app/v1/integration-import \
-H "Authorization: Bearer rm_at_your_token_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-batch-2026-03-09-001" \
-d '{
"external_job_id": "BATCH-2026-03-09-A",
"title": "Morning Deliveries - Zone A",
"driver_email": "driver@example.com",
"scheduled_date": "2026-03-09",
"timezone": "Australia/Brisbane",
"stops": [
{
"external_stop_id": "ORD-1001",
"address": "1 George St, Brisbane QLD 4000, Australia",
"label": "John Smith",
"notes": "Leave at front door",
"priority": 1,
"parcel_count": 2
},
{
"external_stop_id": "ORD-1002",
"address": "100 Queen St, Brisbane QLD 4000, Australia",
"label": "Jane Doe",
"notes": "Ring doorbell",
"priority": 2
},
{
"external_stop_id": "ORD-1003",
"address": "South Bank Parklands, Brisbane QLD 4101, Australia",
"label": "Bob Wilson",
"priority": 3,
"parcel_count": 1
}
]
}'Success Response 200
{
"job_id": "e7ad097b-bc51-4d01-9b04-0e372d80613a",
"external_job_id": "BATCH-2026-03-09-A",
"route_id": "1c2a8c0b-1b69-4a04-85b1-608b2ebe3878",
"driver_email": "driver@example.com",
"driver_resolved": true,
"driver_user_id": "573b6bee-aa3d-4cb0-af87-620bba0db049",
"stops_imported": 3,
"stops_geocoded": 3,
"stops_geocode_failed": 0,
"optimization": {
"total_distance_km": 12.4,
"total_duration_minutes": 25,
"encoded_polyline": "fvmfDqmfe\\bPtA..."
},
"stops": [
{
"external_stop_id": "ORD-1001",
"address": "1 George St, Brisbane QLD 4000, Australia",
"latitude": -27.4710,
"longitude": 153.0234,
"label": "John Smith",
"notes": "Leave at front door",
"sort_order": 0,
"priority": 1,
"parcel_count": 2,
"status": "pending"
},
{
"external_stop_id": "ORD-1003",
"address": "South Bank Parklands, Brisbane QLD 4101, Australia",
"latitude": -27.4753,
"longitude": 153.0208,
"label": "Bob Wilson",
"notes": null,
"sort_order": 1,
"priority": 3,
"parcel_count": 1,
"status": "pending"
},
{
"external_stop_id": "ORD-1002",
"address": "100 Queen St, Brisbane QLD 4000, Australia",
"latitude": -27.4688,
"longitude": 153.0281,
"label": "Jane Doe",
"notes": "Ring doorbell",
"sort_order": 2,
"priority": 2,
"parcel_count": 1,
"status": "pending"
}
],
"deep_link": "https://app.routemate.app/app/jobs/e7ad097b-...",
"warnings": [
{ "code": "ADDRESS_GEOCODED", "message": "3 stop(s) were geocoded from address text" },
{ "code": "ROUTE_OPTIMIZED", "message": "Route optimized: 12.4 km, 25 min" }
]
}Note: Note: The stops array is returned in optimized delivery order (by sort_order), not the order you submitted them. The sort_order field indicates the recommended delivery sequence.
Warning Codes
| Field | Type | Required | Description |
|---|---|---|---|
| ROUTE_OPTIMIZED | info | No | Route was successfully optimized with distance and duration |
| ADDRESS_GEOCODED | info | No | One or more stops were geocoded from address text |
| DRIVER_AUTO_ADDED | info | No | Driver had a RouteMate account and was auto-added to your organization |
| DRIVER_NOT_FOUND | warning | No | No RouteMate account found for the email. An invitation was created. |
| GEOCODE_FAILED | warning | No | Could not geocode a stop's address. Provide latitude/longitude instead. |
Query Job Status
/v1/integration-jobsQuery Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| external_job_id | string | No | Your external job ID (the one you provided during import) |
| job_id | string | No | RouteMate's internal job UUID |
At least one of external_job_id or job_id must be provided.
Example Request
curl "https://api.routemate.app/v1/integration-jobs?external_job_id=BATCH-2026-03-09-A" \
-H "Authorization: Bearer rm_at_your_token_here"Success Response 200
{
"job_id": "e7ad097b-bc51-4d01-9b04-0e372d80613a",
"external_job_id": "BATCH-2026-03-09-A",
"title": "Morning Deliveries - Zone A",
"status": "assigned",
"driver_email": "driver@example.com",
"driver_resolved": true,
"driver_user_id": "573b6bee-...",
"route_id": "1c2a8c0b-...",
"scheduled_date": "2026-03-09",
"timezone": "Australia/Brisbane",
"metadata": {},
"stop_count": 3,
"status_counts": {
"pending": 2,
"completed": 1
},
"deep_link": "https://app.routemate.app/app/jobs/e7ad097b-...",
"stops": [
{
"external_stop_id": "ORD-1001",
"address": "1 George St, Brisbane QLD 4000",
"latitude": -27.4710,
"longitude": 153.0234,
"label": "John Smith",
"notes": "Leave at front door",
"status": "completed",
"priority": 1,
"parcel_count": 2
},
...
],
"created_at": "2026-03-09T08:30:00.000Z",
"updated_at": "2026-03-09T10:15:00.000Z"
}Job Statuses
| Field | Type | Required | Description |
|---|---|---|---|
| pending | status | No | Job created but driver not yet assigned or resolved |
| assigned | status | No | Driver has been assigned and can see the route in their app |
| in_progress | status | No | Driver has started the route |
| completed | status | No | All stops have been delivered |
Stop Fields
Complete list of fields available for each stop in the import request.
| Field | Type | Required | Description |
|---|---|---|---|
| external_stop_id | string | Yes | Your unique identifier for this stop (must be unique within the job) |
| address | string | Yes | Full street address. Will be geocoded if latitude/longitude are not provided. |
| latitude | number | No | Latitude in decimal degrees. Skips geocoding if provided with longitude. |
| longitude | number | No | Longitude in decimal degrees. Skips geocoding if provided with latitude. |
| label | string | No | Display label (e.g., customer name, order number) |
| notes | string | No | Delivery instructions for the driver |
| service_type | string | No | Type of service (e.g., "delivery", "pickup", "service_call") |
| duration_minutes | number | No | Expected service duration at this stop in minutes |
| time_window_start | string | No | Earliest delivery time (ISO 8601 datetime) |
| time_window_end | string | No | Latest delivery time (ISO 8601 datetime) |
| priority | number | No | Priority level (lower = higher priority). Default: 0 |
| parcel_count | number | No | Number of parcels for this stop. Default: 1 |
| metadata | object | No | Custom key-value data attached to this stop |
Tip: Tip: Providing latitude and longitude directly is faster and more reliable than geocoding. If you have coordinates in your system, always include them to avoid geocoding delays and potential failures.
Route Optimization
Every import automatically optimizes the delivery sequence for minimum driving distance and time.
How Optimization Works
- All stops without coordinates are geocoded from their address text
- The first stop is treated as the origin and the last stop as the destination
- All intermediate stops are optimized for the shortest driving route using Google Routes API
- The sort_order field on each stop is updated to reflect the optimized sequence
- The route polyline, total distance, and estimated duration are calculated
Response Fields
| Field | Type | Required | Description |
|---|---|---|---|
| optimization.total_distance_km | number | No | Total route distance in kilometers |
| optimization.total_duration_minutes | number | No | Estimated total driving time in minutes |
| optimization.encoded_polyline | string | No | Google Encoded Polyline for rendering the route on a map |
Note: Note: Optimization requires at least 2 stops with valid coordinates. If geocoding fails for some stops, the route will be optimized using only the successfully geocoded stops. Stops without coordinates will still appear in the response but won't be part of the optimized sequence.
Idempotency
Safely retry failed requests without creating duplicate jobs.
Include an Idempotency-Key header with a unique value (for example a UUID or your internal batch ID) on import requests. If the same key is sent within 24 hours, the API will return the cached response from the first successful call instead of creating a duplicate job.
curl -X POST https://api.routemate.app/v1/integration-import \
-H "Authorization: Bearer rm_at_your_token" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: batch-2026-03-09-zone-a" \
-d '{ ... }'
# Safe to retry — same Idempotency-Key returns cached response
curl -X POST https://api.routemate.app/v1/integration-import \
-H "Authorization: Bearer rm_at_your_token" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: batch-2026-03-09-zone-a" \
-d '{ ... }'Best Practice: Always include an Idempotency-Key in production. Use a deterministic key like your-system-batch-id so that network retries don't create duplicate data.
Error Handling
All errors return a JSON body with an error field.
{
"error": "Rate limit exceeded",
"retry_after_seconds": 45
}
// or for validation errors:
{
"error": {
"code": "VALIDATION_ERROR",
"messages": [
"external_job_id is required",
"stops[0].address is required"
]
}
}| Status | Meaning | Action |
|---|---|---|
| 200 | Success | Request completed successfully |
| 400 | Bad Request | Check the error messages and fix your request body |
| 401 | Unauthorized | Your token is invalid or expired. Get a new one. |
| 403 | Forbidden | Your integration has been disabled. Contact your admin. |
| 404 | Not Found | The job or endpoint doesn't exist |
| 405 | Method Not Allowed | Use the correct HTTP method (POST or GET) |
| 429 | Too Many Requests | Wait retry_after_seconds before retrying |
| 500 | Server Error | Retry with exponential backoff. Contact support if persistent. |
Rate Limits
Rate limits are applied per integration on a rolling 1-minute window.
| Plan | Requests / minute | Max stops / import |
|---|---|---|
| Team | 60 | 200 |
| Enterprise | Custom | Custom |
When rate-limited, the API returns HTTP 429 with a retry_after_seconds field. Implement exponential backoff in your integration for best reliability.
Complete Code Examples
Copy-paste examples for common languages. All examples show the full flow: authenticate, import, and query.
#!/bin/bash
# RouteMate API Integration Example
API_BASE="https://api.routemate.app/v1"
CLIENT_ID="rm_ci_your_client_id"
CLIENT_SECRET="rm_cs_your_client_secret"
# Step 1: Get access token
TOKEN=$(curl -s -X POST "$API_BASE/integration-token" \
-H "Content-Type: application/json" \
-d "{
\"client_id\": \"$CLIENT_ID\",
\"client_secret\": \"$CLIENT_SECRET\"
}" | jq -r '.access_token')
echo "Access Token: $TOKEN"
# Step 2: Import a job with stops
RESULT=$(curl -s -X POST "$API_BASE/integration-import" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: my-batch-001" \
-d '{
"external_job_id": "BATCH-001",
"title": "Morning Deliveries",
"driver_email": "driver@example.com",
"scheduled_date": "2026-03-10",
"timezone": "Australia/Brisbane",
"stops": [
{
"external_stop_id": "STOP-1",
"address": "1 George St, Brisbane QLD 4000",
"label": "Customer A",
"parcel_count": 2
},
{
"external_stop_id": "STOP-2",
"address": "100 Queen St, Brisbane QLD 4000",
"label": "Customer B",
"notes": "Leave at reception"
}
]
}')
echo "$RESULT" | jq .
# Step 3: Query job status
JOB_ID=$(echo "$RESULT" | jq -r '.job_id')
curl -s "$API_BASE/integration-jobs?job_id=$JOB_ID" \
-H "Authorization: Bearer $TOKEN" | jq .Webhooks
Get notified in real-time when deliveries are completed.
Webhook support is coming soon. You'll be able to register a URL and receive POST notifications for events such as:
- job.completed — All stops in a job have been delivered
- stop.completed — A single stop has been delivered (includes proof of delivery)
- stop.failed — A delivery attempt failed
- driver.location — Real-time driver GPS updates
Interested in early access? Contact us.