Charter Boats
Charter Boats API
Boats

List Boats

Search and filter your own company's published boats with pagination

Showing API v1.1 — versionsShowing API v1.0 — versions

GET/boats

Retrieve a paginated list of your own company's boats with optional filters for search, type, capacity, price, and more. Only published boats are returned. A search with d or a price filter lists only boats that have a price; with d, a boat we cannot price for those dates comes back with no price (price_per_day and list_price null). A search with neither also lists the boats we hold no price for, after the priced ones (see unpriced_start_index below).

Authentication

Send your API key in the X-API-Key header, as on every Operator API call, with company_slug set to your own company: the list reads only your own fleet, and without it the answer is a 403. To search other operators' boats use the Discovery API. See Authentication.

Undated responses are cached for 60 seconds; requests with d are computed fresh.

You are reading API v1.1. Send api_version=1.1 to get it: a call with your API key, or 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 with your API key gets when it names no api_version. A row carries no list_price: price_per_day is the charter per day with our auto-applied offer already taken off, its discount fields are over that rate, and required_extras states the fees. New integrations should send api_version=1.1 — see Versions.

Query Parameters

Pagination

ParameterTypeDefaultDescription
limitinteger20Results per page (max 50)
offsetinteger0Number of results to skip
ParameterTypeDescription
qstringBoat NAME filter — matched against title only, literally (% and _ are ordinary characters, not wildcards) and as a WHOLE WORD (Bella matches Bella Luna and Isola Bella, never Marbella; a prefix like Bell matches nothing). For a make or a model, use makes. When it reads as a boat NAME (3+ characters, at least one letter), published boats carrying that name are also appended after the strict results — see name_match_start_index.
dstringAvailability date filter. Single date YYYY-MM-DD or range YYYY-MM-DD,YYYY-MM-DD. Matches weeks where the boat has any availability when the range spans more charter weeks than the stay needs -- i.e. over 14 days, or when the start is not a changeover day (see Availability).

Location Filters

ParameterTypeDescription
location_idintegerFilter by exact location ID
location_typestringOne of country, state, city, municipality, text. Used with location_value.
location_valuestringThe value to match for the given location_type
country_codestringNarrow state/city/municipality location searches by country code, lowercase (hr, gr)

With location_type=text the value is resolved as a free-text place name, which also accepts named sailing regions (Dalmatia, the Dodecanese). state, city and municipality look the value up directly and fall back to that same resolver when the direct lookup finds nothing, so a place is only reported unknown once both have failed. country never falls back: its value is usually an ISO code, the resolver reads place NAMES, and both spellings a country can carry — the code and the country name — are already matched directly. location_value is matched literally — % and _ are ordinary characters, not wildcards.

With d (dates), a location also matches the boats that depart from it in those weeks though based at another marina.

Whatever the type, a value that resolves to no known place returns location_unresolved (the value you sent) with an empty boats array and total: 0. The location is never silently dropped, so a response is never a worldwide search wearing the label of a place.

Boat Filters

ParameterTypeDescription
typestringBoat type. Comma-separated for multiple: yacht, catamaran, sailboat, motorboat, gulet, rib, other
guestsintegerMinimum guest capacity
cabinsintegerMinimum number of guest cabins (crew cabins are not counted)
berthsintegerMinimum number of berths
toiletsintegerMinimum number of toilets
priceMinnumberMinimum daily price
priceMaxnumberMaximum daily price
lengthMinnumberMinimum length in feet
lengthMaxnumberMaximum length in feet
yearMinintegerMinimum build year (values 2000 or under are ignored)
yearMaxintegerMaximum build year (values of the current year or later are ignored)
ratingnumberMinimum average rating
rentalTypesstringComma-separated: bareboat, skippered, no_licence_needed. no_licence_needed means the operator asks no licence of the charterer — it does not mean a skipper comes with the boat. Unknown values are ignored. A boat whose skipper is an obligatory paid extra counts as skippered, not bareboat — the guest cannot sail it themselves.
manufacturerSlugstringFilter by one manufacturer slug. makes does the same and more, and is the one to use
makesstringComma-separated make/model filter, OR semantics across entries. An entry is either a make slug (bali — the whole make) or slug:model (bali:4.2). A make with no model stays unconstrained; models only narrow their own make. Model names are matched across every spelling the fleet stores for them (4.2 also matches boats filed as Bali 4.2); the valid values per make come from GET /api/manufacturers/{slug}/models, which returns { models: [{ id, label, values }] } — pass the label. Naming only unknown makes returns zero boats rather than the whole fleet.
featuresstringComma-separated equipment/amenity terms, AND semantics. Accepts canonical slugs (watermaker), namespaced attribute tags (dinghy:hypalon), and loose synonyms (ac resolves to air_conditioning). The canonical vocabulary is served by GET /api/features. A term that resolves to nothing is ignored rather than rejected, so check terms against that vocabulary. An attribute tag the vocabulary doesn't know, under a concept it does (dinghy:carbon), matches as the concept itself (dinghy) — a broader answer, never a false one.

Equipment Facets

ParameterTypeDescription
facet_counts1Adds a facets object to the response: per-feature boat counts (concept slugs and attribute tags) aggregated over the full matched result set — contextual to every filter in the request, never global. Features no boat in the set carries are omitted. Combine with limit=0 for a cheap counts-only request (boats comes back empty).
# How many of your catamarans carry each feature?
curl "https://charter.boats/api/boats?company_slug=your-company&type=catamaran&facet_counts=1&limit=0" \
  -H "X-API-Key: YOUR_API_KEY"
{
  "boats": [],
  "total": 3995,
  "facets": { "sound_system": 3416, "dinghy": 2905, "dinghy:hard_bottom": 312, "tv:smart": 63 }
}

Sorting

ParameterTypeDefaultDescription
sort or sortBystringrecommendedSort order: recommended, price-low, price-high, rating (aliases price_asc/price_desc accepted). With d, newest (build year, newest first) is also accepted; without d it sorts as recommended. Unknown values sort as recommended.

Company Filters

ParameterTypeDescription
company_slugstringRequired with an API key: your own company. Another company's slug, or none, is a 403; a slug that names no company returns no boats, with company_unresolved echoing it
priority_companystringCompany slug whose boats are floated to the top of the results
idsstringComma-separated boat IDs to fetch specific boats

Request

# List your fleet (first page)
curl "https://charter.boats/api/boats?company_slug=your-company" \
  -H "X-API-Key: YOUR_API_KEY"
 
# Filter by type and capacity
curl "https://charter.boats/api/boats?company_slug=your-company&type=catamaran&guests=6" \
  -H "X-API-Key: YOUR_API_KEY"
 
# Price range with sorting, page 2
curl "https://charter.boats/api/boats?company_slug=your-company&priceMin=100&priceMax=500&sort=price-low&limit=20&offset=20" \
  -H "X-API-Key: YOUR_API_KEY"
 
# Boats around a place, by name
curl "https://charter.boats/api/boats?company_slug=your-company&location_type=text&location_value=Lefkada" \
  -H "X-API-Key: YOUR_API_KEY"

Response

A real response, GET /boats?company_slug=af-yachting&limit=1&location_type=text&location_value=Lefkada&api_version=1.1:

{
  "boats": [
    {
      "id": "55467c1d-bfab-484f-b0c0-bb9352d4d46d",
      "slug": "lagoon-40-af-lag40",
      "title": "AF-LAG40",
      "boat_type": "catamaran",
      "capacity": 12,
      "length_ft": 38.5,
      "year": 2022,
      "price_per_day": 364,
      "price_from": 306.04,
      "price_to": 892.5,
      "discount_percentage": 41,
      "discount_label": "41% discount",
      "original_price_per_day": 616,
      "average_rating": null,
      "review_count": 0,
      "location_id": 11336,
      "manufacturer_id": "2ec214a3-bb8e-4807-9ea8-d7021a9c7a27",
      "avail_weeks": 4503496548139260,
      "hero_image_url": "https://pub-1d6bb2ea901f4e66a126571a99715069.r2.dev/boats/55467c1d-bfab-484f-b0c0-bb9352d4d46d/images/1772350001711_f343af8c.jpg",
      "has_valid_name": false,
      "model": "40",
      "manufacturer": {
        "id": "2ec214a3-bb8e-4807-9ea8-d7021a9c7a27",
        "name": "Lagoon",
        "slug": "lagoon",
        "logo_url": "https://www.catamarans-lagoon.com/_nuxt/img/logo-header.40fb10d.png"
      },
      "location": {
        "id": 11336,
        "city": "Municipal Unit of Lefkada",
        "name": "D-Marin Lefkas",
        "country": "Greece"
      },
      "charter_dates": null,
      "charter_unit": "night",
      "charter_week_shifted": false,
      "rental_type": "bareboat",
      "skippered": "optional",
      "currency": "EUR",
      "internal_discount_pct": null,
      "list_price": {
        "per_day": 364,
        "total": 2544,
        "original_per_day": 616,
        "discount_pct": 41,
        "from": true,
        "week": null,
        "week_start": null,
        "units": 7,
        "unit": "night",
        "party": 12,
        "label": "for 7 nights · all fees in"
      }
    }
  ],
  "total": 733,
  "limit": 1,
  "offset": 0,
  "next_offset": 1,
  "hasMore": true,
  "backfill_start_index": null,
  "name_match_start_index": null,
  "unavailable_start_index": null,
  "unpriced_start_index": null,
  "lists_unpriced": true
}

The same request with api_version=1.0:

{
  "boats": [
    {
      "id": "55467c1d-bfab-484f-b0c0-bb9352d4d46d",
      "slug": "lagoon-40-af-lag40",
      "title": "AF-LAG40",
      "boat_type": "catamaran",
      "capacity": 12,
      "length_ft": 38.5,
      "year": 2022,
      "price_per_day": 307,
      "price_from": 306.04,
      "price_to": 892.5,
      "discount_percentage": 45,
      "discount_label": "45% discount",
      "original_price_per_day": 559,
      "average_rating": null,
      "review_count": 0,
      "location_id": 11336,
      "manufacturer_id": "2ec214a3-bb8e-4807-9ea8-d7021a9c7a27",
      "avail_weeks": 4503496548139260,
      "hero_image_url": "https://pub-1d6bb2ea901f4e66a126571a99715069.r2.dev/boats/55467c1d-bfab-484f-b0c0-bb9352d4d46d/images/1772350001711_f343af8c.jpg",
      "has_valid_name": false,
      "model": "40",
      "manufacturer": {
        "id": "2ec214a3-bb8e-4807-9ea8-d7021a9c7a27",
        "name": "Lagoon",
        "slug": "lagoon",
        "logo_url": "https://www.catamarans-lagoon.com/_nuxt/img/logo-header.40fb10d.png"
      },
      "location": {
        "id": 11336,
        "city": "Municipal Unit of Lefkada",
        "name": "D-Marin Lefkas",
        "country": "Greece"
      },
      "charter_dates": null,
      "charter_unit": "night",
      "charter_week_shifted": false,
      "rental_type": "bareboat",
      "skippered": "optional",
      "currency": "EUR",
      "internal_discount_pct": null,
      "required_extras": {
        "per_day": 0,
        "per_booking": 350,
        "per_person": 0,
        "per_person_per_day": 0,
        "from": true
      }
    }
  ],
  "total": 733,
  "limit": 1,
  "offset": 0,
  "next_offset": 1,
  "hasMore": true,
  "backfill_start_index": null,
  "name_match_start_index": null,
  "unavailable_start_index": null,
  "unpriced_start_index": null,
  "lists_unpriced": true
}

Response Fields

FieldTypeDescription
boatsarrayArray of boat objects
totalintegerTotal number of matching boats
limitintegerPage size used
offsetintegerOffset used
next_offsetintegerOffset to pass for the next page
hasMorebooleanWhether more results exist beyond this page
location_unresolvedstringOnly when the location you named resolved to no known place: echoes the value. boats is then empty and total is 0
backfill_start_indexinteger|nullIndex in boats where alternatives begin, or null. When the first page's strict results don't fill it, the shared ladder (the AI tool's alternatives come from the same ladder) loosens ONE thing at a time and stops as soon as the page is made up: the dates (the week either side), then the place (nearby bases), then the model (the make kept), then the name (any one of its words, whole words only). Type, guests, cabins, berths, length, year, budget, features and the referral operator are never loosened. Each of these rows carries relaxed — the list of what it gave up — and none is counted in total
name_match_start_indexinteger|nullIndex in boats where name matches begin, or null. Only on a name-shaped q, only on the first page, and only when the strict page is not full. These are published boats whose title carries the searched name as a whole word (Bella matches Bella Luna, never Marbella) that the filters could not offer — unavailable for the dates, or with no sellable season at all. They sit after the strict results and before any backfill, are capped at 6, and are not counted in total.
unavailable_start_indexinteger|nullIndex in boats where a last-resort listing begins, or null. Only when a plain search by a single exact location (+ optional dates, no other filters) returns nothing at all — no strict matches, no name matches, no backfill. These are published boats at that exact location with no sellable offer right now: their price_per_day and related pricing fields are forced to null. Capped at 6, not counted in total.
unpriced_start_indexinteger|nullIndex in boats where boats we hold no prices for begin on this page, or null. A search with no dates and no priceMin/priceMax lists every published boat that matches — the priced ones first in the order you asked for, then these. Unlike the three tiers above they are matches: they are inside total, inside the facet counts, and they page normally, so the index is page-relative and appears on whichever page the boundary falls on. Their price_per_day, original_price_per_day, discount_percentage, discount_label, required_extras and charter_dates are all null — there is nothing to quote. Changed 2026-09-21: such boats used to be absent from search entirely, so a marina page listing 436 boats led to a search answering 404.
lists_unpricedbooleantrue when this search admits the boats above at all (no dates, no price filter) — so total counts them whether or not this page shows one. false for every dated or price-capped search, which stays priced-only: a boat with no price can be neither free in a week nor inside a budget.
priority_companystringEchoes the priority_company query value when provided
facetsobjectOnly with facet_counts=1: map of feature slug/attribute tag → number of boats in the full matched set carrying it. Zero-count features are omitted.

The two fallback tiers (name_match_start_index, unavailable_start_index) are appended only on the first page and counted in no total; the unpriced tail is neither — it is part of the match set and pages with it.

Known limitation — paging. With some filter combinations total (and so hasMore) is counted more loosely than the rows are filtered, so it can promise more boats than the pages deliver. Keep paging until a page brings back fewer strict results than limit (or none), rather than relying on hasMore alone.

Boat Object

FieldTypeDescription
idstringUnique boat identifier (UUID)
slugstringURL-friendly identifier
titlestringBoat name
boat_typestringType of boat
capacityintegerMaximum guests
length_ftnumberLength overall (LOA) in feet
yearintegerYear built
price_fromnumber|nullLowest seasonal price
price_tonumber|nullHighest seasonal price
average_ratingnumber|nullAverage rating (1-5)
review_countintegerNumber of reviews
location_idinteger|nullLocation ID — null for a boat not yet placed on a marina
manufacturer_idstring|nullManufacturer UUID
avail_weeksintegerPacked availability bitmask (see Availability)
hero_image_urlstring|nullPrimary image, as the un-resized master — the web app rewrites it to a size variant client-side. Don't hotlink the URL as returned: serve the image from https://media.charter.boats, keeping the path from /boats/… onward, and insert a variant suffix before the extension — _card (400×300), _hero (1200×800), _thumb (150×150), all WebP behind a .jpg filename. The master itself can be pruned once variants exist, so the bare path can 404 where a variant won't. /api/ai/boats and /api/agent/boats return already-resolved variants.
has_valid_nameboolean|nullWhether the boat has a human-readable name
modelstring|nullBoat model name
manufacturerobject|null{ id, name, slug, logo_url }
locationobject|null{ id, city, name, country }; null when location_id is
rental_typestring|nullbareboat, skippered, or no_licence_needed — the operator's product class. no_licence_needed means the operator requires no licence of the charterer, which is not the same as a skipper coming along. Read it alongside skippered: a false skippered covers bareboat, no-licence-needed and boats that state neither, and only rental_type tells those apart
skipperedboolean|stringtrue (skipper included in the charter price), "required" (the boat comes with a skipper billed as a mandatory fee — see its fees), "optional", or false. ⚠️ true is checked against the boat's live fee set, not read off rental_type: a boat whose operator sells a crewed product AND bills an obligatory skipper fee reports "required", because the charter price does not cover him
currencystringCurrency code (e.g. EUR)
internal_discount_pctnumber|nullInternal discount percentage when applicable
charter_week_shiftedbooleantrue when charter_dates names a different week from the one you searched — the boat is sold that week and not yours. false when it is the week you asked for, including when the changeover day moves its start date (which happens on nearly every result of an off-changeover search and is therefore not per-boat news).
charter_datesstring|nullThe charter week this row is quoted for, as YYYY-MM-DD,YYYY-MM-DD on this boat's changeover day — not the dates you searched. null when the boat has no sellable week in the range at all, and whenever there is no week to name: no d filter, a d outside the 52-week horizon, or a week that has already begun. See Changeover days.
charter_unitstringday on a boat whose check-out is later in the day than its check-in — there a stay of N nights is N + 1 charter days, and price_per_day is the rate per one of them — night otherwise. The word to print beside a stay's count. Added 2026-09-24

Price fields in v1.1:

FieldTypeDescription
price_per_daynumber|nullThe boat's dateless LIST figure: the lowest grand total at max guests — charter less the auto discount, every required fee charged now or at pickup, the 45 EUR platform fee; deposits never — per day for the boat, over its priced weeks from today. Null when the boat has no current pricing — a listing stays published on its specs, images and equipment even when its seasons lapse, so treat this as optional rather than guaranteed.
discount_percentagenumber|nullThe list figure's discount: list_price.discount_pct
discount_labelstring|nullDiscount label text
original_price_per_daynumber|nullThe list figure with the partner's discount undone: list_price.original_per_day
list_priceobject|nullThe row's ONE figure, for the week named in charter_dates, or with no week the stored lowest (from: true): per_day — the grand total at max guests (the charter less the boat's auto discount, every required fee charged now or at pickup, the 45 EUR platform fee; deposits never) of the default package over 7 nights (8 days on a day-clock boat) ÷ those days, per day for the boat, a whole euro rounded up; total — that week's grand total (dated only, null without a week; per_day × units may exceed it by up to units − 1 EUR, since only per_day is rounded up); original_per_day — the same week with the partner's discount undone and no coupon, per day (null without a discount); discount_pct — derived from per_day and original_per_day, null under 1%; from is true when no week was quoted and it is the boat's lowest; week, week_start, units, unit (night/day) say what it covers; party the max guests the fees are priced for; label the words beside the stay total (dated "for 7 nights · all fees in", dateless "7 nights · all fees in", "8 days" on a day-clock boat). The fees are inside it — there is no separate fee figure. null when nothing is priced

Price fields in v1.0:

FieldTypeDescription
price_per_daynumber|nullThe charter per day with our auto-applied offer already taken off — no fees inside it. Null when the boat has no current pricing
discount_percentagenumber|nullDiscount over price_per_day
discount_labelstring|nullDiscount label text
original_price_per_daynumber|nullprice_per_day before the discount
required_extrasobjectThe required fees beside the day rate: per_day, per_booking, per_person, per_person_per_day (EUR), and from — true when they are the boat's lowest, not a quoted week's. The 45 EUR platform fee is not in them: rate × days + these fees + 45 EUR is the v1.1 total

Availability

avail_weeks is a packed bitmask of bookable weeks relative to an anchor date. To query availability, pass the d date filter rather than decoding the mask yourself.

A week's bit is set when that week's Sunday–Friday nights are all unblocked and at least one is priced. A charter occupies the nights from its check-in up to but not including its check-out day — the boat is handed back in the morning and can sail again the same afternoon — so one guest disembarking and the next embarking share that day, and a boat whose bookings sit either side of a free week is bookable for that week.

Note the asymmetry: a charter's check-in day is occupied (the guest sleeps aboard), only its check-out day is free. The bit does not attest the week's own Saturday, so confirm exact dates with GET /boats/availability before quoting them.

Changeover days

The bitmask is anchored to Saturday, but boats set their own changeover day (charter_day on the boat). A one-week stay that starts on any other day therefore touches two anchor weeks, only one of which it actually needs. When d spans more weeks than the stay requires, the filter matches boats free in any of them rather than all — so a Sunday-to-Sunday search returns the fleet available that week instead of only boats free for a fortnight.

Results are consequently a discovery set, not a quote: a returned boat may only be sellable for the adjacent charter week. Confirm the sellable dates with GET /boats/availability before presenting them.

charter_dates settles that per boat, and price_per_day is quoted from the same week. Two things move it off the dates you asked for:

  • The changeover day. A Tuesday search returns Saturday boats reading 2026-09-19,2026-09-26 and Sunday boats reading 2026-09-20,2026-09-27 — each on the day that boat actually turns around.
  • The week itself. Where the range covers more than the stay needs, a boat is matched if it is free in any of those weeks, so a hull booked for the week you typed but open the week after is returned for the week after — and quoted for it.

So pass charter_dates back as d when deep-linking, and name that range to a guest rather than the one you searched. A null means the boat has no sellable week in the range at all — it is here as a near miss, and its price_per_day is whatever the week you searched happens to say (the yearly "from" rate when that week has no price of its own). Neither is a quote for a real charter; get its dates from GET /boats/availability.

⚠️ priceMin and priceMax are applied to the week you searched, while price_per_day may describe the week the boat is actually sold in. A row quoted from a neighbouring week is dropped when it falls outside your cap, so every counted match respects the filter — but the totals in total and the price-low / price-high ordering are still computed on the searched week, so a capped search can report more matches than it returns. The two fallback tiers marked by name_match_start_index and unavailable_start_index are exempt from price filters by design, as documented above.

Even when populated this is a discovery answer, not a quote. The week's bit does not attest its own check-in day and the price is a cached weekly aggregate, so confirm both against GET /boats/availability (and the price against Resolve Pricing) before presenting them.

On this page