Charter Boats
Charter Boats API
Sea Routing

Calculate Sea Route

Get the sailing route between two or more points, navigating around land

Endpoint

GET /searoute/route

Description

Calculates the actual sea route between coordinates, navigating around coastlines and islands. Uses OSM-derived land/water rasters at 25 m resolution with A* pathfinding for accurate maritime routing.

Supports both simple point-to-point routes and multi-waypoint routes. Long routes are automatically split into segments and stitched together.

Responses are cached for 24 hours.

Authentication

Send X-API-Key: YOUR_API_KEY on every call from outside charter.boats — without it Cloudflare bot protection answers with an HTML 403 page, not JSON. Call it from your server: this route sends no CORS headers. See Authentication.

Query Parameters

Use either from+to or waypoints, not both.

ParameterTypeRequiredDescription
fromstringYes*Origin as longitude,latitude
tostringYes*Destination as longitude,latitude
waypointsstringYes*Multiple points as lon,lat|lon,lat|... (min 2)
draughtnumberNoBoat draught in metres (alias: draft). When set, water shallower than the boat can safely use is penalised — the route prefers deeper water and only crosses shallows when no reasonable alternative exists. Omitted = all water costs the same.

* Provide either from+to or waypoints.

Shallow water is avoided, never forbidden: a 25 m grid cannot see individual rocks, so depth influences route preference and is not a depth guarantee. Charts and local knowledge remain the authority on where a hull may actually go.

Example Requests

Point to Point

curl "https://charter.boats/api/searoute/route?from=20.7114,38.8289&to=20.5897,38.6309" \
  -H "X-API-Key: YOUR_API_KEY"

Multi-Waypoint

curl "https://charter.boats/api/searoute/route?waypoints=20.7114,38.8289|20.5897,38.6309|20.4883,38.4127" \
  -H "X-API-Key: YOUR_API_KEY"

With Draught

curl "https://charter.boats/api/searoute/route?from=20.7114,38.8289&to=20.5897,38.6309&draught=2.1" \
  -H "X-API-Key: YOUR_API_KEY"

Example Response

{
  "type": "Feature",
  "geometry": {
    "type": "LineString",
    "coordinates": [
      [20.7114, 38.8289],
      [20.714758424503625, 38.829617043440415],
      [20.719250003691684, 38.822172689977755]
    ]
  },
  "properties": {
    "distance_km": 39.6,
    "distance_nm": 21.4,
    "waypoints": 25,
    "segments": 1,
    "tiles_used": ["raster_20.4_38.4", "raster_20.4_38.8"],
    "fallback_segments": 0,
    "total_iterations": 884757,
    "shallowest_band": 4,
    "band_km": [0, 20.9, 2.1, 1.8, 14.8],
    "depth_data": true,
    "compute_ms": 95
  }
}

Returns a GeoJSON Feature with a LineString geometry. The coordinates array is [longitude, latitude] pairs (truncated above for readability).

Response Properties

PropertyTypeDescription
distance_kmnumberTotal route distance in kilometers
distance_nmnumberTotal route distance in nautical miles
waypointsnumberNumber of coordinates in the route geometry
tiles_usedstring[]Coastline raster tiles used for pathfinding (e.g. raster_20.4_38.8)
total_iterationsnumberA* algorithm iterations
segmentsnumberNumber of route segments (always present; 1 for direct routes)
fallback_segmentsnumber|undefinedCount of segments the router could not route and replaced with a straight line (e.g. an endpoint on land). Any value above 0 means part of the geometry may cross land. Omitted on multi-segment routes when zero
compute_msnumberComputation time in milliseconds
fallbackboolean|undefinedtrue only when nothing was routed — no coverage tile was touched and the solver never iterated — so the whole route is one straight line. Emitted on both single- and multi-segment routes; omitted otherwise. distance_km/distance_nm are great-circle in that case. A segment that was attempted and failed is counted in fallback_segments instead and does not necessarily set this flag, so check both.
shallowest_bandnumber|undefinedShallowest depth band the route crosses: 1 deeper than 3 m, 2 = 2–3 m, 3 = 1–2 m, 4 = under 1 m
band_kmnumber[]|undefinedKilometres of the route spent in each band, indexed by band number (index 0 unused)
depth_databoolean|undefinedtrue when at least one tile on the route carries surveyed depth bands. When false or absent, every water pixel reads as deep by default — treat shallowest_band: 1 as "not surveyed", not "sounded and clear".
draught_m, max_safe_bandnumber|undefinedEcho of the requested draught and the deepest band it may safely use (single-segment routes only)

Coverage

Land/water routing covers the Mediterranean and all charter regions at 25 m resolution, with worldwide coverage rolling out. Depth bands are being added progressively, starting with the Ionian; depth_data in the response says whether the route you asked for has them.

Requests outside coverage do not error — they return 200 with a straight-line route and properties.fallback: true. Always check fallback and fallback_segments rather than assuming a 200 means the path avoided land.

Fallback Behavior

The API falls back to a straight line in two distinct situations, reported differently:

  • Nothing routed — no coastline tile covers the area (outside coverage, or open water with no land in the way), so the solver never ran: tiles_used: [], total_iterations: 0, and top-level fallback: true.
  • A segment could not be routed — e.g. an endpoint on land or in an enclosed body of water. That segment is a straight line and is counted in fallback_segments. The top-level fallback is set only if nothing else on the route was routed either.

Either way the straight part is a great-circle line, so distance_km / distance_nm ignore any land it crosses.

Error Responses

400 Bad Request

{
  "statusCode": 400,
  "message": "Invalid waypoints format. Use: waypoints=lon,lat|lon,lat|..."
}

502 Routing Failed

{
  "statusCode": 502,
  "message": "Failed to fetch sea route: ..."
}

On this page