API Documentation
Complete reference for the Route Optimization API. Base URL: http://localhost:8000
Getting Started
The Route Optimization API solves vehicle routing problems (VRP) with time windows, capacities, skills, and multi-objective optimization. There are two authentication methods and two execution modes:
API KeyPublic API
For programmatic access. Pass your key via the Authorization header. Use for optimization, matrix, jobs, and re-optimization endpoints.
JWT AuthPortal API
For web portal access. Register, then login to receive a JWT token. Pass it as Bearer <token> in the Authorization header.
Synchronous Mode
POST /v1/optimize blocks until the solver finishes and returns the result directly. Best for small problems (< 50 nodes).
Asynchronous Mode
POST /v1/solve returns a job ID immediately. Poll GET /v1/jobs/{id} until status is COMPLETED. Best for large problems or production use. solve:write is enough to poll jobs created with the same key.
Quick Start (cURL)
# 1. Register an account
curl -X POST http://localhost:8000/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "securepassword"}'
# Response: {"access_token": "eyJ...", "token_type": "bearer"}
# 2. Create an API key (using JWT from step 1)
curl -X POST http://localhost:8000/v1/portal/api-keys \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{"name": "my-key", "scopes": ["solve:write"]}'
# 3. Synchronous optimization (blocks until complete)
curl -X POST http://localhost:8000/v1/optimize \
-H "Authorization: sk-abc123..." \
-H "Content-Type: application/json" \
-d @optimize-request.json
# 4. Async optimization (returns job ID, poll for result)
JOB_ID=$(curl -s -X POST http://localhost:8000/v1/solve \
-H "Authorization: sk-abc123..." \
-H "Content-Type: application/json" \
-d @optimize-request.json | jq -r '.id')
# Poll until complete
curl http://localhost:8000/v1/jobs/$JOB_ID \
-H "Authorization: sk-abc123..."Quick Start (Python)
import requests, time
BASE_URL = "http://localhost:8000"
# Register & get token
resp = requests.post(f"{BASE_URL}/v1/auth/register", json={
"email": "user@example.com",
"password": "securepassword"
})
token = resp.json()["access_token"]
# Create API key
resp = requests.post(
f"{BASE_URL}/v1/portal/api-keys",
headers={"Authorization": f"Bearer {token}"},
json={"name": "my-key", "scopes": ["solve:write"]}
)
api_key = resp.json()["key"]
headers = {"Authorization": api_key}
# Define the problem
problem = {
"problem_type": "vrptw",
"objectives": {"primary": "minimize_total_duration"},
"vehicles": [{
"id": "v1",
"start_location": {"lat": 50.85, "lng": 4.35},
"capacity": {"weight": 500},
"available_time_windows": [
{"start": "2025-01-15T08:00:00", "end": "2025-01-15T17:00:00"}
]
}],
"tasks": [{
"id": "t1",
"location": {"lat": 50.83, "lng": 4.37},
"service_duration_minutes": 30,
"demand": {"weight": 10},
"time_windows": [
{"start": "2025-01-15T09:00:00", "end": "2025-01-15T12:00:00"}
]
}],
"options": {
"include_route_geometry": True,
"calculate_carbon_footprint": True
}
}
# Option A: Synchronous (blocks)
result = requests.post(f"{BASE_URL}/v1/optimize", headers=headers, json=problem).json()
# Option B: Async (submit + poll)
job = requests.post(f"{BASE_URL}/v1/solve", headers=headers, json=problem).json()
while job["status"] in ("PENDING", "RUNNING"):
time.sleep(1)
job = requests.get(f"{BASE_URL}/v1/jobs/{job['id']}", headers=headers).json()
result = job["result"]
# Print results
print(f"Routes: {len(result['routes'])}")
for route in result["routes"]:
stops = [s for s in route["stops"] if s["type"] == "task"]
print(f" {route['vehicle_id']}: {len(stops)} tasks, "
f"{route['total_distance_km']:.1f} km, "
f"{route['total_duration_minutes']:.0f} min")Authentication
API Key Scopes
| Scope | Description |
|---|---|
| solve:write | Submit optimization jobs (sync and async) |
| optimize:write | Alias for solve:write (backward compatibility) |
| jobs:read | Read/list jobs without solve permissions (optional for read-only monitoring keys) |
| analytics:read | Access route analytics |
| webhooks:write | Manage webhook endpoints |
| reports:write | Manage reports and schedules |
Optimization
Async Jobs
For production use, submit jobs asynchronously. The solver runs in a background worker (Celery) and you poll for the result. The request body is the same OptimizeRequest format as the synchronous endpoint.
Distance Matrix
Matrix responses are cached (Redis + in-memory LRU). Tune withMATRIX_CACHE_ENABLED,MATRIX_CACHE_TTL_SECONDS, andMATRIX_CACHE_MEMORY_MAX_ENTRIES.
Re-optimization
Exports
Analytics
Webhooks
Subscribe to real-time event notifications. When an event occurs, a POST request is sent to your URL with the event payload. Deliveries are retried up to 3 times.
Supported Event Types
Signature Verification
If you provide a secret when creating the webhook, each delivery includes an X-Webhook-Signature header with an HMAC-SHA256 signature:
import hmac, hashlib
def verify_webhook(payload_bytes, signature_header, secret):
expected = hmac.new(
secret.encode(), payload_bytes, hashlib.sha256
).hexdigest()
return hmac.compare_digest(
signature_header.replace("sha256=", ""), expected
)Reports
Create report templates and schedules for automated report generation. Reports are generated as CSV/JSON artifacts that can be downloaded.
AI Chat
Multi-turn AI conversation interface. Supports multiple context types for building optimization problems, explaining results, analyzing anomalies, and getting recommendations.
LLM Tools
Standalone AI-powered tools for explaining results, detecting anomalies, and parsing natural language problem descriptions. Each call is stateless (not part of a conversation).
Billing & Usage
Usage is metered by node count (vehicles + tasks per job). Free-tier accounts get FREE_TIER_NODE_LIMIT nodes per month. Paid plans are billed via Stripe.
Portal Management
All portal endpoints require JWT authentication. User roles: owner (full access), admin (manage team & keys), manager (view & configure), analyst (read-only analytics), member (basic access).
Profile & Organization
API Keys
Team Management
Jobs (Portal)
ML Models
Audit Logs
GDPR Data Management
Health Checks
Error Handling
All errors follow a consistent structured format with error codes, human-readable messages, and actionable suggestions.
{
"error": {
"code": "CAPACITY_EXCEEDED",
"message": "Total task demand (750 kg) exceeds total vehicle capacity (500 kg).",
"details": {
"total_demand_weight": 750,
"total_capacity_weight": 500
},
"request_id": "req-a1b2c3",
"timestamp": "2025-01-15T10:30:00Z",
"suggestions": [
"Add more vehicles or increase vehicle capacity.",
"Remove lower-priority tasks to reduce total demand."
]
}
}Error Codes
| Code | HTTP | Description |
|---|---|---|
| INVALID_REQUEST | 400 | Missing required fields, invalid values, or malformed JSON |
| DUPLICATE_ID | 400 | Duplicate vehicle or task IDs in the request |
| AUTHENTICATION_FAILED | 401 | Missing, invalid, or expired API key / JWT token |
| AUTHORIZATION_FAILED | 403 | Valid credentials but insufficient permissions or scopes |
| RESOURCE_NOT_FOUND | 404 | Job, webhook, report, or other resource not found |
| RATE_LIMIT_EXCEEDED | 429 | Too many requests. Implement exponential backoff |
| CAPACITY_EXCEEDED | 422 | Total demand exceeds capacity, or node/unit limits exceeded |
| SKILL_MISMATCH | 422 | Tasks require skills not available on any vehicle |
| TIME_WINDOW_CONFLICT | 422 | Time windows are impossible to satisfy |
| INFEASIBLE_SOLUTION | 422 | Solver could not find any feasible solution |
| TIMEOUT | 408 | Job exceeded maximum computation time |
| INTERNAL_ERROR | 500 | Unexpected server error |
| SERVICE_UNAVAILABLE | 503 | Database or dependency unavailable |
Warning Types
Warnings are returned in the response warnings array. They indicate non-fatal issues:
| Type | Description |
|---|---|
| tight_time_window | Task has a very narrow time window that may be hard to satisfy |
| skill_mismatch | Task requires skills not available on all vehicles |
| route_geometry_unavailable | OSRM geometry could not be fetched (routing engine not configured or unreachable) |
| zone_restrictions_partial | Zone restrictions without task_ids or allowed_vehicles were ignored |
| capacity_tight | Total demand is close to total capacity |
Rate Limiting
API requests are rate-limited per API key (default: 60 requests/minute). LLM endpoints have a separate limit (default: 20 requests/minute). When exceeded, you receive a 429 response with a RATE_LIMIT_EXCEEDED error code.
# Recommended retry strategy: exponential backoff
import time
def call_api_with_retry(func, max_retries=3):
for attempt in range(max_retries):
response = func()
if response.status_code == 429:
wait = 2 ** attempt # 1s, 2s, 4s
time.sleep(wait)
continue
return response
raise Exception("Rate limit exceeded after retries")