{"openapi":"3.1.0","info":{"title":"Boatfront API","version":"1.0.0","summary":"Read and manage used-yacht classified listings on Boatfront.","description":"Boatfront is a worldwide marketplace for buying and selling used yachts.\nSellers are private individuals and brokers; buyers browse listings, save\nthem, and contact the seller through an enquiry.\n\n**Status: pre-launch.** The marketplace is not open yet. The endpoints below are live but the public collections are empty until launch.\n\nAuthentication is optional for the public read endpoints and required for\neverything that touches a specific account. Register an OAuth 2.1\napplication or mint an API key in the developer portal.\n\nTerms: https://boatfront.com/en/terms · Privacy: https://boatfront.com/en/privacy","contact":{"name":"Boatfront","email":"hello@boatfront.com","url":"https://boatfront.com"},"termsOfService":"https://boatfront.com/en/terms","license":{"name":"Proprietary","url":"https://boatfront.com/en/terms"}},"servers":[{"url":"https://boatfront.com","description":"Production"}],"externalDocs":{"description":"Agent guidance","url":"https://boatfront.com/llms.txt"},"tags":[{"name":"Listings","description":"Boat advertisements."},{"name":"Enquiries","description":"Buyer-to-seller contact."},{"name":"Reference","description":"Catalogue data used to build listings and filters."},{"name":"Yacht Guide","description":"The community-edited reference for yacht models and marine engines. Independent of the classifieds: a page exists because the boat exists, not because one is for sale."},{"name":"Account","description":"The authenticated user's own data."}],"paths":{"/api/listings":{"get":{"tags":["Listings"],"summary":"List active listings","description":"Paginated collection of active, non-deleted listings. Public — no credentials required. Pass `mine=true` with a credential to list the caller's own listings instead, including drafts.","operationId":"listListings","security":[{},{"oauth2":["listings:read"]},{"apiKey":[]}],"parameters":[{"name":"page","in":"query","description":"1-based page number.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","description":"Page size. Values above 100 are clamped to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"mine","in":"query","description":"Return the authenticated caller's own listings. Requires a credential.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"A page of listings.","content":{"application/json":{"schema":{"type":"object","required":["data","total","page","limit","hasMore"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Listing"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer"},"hasMore":{"type":"boolean"}}}}}},"401":{"description":"`mine=true` without a valid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Credential lacks the `listings:read` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Listings"],"summary":"Create a listing","description":"Creates a listing as a draft. Publishing is a paid action: set `status` to `pending` and follow `checkoutUrl` to Stripe Checkout. Requires a signed-in session — not available to API keys.","operationId":"createListing","security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","category","price"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"category":{"type":"string","enum":["sail","motor","other"]},"price":{"type":"number"},"currency":{"type":"string","default":"GBP"},"specs":{"type":"object"},"location":{"type":"object"},"images":{"type":"array","items":{"type":"object"}},"featureSlugs":{"type":"array","items":{"type":"string"}},"sellerBrokerId":{"type":"string","description":"Only accepted when the caller is a member of that broker, or an admin."},"status":{"type":"string","enum":["draft","pending"],"default":"draft"}}}}}},"responses":{"200":{"description":"The created listing, plus a Stripe Checkout URL when publishing.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Listing"},"checkoutUrl":{"type":"string","format":"uri"}}}}}},"401":{"description":"No session.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a member of the requested broker.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/listings/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Listings"],"summary":"Get a listing","description":"Active listings are public. A listing in any other state is visible only to its owner, that owner's broker, or an admin — everyone else gets 404 rather than 403, so the response never confirms the id exists. The record carries an additive `guide` object when the boat has been resolved to a Yacht Guide builder, model or engine.","operationId":"getListing","security":[{},{"oauth2":["listings:read"]},{"apiKey":[]}],"responses":{"200":{"description":"The listing.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Listing"}}}}}},"404":{"description":"No such listing, or it is not visible to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/listings/suggestions":{"get":{"tags":["Reference"],"summary":"Autocomplete values from active listings","description":"Distinct makes, or models for a given make. Cached for an hour.","operationId":"listListingSuggestions","parameters":[{"name":"type","in":"query","required":true,"schema":{"type":"string","enum":["make","model"]}},{"name":"make","in":"query","description":"Required when `type=model`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Distinct values, alphabetically.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"string"}}}}}}}}}},"/api/guide/search":{"get":{"tags":["Yacht Guide"],"summary":"Search the Yacht Guide's builders, models and engines","description":"Typeahead over the guide's curated entities. Returns the ids the listing endpoints accept as input in their `guide` object — an id from here is verified server-side before it is written, so a stale one is a 400 rather than a bad link. `q` is clamped to 80 characters. Public, cached for 5 minutes.","operationId":"searchGuide","parameters":[{"name":"type","in":"query","required":true,"schema":{"type":"string","enum":["builder","model","engine"]}},{"name":"q","in":"query","description":"Search string. Accents, spacing and mark designations are folded (\"MD 2030\" matches \"MD2030\").","schema":{"type":"string","maxLength":80}},{"name":"builder","in":"query","description":"Required when `type=model`. The builder's slug, id or name.","schema":{"type":"string"}}],"responses":{"200":{"description":"Matching entities, best match first.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/GuideSearchItem"}}}}}}},"400":{"description":"`type` missing or unknown, or `builder` missing for type=model.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/guide/models/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Yacht Guide"],"summary":"Get a yacht model","description":"One model as JSON: its builder, the specification keyed by the documented catalogue keys (`loa_m` is always metres), the prose sections, its variants and layouts, and the engines fitted to it. A stub or hidden model answers 404, never 403.","operationId":"getGuideModel","responses":{"200":{"description":"The model.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/GuideModel"}}}}}},"400":{"description":"Malformed id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No published model with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/guide/engines/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Yacht Guide"],"summary":"Get a marine engine","description":"One engine as JSON: its manufacturer, the specification keyed by the engine catalogue, the prose sections, and `fitments` — the models this engine was fitted to. A stub or hidden engine answers 404.","operationId":"getGuideEngine","responses":{"200":{"description":"The engine.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/GuideEngine"}}}}}},"400":{"description":"Malformed id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No published engine with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/features":{"get":{"tags":["Reference"],"summary":"Feature catalogue","description":"The active equipment/feature catalogue. `slug` values here are the only ones accepted in a listing's `featureSlugs`.","operationId":"listFeatures","responses":{"200":{"description":"The catalogue.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"label":{"type":"string"},"category":{"type":"string"}}}}}}}}}}}},"/api/enquiries":{"post":{"tags":["Enquiries"],"summary":"Send an enquiry to a seller","description":"Records an enquiry and emails the seller of the listing. The enquiry is always stored; `delivered` reports whether the seller email went out. Anonymous but bot-checked and rate limited: one enquiry per IP per listing per 60 seconds, and ten per IP per hour. This is the marketplace's primary conversion — do not call it speculatively on a buyer's behalf without their intent.","operationId":"createEnquiry","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["listingId","name","email","message"],"properties":{"listingId":{"type":"string","maxLength":64},"name":{"type":"string","minLength":2,"maxLength":120},"email":{"type":"string","format":"email","maxLength":254},"phone":{"type":"string","maxLength":40},"message":{"type":"string","minLength":10,"maxLength":5000}}}}}},"responses":{"200":{"description":"The enquiry was accepted and stored. `delivered` is false when the seller could not be emailed; the enquiry is not lost.","content":{"application/json":{"schema":{"type":"object","required":["success","delivered"],"properties":{"success":{"type":"boolean","const":true},"delivered":{"type":"boolean"}}}}}},"400":{"description":"Validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Rejected as automated traffic.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/users/saved":{"get":{"tags":["Account"],"summary":"List the authenticated user's saved listings","description":"Saved listings that are still active or sold. Session-authenticated.","operationId":"listSavedListings","security":[{"session":[]}],"responses":{"200":{"description":"Saved listings.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Listing"}}}}}}},"401":{"description":"No session.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Account"],"summary":"Save or unsave a listing","operationId":"toggleSavedListing","security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["listingId"],"properties":{"listingId":{"type":"string","format":"uuid"},"action":{"type":"string","enum":["save","unsave"],"default":"save","description":"Anything other than `unsave` saves the listing."}}}}}},"responses":{"200":{"description":"The listing was saved or unsaved."},"400":{"description":"`listingId` missing or not a string.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No session, or the session no longer maps to a user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Listing":{"type":"object","description":"A classified advertisement for a used yacht or boat.","required":["id","slug","title","category","specs","price","currency","location","status"],"properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string","description":"URL segment used by /{locale}/boats/{slug}."},"title":{"type":"string"},"description":{"type":"string"},"category":{"type":"string","enum":["sail","motor","other"]},"specs":{"type":"object","properties":{"make":{"type":"string"},"model":{"type":"string"},"year":{"type":"integer"},"lengthM":{"type":"number","description":"Length overall, metres."},"beamM":{"type":"number"},"draftM":{"type":"number"},"hullType":{"type":"string"},"rigging":{"type":"string"},"boatClass":{"type":"string"},"engineMake":{"type":"string"},"engineModel":{"type":"string"},"engineHours":{"type":"integer"},"fuelType":{"type":"string"},"hullMaterial":{"type":"string"},"cabins":{"type":"integer"},"berths":{"type":"integer"}}},"price":{"type":"number"},"currency":{"type":"string","description":"ISO 4217 code, e.g. GBP."},"negotiable":{"type":"boolean"},"bespoke":{"type":"boolean"},"yachtName":{"type":"string"},"images":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"isPrimary":{"type":"boolean"},"kind":{"type":"string","enum":["image","video"]}}}},"location":{"type":"object","properties":{"city":{"type":"string"},"countryCode":{"type":"string","description":"ISO 3166-1 alpha-2."},"lat":{"type":"number"},"lng":{"type":"number"}}},"seller":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["private","broker"]},"brokerSlug":{"type":"string"}}},"guide":{"type":"object","description":"Resolved Yacht Guide entities for this boat. Omitted when the make and model could not be matched to a guide page. Each `url` is the public page; add `Accept: text/markdown` to get the same content as Markdown.","properties":{"builder":{"type":["object","null"],"properties":{"slug":{"type":"string","description":"Builder slug, e.g. `beneteau`."},"url":{"type":"string","format":"uri"}}},"model":{"type":["object","null"],"properties":{"slug":{"type":"string","description":"`<builder>/<model>`, e.g. `beneteau/oceanis-313`."},"url":{"type":"string","format":"uri"}}},"engine":{"type":["object","null"],"properties":{"slug":{"type":"string","description":"`<manufacturer>/<engine>`, e.g. `volvo-penta/md2030`."},"url":{"type":"string","format":"uri"}}},"model_id":{"type":["string","null"],"description":"The guide model id. This is what the listing endpoints take back in their `guide` input, so a client can read a listing and edit it without resolving the slug again."},"variant_id":{"type":["string","null"],"description":"The guide variant id, when the boat is a known factory alternative."},"engine_id":{"type":["string","null"],"description":"The guide engine id."},"confidence":{"type":["string","null"],"enum":["high","medium","low",null],"description":"How sure the match is. `low` is never linked automatically; it appears only where a person chose it."},"source":{"type":["string","null"],"enum":["import","seller","broker","editor","backfill",null],"description":"Who or what made the match. A `seller`, `broker` or `editor` choice is never overwritten by a later import."},"region":{"type":["string","null"],"description":"The cruising area the boat's location falls in, from a curated list. Server-derived and not settable."}}},"featureSlugs":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["draft","pending","active","expired","sold"]},"views":{"type":"integer"},"saves":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"GuideSearchItem":{"type":"object","description":"One match from the Yacht Guide's typeahead.","required":["id","slug","label"],"properties":{"id":{"type":"string","description":"The guide entity id. Accepted as input by the listing endpoints."},"slug":{"type":"string","description":"URL segment of the entity itself, without its maker."},"label":{"type":"string","description":"What to show in a picker. For an engine, the manufacturer is included."},"subtitle":{"type":"string","description":"Disambiguating context: production years, power, model count."},"maker_id":{"type":"string"},"maker_slug":{"type":"string"},"maker_name":{"type":"string"}}},"GuideModel":{"type":"object","description":"A yacht model in the Yacht Guide.","required":["id","slug","name","builder","category","specs","urls"],"properties":{"id":{"type":"string"},"slug":{"type":"string","description":"`<builder>/<model>`, e.g. `beneteau/oceanis-313`."},"name":{"type":"string"},"builder":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"url":{"type":"string","format":"uri"}}},"category":{"type":"string","enum":["sail","motor","catamaran"]},"designer":{"type":["string","null"]},"years":{"type":"object","properties":{"start":{"type":["integer","null"]},"end":{"type":["integer","null"],"description":"Null while still in production."},"label":{"type":["string","null"],"description":"e.g. \"1999–2004\"."}}},"hullsBuilt":{"type":["integer","null"]},"specs":{"type":"object","description":"Specification keyed by the documented catalogue: `loa_m`, `beam_m`, `draught_m` and `air_draught_m` in metres, `displacement_kg` and `ballast_kg` in kilograms, `sail_area_m2` in square metres, tanks in litres, `hull_material` / `keel_type` / `rig_type` as documented enums. A missing key means not recorded — never assume zero.","additionalProperties":true},"sections":{"type":"object","description":"Prose sections as Markdown. Absent keys simply have not been written yet.","additionalProperties":{"type":"string"}},"variants":{"type":"array","items":{"type":"object"}},"layouts":{"type":"array","items":{"type":"object"}},"fitments":{"type":"array","description":"Engines fitted to this model, with the role they were fitted in.","items":{"type":"object","properties":{"engineId":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"powerHp":{"type":["number","null"]},"role":{"type":"string","enum":["standard","option","common_retrofit","unknown"]},"years":{"type":["string","null"]},"url":{"type":"string","format":"uri"}}}},"listingCount":{"type":"integer","description":"Active listings of this model right now. **Absent** until the marketplace opens — not zero, because zero is a claim about the market and \"not open yet\" is a different one. Treat a missing field as unknown rather than none."},"urls":{"type":"object","properties":{"page":{"type":"string","format":"uri"},"markdown":{"type":"string","format":"uri","description":"Same URL; send `Accept: text/markdown`."},"api":{"type":"string","format":"uri"}}},"updatedAt":{"type":"string","format":"date-time"}}},"GuideEngine":{"type":"object","description":"A marine engine in the Yacht Guide.","required":["id","slug","name","manufacturer","specs","urls"],"properties":{"id":{"type":"string"},"slug":{"type":"string","description":"`<manufacturer>/<engine>`, e.g. `volvo-penta/md2030`."},"name":{"type":"string"},"manufacturer":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"url":{"type":"string","format":"uri"}}},"fuel":{"type":["string","null"],"enum":["diesel","petrol","electric","hybrid",null]},"years":{"type":"object","properties":{"start":{"type":["integer","null"]},"end":{"type":["integer","null"]},"label":{"type":["string","null"]}}},"driveOptions":{"type":"array","items":{"type":"string"}},"specs":{"type":"object","description":"Specification keyed by the engine catalogue: `power_kw` in kilowatts, `power_hp` in metric horsepower, `displacement_cc` in cubic centimetres, `weight_kg` dry, `bore_mm` / `stroke_mm` in millimetres.","additionalProperties":true},"sections":{"type":"object","description":"Prose sections as Markdown. Absent keys simply have not been written yet.","additionalProperties":{"type":"string"}},"fitments":{"type":"array","description":"The models this engine was fitted to.","items":{"type":"object","properties":{"modelId":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"category":{"type":"string","enum":["sail","motor","catamaran"]},"role":{"type":"string","enum":["standard","option","common_retrofit","unknown"]},"years":{"type":["string","null"]},"url":{"type":"string","format":"uri"}}}},"listingCount":{"type":"integer","description":"Active listings with this engine fitted. Absent until the marketplace opens; see the model schema."},"urls":{"type":"object","properties":{"page":{"type":"string","format":"uri"},"markdown":{"type":"string","format":"uri"},"api":{"type":"string","format":"uri"}}},"updatedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error","code","message","status"],"properties":{"error":{"type":"string","description":"Human-readable summary. Same text as `message`."},"code":{"type":"string","description":"Stable machine-readable error code. Branch on this, not on the message.","enum":["bad_request","unauthorized","forbidden","insufficient_scope","not_found","method_not_allowed","rate_limited","internal_error"]},"message":{"type":"string"},"status":{"type":"integer"},"hint":{"type":"string","description":"What the caller should do about it."},"documentation":{"type":"string","format":"uri"}}}},"securitySchemes":{"oauth2":{"type":"oauth2","description":"OAuth 2.1 authorisation code flow with PKCE. Register an application in the developer portal to obtain a client id.","flows":{"authorizationCode":{"authorizationUrl":"https://boatfront.com/api/auth/oauth2/authorize","tokenUrl":"https://boatfront.com/api/auth/oauth2/token","refreshUrl":"https://boatfront.com/api/auth/oauth2/token","scopes":{"listings:read":"View and search boat listings","listings:write":"Create and manage boat listings","broker:read":"View broker profiles and analytics","broker:write":"Manage broker profiles and team"}}}},"apiKey":{"type":"http","scheme":"bearer","description":"An API key minted in the developer portal, sent as `Authorization: Bearer <key>`. Keys default to the `listings:read` scope."},"session":{"type":"apiKey","in":"cookie","name":"better-auth.session_token","description":"Browser session cookie. Not available to server-to-server callers."}}}}