Charter Boats
Charter Boats API
AI Integrations

Search Routes (AI)

Find sailing passages between destinations with distance and difficulty

Same in API v1.0 and v1.1

GET/ai/routes

Search point-to-point sailing passages with distance and difficulty. Distances are in nautical miles. Only passages whose both endpoints are published locations are returned.

Written passages exist only for some pairs of places, so an empty result does not mean the trip can't be sailed. For "where can I sail from X" based on where boats were actually recorded going, use /ai/locations?from= instead.

Authentication

No authentication required. Sending your key is optional -- see the overview. Shares the /api/ai/* rate limit of up to 60 requests per 10 minutes (30 for Custom GPT clients) without a key — a ceiling to stay under rather than an exact allowance, and a valid API key has its own, larger allowance; see Rate limits.

Query Parameters

ParameterTypeRequiredDescription
fromstringNoPlace name, resolved by the same resolver as boat search (marina, city, island, sailing region or country -- a trailing country or region is understood, as in Nydri, Greece). It resolves to every location of that place, so a name shared by a town and its marina finds the passages written against either, and the response says which place it landed on in location_matched. A name that resolves to nothing returns an empty list with from_unresolved -- never passages from somewhere else.
location_idintegerNoExact location ID. Finds passages starting or ending at this location. Takes precedence over from. A value that is not a positive integer, or an id that matches no published location, returns an empty list with location_unresolved.
qstringNoFree-text match against the passage title.
difficultystringNoFilter by difficulty: easy, moderate, challenging
limitintegerNoMax results (default: 5, max: 20)

Request

# Passages from Marina Sivota (Lefkada)
curl "https://charter.boats/api/ai/routes?from=Marina%20Sivota&limit=2"
 
# The same, by location id
curl "https://charter.boats/api/ai/routes?location_id=21099&limit=2"

from and location_id filter passages that start or end at the place.

Response

{
  "location_matched": { "id": "21099", "name": "Ionian Islands", "country": "Greece" },
  "routes": [
    {
      "id": "d138aeae-bfc2-482a-a803-c070cb47c548",
      "title": "Sivota to Kolpiskos Mandras",
      "from": "Lefkas, Marina Sivota, Greece",
      "to": "Kolpiskos Mandras, Greece",
      "distance_nm": 10,
      "estimated_hours": 1.7,
      "difficulty": null,
      "description": null,
      "url": "https://charter.boats/routes/sivota-to-kolpiskos-mandras"
    },
    {
      "id": "af500dca-c807-4033-b793-c8b6368c13d1",
      "title": "Sivota to Bay Roygas",
      "from": "Lefkas, Marina Sivota, Greece",
      "to": "Bay Roygas, Greece",
      "distance_nm": 10,
      "estimated_hours": 1.7,
      "difficulty": null,
      "description": null,
      "url": "https://charter.boats/routes/sivota-to-bay-roygas"
    }
  ]
}

difficulty and description are frequently null — they are only populated for curated routes.

Response Fields

FieldTypeDescription
location_matchedobjectPresent when you sent from: the place it resolved to (id, name, country). A sailing-region match reports id as "region:<Name>".
routesarrayMatching passages, newest first
routes[].idstringRoute UUID
routes[].titlestringRoute title
routes[].fromstringDeparture location (name, country)
routes[].tostringArrival location (name, country)
routes[].distance_nmnumber|nullDistance in nautical miles
routes[].estimated_hoursnumber|nulldistance_nm at 6 knots, rounded to a tenth -- the same cruising speed every AI endpoint uses
routes[].difficultystring|nullDifficulty level
routes[].descriptionstring|nullRoute description
routes[].urlstringLink to the passage page on charter.boats
no_resultsobjectPresent only when routes is empty. See Empty results.
from_unresolvedstringPresent when from matched no place we hold: echoes the term. routes is then empty and no wider search was run.
location_unresolvedstringPresent when location_id was not a location id, or matched no published location: echoes the value. routes is then empty and no wider search was run.
notestringPresent only when routes is empty. One-sentence rendering of no_results.

Empty results

An empty routes array always comes with the shared zero-results block: searched names the filters as resolved -- the place we landed on, not the term you sent -- why explains that passages are only written up for some pairs, and try_next points at the observed-passage search on /ai/locations, plus which of your filters to drop.

A from that resolves to nothing is a different answer, and says so under from_unresolved: no passage search is run at all, rather than one without the place.

{
  "routes": [],
  "no_results": {
    "searched": "difficulty easy",
    "why": "We publish written passages only for pairs that have one, so this returns nothing even when both places exist and boats sail between them.",
    "try_next": [
      "search_locations with `from`=<location id> — where boats were actually recorded sailing from there",
      "drop `difficulty`"
    ]
  },
  "note": "Nothing matched difficulty easy. We publish written passages only for pairs that have one, so this returns nothing even when both places exist and boats sail between them. Try: search_locations with `from`=<location id> — where boats were actually recorded sailing from there; drop `difficulty`."
}

On this page