// ERRORS

One error shape, every time.

Any non-2xx response is JSON with an error code and a human message. Match on the code, show the message, and read Retry-After on a 429.
{ error, message }
the body of every error.
14
codes documented below.
409
the one that hands you the answer.
Retry-After
seconds, on every 429.

The shape

The spec's Error schema has two required strings. The gateway and the sandbox add object: "error" and, where it helps, a docs link or an available list. Match on error, never on the message text.

401.json
curl 'https://vinspeclookup.com/api/v1/fluids?vin=5J6RW2H82KL000007'
# no Authorization header
{ "error": "invalid_api_key", "message": "Unknown API key." }
401 Unauthorized

Every code, with the fix

StatuserrorMessageFromWhat to do
400missing_paramPass ?vin=… OR ?year=&make=&model=[&trim=] OR ?config_key=…APISend exactly one identifier mode.
400invalid_vinA VIN is exactly 17 characters.APIStrip spaces and check the length before you call.
401invalid_api_keyUnknown API key.APISend Authorization: Bearer or x-api-key with a live key. The same code covers a missing or revoked key.
403insufficient_scopeThis key lacks the 'maintenance:read' scope.APIRequest the scope on the key. Free keys carry maintenance:read, labor:read and media:read; service history and enrich need a paid key.
403write_disabledEnrichment writes are disabled in the keyless sandbox.SandboxUse your own key with enrich:write (Pro and Scale).
404not_foundNo enriched vehicle config matches that identifier.APIGrow the dataset with POST /v0/enrich, then poll GET /v0/enrich.
404no_imageVehicle resolved but no image is cached for it yet.APITreat the render as optional. Retrying won't help until a render is cached for this config.
409multiple_matchesMore than one config matched a YMMT lookup.APIRetry with a config_key from matches, or add &trim=.
422unsupported_vehicleThe VIN decoded but no engine code could be resolved.APINothing to retry. Surface it as unsupported.
429rate_limitedLimit is 60 requests/minute for this key.APIBack off for Retry-After seconds. Limits are per key, per minute.
429rate_limitedSandbox limit is 30 requests/minute per IP.SandboxWait for Retry-After, or use your own key.
429quota_exceededDaily enrichment quota (5 scheduled runs/day/key) reached.APIEnrichment credits reset daily. Cache hits and polls never spend them.
502upstream_unreachableThe API backend did not respond. Try again shortly.GatewayRetry with backoff. The gateway forwards nothing it did not receive.
503gateway_unconfiguredThe API gateway is not connected to a backend in this environment.GatewayOnly seen on a misconfigured deployment. Nothing to do client-side.

“From” says which layer writes the body: the API itself, the branded gateway in front of it, or the keyless sandbox at https://vinspeclookup.com/api/sample.

409: candidates, not a dead end

A year/make/model lookup can fit more than one config (two drivetrains, two engines). Instead of guessing, the API answers 409 with every candidate. Pick a config_key and repeat the call, or add &trim=. GET /v1/configs returns the same list as a 200 if you would rather ask first.

409 · sample
409multiple_matches

More than one config matched a YMMT lookup. Retry with a config_key from matches, or add &trim=.

Candidate configs, retry with one
2019_honda_cr_v_ex_l15be2019 Honda CR-V EX · 1.5L L15BE · 4WD
2019_honda_cr_v_ex_l15be_2wd2019 Honda CR-V EX · 1.5L L15BE · 2WD
409.json
curl 'https://vinspeclookup.com/api/v1/fluids?year=2019&make=Honda&model=CR-V' \
-H 'Authorization: Bearer otp_live_…'
{ "object": "error", "error": "multiple_matches", "message": "More than one config matched a YMMT lookup. Retry with a config_key from matches, or add &trim=.", "matches": [ { "config_key": "2019_honda_cr_v_ex_l15be", "label": "2019 Honda CR-V EX · 1.5L L15BE · 4WD" }, { "config_key": "2019_honda_cr_v_ex_l15be_2wd", "label": "2019 Honda CR-V EX · 1.5L L15BE · 2WD" } ] }
409 Conflict · retry with one config_key

What to retry

  • 429 and 502: yes, after Retry-After seconds or with exponential backoff.
  • 404 not_found: not as-is. Grow the dataset with POST /v0/enrich (Pro and Scale), poll GET /v0/enrich, then read again.
  • 400, 401, 403, 422: no. Fix the request, the key, or surface the vehicle as unsupported.

Rate limits and the headers to read are on Rate limits.