Error Codes
All error responses share the envelope { "error": { "code", "message", "details"? } } with a 4xx or 5xx HTTP status. Codes are stable strings; message text is human-readable and may change. Log and branch on code, never on message.
Authentication and authorization
| Code | HTTP | Source | Meaning | Fix |
|---|---|---|---|---|
| UNAUTHORIZED | 401 | all endpoints | Missing, malformed, or revoked Bearer token. | Regenerate the key in Dashboard → Settings → API Key and redeploy. |
| FORBIDDEN | 403 | widget/* | Key is valid but the resource belongs to another seller. | Verify you are using the correct dealer's API key. |
| TIER_RESTRICTED | 403 | widget/chat, market-data | Endpoint requires Pro or Enterprise tier. | Upgrade subscription at Dashboard → Billing. |
Validation (request envelope)
| Code | HTTP | Source | Meaning | Fix |
|---|---|---|---|---|
| VALIDATION_ERROR | 400 | POST /v1/inventory | Request body failed Zod parse (root envelope). | Confirm JSON is well-formed and records is an array. |
| BATCH_TOO_LARGE | 400 | POST /v1/inventory | records array exceeds 500 items. | Split batch client-side into ≤500-item chunks. |
| INVALID_PAYLOAD | 400 | POST /v1/inventory, PATCH /v1/inventory, POST/PATCH /v1/dealers | Required top-level field missing (vin / slug / slug + name). | Include the required field per the endpoint reference. |
| INVALID_VIN | 400 | GET /v1/inventory/:vin | VIN parameter is not exactly 17 characters. | Send the full 17-char VIN, uppercase, with no hyphens or spaces. |
| INVALID_STATUS | 400 | PATCH /v1/inventory | status is not sold or expired. | Use one of: sold, expired. |
| INVALID_BODY | 400 | widget/* | Body is not valid JSON or lacks required widget params. | Include message, dealerSlug, visitorId per the widget reference. |
| INVALID_PARAMS | 400 | market-data, widget/lead | Query or path params failed schema check. | Refer to the endpoint parameter reference. |
Pipeline validate-stage rejects
Returned as PIPELINE_FAILED with details.stages.validate set to one of the codes below.
| Code | HTTP | Source | Meaning | Fix |
|---|---|---|---|---|
| PIPELINE_FAILED | 422 | POST /v1/inventory (single record) | Pipeline stage threw. details.stages names the failing stage. | Inspect details.stages; see the per-stage codes below. |
| VIN_MISSING | 422 | validate stage | No vin, VIN, or vehicle_identification_number field found. | Supply vin on every record. |
| VIN_LENGTH | 422 | validate stage | VIN is not exactly 17 characters after whitespace and dashes are stripped. | Only modern 17-char VINs accepted. Pre-1981 serials are out of scope. |
| YEAR_MISSING | 422 | validate stage | No year, model_year, or modelYear field found. | Supply a 4-digit model year. |
| YEAR_OUT_OF_RANGE | 422 | validate stage | Year is outside 1990 to current year + 2. | Fix the year on the record or exclude it from the feed. |
| MAKE_MISSING | 422 | validate stage | No make, Make, manufacturer, or brand field found. | Supply make; the pipeline resolves aliases. |
| GVWR_BELOW_FLOOR | 422 | validate stage | GVWR is present and below the 10,001 lb Class 3 minimum. | Omit the unit (consumer vehicle) or correct the GVWR. |
| BODY_TYPE_BLOCKED | 422 | validate stage | body_type is sedan, suv, coupe, crossover, hatchback, convertible, minivan, or wagon. | These are consumer bodies; remove from feed. |
| CLASS_BELOW_FLOOR | 422 | validate stage | Explicit class_1 or class_2 value. | Class 1–2 are out of scope. Remove from feed. |
| DUAL_USE_QUARANTINED | 422 | validate stage | Dual-use OEM (Ford, Chevy, GMC, RAM, Dodge) without explicit truck class or GVWR ≥ 10,001. | Add class or gvwr to confirm it is commercial-grade. |
| PRICE_INVALID | 422 | validate stage | Price under $1,000 (junk), or extreme outlier for vehicle age. | Feed-side: verify price field; null out if 'Call for Price'. |
Dedup, enrich, and index
| Code | HTTP | Source | Meaning | Fix |
|---|---|---|---|---|
| DUPLICATE_DEDUP_HASH | 422 | deduplicate stage | Same SHA-256(VIN + source_provider_id) already exists. Merge path takes over automatically; this code surfaces only on hard insert collisions. | Retry after a few seconds; usually a race between concurrent batches. |
| ENRICH_FAILED | 422 | enrich stage | Geocode, quality scoring, or image fetch failed. | Retry the batch; transient third-party dependency. |
| INDEX_FAILED | 422 | index stage | Elasticsearch upsert failed. | Retry; if persistent, email api@truckradar.ai. |
Infrastructure and rate limiting
| Code | HTTP | Source | Meaning | Fix |
|---|---|---|---|---|
| NOT_FOUND | 404 | GET /v1/inventory/:vin, PATCH /v1/dealers | No resource matches the given VIN or slug. | For VIN: run POST /v1/inventory first. For slug: verify the dealer slug. |
| ALREADY_CAPTURED | 409 | widget/lead | Lead already captured for this conversation. | Idempotent; no action needed. |
| RATE_LIMITED | 429 | all endpoints (fair-use) | Request burst exceeded the recommended 60/minute per key. | Back off and retry with exponential delay. See Rate Limits. |
| CONFIG_ERROR | 500 | widget/chat | Server-side AI provider not configured. | Transient; contact support if persists. |
| INTERNAL_ERROR | 500 | all endpoints | Unexpected server exception. Captured in Sentry with a request ID. | Retry; include x-request-id when emailing support. |
Retry guidance
- 4xx — client error. Do not retry without changing the payload.
- 422 — record-level reject. Do not retry the same record; fix the data.
- 429 — rate limited. Exponential backoff starting at 1s, jittered, max 60s.
- 5xx — transient. Retry up to 3 times with exponential backoff.
Include request IDs in support tickets
Every response sets
x-request-id. Forwarding it when you email api@truckradar.ai lets us jump straight to the Sentry event and find the stack trace without a repro.