Integrate yacht data (listings, details, seasons, availability) into your website or app.
https://api.centralyachtagent.com/public/api/v1
Every request must include your API key in the X-API-Key header.
Requests without a valid key return 403.
X-API-Key: your_api_key_here
Keep your API key server-side. Never expose it in browser/frontend code — proxy the calls through your own backend (see examples below).
A key may also be restricted to a specific server IP (allowed_ip) or
domain (allowed_domain). If your key is IP-restricted, calls must originate
from that server's egress IP — another reason to call the API from your backend, not the browser.
| Scope | Limit |
|---|---|
| Per API key | 60 requests / minute |
Retry-After header.429 response body
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded: 60 per 1 minute",
"detail": "Too many requests. Please slow down and try again later.",
"retry_after": 60
}
Response headers on 429
Retry-After: 60
X-RateLimit-Limit: 60 per 1 minute
Best practices
/yachts/{user_id} is cached server-side for 60s
and /bookings for 30s, so you don't need to poll them aggressively.429, back off and retry after the number of seconds in Retry-After.All paths are relative to the base URL https://api.centralyachtagent.com/public/api/v1.
Use these to map filter IDs to human labels (areas, amenities, watersports).
| Method | Path | Description |
|---|---|---|
| GET | /operating_areas | All cruising/operating areas with IDs + labels |
| GET | /amenities | All yacht amenities with IDs + labels |
| GET | /watersports | All watersports equipment with IDs + labels |
| Method | Path | Description |
|---|---|---|
| GET | /yachts/{user_id} | List yachts owned by a user (cached 60s) |
| GET | /yachts_get/{yacht_id} | Get one yacht by ID. Optional ?include= to limit fields |
| POST | /yachts_search | Search yachts with filters + pagination |
| GET | /{yacht_id}/yacht/seasons | Seasons + pricing for a yacht |
/yachts_get/{yacht_id} include parameter
By default the full yacht document is returned. Use include (comma-separated)
to return only specific sub-collections and keep responses small:
GET /yachts_get/670f8ec038d3078885154e8d?include=images,seasons,reviews
Available values: images, amenities, watersports, videos, reviews, seasons.
| Method | Path | Description |
|---|---|---|
| GET | /bookings?yacht_id={yacht_id} | Bookings for a yacht for the current year (cached 30s) |
POST /yachts_search — request body{
"page": 1,
"limit": 10,
"searchTerm": "",
"filters": {
"yachtTypes": ["motor", "sail"],
"cabins": [3, 8],
"guests": [6, 16],
"length": [20, 60],
"length_unit": "meter",
"yearBuild": [2018, 2026],
"season_price": [20000, 100000],
"user_currency": "USD",
"location_areas": [25, 20],
"watersports": [3, 13, 17],
"start_date": "2026-07-01",
"end_date": "2026-07-14",
"available_days": 7
}
}
| Field | Type | Default | Notes |
|---|---|---|---|
page | int | 1 | Pagination page |
limit | int | 10 | Items per page |
searchTerm | string | "" | Matches yacht name |
filters.yachtTypes | string[] | — | e.g. ["sail","motor"] |
filters.cabins | int[2] | — | [min, max] |
filters.guests | int[2] | — | [min, max] |
filters.length | float[2] | — | [min, max] |
filters.length_unit | string | "meter" | "meter" or "feet" |
filters.yearBuild | int[2] | — | [min, max] |
filters.season_price | float[2] | — | [min, max] |
filters.user_currency | string | "USD" | USD, Euro, Pounds, CAD |
filters.location_areas | int[] | — | Area IDs from /operating_areas |
filters.watersports | int[] | — | IDs from /watersports |
filters.start_date | string | — | YYYY-MM-DD |
filters.end_date | string | — | YYYY-MM-DD |
filters.available_days | int | — | Min available days in date range |
Response shape (search & list)
{
"yachts": [
{
"id": "670f8ec038d3078885154e8d",
"yachtName": "Ocean Spirit",
"yachtType": "sail",
"length": 24.5,
"units": "meter",
"year_build": 2020,
"cabins": 4,
"pax": 8,
"seasonAreas": ["Greece", "Croatia"],
"image": "https://cya.sfo3.cdn.digitaloceanspaces.com/yachts/ocean_spirit_main.jpg",
"min_price": 35000,
"currency": "Euro"
}
],
"totalYachts": 1
}
Replace YOUR_API_KEY with your key. Server-side calls only.
# List yachts for a user
curl -X GET "https://api.centralyachtagent.com/public/api/v1/yachts/12345" \
-H "X-API-Key: YOUR_API_KEY"
# Get one yacht, only images + seasons
curl -X GET "https://api.centralyachtagent.com/public/api/v1/yachts_get/670f8ec038d3078885154e8d?include=images,seasons" \
-H "X-API-Key: YOUR_API_KEY"
# Search yachts
curl -X POST "https://api.centralyachtagent.com/public/api/v1/yachts_search" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"page": 1,
"limit": 10,
"filters": { "yachtTypes": ["motor"], "guests": [6, 16] }
}'
# Bookings for a yacht
curl -X GET "https://api.centralyachtagent.com/public/api/v1/bookings?yacht_id=670f8ec038d3078885154e8d" \
-H "X-API-Key: YOUR_API_KEY"
Store the key in config/services.php and .env, never hard-coded.
# .env
CYA_API_BASE=https://api.centralyachtagent.com/public/api/v1
CYA_API_KEY=YOUR_API_KEY
// config/services.php
'cya' => [
'base' => env('CYA_API_BASE'),
'key' => env('CYA_API_KEY'),
],
use Illuminate\Support\Facades\Http;
class CyaApi
{
private function client()
{
return Http::baseUrl(config('services.cya.base'))
->withHeaders(['X-API-Key' => config('services.cya.key')])
->acceptJson()
->timeout(15);
}
// GET /yachts/{user_id}
public function yachtsForUser(string $userId): array
{
return $this->client()->get("/yachts/{$userId}")->throw()->json();
}
// GET /yachts_get/{yacht_id}?include=...
public function yacht(string $yachtId, array $include = []): array
{
$query = $include ? ['include' => implode(',', $include)] : [];
return $this->client()
->get("/yachts_get/{$yachtId}", $query)
->throw()->json();
}
// POST /yachts_search
public function search(array $filters = [], int $page = 1, int $limit = 10): array
{
$res = $this->client()->post('/yachts_search', [
'page' => $page,
'limit' => $limit,
'filters' => $filters,
]);
// Handle rate limiting gracefully
if ($res->status() === 429) {
$retryAfter = (int) $res->header('Retry-After', 60);
throw new \RuntimeException("Rate limited. Retry after {$retryAfter}s.");
}
return $res->throw()->json();
}
// GET /bookings?yacht_id=...
public function bookings(string $yachtId): array
{
return $this->client()
->get('/bookings', ['yacht_id' => $yachtId])
->throw()->json();
}
}
Usage in a controller
$api = new CyaApi();
$results = $api->search(
filters: ['yachtTypes' => ['motor'], 'guests' => [6, 16]],
page: 1,
limit: 10
);
$yacht = $api->yacht('670f8ec038d3078885154e8d', ['images', 'seasons']);
<?php
$base = 'https://api.centralyachtagent.com/public/api/v1';
$key = 'YOUR_API_KEY';
// --- GET example ---
$ch = curl_init("$base/yachts/12345");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: $key"],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status === 429) {
// back off and retry later
}
$data = json_decode($body, true);
// --- POST example ---
$payload = json_encode([
'page' => 1,
'limit' => 10,
'filters' => ['yachtTypes' => ['motor'], 'guests' => [6, 16]],
]);
$ch = curl_init("$base/yachts_search");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
"X-API-Key: $key",
'Content-Type: application/json',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
const BASE = "https://api.centralyachtagent.com/public/api/v1";
const API_KEY = process.env.CYA_API_KEY; // keep server-side
async function searchYachts() {
const res = await fetch(`${BASE}/yachts_search`, {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
page: 1,
limit: 10,
filters: { yachtTypes: ["motor"], guests: [6, 16] },
}),
});
if (res.status === 429) {
const retryAfter = Number(res.headers.get("Retry-After") || 60);
throw new Error(`Rate limited. Retry after ${retryAfter}s.`);
}
if (!res.ok) throw new Error(`API error ${res.status}`);
return res.json();
}
async function getYacht(id, include = []) {
const qs = include.length ? `?include=${include.join(",")}` : "";
const res = await fetch(`${BASE}/yachts_get/${id}${qs}`, {
headers: { "X-API-Key": API_KEY },
});
return res.json();
}
import axios from "axios";
const cya = axios.create({
baseURL: "https://api.centralyachtagent.com/public/api/v1",
headers: { "X-API-Key": process.env.CYA_API_KEY },
timeout: 15000,
});
// GET
const { data } = await cya.get("/yachts/12345");
// POST
const search = await cya.post("/yachts_search", {
page: 1,
limit: 10,
filters: { yachtTypes: ["motor"], guests: [6, 16] },
});
| Status | Meaning | What to do |
|---|---|---|
| 400 | Bad request (invalid ID or filter value) | Check the yacht/user ID format and filter types |
| 403 | Missing / invalid API key, or IP/domain not allowed | Verify the X-API-Key header and key restrictions |
| 404 | Resource not found (or hidden no_api yacht) | The yacht/user doesn't exist or isn't public |
| 429 | Rate limit exceeded | Back off and retry after Retry-After seconds |
| 500 | Internal server error | Retry with backoff; contact support if it persists |
X-API-Key on every request.429 with Retry-After backoff./operating_areas, /amenities, /watersports to resolve filter IDs.?include= on /yachts_get to keep payloads small.