Charter Boats
Charter Boats API
AI Integrations

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

GET/ai/locations

Search 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 — so q=split also 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, or city without q and 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

ParameterTypeRequiredDescription
qstringNo*Name search (minimum 2 characters). Marina name, city, island, or country.
regionstringNo*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.
countrystringNo*Country name or 2-letter code
citystringNo*City name
typestringNomarina | 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
monthintegerNo1–12 — the month popularity is ranked for (default: current month)
sortstringNopopularity (default) | boats | rating
fromstringNo*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_nmintegerNoHow 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
limitintegerNoMax results (default: 5, max: 20)
currencystringNoEUR (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

curl "https://charter.boats/api/ai/locations?q=split&limit=3&api_version=1.1"
 
# Most popular spots in a region for a given month
curl "https://charter.boats/api/ai/locations?region=Dalmatia&month=8&limit=5&api_version=1.1"
curl "https://charter.boats/api/ai/locations?q=split&limit=3"
 
# Most popular spots in a region for a given month
curl "https://charter.boats/api/ai/locations?region=Dalmatia&month=8&limit=5"

Response

{
  "locations": [
    {
      "id": 20804,
      "name": "Marina Trogir (SCT)",
      "city": "Grad Trogir",
      "country": "Croatia",
      "type": "marina",
      "lat": 43.51342,
      "lon": 16.24422,
      "boat_count": 181,
      "hero_image_url": "https://media.charter.boats/locations/20804/images/1775052577067_a933feac.jpg",
      "from_per_day": 89,
      "overnight_pct": 91,
      "hidden_gem": false,
      "rating": 4.4,
      "rating_count": 155,
      "busyness": "busy",
      "url": "https://charter.boats/locations/marina-trogir-sct"
    },
    {
      "id": 12132,
      "name": "Marina Kaštela",
      "city": "Grad Kaštela",
      "country": "Croatia",
      "type": "marina",
      "lat": 43.5455422,
      "lon": 16.4032056,
      "boat_count": 307,
      "hero_image_url": "https://media.charter.boats/locations/12132/images/1774532443692_c21f5881.jpg",
      "from_per_day": 75,
      "overnight_pct": 88,
      "hidden_gem": false,
      "rating": 2.7,
      "rating_count": 15,
      "busyness": "packed",
      "url": "https://charter.boats/locations/marina-kastela"
    }
  ],
  "ranking_note": "Ordered by observed vessel traffic (AIS) in September; locations without traffic coverage rank after covered ones by fleet size. \"busyness\" compares a location to its own peak month only."
}

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.

curl "https://charter.boats/api/ai/locations?from=aci-marina-split&within_nm=20&limit=2&api_version=1.1"
curl "https://charter.boats/api/ai/locations?from=aci-marina-split&within_nm=20&limit=2"
{
  "locations": [
    {
      "id": 24394,
      "name": "Zavraće Cove",
      "city": "Općina Milna",
      "country": null,
      "type": "spot",
      "lat": 43.32579,
      "lon": 16.425877,
      "estimated_nm": 10.7,
      "hours": 1.8,
      "boats_observed": 111,
      "trips_observed": 237,
      "boat_count": 0,
      "busyness": null,
      "url": "https://charter.boats/locations/uvala-zavrace"
    }
  ],
  "reachable_from": { "id": 12908, "name": "ACI Marina Split", "url": "https://charter.boats/locations/aci-marina-split" },
  "within_nm": 20,
  "basis": "observed",
  "total_within_ring": 99,
  "legs_with_time": 99,
  "ranking_note": "Places boats sail to from ACI Marina Split within 20nm, most-travelled first. A full 20nm leg is about 3.3h. Hours are the estimated distance at 6 knots; …"
}

Reachability rows carry no rating or rating_count, and type applies to them as a filter on the returned places.

Response Fields

FieldTypeDescription
locationsarrayMatching locations, in ranking order
locations[].idnumberLocation ID
locations[].namestringLocation 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[].citystring|nullCity
locations[].countrystring|nullCountry
locations[].typestringmarina | harbour | anchorage | mooring | bay | spot. spot is an unclassified stop — a beach, spit or headland — never a marina
locations[].latnumberLatitude
locations[].lonnumberLongitude
locations[].boat_countintegerNumber of published charter boats based at this location (listed on charter.boats — not tracked AIS traffic)
locations[].hero_image_urlstringThe place's first photo, as on its page. Absent when we hold none
locations[].from_per_daynumberThe 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_pctintegerShare of recorded visits that stay the night. Absent below the AIS evidence gate
locations[].hidden_gembooleanThe 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[].ratingnumber|nullGoogle rating (name and area searches only)
locations[].rating_countnumber|nullGoogle review count (name and area searches only)
locations[].busynessstring|nullTraffic 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[].urlstringLink to the location page on charter.boats, by slug (https://charter.boats/locations/marina-kastela)
ranking_notestringPresent on popularity sorts and on every reachability response — explains the ranking basis
region_matchedstringPresent when region resolved — the canonical region name
region_unresolvedstringPresent when region named an area we don't cover; locations is empty and note explains
no_resultsobjectPresent 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
notestringPresent alongside no_results — a one-sentence rendering of it, generated from it so the two never disagree
reachable_fromobjectReachability mode only — the origin that was resolved: id, name, url
within_nmintegerRing actually used
within_nm_requestedintegerPresent only when the request was snapped to a ring
basisstringWhat 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_ringintegerHow 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_timeintegerHow many carry a distance, and therefore a time
locations[].estimated_nmnumber|nullEstimated distance (haversine ×1.3 for most pairs) — never a charted distance
locations[].hoursnumber|nullestimated_nm ÷ 6 knots — a planning figure inheriting the distance's uncertainty, not a recorded duration
locations[].boats_observed / trips_observedinteger|nullHow much evidence sits behind the leg. null on a place added only because it is near
from_unresolvedstringPresent when from matched nothing; locations is empty and note explains

Errors

StatusMessage
400Provide 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

GET/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_times and hidden_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.

curl "https://charter.boats/api/ai/locations/port-of-hvar?api_version=1.1"
curl "https://charter.boats/api/ai/locations/port-of-hvar"

Response

{
  "id": 20815,
  "name": "Port of Hvar",
  "type": "marina",
  "city": "Hvar town",
  "municipality": "Split-Dalmatia County",
  "country": "Croatia",
  "lat": 43.17238,
  "lon": 16.44077,
  "url": "https://charter.boats/locations/port-of-hvar",
  "rating": 4.5,
  "rating_count": 174,
  "boat_count": 6,
  "from_per_day": 124,
  "description": "The Port of Hvar puts you right in the heart of one of Croatia's most celebrated island towns, with **the old town's medieval streets and UNESCO-liste...",
  "nearby": {
    "bank": 3,
    "fuel": 1,
    "water": 1,
    "fishing": 1,
    "grocery": 14,
    "medical": 2,
    "parking": 4,
    "worship": 8,
    "pharmacy": 2,
    "restaurant": 91,
    "entertainment": 17
  },
  "popular_times": {
    "overnight_pct": 86,
    "typical_day_stop_hours": 3.05,
    "fullest_hour": 21,
    "be_in_by_hour": 17
  },
  "busyness": "busy",
  "busyness_by_month": [
    "very-quiet",
    "very-quiet",
    "very-quiet",
    "quiet",
    "moderate",
    "busy",
    "packed",
    "packed",
    "busy",
    "quiet",
    "very-quiet",
    "very-quiet"
  ],
  "sails_to": [
    {
      "id": 24310,
      "name": "Mlini Bay",
      "type": "spot",
      "url": "https://charter.boats/locations/mlini-bay",
      "estimated_nm": 1.3,
      "hours": 0.2,
      "boat_count": 0,
      "trips_share_pct": 18
    },
    {
      "id": 24341,
      "name": "Uvala Vinogradišće",
      "type": "spot",
      "url": "https://charter.boats/locations/uvala-vinogradisce",
      "estimated_nm": 2.4,
      "hours": 0.4,
      "boat_count": 0,
      "trips_share_pct": 12
    }
  ]
}

Response Fields

FieldTypeDescription
idnumberLocation ID
namestringLocation name
typestringmarina | 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 / countrystringWhere it is
lat / lonnumberCoordinates
urlstringLink to the location page on charter.boats, by slug
rating / rating_countnumberGoogle rating and review count
hidden_gembooleanPresent (as true) only where the page shows the Hidden gem badge
land_accessstringPresent only where the page badges it: track (Rough track only), foot (No road access), none (Only reachable by boat)
boat_countintegerTotal published charter boats based here — not a page of them, and not AIS traffic
from_per_daynumberThe 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_baseobjectFor a place with no fleet of its own, the nearest one that does: id, name, distance_nm, boat_count, url
descriptionstringDescription, cut at a sentence within 500 characters. May contain Markdown emphasis
facilitiesobjectThe page's facility tiles and contacts: berths, max_draft_m, max_length_m, vhf_channel, amenities[], website, phone — each only when held
nearbyobjectNearby Places: points of interest around the place, counted by category (restaurant, grocery, fuel, chandlery…)
popular_timesobjectPopular 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)
busynessstringTraffic 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_montharrayTwelve tiers, January to December
sails_toarrayWhere 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

StatusMessage
400Location ID required
404Location not found

On this page