Concepts & Guides

Understand the fundamentals of vehicle routing optimization and how to get the most out of the API.

Vehicle Routing Problem (VRP)

The Vehicle Routing Problem is the challenge of finding the optimal set of routes for a fleet of vehicles to service a collection of tasks or customers. It sits at the heart of logistics, field service, and delivery operations. Finding the best routes saves fuel, reduces labour costs, and improves customer satisfaction.

VRP is classified as NP-hard, meaning there is no known algorithm that can solve it in polynomial time. With just 20 stops, there are over 2.4 quintillion possible routes. Our API uses Google OR-Tools with metaheuristic search strategies to find near-optimal solutions in seconds rather than centuries.

Common problem variants include CVRP (capacitated), VRPTW (with time windows), PDPTW (pickup and delivery with time windows), and TSP (single vehicle, no constraints). The API handles all of these transparently based on the data you provide.

Route Topology

DepotT1T2T3T4Depot
Related: POST /api/v1/optimize

Problem Profiles (Phase 2.1)

Problem profiles apply domain-specific defaults before solving. They reduce setup time and improve baseline behavior for common operations while preserving explicit request settings.

ProfileDefault FocusTypical Adaptations
last_mile_deliveryOn-time + route efficiencyBalanced policy defaults, duration/distance weights, optional default max route duration.
field_serviceSLA and technician fitSLA-first policy and optional strict multi-skill matching (required_skills_mode=all).
sales_routesCoverage and fairnessBalanced workload defaults and daily variance target for territory distribution.
{
  "problem_type": "vrptw",
  "problem_profile": "field_service",
  "profile_options": {
    "field_service": {
      "require_all_skills_for_multi_skill_tasks": true,
      "default_min_technician_rating": 4.0
    }
  }
}

Time Windows

Time windows constrain when a vehicle can arrive at a task location. They are essential for modelling real-world deliveries where customers are only available during certain hours, or where regulatory limits apply (e.g. no deliveries before 7 AM in residential areas).

Hard time windows must be strictly respected — a vehicle arriving outside the window causes the task to be marked unassigned. Soft time windows allow violations but apply a penalty cost that the optimizer minimises, giving you flexibility when strict adherence is impractical.

Delivery Windows (24h timeline)

Task A
8:00–12:00
Task B
9:00–11:00
Task C
13:00–17:00
Task D
10:00–15:00
Task E
14:00–18:00
06:0010:0014:0018:0020:00
{
  "tasks": [
    {
      "id": "delivery-001",
      "location": { "lat": 51.5074, "lng": -0.1278 },
      "time_window": {
        "start": "2024-01-15T09:00:00Z",
        "end": "2024-01-15T12:00:00Z"
      },
      "service_duration": 600
    }
  ]
}
Related: POST /api/v1/optimize

Vehicle Capacities

Capacity constraints ensure that vehicles are never overloaded. The API supports multi-dimensional capacity modelling so you can track weight, volume, and pallet count simultaneously. Each task specifies its demand across the same dimensions.

For example, a truck might have a maximum weight of 10,000 kg and a volume of 40 m³. A delivery requiring 500 kg and 2 m³ is only assigned to that truck if sufficient capacity remains after all previously assigned tasks.

DimensionUnitExample VehicleExample Task
Weightkg10,000500
Volume402
Palletscount181
{
  "vehicles": [
    {
      "id": "truck-01",
      "capacity": [10000, 40, 18]
    }
  ],
  "tasks": [
    {
      "id": "delivery-001",
      "demand": [500, 2, 1]
    }
  ]
}
Related: POST /api/v1/optimize

Skills & Requirements

Skills matching ensures that tasks are only assigned to vehicles (and drivers) that have the necessary capabilities. A task may require refrigeration, hazmat certification, a tail-lift, or a specific language spoken by the driver.

The vehicle must possess all skills listed on the task for it to be eligible. If no vehicle can satisfy a task's requirements, the task will appear in the unassigned list in the response, along with the reason.

Matching Example

Vehicle: truck-01

refrigeration
tail-lift
hazmat

Task: delivery-005

refrigeration
tail-lift

Match — vehicle has all required skills

{
  "vehicles": [
    {
      "id": "truck-01",
      "skills": ["refrigeration", "tail-lift", "hazmat"]
    }
  ],
  "tasks": [
    {
      "id": "delivery-005",
      "skills": ["refrigeration", "tail-lift"]
    }
  ]
}
Related: POST /api/v1/optimize

Objectives & Trade-offs

Route optimization rarely has a single goal. You might want to minimise total distance driven, but also minimise the number of vehicles used. These objectives often compete — fewer vehicles means longer individual routes.

The API supports multi-objective optimization through configurable weights. Each objective is assigned a weight between 0 and 1 that controls its relative importance. The solver blends these into a single cost function and finds the best compromise.

Objective Weight Sliders

Minimise Distance
0.8
Minimise Time
0.6
Minimise Vehicles
0.4
Minimise Cost
0.7
{
  "objectives": {
    "minimize_distance": 0.8,
    "minimize_time": 0.6,
    "minimize_vehicles": 0.4,
    "minimize_cost": 0.7
  }
}
Related: POST /api/v1/optimize

Depots & Multi-Depot

A depot is where vehicles start and (optionally) end their routes. In a single-depot scenario all vehicles originate from one location. Multi-depot routing assigns each vehicle a home depot, and the optimizer considers travel distance from each depot to each task when building routes.

You can also configure open routes where vehicles do not return to the depot after their last task. This is useful for field-service teams that go home at the end of the day rather than returning to a warehouse.

Depot Configurations

1

Single Depot

All vehicles start and return to one location

N

Multi-Depot

Vehicles assigned to different home locations

Open Routes

Vehicles end at last task, no return trip

{
  "depots": [
    {
      "id": "warehouse-north",
      "location": { "lat": 51.55, "lng": -0.10 }
    },
    {
      "id": "warehouse-south",
      "location": { "lat": 51.45, "lng": -0.12 }
    }
  ],
  "vehicles": [
    {
      "id": "van-01",
      "depot_id": "warehouse-north",
      "return_to_depot": false
    }
  ]
}
Related: POST /api/v1/optimize

Distance Matrices

A distance matrix is a table of travel times and distances between every pair of locations in your problem. The optimizer uses this matrix millions of times during search, so having an accurate and fast matrix is critical for solution quality.

You can either let the API compute the matrix automatically using OSRM (self-hosted, no rate limits) or provide a precomputed matrix. For production workloads, you can also integrate Google Maps for traffic-aware travel times.

Pass locations only — the API calculates the full matrix using OSRM. Best for most use cases with up to 2,000 locations.

POST /api/v1/matrix
{
  "locations": [
    { "lat": 51.5074, "lng": -0.1278 },
    { "lat": 51.5155, "lng": -0.1419 },
    { "lat": 51.5033, "lng": -0.1195 }
  ],
  "engine": "osrm"
}
Related: POST /api/v1/matrix

Re-optimization

Real-world operations rarely go as planned. New orders arrive, customers cancel, and vehicles break down. Re-optimization takes your current plan and adjusts it to accommodate changes without rebuilding from scratch.

The API accepts a previous solution along with a set of changes (added tasks, removed tasks, or updated constraints). It preserves already-started routes and only rearranges what is necessary. The response includes a changes object that details every modification for auditing purposes.

Re-optimization Flow

Current Plan
Add / Remove Tasks
Re-optimize
Updated Plan + Changes
POST /api/v1/reoptimize
{
  "previous_job_id": "job_abc123",
  "add_tasks": [
    {
      "id": "urgent-delivery-099",
      "location": { "lat": 51.51, "lng": -0.13 },
      "time_window": {
        "start": "2024-01-15T14:00:00Z",
        "end": "2024-01-15T16:00:00Z"
      }
    }
  ],
  "remove_task_ids": ["delivery-003"]
}
Related: POST /api/v1/reoptimize

ML-Enhanced Routing

Traditional routing engines use static road-network speeds. Our ML layer learns from your historical delivery data to predict actual travel times more accurately, accounting for time-of-day patterns, weather effects, and driver behaviour.

When ML predictions are active, each route segment includes a confidence score indicating prediction reliability. Models are versioned and can be retrained as new data accumulates. This is a key differentiator over generic routing APIs.

ML Pipeline

1

Historical Data

GPS traces, ETAs

2

Train Model

Feature engineering

3

Predict Times

Per segment

4

Optimize

Better solutions

// Enable ML predictions in optimization
POST /api/v1/optimize
{
  "options": {
    "use_ml_predictions": true,
    "ml_model_version": "latest"
  },
  ...
}

// Check ML model status
GET /api/v1/ml/status
// Response includes: model_version, accuracy, last_trained
Related: GET /api/v1/ml/status

Async vs Sync Execution

For small problems (fewer than 50 tasks), synchronous execution returns results directly in the HTTP response within seconds. For larger problems, asynchronous execution queues the job and returns a job ID immediately so your application is not blocked.

With async mode, you can either poll the job status endpoint or register a webhook to be notified when the job completes. Polling is simpler to implement while webhooks are more efficient for high-throughput systems.

// Sync: result returned directly
POST /api/v1/optimize
{ "mode": "sync", "tasks": [...], "vehicles": [...] }

// Response: 200 OK with full solution

Sync Mode

  • Best for < 50 tasks
  • Result in HTTP response
  • Simpler integration

Async Mode

  • Best for 50+ tasks
  • Non-blocking execution
  • Webhook notifications
Related: GET /api/v1/jobs/:id

Webhooks & Events

Webhooks let you receive real-time notifications when events occur in the optimization service. Instead of polling for job status, you register a URL and the API sends an HTTP POST with the event payload as soon as the event fires.

Failed deliveries are retried with exponential backoff up to 5 times over 24 hours. Each delivery includes a signature header so you can verify authenticity. Events are delivered at least once; your endpoint should be idempotent.

Supported Event Types

job.completedOptimization job finished
job.failedJob encountered an error
job.progressIntermediate progress update
model.trainedML model training completed
report.readyScheduled report generated
usage.thresholdUsage limit approaching
POST /api/v1/webhooks
{
  "url": "https://your-app.com/webhooks/optiroute",
  "events": ["job.completed", "job.failed"],
  "secret": "whsec_your_signing_secret"
}

// Webhook payload example
{
  "event": "job.completed",
  "job_id": "job_abc123",
  "timestamp": "2024-01-15T14:32:00Z",
  "data": {
    "total_distance": 142500,
    "total_duration": 28800,
    "vehicles_used": 3
  }
}
Related: POST /api/v1/webhooks

Rate Limits & Best Practices

The API enforces rate limits to ensure fair usage across all tenants. The default tier allows 60 requests per minute. Rate limit headers are included in every response so you can track your remaining quota in real time.

When you receive a 429 response, implement exponential backoff: wait 1 second, then 2, then 4, up to a maximum of 32 seconds. For bulk operations, batch your tasks into a single optimization request rather than sending one request per task.

Rate Limit Tiers

TierRequests/minMax Tasks/reqConcurrent Jobs
Free20501
Starter605003
Professional2002,00010
EnterpriseCustom10,000+Unlimited

Tips for Production

  • Cache distance matrices when locations are reused across requests
  • Use async mode for anything above 50 tasks to avoid HTTP timeouts
  • Set realistic time windows — overly tight windows increase unassigned tasks
  • Monitor the X-RateLimit-Remaining header
  • Enable ML predictions after collecting at least 2 weeks of historical data
// Exponential backoff example (JavaScript)
async function callWithRetry(fn, maxRetries = 5) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (err) {
      if (err.status !== 429 || i === maxRetries - 1) throw err;
      const delay = Math.min(1000 * Math.pow(2, i), 32000);
      await new Promise(r => setTimeout(r, delay));
    }
  }
}
Related: All endpoints