Search Routes (AI)
Find sailing passages between destinations with distance, difficulty, and ratings
/ai/routesSearch 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
| Parameter | Type | Required | Description |
|---|---|---|---|
from | string | No | Place 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_id | integer | No | Exact 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. |
q | string | No | Free-text match against the passage title. |
difficulty | string | No | Filter by difficulty: easy, moderate, challenging |
limit | integer | No | Max results (default: 5, max: 20) |
Request
from and location_id filter passages that start or end at the place.
Response
difficulty, rating, and description are frequently null — they are only populated for curated routes.
Response Fields
| Field | Type | Description |
|---|---|---|
location_matched | object | Present when you sent from: the place it resolved to (id, name, country). A sailing-region match reports id as "region:<Name>". |
routes | array | Matching passages sorted by rating |
routes[].id | string | Route UUID |
routes[].title | string | Route title |
routes[].from | string | Departure location (name, country) |
routes[].to | string | Arrival location (name, country) |
routes[].distance_nm | number|null | Distance in nautical miles |
routes[].estimated_hours | number|null | distance_nm at 6 knots, rounded to a tenth -- the same cruising speed every AI endpoint uses |
routes[].difficulty | string|null | Difficulty level |
routes[].rating | number|null | Average community rating |
routes[].variant_count | integer|null | Number of route variants (direct, scenic, etc.) |
routes[].description | string|null | Route description |
routes[].url | string | Link to the passage page on charter.boats |
no_results | object | Present only when routes is empty. See Empty results. |
from_unresolved | string | Present when from matched no place we hold: echoes the term. routes is then empty and no wider search was run. |
location_unresolved | string | Present when location_id was not a location id: echoes the value. |
note | string | Present 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.