Skip to content

Bodek API documentation

Every Bodek API follows the same rules: one GET per collection, JSON in and out, and the same four query conventions for filtering, searching, sorting and paging. Learn them once and every dataset on this site works the same way.

Bodek API is part of your Bodek subscription. Every endpoint needs a key, and a key works for as long as the account that owns it has access to the API.

Quickstart

Create a key on your keys page, then call a collection. Every endpoint takes the same bearer header.

export BODEK_API_KEY=bk_live_…

curl "https://api.bodek.us/v1/vehicles/makes?limit=3" \
  -H "Authorization: Bearer $BODEK_API_KEY"

The catalogue is the one endpoint that needs no key: it lists every published API, so you can see what is available before you have one.

curl "https://api.bodek.us/v1"

Authentication

Every endpoint except /v1 and /v1/health needs a key, sent as a bearer token. Sign in to create one.

curl "https://api.bodek.us/v1/vehicles/makes" \
  -H "Authorization: Bearer $BODEK_API_KEY"

X-API-Key: <your_key> works too. A key in the query string does not: query strings end up in server logs, browser history and Referer headers, so ?key= is rejected with a 400 rather than quietly accepted.

A key carries the list of APIs it may read, an optional expiry, its own rate limits, and an optional restriction to specific domains or IP addresses. Treat it like a password: it is shown once, stored only as a hash, and can be revoked at any time.

A key is only as live as the account behind it. If that account's access to Bodek API ends — the subscription lapses, the plan changes, or an owner revokes the grant — its keys stop working at once and answer 402 subscription_required, without anyone having to go and revoke them one by one.

Response shape

A collection returns a data array and a pagination object.

{
  "data": [
    { "id": 1, "name": "Acura", "slug": "acura" }
  ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 163,
    "has_more": true,
    "next": "https://api.bodek.us/v1/vehicles/makes?limit=50&offset=50"
  }
}

A single record returns just data, as an object.

{
  "data": { "id": 1, "name": "Acura", "slug": "acura" }
}

Add ?pretty=1 to any request to get indented JSON while you explore.

Filtering

Any field marked filterable on an API's reference page can be used as a query parameter. Bare equality needs no operator; everything else uses a __ suffix.

OperatorExampleMeaning
(none)?make=toyotaexactly equal
__ne?make__ne=toyotanot equal
__gt __gte?year__gte=2015greater than (or equal)
__lt __lte?year__lte=1999less than (or equal)
__in?body_type__in=SUBN,PICKany of a comma-separated list
__nin?body_type__nin=MCY,ATVnone of a list
__contains?name__contains=civsubstring (text fields)
__starts __ends?name__starts=camprefix / suffix (text fields)
__null?years__null=trueis (or is not) empty

Filters combine with AND. A parameter that names an unknown or non-filterable field is an error rather than being ignored, so a typo never silently returns the whole table. At most 12 filters and 50 values per list.

q matches a substring across every field marked searchable on that collection. Use it for type-ahead and lookup boxes; use filters when you know the exact value.

curl "https://api.bodek.us/v1/vehicles/makes?q=accord&limit=5"

Sorting

sort takes up to 3 comma-separated field names. Prefix a name with - for descending. Results are always given a stable tiebreaker, so the same record never appears on two pages.

curl "https://api.bodek.us/v1/vehicles/makes?sort=-year_max,name"

Choosing fields

fields trims the response to the columns you actually use.

curl "https://api.bodek.us/v1/vehicles/makes?fields=id,name&limit=3"

Pagination

limit (max 200, default 50) with either offset or page. Follow pagination.next until it is null rather than building the URL yourself.

Paging stops at offset 50,000. Past that, narrow the query with a filter — deep offsets get slower for everyone and there is almost always a better filter available.

Caching

Reference data changes rarely, so responses carry Cache-Control and an ETag. Send the ETag back as If-None-Match and an unchanged response comes back as an empty 304, which does not count against your rate limit's payload.

curl "https://api.bodek.us/v1/vehicles/makes" \
  -H 'If-None-Match: "a1b2c3…"'

Rate limits

Every response carries the current ceilings:

HeaderMeaning
X-RateLimit-Limit-Minuterequests allowed per minute
X-RateLimit-Limit-Dayrequests allowed per day
Retry-Afterseconds to wait, sent with a 429

Limits are set per key when you create it, with a ceiling of 600 requests a minute and 200,000 a day. Give each application its own key so one noisy job cannot use up another's budget.

Errors

Every failure uses the same envelope. code is stable and safe to branch on; message is for humans.

{
  "error": {
    "code": "unknown_parameter",
    "message": "Unknown query parameter 'colour'.",
    "details": { "filterable": ["make", "body_type", "year_min"] },
    "request_id": "3f9c1a2b7d4e5f60"
  }
}
StatusWhen
400A parameter is unknown, the wrong type, or out of range.
401A key is required, missing, malformed, revoked or expired.
402The account that owns the key no longer has access to Bodek API.
403The key is valid but not allowed to read this API, origin or IP.
404No such API, collection or record.
405Anything other than GET, HEAD or OPTIONS.
429Rate limited. Wait for Retry-After seconds.
503The dataset's source is briefly unavailable. Retry.

A 500 carries a request_id, also sent as the X-Request-Id header. Quote it to support and we can find the exact request in our logs.

OpenAPI

Each API publishes an OpenAPI 3.1 description, generated from the same definition that serves the data.

curl "https://api.bodek.us/v1/vehicles/openapi.json"

Point a client generator at it, or load it into Postman or Insomnia.