RoutePlanAddresses → driver routes

API

No key, no account, no rate-limit tier to buy. This is the same endpoint the map calls — there is no private path from the app to the solver, so the API is exercised on every plan and cannot quietly rot.

Plan a route

One POST. The depot, your stops, how many vehicles. Everything else has a sensible default.

curl -X POST https://your-host/api/v1/solve \
  -H 'Content-Type: application/json' \
  -d '{
    "depot": { "lat": 48.8443, "lng": 2.3743 },
    "stops": [
      { "id": "s1", "label": "Louvre",     "lat": 48.8606, "lng": 2.3376 },
      { "id": "s2", "label": "Notre-Dame", "lat": 48.8530, "lng": 2.3499 }
    ],
    "vehicles": [{ "id": "v1" }, { "id": "v2" }],
    "options": { "objective": "balanced", "roundTrip": true }
  }'

You get back the routes, in order, with the numbers already worked out:

{
  "routes": [{
    "vehicleId": "v1",
    "vehicleIndex": 0,
    "stops": [{
      "stopId": "s1",
      "stopIndex": 0,
      "sequence": 0,
      "arrivalOffset": 412,          // seconds from departure
      "distanceFromPrevious": 3820.4 // metres
    }],
    "totalDistance": 7640,
    "totalDuration": 1224
  }],
  "summary": {
    "totalDistance": 7640,
    "longestRouteDistance": 7640,
    "vehiclesUsed": 1,
    "stopsServed": 2,
    "baselineDistance": 8100        // your list order, for comparison
  },
  "meta": {
    "solver": "greedy",
    "solveTimeMs": 3,
    "matrixSource": "osrm",         // or "haversine" — see below
    "warnings": []
  }
}
Distances are always metres and durations always seconds. Formatting happens at the edge, never in the data.

Endpoints

EndpointWhat it does
GET /api/v1/solveAvailable engines and objectives. The app's own selector reads this, so it never hardcodes engine names.
POST /api/v1/solvePlan routes. The one that matters.
POST /api/v1/geocodeAddresses to coordinates, up to 10 at a time. Returns a precision grade and any alternatives.
POST /api/v1/route-geometryRoad shapes for drawing. Separate from the plan on purpose: a failure here costs you the drawing, not the routes.
POST /api/v1/shareEncode a plan into a URL. There is no database — the link is the storage.
GET /api/healthLiveness and which services are configured.

Options

FieldDefaultMeaning
objectivebalancedbalanced evens the workload; distance minimises total kilometres and may leave vehicles idle.
roundTriptrueFalse means each vehicle finishes at its last stop.
defaultServiceTime300Seconds at each stop. Overridable per stop.
solvergreedygreedy is always available. ortools needs a sidecar; ask GET /api/v1/solve what is on.

Errors

Every error is a code and a sentence written for a person, never a stack trace.

{
  "error": {
    "code": "TOO_MANY_STOPS",
    "message": "This plan has 140 stops. The limit is 99. Split it into two plans."
  }
}
CodeHTTPCause
INVALID_PROBLEM400Malformed body. detail names the fields.
TOO_MANY_STOPS413Over 99 stops.
SOLVER_UNAVAILABLE400That engine is not set up here.
SOLVER_TIMEOUT504The engine took too long.
INFEASIBLE400No plan satisfies the constraints.
RATE_LIMITED429Too many requests. Retry-After says how long.

Two things worth knowing

It degrades rather than failing. If road distances are unavailable you still get a plan, computed from straight-line estimates, with meta.matrixSource set to haversine and a warning attached. Check that field before presenting distances as fact.

Vehicles are always an array of objects, even when you only care about the count. Heterogeneous fleets — two vans and a bike — are the first thing real operators ask for, and an integer count would be a breaking change later.

Rate limits

Per IP, per minute: 20 plans, 60 geocodes. Every response carries RateLimit-Limit and RateLimit-Remaining so you can slow down before being refused rather than after.

Running your own copy removes all of this. It is MIT licensed and the source is on GitHub.

Try it in the app