Charter Boats
Charter Boats API
Boats

Get Boat

Get detailed information about a specific boat

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

GET/boats/:id

Retrieve complete details for a single boat, including location, images, equipment, reviews, seasonal pricing, and calculated ratings.

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. There is no list_price: price_per_day and best_price are the charter day rate and lowest_price_per_day the same, with our auto-applied offer already taken off and no fees inside. New integrations should send api_version=1.1 — see Versions.

Authentication

Send your API key in the X-API-Key header, as on every Operator API call. It reads only boats your company operates; another operator's boat is a 403 — sell those through the Discovery API. See Authentication.

Responses are cached for 60 seconds (served stale while refreshing), so a change can take up to a minute to show.

Path Parameters

ParameterTypeRequiredDescription
idstringYesThe boat's UUID or slug. Store the UUID: a slug can change when a listing is renamed, and an old slug then answers 404

Request

# By UUID
curl https://charter.boats/api/boats/3a121be0-c1f8-4432-8e18-4a125b46da65 \
  -H "X-API-Key: YOUR_API_KEY"
 
# By slug
curl https://charter.boats/api/boats/beneteau-oceanis-393-skuribanda-5b46da65 \
  -H "X-API-Key: YOUR_API_KEY"

Response

Trimmed: long arrays are shortened and some location fields omitted.

{
  "id": "3a121be0-c1f8-4432-8e18-4a125b46da65",
  "slug": "beneteau-oceanis-393-skuribanda-5b46da65",
  "title": "Škuribanda",
  "boat_type": "sailboat",
  "mmsi": null,
  "capacity": 6,
  "length_ft": 39.2,
  "year": 2006,
  "cabins": 3,
  "crew_cabins": 0,
  "berths": 6,
  "toilets": 2,
  "specs": {
    "beam": 3.96,
    "draft": 1.9,
    "engine_desc": "Volvo 56 hp",
    "transit_log": 270,
    "fuel_capacity": 150,
    "water_capacity": 500
  },
  "specs_from_model": [],
  "ais": null,
  "description": null,
  "description_ai": null,
  "pricing_source": "mmk",
  "price_per_day": 133.98,
  "original_price_per_day": 231,
  "discount_percentage": 40,
  "discount_label": "40% discount",
  "price_from": 133.98,
  "price_to": 264.6,
  "charter_day": 6,
  "check_in_periods": [
    {
      "dateTo": "31.12.2099",
      "dateFrom": "01.01.2020",
      "checkInDays": [6],
      "checkInTime": "17:00",
      "checkOutDays": [6],
      "checkOutTime": "09:00",
      "minimalReservationDuration": 7
    }
  ],
  "checkin_time": "17:00:00",
  "checkout_time": "08:30:00",
  "min_charter_days": 7,
  "instant_book": true,
  "security_deposit": 1500,
  "deposit_with_waiver": null,
  "seo_released_at": "2026-06-24T07:15:08.279+00:00",
  "location_id": 20810,
  "model": "Oceanis 393",
  "updated_at": "2026-09-09T16:29:20.313929+00:00",
  "features": ["autopilot", "chartplotter", "deck_shower", "dinghy", "electric_windlass", "solar_panels"],
  "manufacturer_id": "1c0513d4-45a3-46e9-80eb-b891e12652d8",
  "rental_type": "skippered",
  "skipper": 1,
  "skipper_charged": false,
  "skipper_for_hire": false,
  "crew": null,
  "average_rating": null,
  "review_count": 0,
  "is_published": true,
  "photo_descriptions": {
    "deck": "Relax on the spacious aft deck, perfect for sunbathing or scenic views.",
    "lounge": "Inviting salon features a comfortable dining area and dedicated navigation station."
  },
  "seo_variant": "A",
  "has_valid_name": true,
  "has_multi_port": false,
  "hero_image_url": "https://…/boats/3a121be0-c1f8-4432-8e18-4a125b46da65/images/1772350897275_0b0ba49e.jpg",
  "tour_360": null,
  "avail_weeks": 1125341561094160,
  "avail_weeks_anchor": "2026-09-05",
  "discounted_price_per_day": [0, 0, 0, 0, 154, 0, "…", 219, 203, 0, 0],
  "discount_pct": [0, 0, 0, 0, 42, 0, "…", 42, 42, 0, 0],
  "extras_per_day": [0, 0, 0, 0, 170, 0, "…", 170, 170, 0, 0],
  "extras_per_booking": [0, 0, 0, 0, 360, 0, "…", 360, 360, 0, 0],
  "extras_per_person": [0, 0, 0, 0, 0, 0, "…", 0, 0, 0, 0],
  "extras_per_person_per_day": [0, 0, 0, 0, 1, 0, "…", 1, 1, 0, 0],
  "best_internal_discount_pct": 0,
  "company": {
    "base_currency": "EUR",
    "cancel_refund_days": 30,
    "cancel_refund_percent": 50,
    "terms_guest": {
      "insurance_requirements": "All vessels covered with Kasko insurance...",
      "damage_liability": "Client liable for all damage...",
      "license_requirements": "Client must have original navigation licenses..."
    },
    "terms_faq": [
      { "q": "Can I bring pets aboard?", "a": "Pets such as dogs, cats, or birds are only allowed with our prior written consent..." }
    ]
  },
  "manufacturer": {
    "id": "1c0513d4-45a3-46e9-80eb-b891e12652d8",
    "name": "Beneteau",
    "slug": "beneteau",
    "logo_url": null
  },
  "location": {
    "id": 20810,
    "slug": "marina-zenta-split",
    "name": "Marina Zenta, Split",
    "city": "Split",
    "municipality": null,
    "state": null,
    "country": "Croatia",
    "country_code": "hr",
    "lat": 43.49955,
    "lon": 16.45676,
    "rating": 3.7,
    "neighbors": null,
    "description": "Marina Zenta sits just east of Split's main port in the sheltered Zenta bay..."
  },
  "geo_crumbs": [
    { "label": "Croatia", "href": "/locations/country/croatia" },
    { "label": "Dalmatia", "href": "/locations/region/dalmatia" },
    { "label": "Split", "href": "/locations/city/split" }
  ],
  "images": [
    {
      "id": "d0a4be7a-0950-471c-9f03-2acb4bfcd2d2",
      "url": "https://…/boats/3a121be0-c1f8-4432-8e18-4a125b46da65/images/1772350897682_d0a4be7a.jpg",
      "category": "cockpit",
      "is_active": true,
      "is_primary": false,
      "sort_order": 2
    }
  ],
  "videos": [],
  "equipment": [
    {
      "group": "Deck",
      "items": [
        { "name": "Gangway", "value": null },
        { "name": "Teak deck (cockpit)", "value": null },
        { "name": "Dinghy", "value": null }
      ]
    }
  ],
  "rare_features": [],
  "reviews": [],
  "prices": [
    {
      "id": "28772252-9c56-4e95-8b53-288c784efd57",
      "name": "High Season 2027",
      "start_month": 6,
      "start_day": 12,
      "end_month": 6,
      "end_day": 19,
      "product_name": "Bareboat",
      "discount_name": "35% discount",
      "price_per_day": 200.2,
      "valid_from_year": 2027,
      "valid_to_year": 2027,
      "discount_percentage": 35,
      "original_price_per_day": 308,
      "departs_from": { "id": 20810, "name": "Marina Zenta, Split", "city": "Split" },
      "departs_from_count": 1
    }
  ],
  "has_day_trips": false,
  "best_source": "mmk",
  "best_currency": "EUR",
  "price_currency": "EUR",
  "internal_discount_pct": null,
  "mmk_products": [
    { "name": "Bareboat", "isDefault": false },
    { "name": "Crewed", "isDefault": true }
  ]
}

The price fields of a real v1.1 response (GET /boats/55467c1d-bfab-484f-b0c0-bb9352d4d46d?api_version=1.1):

{
  "price_per_day": 364,
  "original_price_per_day": 553,
  "discount_percentage": 45,
  "discount_label": "45% discount",
  "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"
  }
}

The price fields of a real v1.0 response, the same boat with api_version=1.0 — best_price and lowest_price_per_day are in v1.0 only, list_price in v1.1 only:

{
  "price_per_day": 307,
  "original_price_per_day": 553,
  "discount_percentage": 45,
  "discount_label": "45% discount",
  "best_price": 307,
  "lowest_price_per_day": 307
}

Note on the company block. The live response carries a company object. The documented API contract exposes only the currency, cancellation- and guest-terms fields shown above (base_currency, cancel_refund_days, cancel_refund_percent, terms_guest, terms_faq). Operator identity is not part of it — including the operator's id, which was removed from this response on 2026-09-15: a boat never names the company that runs it.

Images. images[].url and hero_image_url are un-resized masters (the example shortens their host). Don't hotlink them as returned: serve each image from https://media.charter.boats, keeping the path from /boats/… onward, and insert a size suffix before the extension — _card (400×300), _hero (1200×800) or _thumb (150×150), all WebP behind a .jpg filename. Masters can be pruned once the variants exist, so a bare path can 404 where a variant won't. (A URL whose path does not start /boats/ is not stored with us — typically an imported boat's source image.) images arrive in no particular order: sort by sort_order (and use is_primary for the cover).

Response Fields

FieldTypeDescription
idstringUnique boat identifier (UUID)
slugstringURL-friendly identifier
titlestringBoat name
boat_typestringyacht, catamaran, sailboat, motorboat, gulet, rib, or other
mmsistring|nullMaritime MMSI number, if known
capacityintegerMaximum guests
length_ftnumberLength overall (LOA) in feet — see the note below
yearintegerYear built
cabinsinteger|nullNumber of cabins as stored, crew cabins included — the record an operator edits. Guest cabins are cabins − crew_cabins, the figure the boat page, the cabins filter and the AI and agent APIs state
crew_cabinsinteger|nullNumber of crew cabins, counted in cabins
berthsinteger|nullNumber of berths
toiletsinteger|nullNumber of toilets
specsobject|nullExtra specs (beam, draft, engine_desc, fuel_capacity, water_capacity, transit_log, …). Metres for beam/draft
specs_from_modelarrayWhich specs keys were filled from the manufacturer's model rather than sent for this hull — [], ["beam"] or ["beam","draft"]. Beam is a model constant, but the same model ships with more than one keel, so an inherited draft is typical rather than measured on this boat
aisobject|nullWhat this hull has been observed doing, from its AIS track. null for most boats. 🚨 Not utilisation — AIS records movement, and a fully booked boat can sit in one bay all week
ais.speedobject|null{ low, high, median } knots, the p25–p75 of this boat's speed when under way. Always a range: the spread within one boat (skipper, sea state, wind) exceeds the spread between boats, so a single figure would read as a promise. Not comparative — a boat's number reflects where it sails as much as how
ais.sailingDaysPerYearnumber|nullDays a year this hull was recorded on the move, normalised over ais.trackedYears. A day counts when that day's fixes span at least a kilometre — displacement, never speed: a yacht sailing around its own anchor in a blow logs 1–4 knots without going anywhere, so speed cannot separate a passage from a windy night at anchor. Deliberately a rate and not a "moved on X of Y days" fraction — that division reads as an idle boat, which is invalid here, since a fully chartered week can be spent at anchor. null under a full year of track — scaling a single season to a year assumes winter looks like August, which for a charter yacht is false
ais.trackedYearsnumber|nullYears of AIS track behind that rate — calendar time from the first fix to the last, including the stretches the hull sent nothing. Silence over the winter is evidence the boat was not sailing, so it counts; a denominator of days-that-have-a-fix would converge on the numerator for any yacht that switches AIS off on the berth, and push every rate toward 365
ais.placesVisitednumber|nullDistinct harbours and bays it was recorded stopping at
descriptionstring|nullFull description: the platform's own text, else our generated one (description_ai), else the operator's highlights. null when the boat has none or when we have withheld it — a moderator marks a listing's description inaccurate or carrying personal details, and no rung of that ladder is then published
description_aistring|nullThe generated description on its own, when there is one — null too whenever description is withheld
pricing_sourcestringActive pricing source: direct, nausys, or mmk
original_price_per_daynumber|nullPrice before discount
discount_percentagenumber|nullActive discount percentage
discount_labelstring|nullDiscount label text
price_fromnumber|nullLowest seasonal price
price_tonumber|nullHighest seasonal price
charter_dayinteger|nullPreferred charter start weekday (0=Sun … 6=Sat)
check_in_periodsarray|nullCheck-in/out windows from the source platform. minimalReservationDuration is the shortest stay the boat is sold for in that window. For boats priced from MMK it is the length the current price was quoted for (7 nights, or 6 on boats that check in and out on the same clock), refreshed on every pricing run — the same number the booking flow enforces. For boats from Nausys it is Nausys's own figure, 0 or null where it names none (no minimum in that window).
checkin_timestring|nullThe boat's own check-in time (e.g. 17:00:00) — the hour the season in force states, or failing that the one its charter base states. null means nobody has stated one — it is not a default; the season's check_in_periods[].checkInTime answers first where one covers the date, and where neither does, show "TBD" rather than assuming an hour.
checkout_timestring|nullThe boat's own check-out time (e.g. 09:00:00), read the same way, with the same meaning for null.
min_charter_daysinteger|nullThe platform's own yacht-level minimum, as sent; on Nausys boats, the lowest minimum among the check-in periods not yet ended (null when none states one). 0 means the operator set none, not that single nights are sold — read check_in_periods[].minimalReservationDuration for the enforced minimum.
instant_bookbooleanWhether instant booking is enabled
security_depositnumber|nullSecurity deposit amount
deposit_with_waivernumber|nullReduced deposit when a damage waiver is taken
seo_released_atstring|nullTimestamp the detail page was released for indexing
location_idinteger|nullLocation ID
modelstring|nullBoat model name, as the source platform spells it
updated_atstringISO 8601 last-updated timestamp
featuresstring[]Canonical equipment slugs and attribute tags this boat carries — the values the features filter on List Boats matches (vocabulary)
manufacturer_idstring|nullManufacturer UUID
rental_typestringbareboat, skippered, or no_licence_needed — the operator's product class. skippered means a skipper comes with the boat, not merely that one is available, and no_licence_needed means the operator requires no licence of the charterer (which is not the same as a skipper coming along). It does not say who pays: read skipper_charged for that; see Skipper
skipperinteger|nullPer-boat skipper flag: 1 = the boat comes with a skipper (included, or an obligatory paid extra), null = a skipper is available on request, any other value = you sail it yourself. Read it together with rental_type; see Skipper
skipper_chargedbooleanWhether the obligatory skipper is billed: true = a required, live, non-zero skipper fee applies to this boat under its active pricing source, so the charter price does not cover him and the charge appears in List Boat Fees. false = nothing is billed for him. Derived from the fee set on every read, so it follows the operator's current schedule rather than a stored flag; see Skipper
skipper_for_hirebooleanWhether a skipper is offered as a priced optional extra on a boat with no named crew: true = a live, non-zero, non-required skipper fee applies under the active pricing source and crew is null, so the charter price does not cover him and he is tickable in List Boat Fees. On a rental_type: "skippered" boat with skipper_charged: false this is what turns skippered into "optional". Derived from the fee set on every read; see Skipper
crewobject|nullThe crew that comes with the boat, when the operator names one: { count, roles[] }. Roles are the operator's own words. null when no roster is published — which is not a statement that there is no crew; see Skipper
average_ratingnumber|nullAverage review rating (1-5)
review_countintegerTotal number of reviews
is_publishedbooleanAlways true here: an unpublished listing answers 404, not a row (see Errors). Publication is not a guarantee of bookability — a published boat may have no current pricing or availability; check price_per_day.
photo_descriptionsobject|nullAI-generated per-category photo captions
seo_variantstring|nullSEO copy variant label
has_valid_nameboolean|nullWhether the boat has a human-readable name
has_multi_portbooleanWhether the boat rotates between marinas seasonally
hero_image_urlstring|nullPrimary image master — see the Images note above
tour_360string|nullLink to a 360° virtual tour, when the platform provides one
avail_weeksintegerPacked availability bitmask, relative to avail_weeks_anchor. A set bit means the hull is free and sellable that week somewhere — it does not assert the boat is at location, since a week may be sold from a second base (see Where each week departs from)
best_internal_discount_pctnumberBest internal discount percentage available
discounted_price_per_daynumber[]52 weekly entries indexed from avail_weeks_anchor (entry w = the week starting w × 7 days after it): the per-day price for that week. 0 means the boat is not sold that week — never free
discount_pctnumber[]Same indexing: the discount percentage behind that week's price
extras_per_day, extras_per_booking, extras_per_person, extras_per_person_per_daynumber[]Same indexing: required extras for that week, by how they are charged. A cached weekly aggregate for charts and "from" prices — quote a real stay with Resolve Pricing and List Boat Fees
companyobjectCancellation/guest-terms fields only (see note above)
manufacturerobject|null{ id, name, slug, logo_url }
locationobject|nullMarina/location details (see below); null when the boat has no location
geo_crumbsarrayBreadcrumb trail up the place hierarchy, [{ label, href }] — country, region, city. href is a charter.boats path (prefix https://charter.boats). Only indexable hub pages are included, so the trail can be shorter or empty
imagesarrayActive images with id, url, category, is_active, is_primary, sort_order, in no particular order — sort by sort_order
images[].tagobject | nullThe place the photo also shows, when the uploader named one: { href, label, icon } — icon is pin; label is the place, its town and its country (Lakka Osmos, Paxos, GR). null for most photos
videosarray[{ id, url, thumbnail_url, sort_order }], ordered by sort_order; usually empty. thumbnail_url is a poster frame grabbed from the clip at upload, null on a video uploaded before that existed — fall back to the boat's own hero image. The bucket key is internal and is not returned
equipmentarrayThe boat's equipment in display sections: [{ group, items: [{ name, value, rare?, pct?, hidden?, standard? }] }]. value is a detail such as a count or type ("Halyard"), null when none. rare: true with pct (the share of the fleet carrying it) marks unusual kit; kit standard on the boat's model follows the same rule, and is listed even when the operator did not list it — standard: true marks such a line, which no equipment row of the boat names. hidden: true marks an item the operator hid — don't show those to guests
rare_featuresarrayUp to three of the boat's rarest items, rarest first, [{ label, pct }] — the "rare find" highlight. Safety and miscellaneous items are left out
reviewsarrayReviews with id, rating, comment, created_at
pricesarraySeasonal pricing rows for the active pricing source. Each row names its departure marina; one-way delivery legs and implausible quotes (partner-side typos priced under €10/day) are excluded — see Where each week departs from
has_day_tripsbooleanThe operator also quotes day charters for this boat. They are not sold on charter.boats yet and never appear in prices
best_sourcestringWhere the boat's price comes from: mmk, nausys or direct
best_currencystringThe currency every amount in this response is in: EUR
price_currencystringThe currency the operator prices the boat in (EUR or USD); a guest charged in it pays the operator's own figure
internal_discount_pctnumber|nullInternal discount percentage when applicable
mmk_productsarray|nullMMK product variants with name and isDefault (MMK boats only)
avail_weeks_anchorstring|nullAnchor date (YYYY-MM-DD) for decoding avail_weeks

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. Null when the boat has no current pricing — a listing stays published on its specs, images and equipment even when its seasons lapse
list_priceobject|nullThe row's ONE figure, the boat's 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 day rate, our auto-applied offer already taken off, no fees inside. Null when the boat has no current pricing
best_pricenumber|nullSame value as price_per_day; null when the boat has no current pricing
lowest_price_per_daynumber|nullLowest effective per-day price after any internal discount; null when the boat has no current pricing

length_ft is length overall (LOA), the figure charter platforms and manufacturers advertise — not hull length, which runs shorter. For boats priced from a connected platform it is the length of the boat's model, so sister ships of the same model report the same figure rather than each platform's per-hull number. Boats you manage directly keep whatever length you set.

Where each week departs from

Every row in prices carries the marina that week starts from:

fieldtypedescription
prices[].departs_fromobject|null{ id, name, city } of the departure marina. The boat's own marina for most weeks; a different one where the operator sells that week from a second base. null when several marinas share the week — see below
prices[].departs_from_countintegerHow many marinas that week can start from. 1 normally; higher when the operator offers the same week from several bases

A boat is not always in one place. Around 8% of the MMK fleet is quoted from more than one base across a year — some relocate seasonally, others are offered from several ports in the same week — so a week's departure marina is not always the marina on location.

Where departs_from_count is above 1, departs_from is null on purpose. We hold one quote for that week but cannot say which of the bases it belongs to, and naming one would be a guess. Show the count, or the boat's own marina, rather than picking.

One-way legs are still excluded. MMK also quotes delivery legs that start and end at different bases. Those are dropped from prices entirely: there is no single marina to name, and a booking attempt against one is rejected. Such a quote is often a fraction of the round-trip rate, so including it would make the boat look cheaper than anything you could book.

price_per_day, price_from/price_to (and v1.0's best_price) are still derived from home-base weeks only, while prices now includes weeks sold from a second base. The lowest value in prices can therefore be below price_from. Use prices when you need what a specific week costs, and the scalar fields when you need what the boat costs from its own marina.

Skipper

rental_type: "skippered" means the charter comes with a skipper — it is not a statement that one can be arranged. A boat that is sold bareboat but also offers a crewed option reports rental_type: "bareboat", and the skipper shows as optional (skippered: "optional" on the list endpoint). When the operator makes the skipper an obligatory paid extra — the fee is marked required, so the guest cannot decline him — the boat counts as skippered for search purposes but reports skippered: "required": the boat cannot be sailed by the charterer, and the skipper's cost appears as a mandatory fee on top of the charter price rather than inside it. rental_type may still read "bareboat" on such boats.

⚠️ A crewed product and an obligatory skipper fee are not exclusive, and this is the case to get right: an operator can sell the boat as rental_type: "skippered" and bill "Skipper (+ food) — 160 EUR per day" as a required fee. The product class alone therefore cannot tell you whether the price covers him — skipper_charged can, and it is what the list endpoints report as skippered: "required" rather than true. Never present such a boat as "skipper included"; the skipper is obligatory and the guest pays for him on top.

The other way round is skipper_for_hire: a crewed product that bills no obligatory skipper but lists him as a priced optional extra (The Moorings' "Powered" cats: "Skipper - HR (subject to availability)", 280 a day) does not include him either. Such a boat reports skippered: "optional" and skipper_for_hire: true — unless the operator names a crew (crew non-null), in which case the priced row is a surcharge beside a crew that is aboard anyway and the boat stays true. The list endpoints derive the same fact from the same fee set, so a list row and its detail report one word.

We take this from the operator's own product setup rather than inferring it: whichever product they mark as the default is what the boat is, and a crewed product sitting alongside a bareboat default makes the skipper an add-on, not an inclusion.

A crewed charter is not always just a skipper, so the wording follows the source: the operator's product is named Crewed, and where they publish an actual roster we return it as crew — { "count": 4, "roles": ["Captain", "Chef", "Steward", "Deckhand"] }. Roles are reproduced as written, not mapped onto a vocabulary of ours, so expect free text and the occasional operator typo. crew is null on most crewed boats: a roster is published for a minority of them, and its absence says nothing about whether a crew is aboard — rental_type remains the answer to that.

Names, ages, photographs and biographies are deliberately not part of this response, though operators supply them.

Whether a licence is required is a separate question, and one we do not always know — it is absent from this response rather than guessed.

Company Object

FieldTypeDescription
base_currencystringDefault currency (e.g. EUR)
cancel_refund_percentnumber|nullRefund percentage under the operator's cancellation policy; null when none applies. A boat sold directly on charter.boats (not through MMK or Nausys) is always null: no refund after the first 24 hours from payment
cancel_refund_daysnumber|nullDays before charter that the refund window applies; null when no policy applies, as above
terms_guestobject|nullGuest-facing terms: insurance_requirements, damage_liability, license_requirements
terms_faqarray|nullGuest questions answered from the operator's terms, [{ q, a }]

Location Object

FieldTypeDescription
idintegerLocation ID
slugstring|nullURL slug of the location page (https://charter.boats/locations/{slug})
namestringMarina/location name
citystring|nullCity
municipalitystring|nullMunicipality
statestring|nullState/region
countrystring|nullCountry name
country_codestring|nullISO country code (lowercase)
latnumber|nullLatitude
lonnumber|nullLongitude
websitestring|nullMarina website
phonestring|nullContact phone
image_urlstring|nullMarina image
ratingnumber|nullMarina rating
capacitynumber|nullBerth capacity
max_draftnumber|nullMaximum draft (m)
max_lengthnumber|nullMaximum vessel length (m)
vhf_channelstring|nullVHF hailing channel
neighborsobject|nullNearby location IDs grouped by distance band: keys like "12nm", "20nm" (nautical miles), each a comma-separated string of location IDs
descriptionstring|nullMarina description

Errors

StatusMessage
400Boat ID is required
404Boat not found, or not published

A listing that is not published — a draft, one an operator withdrew, or one belonging to a deactivated company — answers 404 exactly as a boat that never existed does, for every caller including your own boats. Read and edit your unpublished listings through the dashboard, not here.

When the boat you asked for — by either slug or UUID — has since been merged into another, the same hull listed twice and reconciled, the 404 carries the survivor so you can follow it instead of dropping the boat:

{
  "statusCode": 404,
  "message": "Boat not found",
  "data": { "moved_to": "bali-4-2-sunny" }
}

data.moved_to is a slug (or, for a survivor with no slug yet, a UUID) for this same endpoint. Treat it as a permanent move and update any stored id. It is absent — a plain 404 — when the boat never existed or when it was merged before this was recorded, so branch on its presence rather than assuming it.

A 404 for a listing that is no longer published may instead carry data.suggest: the search terms for the boat, never the boat. It is what you would type to look for one like it, and it is deliberately not a redirect — a hull withdrawn by one operator is often listed by another, but matching on a name asserts an identity we cannot prove (Merry Fisher is a model, so two different boats carry it).

It appears only for hulls listed through a booking partner, whose name, model and base that partner publishes itself. A listing an owner manages directly answers a bare 404 whether it exists or not, so suggest is never a way to test whether an unpublished listing is there.

{
  "statusCode": 404,
  "message": "Boat not found",
  "data": {
    "suggest": {
      "name": "Magnum Opus",
      "make_slug": "lagoon",
      "model_label": "50",
      "location_id": 4321,
      "location_name": "Marina Kaštela"
    }
  }
}

Every field is optional and any of them may be missing (a listing with no usable name carries no name; one with no manufacturer carries neither make_slug nor model_label; and a model label containing a comma is dropped, since that is the separator makes= uses). Feed them to GET /api/boats as q=<name>&makes=<make_slug>:<model_label>, falling back to makes=<make_slug>:<model_label>&location_id=<location_id> when that is empty. Nothing identifying is in it — no boat id, no operator, no price — and the response is still a 404, so do not treat it as a row.

On this page