// AUTHENTICATION

Two headers, one key.

Every authenticated call to vinspeclookup.com/api carries your key in one of two headers. No key yet? The same routes run on free sample data at /api/sample, no signup.
Bearer
Authorization header, the default.
x-api-key
the same key, as a plain header.
5 scopes
four read scopes and one write scope.
0 keys
needed for the sandbox.

Send the key

Keys look like otp_live_…. Send one as a Bearer token, or in x-api-key if your HTTP client cannot set Authorization. Both are accepted on every route; pick one and keep it out of query strings and logs.

auth.sh
curl 'https://vinspeclookup.com/api/v1/fluids?vin=5J6RW2H82KL000007' \
-H 'Authorization: Bearer otp_live_…'
# or, the same call with the plain header
curl 'https://vinspeclookup.com/api/v1/fluids?vin=5J6RW2H82KL000007' \
-H 'x-api-key: otp_live_…'
200 OK · the response is identical either way

A request with no key, an unknown key, or a revoked key returns 401 invalid_api_key. A key without the scope a route needs returns 403 insufficient_scope. Both bodies have the same two fields as every other error, described on Errors.

No key yet: the sandbox

Every read route is mirrored at https://vinspeclookup.com/api/sample with no authentication, rate-limited to 30 requests per minute per IP address. Responses have the exact shape of the live API for the reference vehicle, so you can build the integration first and paste a key later.

sandbox.sh
curl 'https://vinspeclookup.com/api/sample/v1/fluids?config_key=2019_honda_cr_v_ex_l15be'
200 OK · X-RateLimit-Limit: 30 · no key

The sandbox is read-only. The only write, POST /v0/enrich, answers 403 write_disabled there. Keys are issued by hand while OtoIndex is pre-launch: use the request access form.

Scopes

A key carries scopes. Free keys carry maintenance:read, labor:read and media:read; service_history:read and enrich:write come with paid plans. The scope a route needs is listed on each operation in the reference and repeated here.

ScopeGrantsEndpoints
maintenance:readIdentity, fluids, spec sheet, tires, service intervals, parts and prices/v1/decode, /v1/configs, /v1/vehicle, /v1/fluids, /v1/specs, /v1/tires, /v1/maintenance-schedule, /v1/parts, /v0/maintenance
labor:readLabor time estimates/v0/labor
media:readThe cached exterior render/v0/vehicle-image
service_history:readSanitized per-VIN service history/v0/service-history
enrich:writeSchedule enrichment for a VIN we do not hold yet (Pro and Scale)POST /v0/enrich, GET /v0/enrich

From a browser

The gateway answers preflights and allows Authorization, Content-Type and x-api-key from any origin, so calls work from a web app. Treat that as a convenience for prototypes: a key in client-side code is a key anyone can read. In production, call the API from your server.

Before you go live

  • Keep the key in an environment variable on the server that makes the call. Never in a repository, a mobile bundle, or a query string.
  • Build against the sandbox first. It returns the live shape for the reference vehicle, so parsing, error handling and the 409 flow can be finished before a key exists.
  • Store the config_key once a VIN has resolved, and use it for every later call. It is stable, exact, and skips the decode.
  • Handle 401 and 403 as configuration errors, not retries: a rotated key or a missing scope will not fix itself. Log the error code, not the message.
  • To rotate a key while the dashboard is not yet live, ask through the request-access form; a new key is issued and the old one revoked on a date you choose.