Charter Boats
Charter Boats API
AI Integrations

Search Routes (AI)

Find sailing passages between destinations with distance, difficulty, and ratings

GET/ai/routes

Search point-to-point sailing passages with distance, difficulty, and community ratings. 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 60 requests per 10 minutes (30 for Custom GPT clients).

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 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,
      "rating": null,
      "variant_count": 0,
      "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,
      "rating": null,
      "variant_count": 0,
      "description": null,
      "url": "https://charter.boats/routes/sivota-to-bay-roygas"
    }
  ]
}

difficulty, rating, 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 sorted by rating
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[].ratingnumber|nullAverage community rating
routes[].variant_countinteger|nullNumber of route variants (direct, scenic, etc.)
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: echoes the value.
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