[Developer platform](/en/developers)

# Multi-modal vehicle routing

Send locations, vehicles and shipments. Get tours back. A fleet can mix road, rail, inland waterway, maritime and air, and nothing in the request has to exist in Transportial.

**Runs on:** The Transportial VRP solver, the same code that plans trips inside the product.

| Endpoint        | Permission    | What it does                                                |
| --------------- | ------------- | ----------------------------------------------------------- |
| POST /vrp/solve | calculate:vrp | Solve a pickup-and-delivery problem from the request alone. |

Base URL `https://api.app.transportial.com/api`. Send `x-api-key` and `x-api-secret`, from an API access integration holding the permission above. Note that a missing permission answers 401 and missing authentication answers 403.

## What the solver does

Each shipment is a pickup and a delivery that must be served by the same vehicle, pickup first. The solver assigns shipments to vehicles and orders the stops, within the capacity of each vehicle and the time windows of each stop.

The default search, atomic\_search, starts from a nearest-neighbour construction and improves it with relocate, swap, merge and split moves plus 2-opt. large\_neighborhood\_search repeatedly removes part of the plan and rebuilds it, with simulated-annealing acceptance, and also retries shipments the first construction could not place. Choose with options.solver, and cap the search with options.timeBudgetMs.

## How the modes work

Every vehicle has one transport mode and drives one tour. The solver uses that mode's travel data for the tour and the capacity figures that matter for it: weight, loading metres and volume on road, weight on rail and water, chargeable weight and volume in the air.

locations\[\].transportModes says which non-road modes reach a place, so a barge only gets shipments between quays. Leave it out for every mode, or send an empty list for road only. Road reaches every location.

A shipment stays on one vehicle. Chains that transfer between modes, such as truck to train to truck, are a different problem: POST /routeOptions plans those on saved consignments and scheduled departures.

## Bring your own distances

Send matrices with one entry per mode, or one without a mode to cover the rest, and the solver uses your travel data as is. A mode without a matrix is estimated from straight lines, with a detour factor and an average speed per mode. Estimates are always named in warnings: distance\_matrix\_estimated for road and mode\_distances\_estimated for the others.

## Example

### A truck and a barge from one port

Utrecht and Eindhoven are road only. The steel is too heavy for the truck, and Duisburg is on the water, so it goes by barge. Note the truck reaching Eindhoven as the time window opens.

Call

```
curl -X POST "https://api.app.transportial.com/api/vrp/solve" \
  -H "x-api-key: $TRANSPORTIAL_KEY" \
  -H "x-api-secret: $TRANSPORTIAL_SECRET" \
  -H "Content-Type: application/json" \
  -d @request.json
```

request.json

```
{
  "locations": [
    { "id": "rotterdam", "lat": 51.9244, "lon": 4.4777, "transportModes": ["inlandWaterway"] },
    { "id": "duisburg", "lat": 51.4344, "lon": 6.7623, "transportModes": ["inlandWaterway"] },
    { "id": "utrecht", "lat": 52.0907, "lon": 5.1214, "transportModes": [] },
    { "id": "eindhoven", "lat": 51.4416, "lon": 5.4697, "transportModes": [] }
  ],
  "vehicles": [
    { "id": "truck-1", "startLocationId": "rotterdam", "capacity": { "weightKg": 24000 } },
    { "id": "barge-1", "startLocationId": "rotterdam", "transportMode": "inlandWaterway", "capacity": { "weightKg": 2000000 } }
  ],
  "shipments": [
    {
      "id": "pallets-utrecht",
      "pickup": { "locationId": "rotterdam", "serviceSeconds": 900 },
      "delivery": { "locationId": "utrecht", "serviceSeconds": 900 },
      "load": { "weightKg": 8000 }
    },
    {
      "id": "pallets-eindhoven",
      "pickup": { "locationId": "rotterdam", "serviceSeconds": 900 },
      "delivery": {
        "locationId": "eindhoven",
        "serviceSeconds": 900,
        "timeWindows": [{ "start": "2026-11-10T10:00:00Z", "end": "2026-11-10T14:00:00Z" }]
      },
      "load": { "weightKg": 6000 }
    },
    {
      "id": "steel-duisburg",
      "pickup": { "locationId": "rotterdam", "serviceSeconds": 7200 },
      "delivery": { "locationId": "duisburg", "serviceSeconds": 7200 },
      "load": { "weightKg": 900000 }
    }
  ],
  "options": { "objective": "minimize_distance", "startTime": "2026-11-10T06:00:00Z", "timeBudgetMs": 2000 }
}
```

Response

```
{
  "success": true,
  "routes": [
    {
      "vehicleId": "truck-1",
      "transportMode": "road",
      "distanceKm": 274.01,
      "drivingSeconds": 12330,
      "stops": [
        { "locationId": "rotterdam", "arrival": "2026-11-10T06:00:00Z", "departure": "2026-11-10T06:15:00Z", "activities": [{ "type": "pickup", "shipmentId": "pallets-utrecht" }] },
        { "locationId": "rotterdam", "arrival": "2026-11-10T06:15:00Z", "departure": "2026-11-10T06:30:00Z", "activities": [{ "type": "pickup", "shipmentId": "pallets-eindhoven" }] },
        { "locationId": "eindhoven", "arrival": "2026-11-10T10:00:00Z", "departure": "2026-11-10T10:15:00Z", "activities": [{ "type": "delivery", "shipmentId": "pallets-eindhoven" }] },
        { "locationId": "utrecht", "arrival": "2026-11-10T11:29:09Z", "departure": "2026-11-10T11:44:09Z", "activities": [{ "type": "delivery", "shipmentId": "pallets-utrecht" }] },
        { "locationId": "rotterdam", "arrival": "2026-11-10T12:30:44Z", "departure": "2026-11-10T12:30:44Z", "activities": [] }
      ]
    },
    {
      "vehicleId": "barge-1",
      "transportMode": "inlandWaterway",
      "distanceKm": 433.332,
      "drivingSeconds": 119998,
      "stops": [
        { "locationId": "rotterdam", "arrival": "2026-11-10T06:00:00Z", "departure": "2026-11-10T08:00:00Z", "activities": [{ "type": "pickup", "shipmentId": "steel-duisburg" }] },
        { "locationId": "duisburg", "arrival": "2026-11-11T00:39:59Z", "departure": "2026-11-11T02:39:59Z", "activities": [{ "type": "delivery", "shipmentId": "steel-duisburg" }] },
        { "locationId": "rotterdam", "arrival": "2026-11-11T19:19:58Z", "departure": "2026-11-11T19:19:58Z", "activities": [] }
      ]
    }
  ],
  "unassigned": [],
  "totalDistanceKm": 707.342,
  "totalDrivingSeconds": 132328,
  "warnings": ["distance_matrix_estimated", "mode_distances_estimated"]
}
```

## Limits worth knowing

* At most 500 locations, 100 vehicles and 250 shipments per request.
* Successful calculations are metered on your plan. Past its free allowance, a 402 asks you to agree to the price: repeat the call with hasAgreedToPrice set to true.
* timeBudgetMs is at most 10,000\. The answer is synchronous.
* Each vehicle drives one tour, and shipments are not split.
* Tenant settings such as driver-hour rules do not apply; only the request counts.
* A 429 with Retry-After means all calculation slots are busy.

## Questions

Does a solve store anything on my platform?

No. The solver runs on in-memory data, and no order, trip or location is created.

Are the distances real road distances?

Only if you send them in matrices. Otherwise they are straight-line estimates and the response says so in warnings.

Can one shipment change mode on the way?

Not on this endpoint. Each shipment stays on one vehicle. Mode-changing chains are planned by POST /routeOptions on saved consignments.

[Create a developer account](https://portal.otms.transportial.com/auth/developer/register)[Read the guide](https://docs.transportial.com/examples/multimodal-vrp)[How it works](/en/blog/multi-modal-vehicle-routing-api)

---
Canonical page: https://transportial.com/en/developers/algorithms/vehicle-routing
