Fleet, rental & charter data
Read the boats in your fleet, their hull and rig identities, and the rental and charter settings managed by your club.
Overview
Base URL: https://fix.fleetfixer.io/api/v2
v2 provides read-only fleet data and a hull catalogue. Use the same per-club API key as v1. Your integration manages reservations and assignments; FleetFixer supplies boat records and maintenance state.
Authentication
Every request needs a per-club Bearer token issued from the FleetFixer admin app's Integrations tab.
Authorization: Bearer ff_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Tokens are stored hashed on the server, identify the club they belong to, and can be revoked from the same UI. The full secret is shown to the issuing admin once, at creation time, and never again.
Generating a key
- Sign in to the Fix admin app at
fix.fleetfixer.iowith an admin account. - Open the Config tab (gear icon), then Integrations in the sidebar.
- Click Create key, give it a name (e.g. Asset register sync), select the scopes the integration needs, and confirm.
- Copy the secret token. It is shown once; you cannot recover it later.
Endpoints & scopes
| Method & path | Scope | Response |
|---|---|---|
GET /meta | Valid key; no scope required | API version, legacy version, hullTypes catalogue, and the key's scopes. |
GET /fleet | read:fleet | Paginated boats. Accepts page_size and cursor. |
GET /fleet/{boatId} | read:fleet | One boat by Firestore document id; 404 if absent in this club. |
The v2 root also returns metadata. /availability and /rentals are retired and return 410 feature_removed. Other v1 resource routes and webhook management are available under v1.
Read one hull on demand
GET /api/v2/webhook/fleet?hullId=00123 returns one boat with the same v2 fields and envelope as /api/v2/fleet/{boatId}. Use your existing API key with read:fleet. An optional boatTypeId disambiguates hull numbers shared across boat types. See the lookup parameters, curl example and errors; replace /api/v1 with /api/v2 for v2 fields.
The lookup is available on both existing REST versions, including v1.1 and v2.1 clients. v2.2 snapshot subscribers use the v2 URL; v2.2 is a webhook payload version, not a separate REST URL. Existing fleet reads, webhook configuration and event payloads are unchanged.
Optional group exports · v2.1 / v1.1
This is a backward-compatible minor update. Keep the existing /api/v2 or /api/v1 base URL and API key. The metadata endpoint reports revision as 2.1 or 1.1. No URL migration is needed.
GET /fleet-groups requires read:fleet and returns the club's complete organisational-unit directory in data. Each entry contains id, name, and parentId (empty for top-level units). Use stable IDs rather than names; renaming a unit does not break your integration. This small configuration list is not paginated.
GET /api/v2/fleet-groups
GET /api/v2/fleet?group_id=GROUP_ID&page_size=200
GET /api/v2/fleet?group_id=GROUP_ID&include_descendants=true&page_size=200
group_idis optional. Omit it to read the full fleet, including unassigned boats, exactly as before.- A supplied ID returns only boats assigned directly to that group by default. Set
include_descendants=trueto include all child units. Empty groups return an empty list. Unknown group IDs return404 not_found; blank IDs or invalid boolean options return400 invalid_request. - Group filtering applies to the fleet list, not individual
/fleet/{boatId}reads. Each boat'sfleetGroupIdidentifies its unit. Archived boats remain included, as in an unfiltered export; usearchivedto distinguish them. - Follow
page.next_cursoruntil null, keepinggroup_idandinclude_descendantsunchanged on every request. A cursor for a deleted or moved boat outside the selected scope returns 400; restart the export. These are live reads, not a frozen snapshot. - The API key remains scoped to its club. This filter selects export content; it does not restrict the key's permissions.
const url = new URL('https://fix.fleetfixer.io/api/v2/fleet');
url.searchParams.set('group_id', selectedGroupId); // omit for the entire fleet
url.searchParams.set('include_descendants', 'true');
url.searchParams.set('page_size', '200');
const boats = [];
let cursor;
do {
if (cursor) url.searchParams.set('cursor', cursor);
const response = await fetch(url, {headers: {Authorization: `Bearer ${apiKey}`}});
if (!response.ok) throw new Error(`Fleet export failed: ${response.status}`);
const page = await response.json();
boats.push(...page.data);
cursor = page.page.next_cursor;
} while (cursor);
Fleet fields
v2 includes the base fleet document fields, including id, boatTypeId, boatTypeLabel, hullId, friendlyNumber, assetCode, status, timestamps, and any archive fields. It adds these hull and rig projections:
| Field | Meaning |
|---|---|
hullTypeId | Hull family, resolving legacy class aliases (for example, ilca-6 becomes ilca). Uses saved hullTypeId when present, otherwise boatTypeId. |
hullTypeLabel | Hull family name, falling back to the saved boat label or hull id. |
rigOptions | Array of catalogue rigs with id, label, and requiredSails. An empty array means no catalogue rigs were found. These are compatible rig options, not inventory availability. |
legacyRigId | Rig resolved from the legacy boatTypeId alias, or null. |
Rental and charter fields below are also always included. Keep the physical boat's id as your integration key; several boats can share one hull type.
v2 fleet data
GET /api/v2/fleet and GET /api/v2/fleet/{boatId} include hull identity, rig catalogue information, and the rental and charter fields from the Fleet tab. Both require read:fleet.
| Field | Values | Meaning |
|---|---|---|
rentalUse | not-offered, available, on-request | Fleet tab rental status. available means offered for rental. |
charterUse | not-offered, available, on-request | Fleet tab charter status, independent of rental status. |
charterNotes | string or null | Charter conditions or contact instructions entered by the club. |
These fields are always included in v2. Missing or unrecognised rental and charter statuses default to not-offered, matching the Fleet tab; empty notes return null. Legacy rentalEnabled flags do not determine these values.
{
"rentalUse": "available",
"charterUse": "on-request",
"charterNotes": "Contact the sailing office"
}
These are the club's offering preferences. Consumers must also check maintenance status and archived, and manage booking and equipment availability in their own system. The fields do not calculate the Fleet tab's sail-set readiness.
Boat rental availability and reservation endpoints have been removed and return HTTP 410. Fleet and sail inventory records are preserved.
Status enum
status is the declared operational state of an active (non-archived) boat.
status is the boat's maintenance state. Usage and bookings are tracked elsewhere, so there is no "in use" value; from this API's point of view a sailable boat is just available.
| Value | Meaning | Sailable? |
|---|---|---|
available | Fit to sail. Default. | Yes |
in-repair | On the bench. Actively being worked on; expected to return. | No |
out-of-action | Broken or unsafe. Not in active repair; awaiting a decision. | No |
archived: true is a separate, terminal state and supersedes status. Always check archived before status. status defaults to "available" when missing, so older docs that pre-date the field still behave correctly. Values written by earlier builds (maintenance, out-of-service, in-use) are normalised by the API to the current vocabulary.
Response envelope
Single-resource responses:
{
"data": { /* the resource */ },
"meta": {
"club_id": "dosc",
"request_id": "req_01HX...",
"generated_at": "2026-05-05T12:00:00.000Z"
}
}
List responses also include a page block:
{
"data": [ /* items */ ],
"meta": { "club_id": "dosc", "request_id": "req_01HX...", "generated_at": "..." },
"page": { "size": 50, "next_cursor": "eyJpZCI6IjVvTjcuLi4ifQ" }
}
The v2 metadata endpoint additionally includes meta.api_version: "v2". Fleet responses use the envelope shown above.
The X-Request-Id header is echoed on every response. Log it; if you need to ask FleetFixer about a specific request, that's the handle.
Pagination
Fleet lists are ordered by document id. The default page_size is 50, capped at 200. Pass page.next_cursor back as cursor until it is null. Preserve the cursor as an opaque string. The fleet endpoint does not support a since filter.
v2.2: single-payload fleet push
fleet.snapshot, or leave the event selection empty to allow every permitted event. Existing subscriptions continue to send individual boat events until changed.The v2.2 extension defines an opt-in fleet.snapshot webhook containing every boat belonging to the subscription's club in one JSON request. It is a webhook payload version, not a new REST base URL: fleet reads remain on /api/v2 with their existing pagination.
Opt-in and compatibility
- The subscription must be active, its API key must not be revoked, and it must grant
read:fleet. Only that key's club is included. - The subscription settings are
fleetPushFormat: "snapshot"and an event allow-list containingfleet.snapshot(or all permitted events). Existing subscriptions default tofleetPushFormat: "individual"; no receiver is switched automatically. - Configure the format through
POST /api/v1/webhookwithfleetPushFormat,url, andevents.GET /api/v1/webhookreturns the saved format. Omitting the format preserves the existing value; new subscriptions default toindividual. Removing the webhook clears its format. - In snapshot mode, one click on Push fleet data produces one snapshot event. Normal record changes continue to use
fleet.created,fleet.updated, andfleet.deleted. Receivers should support both shapes.
Request body
This valid example represents a club with one boat. A club with 136 boats receives all 136 objects in data.objects, not six pages or 136 separate webhook requests.
{
"id": "fleet_snapshot_0123456789abcdef",
"type": "fleet.snapshot",
"api_version": "2.2",
"created_at": "2026-10-02T08:30:00.000Z",
"club_id": "example-club",
"data": {
"resource": "fleet",
"change": "snapshot",
"snapshot_at": "2026-10-02T08:29:59.000Z",
"complete": true,
"count": 1,
"objects": [
{
"id": "boat-001",
"boatTypeId": "club-dinghy",
"boatTypeLabel": "Club dinghy",
"hullTypeId": "club-dinghy",
"hullTypeLabel": "Club dinghy",
"rigOptions": [],
"legacyRigId": null,
"status": "available",
"archived": false,
"rentalUse": "not-offered",
"charterUse": "not-offered",
"charterNotes": null
}
]
}
}
| Field | v2.2 contract |
|---|---|
id | Unique snapshot delivery ID, stable across retries. A new manual push gets a new ID. |
api_version | Literal string "2.2"; separate from the existing REST metadata value "v2". |
club_id | The subscription's club. Never combines records from different clubs. |
data.snapshot_at | UTC RFC 3339 time of the consistent fleet read. This is the snapshot's data cutoff, not its delivery time. |
data.complete | Always true for this event: all stored boat records at the cutoff are included, including archived boats and every maintenance status. A partial export must not be emitted as a complete snapshot. |
data.count | Nonnegative integer equal to data.objects.length. |
data.objects | Array of full v2 fleet projections, using the documented fleet fields and the same redaction as v2 reads. Each physical boat appears once, keyed by id, ordered by ID. Empty fleets use [] and count 0. |
The snapshot has no page, cursor, or next_cursor. Unlike individual events, it uses data.objects, not data.object or a single data.id. The sender must finish assembling and validating the entire snapshot before delivery. If it cannot fit the complete payload within the sender’s 10 MiB raw JSON limit (the receiver may impose a smaller limit), it must report a failed push; it must never silently truncate the fleet or label a partial array complete.
Signing and delivery
Use the existing webhook signature protocol. Set X-FleetFixer-Event: fleet.snapshot and X-FleetFixer-Delivery to the body's id. Sign the exact raw JSON bytes with HMAC-SHA256 over <timestamp>.<raw body>, using the UTF-8 hexadecimal sha256(API key) as the secret. The signature header remains t=<timestamp>,v1=<hex>; v1 identifies the signing scheme, not the payload version.
Delivery is at least once. Retries resend the same snapshot contents and delivery ID; receivers must deduplicate that ID. Use the existing policy of up to three attempts on network errors or HTTP 5xx; HTTP 4xx responses, including 413, are permanent rejections. An HTTP 2xx acknowledges the entire snapshot only after it has been applied or durably queued. A receiver must not acknowledge success after accepting only some boats.
Receiver checklist
- Verify the signature and timestamp against the raw request bytes, then parse JSON and check the expected
club_id. - Route by
type. Forfleet.snapshot, require version2.2,complete: true, matching count, unique boat IDs, and valid v2 boat objects before changing stored data. - Stage the whole snapshot and upsert every boat by
(club_id, id). Keep archived records so the website can hide or label them using its own display rules. - Deduplicate retries. Do not let an older snapshot overwrite a newer snapshot or changes received after its cutoff. Coordinate snapshot application with individual events; do not infer deletion solely from absence in an old or failed snapshot.
- Handle signed
webhook.pingevents separately and return HTTP 2xx without expecting boat fields. This lets Send test check the receiver.
Enable single-payload delivery
First implement and test receiver support, including signature verification. Then save the format in the webhook editor, or update the subscription with your existing API key:
curl -X POST 'https://fix.fleetfixer.io/api/v1/webhook' \
-H "Authorization: Bearer $FLEETFIXER_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"url":"https://your-site.example/hooks/fleetfixer","fleetPushFormat":"snapshot","events":["fleet.created","fleet.updated","fleet.deleted","fleet.snapshot"]}'
Click Push fleet data to send the whole fleet in one event. The receiver must acknowledge the entire snapshot; failures appear in the button’s result and webhook delivery status. Ordinary change events remain individual. To switch back, save fleetPushFormat: "individual" with fleet.updated allowed.
Example integration
Fetch a page of fleet data with a key that has read:fleet:
curl 'https://fix.fleetfixer.io/api/v2/fleet?page_size=100' \
-H "Authorization: Bearer $FLEETFIXER_API_KEY"
This JavaScript example runs on your server. It reads all pages and selects active, serviceable boats offered for rental:
const boats = [];
let cursor = null;
do {
const url = new URL('https://fix.fleetfixer.io/api/v2/fleet');
url.searchParams.set('page_size', '200');
if (cursor) url.searchParams.set('cursor', cursor);
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.FLEETFIXER_API_KEY}` }
});
if (!response.ok) throw new Error(`Fleet API: ${response.status}`);
const page = await response.json();
boats.push(...page.data);
cursor = page.page.next_cursor;
} while (cursor);
const offeredForRental = boats.filter(boat =>
!boat.archived && boat.status === 'available' &&
boat.rentalUse === 'available'
);
const charterOnRequest = boats.filter(boat =>
!boat.archived && boat.status === 'available' &&
boat.charterUse === 'on-request'
);
Check compatible equipment and existing assignments in your booking system before confirming a booking. on-request requires a request rather than automatic availability.
Errors
{
"error": {
"code": "unauthorized",
"message": "API key has been revoked.",
"request_id": "req_01HX..."
}
}
| HTTP | code | When |
|---|---|---|
| 401 | unauthorized | Missing, malformed, unrecognised, or revoked API key. |
| 403 | forbidden | Token lacks the scope required for this route. |
| 404 | not_found | Resource id does not exist in this club, or the v2 route or method is unsupported. |
| 410 | feature_removed | Retired availability or rental route. |
| 429 | rate_limited | Too many requests. Back off and retry after the value in Retry-After. |
| 5xx | server_error | Transient. Retry with exponential backoff. |
Rate limits
Each API key is limited to 120 requests per minute by default, measured in a fixed 60 second window. Unauthenticated and failed-authentication traffic is limited per IP. Need a higher ceiling? Ask us and we can raise the limit on a specific key.
Every successful response carries the current limit state in its headers, so a well-behaved client never has to guess:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed in the current window. |
X-RateLimit-Remaining | Requests left in the current window. |
X-RateLimit-Reset | Unix epoch second at which the window resets. |
When you exceed the limit you get 429 rate_limited with a Retry-After header (seconds until the window resets). Honour it: sleep for that long, then retry. For bulk pulls, paginate with page_size=200 and follow next_cursor rather than firing many small requests in parallel.
Using v1 and v2 together
- Keep your existing API key with
read:fleet. - Switch fleet reads to
/api/v2/fleetand/api/v2/fleet/{boatId}. - Use hullTypeId for hull families and rigOptions for compatible rigs. Existing boat identifiers remain in the response.
- Read rentalUse and charterUse independently from maintenance status. Older records default to not-offered, matching the Fleet tab.
- Keep club, jobs, maintenance, inventory, orders, sailors, staff, and webhook management calls on v1.