Charter Boats
Charter Boats API
Locations

Map Search

Search locations inside a map viewport, ranked by observed vessel traffic

Endpoint

GET /locations/map

Description

Returns the locations inside a geographic bounding box, ranked by how many distinct boats have been observed at each one. It powers the map on /locations.

The response is deliberately bounded. A viewport can contain thousands of locations, so the endpoint applies a zoom-dependent budget: it caps the number of results, spreads them across a grid so pins do not stack on one busy harbour, and only returns berth-density imagery once the zoom is close enough for it to be legible.

ZoomMax resultsGrid thinningHeat imagery
≤ 6800coarseno
7–9500yesno
10–11400yesno
≥ 12300noyes

The budget is chosen by the server; zoom is an input to it, not a way to override it.

Authentication

Send X-API-Key: YOUR_API_KEY on every call from outside charter.boats — without it Cloudflare bot protection answers with an HTML 403 page, not JSON. Call it from your server: this route sends no CORS headers (only the exact GET /api/locations index does). See Authentication.

Query Parameters

ParameterTypeDescription
bboxstringRequired. minLat,minLon,maxLat,maxLon. Values outside ±90 / ±180 are clamped.
zoomnumberMap zoom level, 0–20. Default 8. Selects the budget above.
categorystringFilter by place type: marina, harbour, anchorage, bay, mooring, spot. spot is a location with no category recorded.
staystringnight, day or mixed — how the place is typically used, from observed overnight share. Only applied to locations with enough recorded visits to support the verdict.
boats1Only locations with boats available to charter.
poisstringComma-separated poi_counts keys (e.g. restaurant,grocery). A location matches when any of them is present nearby. ⚠️ POI counts are recorded on only ~2,145 places, so this filter hides most of the map.

Example Request

curl "https://charter.boats/api/locations/map?bbox=41.35,9.10,41.42,9.22&zoom=13" \
  -H "X-API-Key: YOUR_API_KEY"

Example Response

{
  "places": [
    {
      "id": 22546,
      "slug": "port-de-bonifacio",
      "name": "Bonifacio Marina",
      "area": "Bonifacio / Bunifaziu",
      "country": "France",
      "lat": 41.388521,
      "lon": 9.163671,
      "type": "marina",
      "rating": null,
      "boat_count": 16,
      "pop": 5055,
      "stay": "overnight"
    },
    {
      "id": 24781,
      "slug": "plage-de-paragan",
      "name": "Paragan Beach",
      "area": "Bonifacio / Bunifaziu",
      "country": null,
      "lat": 41.39802,
      "lon": 9.129624,
      "type": "spot",
      "rating": null,
      "boat_count": 0,
      "pop": 819,
      "stay": "mixed"
    }
  ],
  "rasters": [
    {
      "id": 22546,
      "url": "https://media.charter.boats/locations/22546/piers.png?v=1788709047",
      "bounds": [41.383467, 9.153652, 41.390406, 9.167751]
    }
  ],
  "zoom": 13,
  "capped": false,
  "thinned": false,
  "heat": true
}

Response Fields

FieldTypeDescription
places[].namestringDisplay name. A place with no name of its own carries its bare type label (e.g. Anchorage); where it is lives in area, never folded into the name.
places[].areastring | nullCity, else municipality, else country.
places[].typestringmarina, harbour, anchorage, bay, mooring, or spot when no category is recorded.
places[].boat_countnumberPublished boats based at this location.
places[].popnumberDistinct boats observed here. 0 means never observed, not empty.
places[].staystring | nullovernight, day_stop or mixed. null when there are too few recorded visits to say.
rastersarrayBerth-density images in view: { id, url, bounds }, bounds = [minLat, minLon, maxLat, maxLon], url on media.charter.boats with a ?v= version stamp (keep it). Empty below zoom 12. A separate layer that ignores every filter — it is a fact about the water, not about the query — drawn for the 300 places in view with the most distinct boats observed, where they have an image.
cappedbooleanThe viewport held more results than the budget allows.
thinnedbooleanGrid thinning ran, so this is the best location per grid cell rather than every location in view.
heatbooleanWhether detail zoom (≥ 12) was reached, i.e. whether rasters is populated.

pop counts distinct boats rather than total visits on purpose. Ranking by visits promotes places with heavy repeat traffic from a small number of vessels — commuter and ferry berths — over the harbours and bays that many different crews choose.

Errors

StatusMeaning
400bbox missing or not four finite numbers
500Query failed

On this page