REST API Reference
Base URL: https://myoptimizedroute.com/api/v1
Authentication
All API requests require a Bearer token in the Authorization header.
Generate API keys from Settings → API Keys in your client portal.
Example Request
curl https://myoptimizedroute.com/api/v1/customers \ -H "Authorization: Bearer mor_live_your32hexcharshere"
mor_test_ keys are a labeling convention.
They help you identify traffic from development or staging environments in the API request log.
There is no separate sandbox environment.
401 Unauthorized
Missing, invalid, or revoked API key.
429 Too Many Requests
Rate limit exceeded. See header Retry-After.
Rate Limit
60 requests per minute per API key.
Response Format
All responses are JSON with the following envelope:
{
"data": <object | array | null>,
"error": null | { "message": "Description of the error" },
"meta": {} | { "page": 1, "per_page": 50, "total": 243 }
}
HTTP status codes: 200 OK,
201 Created,
400 Bad Request,
401 Unauthorized,
404 Not Found,
409 Conflict,
422 Unprocessable,
429 Rate Limited.
Errors
All error responses follow the standard envelope format with data: null
and an error.message describing the issue.
| Status | Meaning | Common Causes |
|---|---|---|
| 400 | Bad Request | Missing required fields or invalid field values. Check error.message for specifics. |
| 401 | Unauthorized | Missing, invalid, or revoked API key. Verify your Authorization header is correct and the key has not been revoked in Settings → API Keys. |
| 403 | Forbidden | Your plan does not include API access. API access requires a Pro or Enterprise plan. |
| 404 | Not Found | The requested resource does not exist or does not belong to your tenant. |
| 409 | Conflict | A visible customer with the same address already exists. Use PATCH to update the existing record. |
| 422 | Unprocessable | The request was understood but could not be processed. Common causes: monthly stop limit reached, invalid stop status transition, driver does not belong to your tenant. |
| 429 | Too Many Requests | Rate limit exceeded. Check the Retry-After header for how long to wait before retrying. |
| 500 | Internal Server Error | An unexpected server error occurred. If this persists, contact support. |
Example 422 Response
{
"data": null,
"error": { "message": "Monthly stop limit reached. Upgrade your plan to continue creating routes." },
"meta": {}
}
Customers
/api/v1/customers
— List customers
Returns a paginated list of customers in your address library.
Query Parameters
| Name | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number. |
| per_page | integer | 50 | Results per page (max 100). |
| search | string | — | Filter by name or address (partial match). |
Response 200 OK
[
{
"id": 42,
"customer_name": "Jane Smith",
"first_name": "Jane",
"last_name": "Smith",
"address": "123 Main St",
"address2": "",
"city": "Springfield",
"state": "IL",
"zip": "62701",
"email": "jane@example.com",
"phone": "555-1234",
"geocode_verified": true,
"created_at": "2026-01-15 09:30:00"
}
]
/api/v1/customers
— Create customer
Adds a customer to your address library. Returns the existing customer if a hidden match is found (unhide-on-match). Returns 409 if a visible customer with the same address already exists.
Request Body (JSON)
{
"address": "123 Main St", // required
"city": "Springfield", // required
"state": "IL", // required
"zip": "62701", // required
"customer_name": "Jane Smith", // optional
"first_name": "Jane", // optional (preferred over customer_name)
"last_name": "Smith", // optional
"email": "jane@example.com",
"phone": "555-1234",
"notes": "Gate code: 1234"
}
Response 201 Created
{ "id": 42, "customer_name": "Jane Smith", ... }
/api/v1/customers/{id}
— Get customer
Returns a single customer by ID.
Response 200 OK
{ "id": 42, "customer_name": "Jane Smith", ... }
/api/v1/customers/{id}
— Update customer
Updates any subset of customer fields. If address fields change, geocode_verified is reset to false.
Request Body (JSON)
{
"first_name": "Janet",
"phone": "555-9999"
}
Response 200 OK
{ "id": 42, "customer_name": "Janet Smith", ... }
Routes
/api/v1/routes
— List routes
Returns a paginated list of routes for your tenant.
Query Parameters
| Name | Type | Description |
|---|---|---|
| page | integer | Page number (default 1). |
| per_page | integer | Results per page (max 100, default 50). |
| status | string | Filter by: planned, active, completed, cancelled, incomplete. |
| date | YYYY-MM-DD | Filter by route date. |
Response 200
[
{
"id": 7,
"driver_id": 3,
"driver_name": "Alex Johnson",
"route_date": "2026-03-30",
"status": "completed",
"fixed_order": false,
"stop_total": 8,
"stop_delivered": 7,
"stop_failed": 1,
"started_at": "2026-03-30 09:15:00",
"completed_at": "2026-03-30 12:45:00",
"created_at": "2026-03-29 18:00:00"
}
]
/api/v1/routes
— Create route
Creates a new planned route with stops. Enforces the monthly stop limit.
Request Body (JSON)
{
"driver_id": 3, // required — must belong to your tenant
"route_date": "2026-04-01", // required — YYYY-MM-DD
"fixed_order": false, // optional — driver cannot re-optimize if true
"stops": [ // required — at least one stop
{ "customer_id": 42, "deliverables": "2x Crackers" },
{ "customer_id": 18 }
]
}
Response 201 Created
{
"id": 12, "status": "planned", "stop_total": 2,
"stops": [
{ "stop_id": 101, "stop_order": 1, "status": "not_started",
"customer_id": 42, "customer_name": "Jane Smith",
"address": "123 Main St", "deliverables": "2x Crackers", ... },
{ "stop_id": 102, "stop_order": 2, "status": "not_started",
"customer_id": 18, "customer_name": "Bob Lee", ... }
]
}
/api/v1/routes/{id}
— Get route with stops
Returns a single route with its full stop list including customer details and timing.
Response 200
{
"id": 7,
"driver_id": 3,
"driver_name": "Alex Johnson",
"route_date": "2026-04-01",
"status": "active",
"fixed_order": false,
"stop_total": 3,
"stop_delivered": 1,
"stop_failed": 0,
"started_at": "2026-04-01 09:15:00",
"completed_at": null,
"created_at": "2026-03-31 18:00:00",
"stops": [
{
"stop_id": 101,
"stop_order": 1,
"status": "delivered",
"customer_id": 42,
"customer_name": "Jane Smith",
"address": "123 Main St",
"city": "Springfield",
"state": "IL",
"zip": "62701",
"deliverables": "2x Crackers",
"driver_notes": "Left at door",
"en_route_at": "2026-04-01 09:20:00",
"arrived_at": "2026-04-01 09:31:00",
"completed_at": "2026-04-01 09:34:00",
"pod_captured": true
},
{
"stop_id": 102,
"stop_order": 2,
"status": "en_route",
"customer_id": 18,
"customer_name": "Bob Lee",
"address": "456 Oak Ave",
"city": "Springfield",
"state": "IL",
"zip": "62702",
"deliverables": "1x Beef Formula",
"driver_notes": null,
"en_route_at": "2026-04-01 09:38:00",
"arrived_at": null,
"completed_at": null,
"pod_captured": false
},
{
"stop_id": 103,
"stop_order": 3,
"status": "not_started",
"customer_id": 27,
"customer_name": "Maria Santos",
"address": "789 Elm St",
"city": "Springfield",
"state": "IL",
"zip": "62703",
"deliverables": null,
"driver_notes": null,
"en_route_at": null,
"arrived_at": null,
"completed_at": null,
"pod_captured": false
}
]
}
Stops
Stop Object
Stop objects are returned as part of GET /api/v1/routes/{id}
and POST /api/v1/routes responses under the
stops array.
| Field | Type | Description |
|---|---|---|
| stop_id | integer | Unique stop identifier. |
| stop_order | integer | Position in the route sequence (1-based). |
| status | string | not_started, en_route, arrived, delivered, failed |
| customer_id | integer | ID of the customer in your address library. |
| customer_name | string | Full name of the customer. |
| address | string | Street address. |
| city | string | City. |
| state | string | State abbreviation. |
| zip | string | ZIP / postal code. |
| deliverables | string | null | Free-text dispatcher note describing what is being delivered. |
| driver_notes | string | null | Notes recorded by the driver at the time of delivery. |
| en_route_at | datetime | null | Timestamp when the driver marked the stop as en route. |
| arrived_at | datetime | null | Timestamp when the driver marked arrival. |
| completed_at | datetime | null | Timestamp when the stop reached a terminal status (delivered or failed). |
| pod_captured | boolean | Whether proof of delivery (photo or signature) was collected. |
not_started → en_route → arrived → deliveredarrived → failed
/api/v1/routes/{route_id}/stops/{stop_id}
— Update stop status
Advances a stop through its status lifecycle. Fires delivery notifications
(email/SMS) the same as the driver portal. Marks the route completed
when all stops reach a terminal status.
not_started → en_route → arrived → delivered or arrived → failed
Request Body (JSON)
{
"new_status": "delivered", // required
"notes": "Left at door" // optional — shown to dispatcher
}
Deliveries
/api/v1/deliveries
— List deliveries
Returns individual stop-level delivery records across all routes.
Query Parameters
| Name | Type | Description |
|---|---|---|
| page / per_page | integer | Pagination. |
| status | string | delivered, failed, or processing (in-progress). |
| date_from | YYYY-MM-DD | Filter route_date ≥ this date. |
| date_to | YYYY-MM-DD | Filter route_date ≤ this date. |
| driver_id | integer | Filter by driver. |
Response 200
[
{
"id": 55,
"customer_name": "Jane Smith",
"address": "123 Main St",
"city": "Springfield",
"state": "IL",
"zip": "62701",
"driver_name": "Alex Johnson",
"status": "delivered",
"deliverables": "2x Crackers",
"en_route_at": "2026-03-30 10:00:00",
"arrived_at": "2026-03-30 10:12:00",
"completed_at": "2026-03-30 10:15:00",
"notes": "Left at door",
"route_id": 7,
"route_date": "2026-03-30",
"pod_captured": true
}
]
Rate Limits
Each API key is limited to 60 requests per minute. The counter resets
at the start of each new 60-second window. When exceeded, a
429 Too Many Requests response is returned
with a Retry-After header indicating
how many seconds to wait.
HTTP/1.1 429 Too Many Requests
Retry-After: 23
Content-Type: application/json
{
"data": null,
"error": { "message": "Rate limit exceeded. Maximum 60 requests per minute." },
"meta": {}
}