Most vehicle routing APIs assume every vehicle is a truck. Plenty of freight doesn't work that way: a planner in Rotterdam can have trucks, a barge and a rail slot in the same day's work, and each moves at a different speed, reaches different places and is limited by different things.
Our solver has handled that inside Transportial for a while. It is now available on its own, as POST /vrp/solve. You send the whole problem in one request and get the tours back. You don't need any orders, vehicles or locations in Transportial, and the call stores nothing.
What you send
Three lists and some options: locations, vehicles and shipments. A shipment is a pickup and a delivery that the same vehicle must serve, in that order. This is a truck and a barge leaving the same port:
{
"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": [] }
],
"vehicles": [
{ "id": "truck-1", "startLocationId": "rotterdam", "capacity": { "weightKg": 24000 } },
{ "id": "barge-1", "startLocationId": "rotterdam", "transportMode": "inlandWaterway", "capacity": { "weightKg": 2000000 } }
],
"shipments": [
{ "id": "pallets", "pickup": { "locationId": "rotterdam" }, "delivery": { "locationId": "utrecht" }, "load": { "weightKg": 8000 } },
{ "id": "steel", "pickup": { "locationId": "rotterdam" }, "delivery": { "locationId": "duisburg" }, "load": { "weightKg": 900000 } }
]
}
The steel is far too heavy for the truck. Utrecht has an empty transportModes list, so only road reaches it. The solver sends each shipment to the only vehicle that can take it, and with more shipments it decides who carries what and in which order.
What makes it multi-modal
Each vehicle has one mode and drives one tour, and three things change with that mode.
Travel times
Every mode has its own travel data. You can send a matrix per mode, from your own routing engine or sailing schedules. A mode without one is estimated from the straight-line distance with a detour factor and average speed: 13 km/h and a 1.3 detour for a barge by default, against 80 km/h and a 1.3 detour for a truck. Estimates are never silent, because the response names them in warnings.
Where a vehicle can go
A location can list the non-road modes that reach it, which is how a quay or rail terminal differs from a warehouse. A barge only gets shipments between places on the water. Road reaches everywhere.
What fills up first
A truck can run out of weight, loading metres or volume. Water and rail are checked on weight, and air on chargeable weight and volume.
How it searches
The default search starts with a nearest-neighbour plan, then improves it with moves between tours (relocating, swapping, merging and splitting) and 2-opt within a tour. The alternative, large_neighborhood_search, repeatedly tears out part of the plan and rebuilds it with greedy and regret insertion, using simulated-annealing acceptance so it can escape a local optimum. It also retries shipments the first plan could not place.
Both respect time windows, waiting at a stop when the vehicle arrives early, and both stop when your time budget runs out, up to ten seconds. Shipments that didn't make it are returned with a reason, so nothing disappears quietly.
What it doesn't do
A shipment stays on one vehicle on this endpoint. If your freight goes truck to train to truck with a transfer at a terminal, that is a different problem, and POST /routeOptions plans those chains from saved consignments and scheduled departures. Driver-hour rules aren't applied here either, since they depend on platform settings and this endpoint only looks at your request.
And an estimate is an estimate: without your own matrices, the distances are good enough to compare plans, not to promise an arrival time.
Try it
The vehicle routing page has the full example with its real response. It sits alongside the rest of what you can call the same way, including 3D load planning. For a step-by-step version, see the guide in our documentation.


