TL;DR

Two trucks (truck-1, truck-2) sit at the same depot. Four jobs around Amsterdam/Rotterdam need visiting. No GPS, no matrix computation — just a plain distance_matrix in meters. The solver splits the jobs between the two trucks to minimize total distance.

Basic Routing

This is the simplest non-trivial FG-API request: a small explicit distance matrix, two vehicles, and a handful of jobs with no time windows, no skills, no capacities. It is a good starting point for understanding the request/response shape before adding constraints.

The distance matrix

Five locations: index 0 is the shared depot, indices 1-4 are job stops. Values are meters.

0 (depot)1 (job-4821)2 (job-4822)3 (job-4823)4 (job-4824)
005200870061009400
1520004300780010200
287004300055006900
361007800550003100
4940010200690031000

Locations 1-2 (job-4821, job-4822) and 3-4 (job-4823, job-4824) form two natural clusters: the 1-2 pair is 4300m apart, the 3-4 pair is 3100m apart, while crossing between the clusters costs 5500-10200m. With two trucks available, the cheapest plan is one truck per cluster instead of one truck zig-zagging across both.

Request

{
  "metadata": { "problem_id": "basic-routing-example" },
  "travel": {
    "distance_matrix": [
      [0, 5200, 8700, 6100, 9400],
      [5200, 0, 4300, 7800, 10200],
      [8700, 4300, 0, 5500, 6900],
      [6100, 7800, 5500, 0, 3100],
      [9400, 10200, 6900, 3100, 0]
    ]
  },
  "resources": [
    { "id": "truck-1", "depot_index": 0 },
    { "id": "truck-2", "depot_index": 0 }
  ],
  "jobs": [
    { "id": "job-4821", "location_index": 1, "service_time": 300 },
    { "id": "job-4822", "location_index": 2, "service_time": 300 },
    { "id": "job-4823", "location_index": 3, "service_time": 240 },
    { "id": "job-4824", "location_index": 4, "service_time": 240 }
  ],
  "options": { "time_limit_seconds": 5 }
}

Response

{
  "status": "optimal",
  "problem_id": "basic-routing-example",
  "solve_time_ms": 38,
  "routes": [
    {
      "resource_id": "truck-1",
      "day": null,
      "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": "service", "job_id": "job-4822", "location_index": 2, "arrival_time": 9800, "departure_time": 10100, "service_time": 300, "distance_from_prev": 4300, "time_from_prev": 4300 },
        { "type": "end", "location_index": 0, "arrival_time": 18800, "distance_from_prev": 8700, "time_from_prev": 8700 }
      ],
      "summary": {
        "total_distance": 18200,
        "total_travel_time": 18200,
        "total_service_time": 600,
        "total_waiting_time": 0,
        "total_time": 18800,
        "num_stops": 2,
        "num_jobs": 2,
        "cost": 18200.0
      },
      "violations": []
    },
    {
      "resource_id": "truck-2",
      "day": null,
      "activities": [
        { "type": "start", "location_index": 0, "departure_time": 0 },
        { "type": "service", "job_id": "job-4823", "location_index": 3, "arrival_time": 6100, "departure_time": 6340, "service_time": 240, "distance_from_prev": 6100, "time_from_prev": 6100 },
        { "type": "service", "job_id": "job-4824", "location_index": 4, "arrival_time": 9440, "departure_time": 9680, "service_time": 240, "distance_from_prev": 3100, "time_from_prev": 3100 },
        { "type": "end", "location_index": 0, "arrival_time": 19080, "distance_from_prev": 9400, "time_from_prev": 9400 }
      ],
      "summary": {
        "total_distance": 18600,
        "total_travel_time": 18600,
        "total_service_time": 480,
        "total_waiting_time": 0,
        "total_time": 19080,
        "num_stops": 2,
        "num_jobs": 2,
        "cost": 18600.0
      },
      "violations": []
    }
  ],
  "unserved": [],
  "score": {
    "total": 36800.0,
    "travel_distance": 36800.0,
    "travel_time": 36800.0,
    "fixed_vehicle_cost": 0.0,
    "penalty_dropped": 0.0,
    "fairness_cost": 0.0,
    "num_vehicles_used": 2
  },
  "warnings": []
}

What happened

The solver assigned truck-1 to the job-4821/job-4822 cluster and truck-2 to the job-4823/job-4824 cluster:

  • truck-1 drives depot → job-4821 (5200m) → job-4822 (4300m) → depot (8700m), total 18200m.
  • truck-2 drives depot → job-4823 (6100m) → job-4824 (3100m) → depot (9400m), total 18600m.

Combined distance is 36800m. Compare that to sending a single truck through all four stops in any order — the cheapest single-vehicle tour still has to cross between the two clusters twice (at least 2 × 5500m extra versus splitting), which is why using both trucks wins under the default min_distance objective even though there is no vehicle_fixed_cost_weight pushing toward fewer vehicles.

Both routes are "violations": [] because there are no time windows, capacities, or skills in this request — with nothing to violate, the only thing left to optimize is total distance.

No time_matrix supplied

When only distance_matrix is set, FG-API uses it as a stand-in for travel time too, so arrival_time/departure_time in the response are expressed in the same units as the matrix. Supply a separate time_matrix if distance and time should diverge (e.g. highway vs. local roads).

Next steps

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