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.
curl 'https://vinspeclookup.com/api/v1/fluids?vin=5J6RW2H82KL000007'# no Authorization header{
"error": "invalid_api_key",
"message": "Unknown API key."
}Every code, with the fix
| Status | error | Message | From | What to do |
|---|---|---|---|---|
| 400 | missing_param | Pass ?vin=… OR ?year=&make=&model=[&trim=] OR ?config_key=… | API | Send exactly one identifier mode. |
| 400 | invalid_vin | A VIN is exactly 17 characters. | API | Strip spaces and check the length before you call. |
| 401 | invalid_api_key | Unknown API key. | API | Send Authorization: Bearer or x-api-key with a live key. The same code covers a missing or revoked key. |
| 403 | insufficient_scope | This key lacks the 'maintenance:read' scope. | API | Request the scope on the key. Free keys carry maintenance:read, labor:read and media:read; service history and enrich need a paid key. |
| 403 | write_disabled | Enrichment writes are disabled in the keyless sandbox. | Sandbox | Use your own key with enrich:write (Pro and Scale). |
| 404 | not_found | No enriched vehicle config matches that identifier. | API | Grow the dataset with POST /v0/enrich, then poll GET /v0/enrich. |
| 404 | no_image | Vehicle resolved but no image is cached for it yet. | API | Treat the render as optional. Retrying won't help until a render is cached for this config. |
| 409 | multiple_matches | More than one config matched a YMMT lookup. | API | Retry with a config_key from matches, or add &trim=. |
| 422 | unsupported_vehicle | The VIN decoded but no engine code could be resolved. | API | Nothing to retry. Surface it as unsupported. |
| 429 | rate_limited | Limit is 60 requests/minute for this key. | API | Back off for Retry-After seconds. Limits are per key, per minute. |
| 429 | rate_limited | Sandbox limit is 30 requests/minute per IP. | Sandbox | Wait for Retry-After, or use your own key. |
| 429 | quota_exceeded | Daily enrichment quota (5 scheduled runs/day/key) reached. | API | Enrichment credits reset daily. Cache hits and polls never spend them. |
| 502 | upstream_unreachable | The API backend did not respond. Try again shortly. | Gateway | Retry with backoff. The gateway forwards nothing it did not receive. |
| 503 | gateway_unconfigured | The API gateway is not connected to a backend in this environment. | Gateway | Only 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.
More than one config matched a YMMT lookup. Retry with a config_key from matches, or add &trim=.
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"
}
]
}What to retry
- 429 and 502: yes, after
Retry-Afterseconds or with exponential backoff. - 404 not_found: not as-is. Grow the dataset with
POST /v0/enrich(Pro and Scale), pollGET /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.