Charter Boats
Charter Boats API
Points of Interest

Search POIs

Find points of interest near a location or coordinates

Endpoint

GET /pois/search

Description

Search for points of interest (restaurants, shops, fuel stations, etc.) near a location. Supports three search modes: by location ID, by coordinates with radius, or by geographic region (country, state, city, or a free-text place such as a sailing region). The three modes return slightly different row shapes — see POI Fields.

Car parks are de-duplicated. OpenStreetMap draws one marina car park as a polygon per aisle, and a second mapper often adds points on top of the same lot — D-Marin Lefkas arrived as 21 rows all reading "Parking". A location returns one entry per car park you could tell from the next: per distinct name, per distinct capacity where there is no name, and never two within 60 m. A car park with neither a name nor a capacity is returned only where it is the nearest one and nothing else at that location speaks for parking.

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. See Authentication.

Query Parameters

Use one of the three search modes. Every search must be anchored to a place — a location ID, a coordinate pair, or a region. There is no mode that searches all POIs everywhere: q narrows results within whichever mode you are in — all three — and is not a search mode on its own. A request with no anchor returns 400. If several anchors are sent, region mode wins, then location ID, then coordinates.

minRating is accepted and ignored — POIs carry no ratings.

Mode 1: By Location ID

ParameterTypeRequiredDescription
location_idnumberYesLocation ID to find nearby POIs. An unknown or unpublished location returns 404
categorystringNoFilter by POI category
qstringNoFilter by name (matches name or name_en). Matched against the stored name, so a POI that has no name of its own is never returned by q — see the note under name below
limitnumberNoMax results (default: 50, max: 200)

Results are the POIs linked to that location, nearest first. category and q are applied in the query, so limit means "this many restaurants called Konoba", not "this many of the nearest POIs, then narrowed".

Mode 2: By Coordinates

ParameterTypeRequiredDescription
latnumberYesLatitude
lonnumberYesLongitude
radiusnumberNoSearch radius in km (default: 1, max: 10)
categorystringNoFilter by POI category
qstringNoFilter by name (name or name_en). Applied to the whole radius, not to the page: with q the search reads up to 500 candidates in distance order and the name filter decides which limit come back, so a match further out than the nearest limit is still returned. A radius holding more than 500 POIs is the one case where a distant match can be missed — narrow radius there
limitnumberNoMax results (default: 50, max: 200)

Results are ordered by distance from the point.

Mode 3: By Region

ParameterTypeRequiredDescription
location_typestringYesOne of: country, state, city, municipality, or text — a place no column holds, such as a sailing region (Dalmatia, the Dodecanese) or a free-text place name, resolved the same way boat search resolves it. A text value that resolves to nothing returns an empty list
location_valuestringYesRegion name or country code (or the free text for text)
country_codestringNoISO country code (narrows state/city/municipality results)
categorystringNoFilter by POI category
qstringNoFilter by name (matches name or name_en). Matched against the stored name, so a POI that has no name of its own is never returned by q — see the note under name below.
limitnumberNoMax results (default: 50, max: 200)
offsetnumberNoPagination offset (default: 0, max: 5000). Beyond that the request is rejected rather than clamped, so a caller paging to the end never silently re-reads earlier pages. Narrow with category or a smaller region instead of paging deeper. Results are ordered by POI id, so pages are stable.

Example Requests

By Location

curl "https://charter.boats/api/pois/search?location_id=11336&category=parking" \
  -H "X-API-Key: YOUR_API_KEY"

By Coordinates

curl "https://charter.boats/api/pois/search?lat=38.83&lon=20.71&radius=2" \
  -H "X-API-Key: YOUR_API_KEY"

By Country

curl "https://charter.boats/api/pois/search?location_type=country&location_value=gr&category=restaurant&limit=20" \
  -H "X-API-Key: YOUR_API_KEY"

By Sailing Region

curl "https://charter.boats/api/pois/search?location_type=text&location_value=Dalmatia&category=chandlery" \
  -H "X-API-Key: YOUR_API_KEY"

Example Response (Location Mode)

{
  "pois": [
    {
      "id": "641856c4-c061-4e8d-a362-5de5eb8ef7b7",
      "osm_id": 1339643531,
      "name": "Marina Parking",
      "name_en": null,
      "category": "parking",
      "subcategory": "parking",
      "lat": 38.8285586,
      "lon": 20.7115304,
      "address": null,
      "city": null,
      "country": null,
      "phone": null,
      "website": null,
      "opening_hours": null,
      "is_seasonal": false,
      "description": null,
      "cuisine": null,
      "image_url": null,
      "image_attribution": null,
      "nearest_location_name": "D-Marin Lefkas",
      "poi_id": "641856c4-c061-4e8d-a362-5de5eb8ef7b7",
      "distance_meters": 41,
      "walking_time_minutes": 1,
      "location_name": "D-Marin Lefkas"
    }
  ],
  "total": 9,
  "location_id": 11336
}

Example Response (Coordinate Mode)

{
  "pois": [
    {
      "poi_id": "7157f9a6-da60-4420-af98-b8535ad4a60b",
      "name": "Supermarket",
      "name_en": null,
      "category": "grocery",
      "subcategory": "supermarket",
      "lat": 38.829399,
      "lon": 20.710848,
      "distance_km": 0.0993063150473661,
      "phone": null,
      "website": null,
      "opening_hours": null,
      "description": null,
      "image_url": null,
      "location_name": "D-Marin Lefkas",
      "distance_meters": 70
    }
  ],
  "total": 1,
  "radius_km": 2
}

Here distance_km is from the point you queried, and distance_meters is from the POI to its nearest charter location (location_name) — not from your point.

Example Response (Region Mode)

{
  "pois": [
    {
      "id": "000684fe-54f1-4a8e-82b8-82a4b54f741f",
      "osm_id": 6068989553,
      "name": "Warehouse Edge",
      "name_en": null,
      "category": "restaurant",
      "subcategory": "bar",
      "lat": 37.941402,
      "lon": 23.689896,
      "address": null,
      "city": null,
      "country": null,
      "phone": "+30 21 09427722",
      "website": null,
      "opening_hours": null,
      "is_seasonal": false,
      "cuisine": null,
      "image_url": "https://media.charter.boats/pois/c1b747f7-ac0b-4d48-9915-7af028e58d2d.jpg",
      "image_attribution": null,
      "is_active": true,
      "nearest_location_name": "Navtathlitiki marina Dimou Kallitheas",
      "poi_id": "000684fe-54f1-4a8e-82b8-82a4b54f741f",
      "distance_meters": 493,
      "location_name": "Navtathlitiki marina Dimou Kallitheas"
    }
  ],
  "total": 20,
  "hasMore": true,
  "location_type": "country",
  "location_value": "gr"
}

The total field is the size of the returned page, not the global count.

Response Fields

FieldTypeDescription
poisarrayList of POIs
totalnumberNumber of POIs in this response (page size, not a global count)
hasMorebooleanWhether more results exist (region mode with pagination)
location_idnumberEchoed location ID (location mode)
radius_kmnumberEchoed search radius (coordinate mode)
location_typestringEchoed location type (region mode)
location_valuestringEchoed location value (region mode)

POI Fields

Location and region modes return the full row below. Coordinate mode returns a reduced row: poi_id (there is no id), name, name_en, category, subcategory, lat, lon, distance_km, phone, website, opening_hours, description, image_url, location_name and distance_meters — no osm_id, address, city, country, is_seasonal, cuisine, image_attribution, nearest_location_name or walking_time_minutes. Key on poi_id, which every mode returns.

FieldTypeDescription
idstringPOI ID (UUID). Location and region modes
poi_idstringPOI ID (UUID) — present in every mode
osm_idnumber|nullOpenStreetMap ID
namestringPOI name (local language), or the POI's type where OSM gave it no name — "Parking", "Pharmacy", "Supermarket". Never null. Roughly 9% of POIs are unnamed car parks, pharmacies and grocery shops that are still worth listing near a marina; they appear in location results and are excluded from q name search. Use category + subcategory if you want to render your own label.
name_enstring|nullEnglish name
categorystringPOI category
subcategorystring|nullMore specific category
latnumberLatitude
lonnumberLongitude
addressstring|nullStreet address
citystring|nullCity
countrystring|nullCountry
phonestring|nullPhone number
websitestring|nullWebsite URL
opening_hoursstring|nullOpening hours (OSM format)
is_seasonalboolean|nullWhether the business is seasonal
descriptionstring|nullDescription (markdown). Location and coordinate modes only. Region mode omits it: descriptions are long text stored out-of-line, they were 30% of that response's bytes across a 100-row page, and no caller rendered them in a list. Fetch a single POI (/pois/{id}) for its description.
cuisinestring|nullCuisine type (restaurants only)
image_urlstring|nullImage URL. Our own copies are served from https://media.charter.boats/; if you receive one on another storage host, request the same path from media.charter.boats. Third-party image URLs are returned as-is
image_attributionstring|nullImage attribution text
is_activebooleanActive flag (present in region mode only)
nearest_location_namestring|nullName of the nearest charter location (location and region modes)
location_namestring|nullName of the POI's nearest charter location (every mode)
distance_metersnumber|nullStraight-line distance in metres. Location mode: from the requested location. Region mode: from the POI's nearest location, computed from both current coordinates. Coordinate mode: from the POI to its nearest location (location_name) — not from the queried point; use distance_km for that
distance_kmnumberDistance from the queried point, in km (coordinate mode only)
walking_time_minutesnumber|nullEstimated walking time (location mode only)

Error Responses

400 Bad Request

{
  "statusCode": 400,
  "message": "A location is required: pass location_id, or lat and lon, or location_type with location_value. `q` narrows a search within one of those modes and is not a mode on its own."
}

Also returned for:

  • an invalid category value (Invalid category: <value>)
  • an offset above 5000

Earlier builds accepted a bare q with no location and answered it by searching every POI. That was never a documented mode — all three modes above have always listed their anchor as required — and it is now rejected. To search POIs by name across places, use /ai/pois, which is built for it.