Charter Boats
Charter Boats API
Boats

Import Boat

Import a boat listing with images

POST/import/boat

Import a new boat listing in a single API call. Handles creating the boat, storing image URLs, and auto-matching to a marina location. The boat is created unpublished — see Publishing.

Authentication

Requires API key in the X-API-Key header.

  • Your API key: the boat is created under a company you belong to — one you own, when you own one — or, if you belong to none, under a personal "…'s Fleet" company created for you. owner_id/owner_email are ignored. To target a specific company, pass company_id — you must be a member of it, or the request fails with 403.
  • Platform key (internal): requires owner_id or owner_email in the request body to identify who the boat belongs to.
X-API-Key: YOUR_API_KEY
Content-Type: application/json

Request Body

Required Fields

FieldTypeDescription
titlestringBoat name/title
boat_typestringOne of: yacht, catamaran, sailboat, motorboat, rib, other
capacityintegerMaximum guests (must be at least 1)

Optional Fields

FieldTypeDefaultDescription
descriptionstringFull description (supports markdown)
price_per_daynumberDaily rate. Ignored when import_source is boataround — those boats are stored with no daily rate, because a rate here would not be backed by any bookable season.
length_ftnumberLength overall (LOA) in feet. Values outside the plausible hull range (~6.5–350 ft) are stored as no length rather than as sent — only boats priced from a connected platform take their length from the model catalogue.
yearintegerYear built
cabinsintegerNumber of cabins
berthsintegerNumber of berths
toiletsintegerNumber of toilets
modelstringBoat model name, stored as sent
manufacturer_namestringThe make. Matched on its slug (the name lowercased, non-alphanumerics turned into -), so send the name exactly as GET /api/manufacturers lists it — Bali, not Bali Catamarans. A name that matches no existing make creates a new one, and boats under a stray make don't answer the site's make filter for the real one.
rental_typestringbareboatbareboat, skippered, or no_licence_needed
fuel_includedbooleanfalseWhether fuel is included
security_depositnumberSecurity deposit amount
instant_bookbooleanfalseAllow instant booking
is_publishedbooleanfalseHas no lasting effect — imported boats end up unpublished (see below)

Publishing

An imported boat is created unpublished, and the import cannot publish it. A listing goes live only when it is complete — a title, a location, a capacity, at least one image, and check-in days — and check-in days cannot be sent through this endpoint. is_published: true runs that completeness check, which therefore always ends with the boat unpublished; the response's boat.is_published reads false.

Open the boat in the dashboard, set its check-in days (and fill anything else it reports missing), and it publishes as soon as it is complete.

Location Fields

The boat is placed on a marina in this order:

  1. Coordinates. With latitude and longitude, the first known location inside a box of roughly ±1 km around the point is used. It is the first one found, not the nearest, so send the marina's own coordinates.
  2. Name. If that finds nothing (or no coordinates were sent), location_address is matched as a substring of a location's name (send the marina name, e.g. D-Marin Lefkas, not a street address).
  3. New marina. If neither matches and coordinates were sent, a new marina is created at them, named after location_address. Without one it has no name and is shown by its type ("Marina") — a name is never made up from the city.

location_city and location_state are only used to fill in a newly created marina — they never find an existing one. A boat sent with neither coordinates nor a matching name gets no location.

FieldTypeDescription
location_citystringCity; stored on a newly created marina only
location_statestringCountry; stored as the country of a newly created marina only
location_addressstringMarina name — matched as a substring of location names, and used to name a new marina
latitudenumberGPS latitude
longitudenumberGPS longitude

Deduplication Fields

When both fields are provided, the endpoint checks for existing imports to prevent duplicates.

FieldTypeDescription
import_sourcestringPlatform name (e.g. boataround)
import_source_idstringUnique ID on that platform

Owner Identification

FieldTypeDescription
company_idstringAssign to a specific company (overrides auto-detection). With your API key you must be a member of it (403 otherwise)
owner_idstringPlatform key only — ignored with your own API key
owner_emailstringPlatform key only — ignored with your own API key
owner_namestringPlatform key only — ignored with your own API key

Images

Array of image URLs or image objects. The first image is set as primary unless specified otherwise.

{
  "images": [
    "https://example.com/image1.jpg",
    { "url": "https://example.com/image2.jpg", "is_primary": true }
  ],
  "download_images": true
}
FieldTypeDefaultDescription
imagesarrayImage URLs (strings) or objects with url and optional is_primary
download_imagesbooleanfalseDownload images to server storage instead of keeping source URLs

Equipment

A boat's equipment (what the features search filter and the listing's equipment list read) is set in the dashboard after import, not through this endpoint.

Request

curl -X POST https://charter.boats/api/import/boat \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{
    "title": "BALI Catspace - Luxury Catamaran",
    "description": "Beautiful 40ft catamaran with 4 cabins.",
    "boat_type": "catamaran",
    "capacity": 8,
    "price_per_day": 850,
    "length_ft": 40,
    "year": 2023,
    "location_address": "D-Marin Lefkas",
    "location_city": "Lefkada",
    "location_state": "Greece",
    "latitude": 38.8289,
    "longitude": 20.7114,
    "manufacturer_name": "Bali",
    "model": "4.0",
    "rental_type": "bareboat",
    "images": [
      { "url": "https://example.com/exterior.jpg", "is_primary": true },
      "https://example.com/interior.jpg"
    ]
  }'

Response

The boat object is the full boat record (every column on the boats row) plus company, manufacturer, images, and a legacy amenities list (not used by listings — see Equipment). Only the most relevant fields are shown below. Store boat.id — the slug in view_url can change when the listing is renamed.

{
  "success": true,
  "duplicate": false,
  "boat": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "slug": "bali-catspace-luxury-catamaran",
    "title": "BALI Catspace - Luxury Catamaran",
    "boat_type": "catamaran",
    "capacity": 8,
    "length_ft": 40,
    "year": 2023,
    "price_per_day": 850,
    "instant_book": false,
    "rental_type": "bareboat",
    "is_active": true,
    "is_published": false,
    "location_id": 11336,
    "company": {
      "id": "company-uuid",
      "name": "Sail Greece",
      "slug": "sail-greece"
    },
    "manufacturer": {
      "id": "c581bcca-cc2c-441c-9b28-4fb22f97fa99",
      "name": "Bali",
      "slug": "bali"
    },
    "images": [
      {
        "id": "img-uuid",
        "boat_id": "550e8400-e29b-41d4-a716-446655440000",
        "url": "https://example.com/exterior.jpg",
        "storage_path": null,
        "is_primary": true,
        "sort_order": 0
      }
    ],
    "amenities": []
  },
  "images_uploaded": 2,
  "images_downloaded": 0,
  "amenities_added": 0,
  "view_url": "https://charter.boats/boats/bali-catspace-luxury-catamaran"
}

Response Fields

FieldTypeDescription
successbooleanWhether the import succeeded
duplicatebooleantrue if an existing boat was returned (same import_source + import_source_id under the same company)
boatobjectCreated (or existing) boat with company, manufacturer and images
images_uploadedintegerNumber of image records created (absent on a duplicate)
images_downloadedintegerNumber of images downloaded to storage when download_images: true (absent on a duplicate)
amenities_addedintegerLegacy counter, 0 unless a legacy amenities array was sent (absent on a duplicate)
view_urlstringPublic URL of the listing — it serves once the boat is published

Deduplication Behavior

When import_source and import_source_id are both provided:

  • Same company: Returns the existing boat with duplicate: true and nothing is changed — the response is { success, duplicate: true, boat, view_url } only (no image or amenity counters)
  • Different company: Returns 409 Conflict

The check runs before the boat, its images or a new marina are created, so a duplicate adds none of them.

Errors

StatusMessage
400title is required
400boat_type is required
400boat_type must be one of: yacht, catamaran, sailboat, motorboat, rib, other
400capacity is required and must be >= 1
400rental_type must be one of: bareboat, skippered, no_licence_needed
400Owner identification required: provide owner_id or owner_email
401API key required. Include X-API-Key header.
401Invalid API key
403API key owner is not a member of the specified company
409This boat has already been imported by another company
500Failed to create company: …
500Failed to create boat: …

A request with no key at all is stopped by bot protection before it reaches this endpoint (an HTML 403 page).

On this page