Charter Boats
Charter Boats API
Locations

Location Card

A compact summary of one location — how it is used, where boats go next, and the nearest charter base

Endpoint

GET /locations/:id/card

Description

A small summary of a single location, built for a map popup or a preview panel. It is the same underlying data as Get Location without the boat listing, the points of interest, or the image gallery — one round trip instead of a page payload.

Two things it adds that the full endpoint does not:

  • neighbors carry coordinates, so a caller can draw the legs out of this location, and are ordered by how many trips were observed rather than by rating.
  • nearest_base answers "where do I charter from, then?" for the majority of locations that have no fleet of their own.

Accepts either a numeric id or a slug.

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.

Path Parameters

ParameterTypeDescription
idnumber | stringLocation ID or slug

Example Request

curl "https://charter.boats/api/locations/port-de-bonifacio/card" \
  -H "X-API-Key: YOUR_API_KEY"

Example Response

{
  "id": 22546,
  "slug": "port-de-bonifacio",
  "name": "Bonifacio Marina",
  "area": "Bonifacio / Bunifaziu",
  "country": "France",
  "lat": 41.388521,
  "lon": 9.163671,
  "type": "marina",
  "rating": null,
  "rating_count": null,
  "image": "https://images.daytrip.com/Bonifacio.jpg?w=2048&q=50",
  "blurb": "Tucked into the dramatic limestone cliffs at Corsica's southern tip…",
  "boat_count": 16,
  "min_price": 390.88,
  "poi_counts": { "restaurant": 74, "grocery": 20, "parking": 11, "fuel": 3, "total": 134 },
  "ais": {
    "kind": "overnight",
    "nightPct": 86,
    "medianStayH": 18.02,
    "fullHour": 18,
    "busiestHour": 10,
    "boats": 5055,
    "stays": 13377,
    "hiddenGem": false,
    "observed": { "from": "2021-08-09", "to": "2026-08-28" }
  },
  "heat": {
    "url": "https://media.charter.boats/locations/22546/piers.png?v=1788709047",
    "bounds": [41.383467, 9.153652, 41.390406, 9.167751],
    "boats": 5055,
    "cells": 107
  },
  "neighbors": [
    {
      "id": 24403,
      "slug": "cala-lazarina",
      "name": "Cala Lazarina",
      "lat": 41.337944,
      "lon": 9.252952,
      "type": "spot",
      "rating": null,
      "boat_count": 0,
      "pop": 1279,
      "hours": 2,
      "boats_observed": 342,
      "trips": 937,
      "nm": 5.1
    }
  ],
  "neighbors_basis": "observed",
  "nearest_base": null
}

For a place with no fleet of its own, nearest_base is filled in (from /api/locations/atherinos/card):

"nearest_base": {
  "id": 21229,
  "slug": "lefkada-nydri-armonia-hotel",
  "name": "Lefkada, Nydri, Armonia Hotel",
  "lat": 38.701136,
  "lon": 20.708009,
  "boat_count": 17,
  "nm": 4.6
}

Response Fields

FieldTypeDescription
namestringNever null: a place with no name of its own carries its type label (e.g. Anchorage). Where it sits is the separate area field.
areastring | nullThe place's city, or its municipality when no city is recorded.
typestringmarina, harbour, anchorage, bay, mooring, or spot when no category is recorded.
blurbstring | nullFirst sentence of the description, as plain text.
boat_countnumberPublished boats based here, every type — same figure Get Location's boat_count shows; that endpoint's listing_total is a narrower, page-specific number, not this one.
min_pricenumber | nullCheapest published day rate among boats based here. null when no boats are based here.
poi_countsobject | nullPoints of interest nearby, total plus one count per category. null when none are recorded.
aisobject | nullHow the place is used. null when there are too few recorded visits to characterise it.
ais.kindstringovernight, day_stop or mixed.
ais.fullHournumber | nullLocal hour by which four in five arrivals have landed — the practical "be there by" time.
ais.busiestHournumber | nullLocal hour with the most MOVEMENTS — arrivals and departures together, which is what "busy" means at a harbour. 🚨 Not an arrival time: at most places it is the morning departure rush. Use fullHour for "when should I turn up".
ais.boats / ais.stays / ais.observednumber / number / objectThe evidence behind the block: distinct boats, recorded stays, and the observation window { from, to }.
heatobject | nullBerth-density image: url (PNG on media.charter.boats), bounds ([minLat, minLon, maxLat, maxLon], the corners to draw it between), boats, cells. The url carries a ?v=<epoch seconds> version stamp that changes when the image is rebaked — keep it, or a cached older image will be stretched over the new bounds.
neighborsarrayAt most 10 places boats sail to from here: id, slug, name, lat, lon, type, rating, boat_count, plus the fields below. Most-travelled first, then closest. Places with no name are left out.
neighbors[].popnumberDistinct boats ever recorded at that neighbour — how popular the place is in its own right, not how many sailed there from here. 0 when unobserved.
neighbors[].boats_observednumber | nullDistinct boats recorded making this hop. null when unobserved.
neighbors[].tripsnumber | nullObserved trips to that location. null means it is merely nearby, not somewhere boats were recorded sailing to.
neighbors[].hoursnumber | nullMedian door-to-door hours recorded for the hop — harbour to harbour, not time under way.
neighbors[].nmnumber | nullApproximate distance. Estimated for most pairs; treat as indicative.
neighbors_basisstringobserved, searoute, nearby or none — what produced the list.
nearest_baseobject | nullClosest location with boats to charter, within 40 NM (straight line): { id, slug, name, lat, lon, boat_count, nm }, nm rounded to 0.1. null when this location has its own boats, or when nothing is in range.

neighbors_basis is worth reading before presenting the list. Only observed means boats were actually recorded making those trips; the others are proximity, which understates real sailing distance around headlands.

Errors

StatusMeaning
404Location not found. If it was merged into another, the body carries data.moved_to with the surviving slug or id.
500Query failed

On this page