VinPartsAI API v1
Resolve OEM part numbers from a VIN or license plate plus a free-text part name — original OE, factory supersessions, and verified analogs. A clean REST API, the machine twin of your dashboard.
Overview
- Base URL —
https://vinpartsai.com/api/v1 - Format — JSON in, JSON out. Send
Accept: application/json. - Async model — a lookup is queued instantly and processed by our engine. You
create a lookup, then poll it until
complete: true. Typical turnaround is minutes; the SLA target is under one hour. - Billing — pay per result. Empty or low-confidence lookups are not billed.
Authentication
Every request carries a personal Bearer token in the Authorization header.
Tokens are issued per client on approval — contact us to get yours.
# Every request Authorization: Bearer <your-token> Accept: application/json
401 Unauthorized. A token belongs to one
account and only ever sees that account's lookups. Rate limit: 120 requests/minute per token.
Create a lookup
POST/api/v1/lookups
| Field | Type | Description | |
|---|---|---|---|
| vehicle | string | required | 17-char VIN, or a license plate. We auto-detect and resolve a plate to its VIN. |
| part | string | required | Free-text part name, e.g. front bumper, right headlight. |
| want_replacements | boolean | optional | Also return factory supersessions. Default false. |
| want_analogs | boolean | optional | Also return verified aftermarket analogs (implies replacements). Default false. |
| lang | string | optional | Reserved for output localization. Accepted, not yet applied. |
| callback_url | string (URL) | optional | We POST the result here when the lookup finishes — see Webhooks. Omit to poll instead. |
# Request curl -X POST https://vinpartsai.com/api/v1/lookups \ -H "Authorization: Bearer <your-token>" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"vehicle": "JTDBR32E720123456", "part": "front bumper"}'
# 201 Created { "data": { "id": 42, // your reference number for this lookup "status": "pending", "status_label": "В очереди", "complete": false, "vehicle": { "vin": "JTDBR32E720123456", "plate": null, "make_model": null }, "part": "front bumper", "scope": { "replacements": false, "analogs": false }, "clarify_question": null, "result": null, "duration_sec": null, "created_at": "2026-07-10T19:07:02+00:00", "updated_at": "2026-07-10T19:07:02+00:00" } }
Keep the returned id — you use it to poll the result.
Get a lookup (poll for the result)
GET/api/v1/lookups/{id}
Poll this endpoint until complete becomes true. Poll gently —
once every few seconds is plenty. While the engine works, result stays null.
# 200 OK — finished lookup { "data": { "id": 42, "status": "done", "status_label": "Готово", "complete": true, "vehicle": { "vin": "JTDBR32E720123456", "plate": null, "make_model": "Toyota Corolla 2018" }, "part": "front bumper", "scope": { "replacements": false, "analogs": false }, "clarify_question": null, "result": { "original": [ { "number": "52119-02988", "name": "Bumper cover, front", "brand": "Toyota", "note": null } ], "replacements": [], "analogs": [] }, "duration_sec": 143, "created_at": "2026-07-10T19:07:02+00:00", "updated_at": "2026-07-10T19:09:25+00:00" } }
Each entry in original / replacements / analogs
is { number, name, brand, note }. number is the OEM part number.
Answer a clarification
POST/api/v1/lookups/{id}/clarify
Some parts have variants (side, model year, trim). When the engine can't disambiguate, the lookup finishes with
status: "needs_clarify" and a human-readable clarify_question
(e.g. “Left or right mirror?”). Post your answer back to the same lookup — it re-enters the
queue and continues. No new lookup is created, and you are not charged twice.
| Field | Type | Description | |
|---|---|---|---|
| answer | string | required | Your reply to clarify_question, e.g. left. |
# Request curl -X POST https://vinpartsai.com/api/v1/lookups/42/clarify \ -H "Authorization: Bearer <your-token>" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"answer": "left"}'
# 200 OK — lookup re-queued, keep polling GET /lookups/42 { "data": { "id": 42, "status": "pending", "complete": false, "clarify_question": null, "result": null /* … */ } }
status is needs_clarify.
Answering any other status returns 404 — so a duplicate reply is safe and simply ignored.
Webhooks (push instead of poll)
Pass a callback_url when you create a lookup and we'll POST the result to it
the moment the lookup reaches a final state — no polling needed. Polling still works as a fallback, so a missed
webhook is never a lost result.
The POST body mirrors the GET response, wrapped with an event type:
# POST to your callback_url { "event": "lookup.completed", "data": { "id": 42, "status": "done", "result": { /* … */ } } }
| event | Fires when |
|---|---|
| lookup.completed | Result is ready (status: done). |
| lookup.needs_clarify | We need one detail — answer via clarify. |
| lookup.failed | Could not complete the lookup. |
| lookup.cancelled | Lookup was stopped. |
Verifying the signature
Every webhook carries an HMAC-SHA256 signature of the raw request body in the
X-VinPartsAI-Signature header (format sha256=<hex>),
keyed with your webhook secret (issued together with your API token). Recompute it and compare —
reject the request if it doesn't match.
# PHP $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $webhookSecret); if (!hash_equals($expected, $_SERVER['HTTP_X_VINPARTSAI_SIGNATURE'])) abort(403); # Node.js const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex'); if (expected !== req.header('X-VinPartsAI-Signature')) return res.sendStatus(403);
X-VinPartsAI-Event (event type), X-VinPartsAI-Delivery
(lookup id), X-VinPartsAI-Signature. Respond 2xx to acknowledge;
we retry a couple of times on failure, then rely on your polling.
List your lookups
GET/api/v1/lookups
Paginated, newest first. Optional query param per_page (1–100, default 25).
# 200 OK { "data": [ { "id": 42, "status": "done", /* … */ } ], "links": { "next": null, "prev": null }, "meta": { "current_page": 1, "per_page": 25, "total": 1 } }
Status values
| status | complete | Meaning |
|---|---|---|
| pending | false | Queued, waiting for the engine. |
| in_progress | false | Engine is working on it. |
| done | true | Finished — result is populated. |
| needs_clarify | true | One detail needed — see clarify_question. |
| wrong | true | Could not complete the lookup (e.g. VIN did not resolve). |
| cancelled | true | Stopped. |
Treat any complete: true as terminal — stop polling.
Errors
| Code | Meaning |
|---|---|
| 401 | Missing or invalid token. |
| 403 | Account is pending approval — lookups not enabled yet. |
| 404 | Lookup not found (or not yours). |
| 422 | Validation error — see the errors object. |
| 429 | Rate limit exceeded (120 req/min). Back off and retry. |
# 422 Unprocessable Content { "message": "The VIN or plate number field is required.", "errors": { "vehicle": [ "The VIN or plate number field is required." ] } }