API reference, v2

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.

Treat the token like a database password. Don't commit it. Don't paste it into a browser dev console on a shared machine. Revoke immediately if leaked.

Generating a key

  1. Sign in to the Fix admin app at fix.fleetfixer.io with an admin account.
  2. Open the Config tab (gear icon), then Integrations in the sidebar.
  3. Click Create key, give it a name (e.g. Asset register sync), select the scopes the integration needs, and confirm.
  4. Copy the secret token. It is shown once; you cannot recover it later.

Endpoints & scopes

Method & pathScopeResponse
GET /metaValid key; no scope requiredAPI version, legacy version, hullTypes catalogue, and the key's scopes.
GET /fleetread:fleetPaginated boats. Accepts page_size and cursor.
GET /fleet/{boatId}read:fleetOne 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_id is 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=true to include all child units. Empty groups return an empty list. Unknown group IDs return 404 not_found; blank IDs or invalid boolean options return 400 invalid_request.
  • Group filtering applies to the fleet list, not individual /fleet/{boatId} reads. Each boat's fleetGroupId identifies its unit. Archived boats remain included, as in an unfiltered export; use archived to distinguish them.
  • Follow page.next_cursor until null, keeping group_id and include_descendants unchanged 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:

FieldMeaning
hullTypeIdHull family, resolving legacy class aliases (for example, ilca-6 becomes ilca). Uses saved hullTypeId when present, otherwise boatTypeId.
hullTypeLabelHull family name, falling back to the saved boat label or hull id.
rigOptionsArray 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.
legacyRigIdRig 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.

FieldValuesMeaning
rentalUsenot-offered, available, on-requestFleet tab rental status. available means offered for rental.
charterUsenot-offered, available, on-requestFleet tab charter status, independent of rental status.
charterNotesstring or nullCharter 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.

ValueMeaningSailable?
available Fit to sail. Default. Yes
in-repair On the bench. Actively being worked on; expected to return. No
out-of-actionBroken 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

Available with explicit opt-in. In Settings → Integrations, edit a webhook and choose Single fleet payload (v2.2). Subscribe to 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 containing fleet.snapshot (or all permitted events). Existing subscriptions default to fleetPushFormat: "individual"; no receiver is switched automatically.
  • Configure the format through POST /api/v1/webhook with fleetPushFormat, url, and events. GET /api/v1/webhook returns the saved format. Omitting the format preserves the existing value; new subscriptions default to individual. 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, and fleet.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
      }
    ]
  }
}
Fieldv2.2 contract
idUnique snapshot delivery ID, stable across retries. A new manual push gets a new ID.
api_versionLiteral string "2.2"; separate from the existing REST metadata value "v2".
club_idThe subscription's club. Never combines records from different clubs.
data.snapshot_atUTC RFC 3339 time of the consistent fleet read. This is the snapshot's data cutoff, not its delivery time.
data.completeAlways 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.countNonnegative integer equal to data.objects.length.
data.objectsArray 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

  1. Verify the signature and timestamp against the raw request bytes, then parse JSON and check the expected club_id.
  2. Route by type. For fleet.snapshot, require version 2.2, complete: true, matching count, unique boat IDs, and valid v2 boat objects before changing stored data.
  3. 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.
  4. 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.
  5. Handle signed webhook.ping events 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..."
  }
}
HTTPcodeWhen
401unauthorizedMissing, malformed, unrecognised, or revoked API key.
403forbidden Token lacks the scope required for this route.
404not_found Resource id does not exist in this club, or the v2 route or method is unsupported.
410feature_removedRetired availability or rental route.
429rate_limitedToo many requests. Back off and retry after the value in Retry-After.
5xxserver_errorTransient. 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:

HeaderMeaning
X-RateLimit-LimitRequests allowed in the current window.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetUnix 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

  1. Keep your existing API key with read:fleet.
  2. Switch fleet reads to /api/v2/fleet and /api/v2/fleet/{boatId}.
  3. Use hullTypeId for hull families and rigOptions for compatible rigs. Existing boat identifiers remain in the response.
  4. Read rentalUse and charterUse independently from maintenance status. Older records default to not-offered, matching the Fleet tab.
  5. Keep club, jobs, maintenance, inventory, orders, sailors, staff, and webhook management calls on v1.

Open the v1 reference →