Sea route
Sea distance, transit time and the ECA and non-ECA miles between two ports or two points, with optional alternative corridors and route geometry. One call is one request, whatever options you set. The example is Shanghai to Rotterdam with alternatives, trimmed to the Panama corridor.
Query parameters
Origin. A UN/LOCODE such as CNSHA, coordinates as lat,lng, or any form from the Locations table under Road, rail and air. Points snap to the nearest point of the sea network.
SEA (default), ROAD, RAIL, AIR or RIVER. SEA keeps the response shape on this page. The other modes answer with sections, see Road, rail and air and Inland waterways.
Destination, in the same forms as from.
Service speed for the transit time, 3 to 40. Default 14.
short (default) takes the shortest open route. safe also closes Bab-el-Mandeb, so Asia to Europe goes through Panama or round the Cape.
Comma-separated passages to close, from the Passages table below. Example: suez,panama.
Also return the other sensible corridors. Closing Suez gives Panama and the Cape, closing Panama gives Suez and the Cape, closing Malacca gives Sunda. Default false.
Include the route polyline as [lat, lng] pairs, for drawing on a map. Default false.
Response schema
15 fields
Nested exactly as the JSON is. Open a branch to read its fields; hover a name for the full dot-path.
data
The resolved points: locode, name, country, country_code, lat, lng. source is network (a port on the routing graph), unlocode (UN/LOCODE coordinates) or coordinates (you sent lat,lng).
The resolved destination, same shape as data.from.
short or safe, as applied.
Every passage closed for this calculation: yours plus the one safe adds.
route
Name of the corridor: via Suez Canal, via Panama Canal, via Cape of Good Hope or direct.
distance
Total sea distance in nautical miles. distance.km carries the same in kilometres.
eca
Miles sailed inside an Emission Control Area.
Miles sailed outside every ECA. eca.nm + eca.non_eca_nm = distance.nm.
ECA miles as a fraction of the total, 0 to 1.
Miles per zone: id, name, nm. Zones the route does not enter are left out.
transit
Time under way at transit.speed_knots, also given as transit.hours. Port stays, canal waiting and bunker calls are not included.
Canals and straits on the route, as id and name.
Only with path=true: the polyline as [lat, lng].
Only with alternatives=true. Same shape as route, plus avoids[] (the passages closed to get it) and extra.nm, extra.days against the main route. Sorted by distance, empty when no other corridor makes sense.
meta
Server time for the calculation.
Errors
A failure answers with an HTTP error status and { "error": { "code", "message" } }. Branch on the code, and log the message.
A parameter is missing or malformed: an unknown mode, speed_knots outside 3 to 40, an unknown passage in avoid, a pol/pod that is not a sea port, or a date that is not YYYY-MM-DD.
The location is well formed but we cannot place it: an unknown code, or an address the geocoder does not find.
No route joins the two points with the modes and passages you allowed, for example SEA only between two inland cities. When a seasonal river closure is the cause, the message names each closure, its typical window and the date it usually reopens.
Free-text address lookup is briefly down. Codes and coordinates keep working.
How the ECA split is measured
The route is walked in steps of at most 20 nautical miles, and each step is counted in the ECA it lies in or as open sea. Boundaries model MARPOL Annex VI to within a few miles at the zone edge. Good for spot-rate models and fuel estimates, not a legal compliance record.
What transit time covers
Distance divided by speed, so time under way. Port stays, canal transits and waiting depend on the service, not the geometry: add them yourself.
Alternatives
Each alternative is a real route with one or more passages closed. A route that already avoids Suez gets no Suez-closed alternative. Anything longer than 2.2 times the main route is dropped as not commercially sensible.
Coverage
14,196 ports: 3,962 on the routing network and the remaining UN/LOCODE seaports with coordinates. Any point at sea or on the coast also works as lat,lng.
Passages
Ids accepted by <code>avoid</code> and returned in <code>passages[]</code> and <code>avoids[]</code>. GET /passages returns the same list.
| id | Passage |
|---|---|
| suez | Suez Canal |
| panama | Panama Canal |
| babalmandab | Bab-el-Mandeb |
| malacca | Strait of Malacca |
| gibraltar | Strait of Gibraltar |
| ormuz | Strait of Hormuz |
| bosporus | Bosporus |
| sunda | Sunda Strait |
| south_africa | Cape of Good Hope |
| chili | Strait of Magellan |
| bering | Bering Strait (always avoided) |
| northwest | Northwest Passage (always avoided) |
ECA zones
Emission Control Areas under MARPOL Annex VI that the split is measured against.
| id | Zone | Regulates | In force since |
|---|---|---|---|
| north-american | North American ECA | SOx, NOx, PM | 2012-08-01 |
| us-caribbean | US Caribbean Sea ECA | SOx, NOx, PM | 2014-01-01 |
| north-sea | North Sea ECA | SOx, NOx (Tier III from 2021) | 2007-11-22 |
| baltic | Baltic Sea ECA | SOx, NOx (Tier III from 2021) | 2006-05-19 |
| mediterranean | Mediterranean Sea ECA | SOx, PM | 2025-05-01 |
curl 'https://api.navo24.com/v1/distance/route?from=CNSHA&mode=SEA&to=NLRTM&speed_knots=14&routing_mode=short&alternatives=true' \
-H "Authorization: Bearer tmcp_YOUR_API_KEY"const res = await fetch("https://api.navo24.com/v1/distance/route?from=CNSHA&mode=SEA&to=NLRTM&speed_knots=14&routing_mode=short&alternatives=true", {
headers: {
"Authorization": "Bearer tmcp_YOUR_API_KEY"
}
});
const data = await res.json();import requests
res = requests.get(
"https://api.navo24.com/v1/distance/route?from=CNSHA&mode=SEA&to=NLRTM&speed_knots=14&routing_mode=short&alternatives=true",
headers={"Authorization": "Bearer tmcp_YOUR_API_KEY"},
)
data = res.json() {
"data": {
"from": {
"locode": "CNSHA",
"name": "Shanghai",
"country": "China",
"country_code": "CN",
"lat": 31.40009,
"lng": 121.49711,
"source": "network"
},
"to": {
"locode": "NLRTM",
"name": "Rotterdam",
"country": "Netherlands",
"country_code": "NL",
"lat": 51.90438,
"lng": 4.44245,
"source": "network"
},
"routing_mode": "short",
"avoided": [],
"route": {
"label": "via Suez Canal",
"distance": {
"nm": 10588.6,
"km": 19610.1
},
"eca": {
"nm": 2340.5,
"non_eca_nm": 8248.1,
"share": 0.221,
"zones": [
{
"id": "north-sea",
"name": "North Sea ECA",
"nm": 405.7
},
{
"id": "mediterranean",
"name": "Mediterranean Sea ECA",
"nm": 1934.8
}
]
},
"transit": {
"speed_knots": 14.0,
"hours": 756.3,
"days": 31.51
},
"passages": [
{
"id": "gibraltar",
"name": "Strait of Gibraltar"
},
{
"id": "suez",
"name": "Suez Canal"
},
{
"id": "malacca",
"name": "Strait of Malacca"
},
{
"id": "babalmandab",
"name": "Bab-el-Mandeb"
}
]
},
"alternatives": [
{
"label": "via Panama Canal",
"distance": {
"nm": 13508.5,
"km": 25017.7
},
"eca": {
"nm": 498.0,
"non_eca_nm": 13010.5,
"share": 0.0369,
"zones": [
{
"id": "us-caribbean",
"name": "US Caribbean Sea ECA",
"nm": 121.2
},
{
"id": "north-sea",
"name": "North Sea ECA",
"nm": 376.7
}
]
},
"transit": {
"speed_knots": 14.0,
"hours": 964.9,
"days": 40.2
},
"passages": [
{
"id": "panama",
"name": "Panama Canal"
}
],
"avoids": [
{
"id": "suez",
"name": "Suez Canal"
}
],
"extra": {
"nm": 2919.9,
"days": 8.69
}
}
]
},
"meta": {
"took_ms": 120
}
}