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:
{
"data": [],
"pagination": { "total": 1482, "limit": 20, "offset": 0 }
}A single resource response carries data alone:
{
"data": {}
}An error carries error, and never a partial data:
{
"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:
| Parameter | Default | Maximum | Notes |
|---|---|---|---|
limit | 20 | 100 | Rows per page |
offset | 0 | — | Rows 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:
| Parameter | Values | Default |
|---|---|---|
sort | See endpoint | Per resource |
order | asc, desc | asc |
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 -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.