TL;DR

Two examples: (1) depots as named locations, with each resource pinned to a different one via start_depot_id; (2) problem.num_days = 3 with a resource restricted to certain days via available_days and a job pinned to a single day via day.

Multi-Depot & Multiday

Example 1: Multiple depots

Instead of a single shared depot_index, declare named depots and point each resource at one via start_depot_id (and optionally a different end_depot_id for one-way routes). Here van-7 starts from the Amsterdam depot and truck-2 starts from the Rotterdam depot; each is closer to a different cluster of jobs.

{
  "metadata": { "problem_id": "multi-depot-example" },
  "depots": [
    { "id": "depot-amsterdam", "location": { "index": 0, "label": "Amsterdam depot" } },
    { "id": "depot-rotterdam", "location": { "index": 1, "label": "Rotterdam depot" } }
  ],
  "travel": {
    "distance_matrix": [
      [0, 76000, 4800, 6200, 71000, 73500],
      [76000, 0, 72000, 70500, 5100, 4300],
      [4800, 72000, 0, 3900, 68200, 70800],
      [6200, 70500, 3900, 0, 66900, 69200],
      [71000, 5100, 68200, 66900, 0, 3600],
      [73500, 4300, 70800, 69200, 3600, 0]
    ]
  },
  "resources": [
    { "id": "van-7", "start_depot_id": "depot-amsterdam", "end_depot_id": "depot-amsterdam" },
    { "id": "truck-2", "start_depot_id": "depot-rotterdam", "end_depot_id": "depot-rotterdam" }
  ],
  "jobs": [
    { "id": "job-amsterdam-12", "location_index": 2, "service_time": 300 },
    { "id": "job-amsterdam-13", "location_index": 3, "service_time": 300 },
    { "id": "job-rotterdam-21", "location_index": 4, "service_time": 300 },
    { "id": "job-rotterdam-22", "location_index": 5, "service_time": 300 }
  ],
  "options": { "time_limit_seconds": 5 }
}

With depot-amsterdam at index 0 only 4800-6200m from the Amsterdam jobs, and depot-rotterdam at index 1 only 3600-4300m from the Rotterdam jobs, while the inter-depot/inter-city legs run 66900-76000m, the solver assigns van-7 to the two Amsterdam jobs and truck-2 to the two Rotterdam jobs — each vehicle stays near its own depot rather than crossing the country.

Example 2: Multi-day planning

num_days lives under problem, not options

num_days is a field on ProblemConfig (problem.num_days), not on SolverOptions (options). Putting it under options is silently ignored by Pydantic (extra fields are dropped), so the request quietly runs as single-day, every available_days/day constraint becomes impossible to satisfy, and the whole solve comes back infeasible with no obvious cause. Always set "problem": { "num_days": 3 }.

When num_days > 1, each resource expands into one virtual vehicle per day it is available. available_days (0-indexed) restricts a resource to a subset of days; omitting it means available every day. A job's day field pins it to one specific day and is intersected with allowed_resource_ids — the job can only go to a resource that is both allowed and available that day. Day windows do not carry over: each vehicle gets a fresh shift every day it runs.

{
  "metadata": { "problem_id": "multiday-example" },
  "problem": { "num_days": 3 },
  "travel": {
    "distance_matrix": [
      [0, 5200, 8700, 6100],
      [5200, 0, 4300, 7800],
      [8700, 4300, 0, 5500],
      [6100, 7800, 5500, 0]
    ]
  },
  "resources": [
    { "id": "truck-1", "depot_index": 0 },
    { "id": "truck-2", "depot_index": 0, "available_days": [0, 2] }
  ],
  "jobs": [
    { "id": "job-4821", "location_index": 1, "service_time": 300 },
    { "id": "job-4822", "location_index": 2, "service_time": 300, "day": 1 },
    { "id": "job-4823", "location_index": 3, "service_time": 300, "day": 2, "allowed_resource_ids": ["truck-2"] }
  ],
  "options": { "time_limit_seconds": 10 }
}

Here truck-2 only runs on days 0 and 2 (available_days: [0, 2]). job-4822 is pinned to day 1, so only truck-1 (available every day by default) can take it. job-4823 is pinned to day 2 and restricted to truck-2 via allowed_resource_ids — both conditions must hold, and day 2 is one of truck-2's available days, so this is feasible. job-4821 has no day, so the solver is free to place it on whichever day/vehicle minimizes cost.

Multi-day response shape

Routes carry a day field so you can tell which virtual vehicle-day each route belongs to:

{
  "status": "optimal",
  "problem_id": "multiday-example",
  "solve_time_ms": 64,
  "routes": [
    {
      "resource_id": "truck-1",
      "day": 1,
      "activities": [
        { "type": "start", "location_index": 0, "departure_time": 0 },
        { "type": "service", "job_id": "job-4822", "location_index": 2, "arrival_time": 8700, "departure_time": 9000, "service_time": 300, "distance_from_prev": 8700, "time_from_prev": 8700 },
        { "type": "end", "location_index": 0, "arrival_time": 17700, "distance_from_prev": 8700, "time_from_prev": 8700 }
      ],
      "summary": { "total_distance": 17400, "total_travel_time": 17400, "total_service_time": 300, "total_waiting_time": 0, "total_time": 17700, "num_stops": 1, "num_jobs": 1, "cost": 17400.0 },
      "violations": []
    },
    {
      "resource_id": "truck-2",
      "day": 2,
      "activities": [
        { "type": "start", "location_index": 0, "departure_time": 0 },
        { "type": "service", "job_id": "job-4823", "location_index": 3, "arrival_time": 6100, "departure_time": 6400, "service_time": 300, "distance_from_prev": 6100, "time_from_prev": 6100 },
        { "type": "end", "location_index": 0, "arrival_time": 12500, "distance_from_prev": 6100, "time_from_prev": 6100 }
      ],
      "summary": { "total_distance": 12200, "total_travel_time": 12200, "total_service_time": 300, "total_waiting_time": 0, "total_time": 12500, "num_stops": 1, "num_jobs": 1, "cost": 12200.0 },
      "violations": []
    },
    {
      "resource_id": "truck-1",
      "day": 0,
      "activities": [
        { "type": "start", "location_index": 0, "departure_time": 0 },
        { "type": "service", "job_id": "job-4821", "location_index": 1, "arrival_time": 5200, "departure_time": 5500, "service_time": 300, "distance_from_prev": 5200, "time_from_prev": 5200 },
        { "type": "end", "location_index": 0, "arrival_time": 10700, "distance_from_prev": 5200, "time_from_prev": 5200 }
      ],
      "summary": { "total_distance": 10400, "total_travel_time": 10400, "total_service_time": 300, "total_waiting_time": 0, "total_time": 10700, "num_stops": 1, "num_jobs": 1, "cost": 10400.0 },
      "violations": []
    }
  ],
  "unserved": [],
  "score": { "total": 40000.0, "travel_distance": 40000.0, "travel_time": 40000.0, "fixed_vehicle_cost": 0.0, "penalty_dropped": 0.0, "fairness_cost": 0.0, "num_vehicles_used": 2 },
  "warnings": []
}
Next steps

FieldGenius VRP API documentation. Generated from the engineering source of truth (.tex docs and app/ source).