Conventions

Rules that hold across every endpoint, so you only have to learn them once.

Response envelope

A list response carries a data array and a pagination object:

JSON
{
  "data": [],
  "pagination": { "total": 1482, "limit": 20, "offset": 0 }
}

A single resource response carries data alone:

JSON
{
  "data": {}
}

An error carries error, and never a partial data:

JSON
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid value for 'limit'",
    "requestId": "01JD8K2M4Q7X9V",
    "details": {}
  }
}

There is no envelope in which both data and error appear. If the status is 2xx you have data; otherwise you have error.

Request IDs

Every response — success or failure — carries a request ID, in the error.requestId field and in the response headers. It appears in our structured logs for that request, so quoting it in a support email is the difference between us finding your exact request and guessing.

Pagination

All list endpoints use offset pagination with the same two parameters:

ParameterDefaultMaximumNotes
limit20100Rows per page
offset0Rows to skip before the page

pagination.total is the number of rows matching your filters, not the number returned. Page through by advancing offset until you have seen total rows:

curl -s "https://api.theboatdb.com/v1/boats?limit=100&offset=0" -H "Authorization: Bearer $KEY"
curl -s "https://api.theboatdb.com/v1/boats?limit=100&offset=100" -H "Authorization: Bearer $KEY"

Ordering is stable. Every sort has an internal tiebreaker applied after your chosen field, so rows that tie on year or length still have one fixed relative order — without that, consecutive pages could repeat a boat or skip one entirely.

total is computed against the same dataset revision as the rows themselves, in a single query. It will not disagree with the page you were served.

Sorting

Sortable fields are an explicit allow-list per resource, published on each endpoint's page. Two parameters control it:

ParameterValuesDefault
sortSee endpointPer resource
orderasc, descasc

A field that is not on the list is a 400, not a silently ignored parameter.

Unknown parameters are rejected

Every query schema is strict. A parameter we do not recognise — including a misspelling of one we do — is a 400 VALIDATION_ERROR, not a parameter we quietly drop.

This is deliberate. ?manufacturor=albin-marine silently ignored returns the entire catalogue and looks like a successful request; the bug surfaces days later as wrong data in your system. Rejecting it surfaces the typo at the moment you make it.

The same applies to the single-resource endpoints, which accept no query parameters at all: ?fields=name on /v1/boats/albin-vega-27 is a 400.

Invalid ranges

A range whose bounds are inverted is a 400, not an empty result set. ?yearFrom=2010&yearTo=1990 and ?lengthMin=12&lengthMax=8 are both rejected — an empty page would be indistinguishable from a legitimately empty result and would send you looking for a data problem that does not exist.

Identifiers

Resources are addressable by either their slug or their document id:

cURL
curl -s "https://api.theboatdb.com/v1/boats/albin-vega-27" -H "Authorization: Bearer $KEY"
curl -s "https://api.theboatdb.com/v1/boats/boat-3f9c1a"     -H "Authorization: Bearer $KEY"

Slugs are the readable form and are what our own site uses. Ids are stable across editorial renames, so store the id if you are persisting a reference and the slug only for display.

Units

Measurements are published in the unit system the manufacturer used, and each record declares which that is. We do not convert them: a converted spec sheet is a different claim about the boat than the one published.

The single exception is lengthOverallMetres, which is always metres. It exists because filtering and sorting need one comparable number across a mixed-unit catalogue — see Boats for how length filters handle the conversion.

What is not in a response

Responses are hand-built allow-lists, not a dump of our CMS documents. A field reaches you because somebody deliberately published it, which means an internal field added to our content model tomorrow cannot leak into your integration — and equally, that new catalogue fields appear here only once we have decided to support them.

Editorial notes, SEO metadata, view and favourite counters, and internal revision fields are all deliberately absent.