Map Search
Search locations inside a map viewport, ranked by observed vessel traffic
Endpoint
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.
| Zoom | Max results | Grid thinning | Heat imagery |
|---|---|---|---|
| ≤ 6 | 800 | coarse | no |
| 7–9 | 500 | yes | no |
| 10–11 | 400 | yes | no |
| ≥ 12 | 300 | no | yes |
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
| Parameter | Type | Description |
|---|---|---|
bbox | string | Required. minLat,minLon,maxLat,maxLon. Values outside ±90 / ±180 are clamped. |
zoom | number | Map zoom level, 0–20. Default 8. Selects the budget above. |
category | string | Filter by place type: marina, harbour, anchorage, bay, mooring, spot. spot is a location with no category recorded. |
stay | string | night, 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. |
boats | 1 | Only locations with boats available to charter. |
pois | string | Comma-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
Example Response
Response Fields
| Field | Type | Description |
|---|---|---|
places[].name | string | Display 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[].area | string | null | City, else municipality, else country. |
places[].type | string | marina, harbour, anchorage, bay, mooring, or spot when no category is recorded. |
places[].boat_count | number | Published boats based at this location. |
places[].pop | number | Distinct boats observed here. 0 means never observed, not empty. |
places[].stay | string | null | overnight, day_stop or mixed. null when there are too few recorded visits to say. |
rasters | array | Berth-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. |
capped | boolean | The viewport held more results than the budget allows. |
thinned | boolean | Grid thinning ran, so this is the best location per grid cell rather than every location in view. |
heat | boolean | Whether 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
| Status | Meaning |
|---|---|
400 | bbox missing or not four finite numbers |
500 | Query failed |