Search Locations (AI)
Search sailing destinations by name, or rank an area's locations by real on-the-water popularity
Showing API v1.1 — versionsShowing API v1.0 — versions
/ai/locationsSearch for sailing locations — marinas, harbours, and anchorages where a boat can berth or drop anchor and stay overnight (charter bases are locations too). Two search modes, freely combinable:
- Name search (
q): normalized matching that handles diacritics and special characters, against the place's name and the town and country text around it — soq=splitalso returns marinas in the Split area, and a multi-word query matches when every word is present. Places with no name are not searchable by name. - Area listing: give
region,country, orcitywithoutqand get the area's locations.
You are reading API v1.1 — send api_version=1.1 to get it. A call to /api/ai/* that names no version gets 1.0; switch the picker at the top to v1.0 to read that shape.
You are reading API v1.0, what a call to /api/ai/* gets when it names no api_version. The answer is the same except that each from_per_day below is named from_price_per_day — the same figure. New integrations should send api_version=1.1 — see Versions.
Either way, results are ranked by popularity unless you ask for another sort — observed vessel traffic from EMODnet AIS climatology for the requested month. Locations without traffic coverage rank after covered ones, by fleet size. A name search is therefore not ordered by how closely the name matches: q=split leads with the busiest marinas in the Split area, not the closest name match.
A third mode, reachability (from), lists the places boats sail to from a given location — see from below.
Authentication
No authentication required. A key is optional — a valid API key records the call against your account; an invalid one is ignored, never a 401. All /ai/* endpoints share one rate-limit budget 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; past it you get 429 with a Retry-After header. See Rate limits.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | No* | Name search (minimum 2 characters). Marina name, city, island, or country. |
region | string | No* | Sailing region as a guest says it: Dalmatia, Istria, Kvarner, the Ionian, the Saronic, the Cyclades, the Dodecanese, the Sporades. An unknown region returns an empty list with a note — never a worldwide result. |
country | string | No* | Country name or 2-letter code |
city | string | No* | City name |
type | string | No | marina | harbour | anchorage | bay | mooring | spot — the same vocabulary locations[].type carries back, so a type read off a result can be sent straight back as a filter. spot is an unclassified stop (beach, spit, headland); a place with no stored category answers type=spot and never type=marina |
month | integer | No | 1–12 — the month popularity is ranked for (default: current month) |
sort | string | No | popularity (default) | boats | rating |
from | string | No* | Origin location id or slug — switches the endpoint into reachability mode: places boats were recorded sailing to from there, most-travelled first. Anything that is not an id or slug shape (spaces, for instance) is a 400; a well-formed id or slug that matches no published location returns an empty list with from_unresolved, never a worldwide result |
within_nm | integer | No | How far, for a from search. One of 10, 20, 35, 55, 100 (default 35); anything else snaps up to the next ring — anything over 100 to 100 — and is echoed as within_nm_requested. A charter boat makes about 6 knots, so these are ~1.7h, ~3.3h, ~5.8h, ~9.2h and ~16.7h |
limit | integer | No | Max results (default: 5, max: 20) |
currency | string | No | EUR (default) or USD: from_per_day is written in it, at the rate the site's currency selector uses |
*At least one of q, region, country, city, or from is required.
Request
Response
Reachability
from answers "where can I sail from here". Places come from, in order: passages boats were actually recorded making from the origin, then computed sea-route neighbours, then — only when those give fewer places than limit — the nearest places inside the ring. basis names what produced the bulk of the list, so an observed list can still end with a few merely-nearby places; those rows carry boats_observed and trips_observed as null and sort after every observed leg.
Reachability rows carry no rating or rating_count, and type applies to them as a filter on the returned places.
Response Fields
| Field | Type | Description |
|---|---|---|
locations | array | Matching locations, in ranking order |
locations[].id | number | Location ID |
locations[].name | string | Location name — the English form where we hold one (Zavraće Cove for Uvala Zavraće). A place with no name of its own shows its type instead (e.g. "Anchorage") — never null |
locations[].city | string|null | City |
locations[].country | string|null | Country |
locations[].type | string | marina | harbour | anchorage | mooring | bay | spot. spot is an unclassified stop — a beach, spit or headland — never a marina |
locations[].lat | number | Latitude |
locations[].lon | number | Longitude |
locations[].boat_count | integer | Number of published charter boats based at this location (listed on charter.boats — not tracked AIS traffic) |
locations[].hero_image_url | string | The place's first photo, as on its page. Absent when we hold none |
locations[].from_per_day | number | The lowest list figure here — the grand total at max guests per day for the boat. Absent where nothing is priced. Replaces from_price_per_day (a day rate), removed 2026-10-01 |
locations[].overnight_pct | integer | Share of recorded visits that stay the night. Absent below the AIS evidence gate |
locations[].hidden_gem | boolean | The page's Hidden gem badge: an anchorage, bay or mooring boats use that Google has no rating for. Absent below the AIS evidence gate |
locations[].rating | number|null | Google rating (name and area searches only) |
locations[].rating_count | number|null | Google review count (name and area searches only) |
locations[].busyness | string|null | Traffic tier for the requested month, relative to this location's own peak: very-quiet | quiet | moderate | busy | packed. null without AIS coverage — absolute counts are never exposed |
locations[].url | string | Link to the location page on charter.boats, by slug (https://charter.boats/locations/marina-kastela) |
ranking_note | string | Present on popularity sorts and on every reachability response — explains the ranking basis |
region_matched | string | Present when region resolved — the canonical region name |
region_unresolved | string | Present when region named an area we don't cover; locations is empty and note explains |
no_results | object | Present when a name or area search, an unresolved region or from, or a from ring holding nothing, comes back empty — never alongside results. { searched, why, try_next[] } — what was actually searched (the name and every area/type filter as applied), why it came back empty, and the concrete calls to try next |
note | string | Present alongside no_results — a one-sentence rendering of it, generated from it so the two never disagree |
reachable_from | object | Reachability mode only — the origin that was resolved: id, name, url |
within_nm | integer | Ring actually used |
within_nm_requested | integer | Present only when the request was snapped to a ring |
basis | string | What produced the bulk of the list: observed = boats were tracked making these passages, searoute = computed sea-route neighbours, nearby = straight-line proximity, none = nothing found. Check each row's trips_observed for the evidence behind that row |
total_within_ring | integer | How many places fell inside the ring before limit. 0 comes with no_results — the ring is empty, or type emptied it — never a bare [] |
legs_with_time | integer | How many carry a distance, and therefore a time |
locations[].estimated_nm | number|null | Estimated distance (haversine ×1.3 for most pairs) — never a charted distance |
locations[].hours | number|null | estimated_nm ÷ 6 knots — a planning figure inheriting the distance's uncertainty, not a recorded duration |
locations[].boats_observed / trips_observed | integer|null | How much evidence sits behind the leg. null on a place added only because it is near |
from_unresolved | string | Present when from matched nothing; locations is empty and note explains |
Errors
| Status | Message |
|---|---|
400 | Provide q (min 2 chars), an area filter (region / country / city), or from (sail from a place) |
400 | `from` must be a location id or slug — from was not an id or slug shape |
Location Details
/ai/locations/{id}Full detail for a single location, following the sections of its page on charter.boats and using the page's own words: facilities, nearby places, Popular Times, how busy it is through the year, and where boats sail on to — the AIS-derived parts are what exists nowhere else.
A key is left out when we hold nothing for it, rather than sent as null — a bay with no berths, VHF or phone gets a short answer. The observed layers come from different data and appear independently:
busyness/busyness_by_month— EMODnet vessel-density climatology. Answers when is it busy here.popular_timesandhidden_gem— recorded stays from the Datalastic archive. Answers how boats use it.sails_to— recorded passages. Answers where boats go from here; times are estimates (distance ÷ 6 knots plus 30 minutes to leave and arrive), not recorded durations.
Request
The {id} accepts the numeric id or the slug (aci-marina-split). Optional currency (EUR, the default, or USD) writes from_per_day in it.
Response
Response Fields
| Field | Type | Description |
|---|---|---|
id | number | Location ID |
name | string | Location name |
type | string | marina | harbour | anchorage | mooring | bay | spot — the same value search_locations gives the place. spot is an unclassified stop — a beach, spit or headland — never a marina |
city / municipality / country | string | Where it is |
lat / lon | number | Coordinates |
url | string | Link to the location page on charter.boats, by slug |
rating / rating_count | number | Google rating and review count |
hidden_gem | boolean | Present (as true) only where the page shows the Hidden gem badge |
land_access | string | Present only where the page badges it: track (Rough track only), foot (No road access), none (Only reachable by boat) |
boat_count | integer | Total published charter boats based here — not a page of them, and not AIS traffic |
from_per_day | number | The lowest list figure here — the grand total at max guests per day for the boat. Replaces from_price_per_day (a day rate), removed 2026-10-01 |
nearest_bookable_base | object | For a place with no fleet of its own, the nearest one that does: id, name, distance_nm, boat_count, url |
description | string | Description, cut at a sentence within 500 characters. May contain Markdown emphasis |
facilities | object | The page's facility tiles and contacts: berths, max_draft_m, max_length_m, vhf_channel, amenities[], website, phone — each only when held |
nearby | object | Nearby Places: points of interest around the place, counted by category (restaurant, grocery, fuel, chandlery…) |
popular_times | object | Popular Times from recorded stays: overnight_pct, typical_day_stop_hours (median length of a daytime stop — night stays excluded), fullest_hour (local hour with the most boats present), be_in_by_hour (local hour by which four fifths of the day's arrivals are in) |
busyness | string | Traffic tier for the current month, relative to this place's own peak month: very-quiet | quiet | moderate | busy | packed. Same vocabulary as the list — never a comparison with another place |
busyness_by_month | array | Twelve tiers, January to December |
sails_to | array | Where boats were recorded sailing from here, most-travelled first: id, name, type, url, estimated_nm (haversine ×1.3 for most pairs — not a charted distance), hours (estimated_nm ÷ 6 knots, a planning figure), boat_count, trips_share_pct (the row's share of the tracked trips to the places listed — not of every trip out of here; absent where none were tracked) |
Errors
| Status | Message |
|---|---|
400 | Location ID required |
404 | Location not found |