Charter Boats
Charter Boats API
Boats

Get Boat

Get detailed information about a specific boat

GET/boats/:id

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

Authentication

Send your API key in the X-API-Key header, as on every Operator API call — 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",
  "company_id": "e33847d2-aaf3-4edf-b672-23dc8239cae9",
  "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,
  "crew": null,
  "average_rating": null,
  "review_count": 0,
  "import_source": "mmk",
  "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_price": 133.98,
  "best_source": "mmk",
  "best_currency": "EUR",
  "internal_discount_pct": null,
  "lowest_price_per_day": 133.98,
  "mmk_products": [
    { "name": "Bareboat", "isDefault": false },
    { "name": "Crewed", "isDefault": true }
  ]
}

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 fields are not part of the documented contract and should not be relied upon.

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, 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
crew_cabinsinteger|nullNumber of crew 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
description_aistring|nullThe generated description on its own, when there is one
pricing_sourcestringActive pricing source: direct, nausys, or mmk
price_per_daynumber|nullEffective daily rate. 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.
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.
checkin_timestring|nullCheck-in time (e.g. 17:00:00)
checkout_timestring|nullCheck-out time (e.g. 09:00:00)
min_charter_daysinteger|nullThe platform's own yacht-level minimum, as sent. 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
company_idstringOperator company UUID
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
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
import_sourcestring|nullSource platform the boat was imported from
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
videosarray[{ id, url, storage_path, thumbnail_url, duration_seconds, sort_order }], ordered by sort_order; usually empty
equipmentarrayThe boat's equipment in display sections: [{ group, items: [{ name, value, rare?, pct?, hidden? }] }]. 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. 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_pricenumber|nullSame value as price_per_day; null when the boat has no current pricing
best_sourcestringPricing source for best_price
best_currencystringCurrency code (e.g. EUR)
internal_discount_pctnumber|nullInternal discount percentage when applicable
lowest_price_per_daynumber|nullLowest effective per-day price after any internal discount; null when the boat has no current pricing
mmk_productsarray|nullMMK product variants with name and isDefault (MMK boats only)
avail_weeks_anchorstring|nullAnchor date (YYYY-MM-DD) for decoding avail_weeks

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

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 cancellation policy
cancel_refund_daysnumber|nullDays before charter that the refund window applies
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.

On this page