posted 2026-07-26 · enrich.vin/blog/how-to-append-vehicle-data-to-vins-by-api
How do I append vehicle data to a list of VINs by API?
Send VINs to the enrich.vin endpoint or MCP tool and each returns the decoded record plus the licensed fields you request — the same append, run programmatically.
Send your VINs to the enrich.vin endpoint or its MCP tool and each one returns the decoded record plus the licensed fields you request, appended to the row — the same capability as the file upload, run programmatically through apis.vin. One key drives both the batch and the single row; the request states which fields you want, and the response returns each appended field with its value, its source, and its timestamp. Reading the structure is free; you pay a stated per-row fee only for the licensed data you actually append, and only once that tier's compliance gate has cleared.
The file a person downloads and the JSON an agent reads render from the same record. That is the load-bearing fact of this door: there is no separate machine catalog and human catalog, no drift between what the upload returns and what the endpoint returns. Same key, same fields, same sources — one is a spreadsheet, one is a payload.
The two shapes of the same call
The upload path takes a CSV or XLSX with a VIN column and hands back the same file plus columns. The programmatic path takes VINs directly and hands back the same record as structured data. You choose by how your VINs already live: if they are in a table a person maintains, upload; if they are in a system a service calls, hit the endpoint. Neither is a lesser version of the other. An operator can enrich a book of business by upload in the morning and a nightly job can enrich new rows by API against the identical catalog — and the two agree, because they read the same record.
Through apis.vin, the enrichment runs one key at a time or in batch, and the estate exposes one MCP server for the whole platform at apis.vin/mcp — so an agent that already speaks MCP calls enrich as a tool without a bespoke integration. The developer surface and the agent surface are the same surface.
What the response carries per VIN
Each VIN in the request resolves to a record with the fields you asked for, drawn from what the catalog licenses:
- decode — year, make, model, trim, style, drivetrain; a zero-marginal face from VIN's own database that always resolves and whose structure reads free.
- specs, values, listings, sticker — the factory numbers, the market values with their basis, the live for-sale state, and the birth-record fields, each appended where the catalog binds a license for it.
- flags — recall status (free), and title-brand and odometer-consistency flags where licensed; the full history report stays its own priced step, never folded silently into a field.
Every appended field posts value, source, and timestamp. A field the catalog does not license for that VIN comes back as a stated gap — not a null you have to guess about, and never a fabricated value. This is the discipline that makes an enriched row safe to act on programmatically: your code can trust that a populated field is traceable and an empty one is honestly empty. This page describes the structure of the response; the coverage — which fields are bound for which vehicles — is cataloged at data.vin.
Resolution is a typed field, not an exception
A programmatic caller cannot afford a silent drop. Enrich returns a resolution state per VIN — resolved, INVALID_VIN, or no record — so a bad key is a value in the response, not a thrown error that halts the batch. INVALID_VIN means the string is not a valid VIN; no record means the catalog has nothing bound for a valid key. Your job reads those states and routes accordingly: retry the mistyped, queue the unbound, act on the resolved. The count of rows you sent equals the count of rows you get back, every one accounted for.
Pricing follows the data's own basis
The per-row fee is stated before the tier transacts, and the structure — the shape of the request and the response — reads free. What you pay for is the licensed data inside the append: the zero-marginal own-database faces like decode carry no per-pull cost, while the licensed, real-money faces are priced through per row. Billing is typed and per-VIN: before any paid batch moves, the offer is on paper — the posted per-row amount, the settlement rail, and the expiry — so an agent transacting on its own account sees the same terms a person would. No price is claimed before the compliance gate clears; until then the batch tier does not transact.
The agent register
An agent is a customer at this address on its own account — the motion is B2A. It authenticates with its own key, requests the fields it needs, and receives the same structure a developer would. Where an agent enriches for a person, the receipt invariant holds: everything the agent does here survives being shown to that person, because every appended field already carries its source and timestamp. There is nothing in the machine response that could not be put in front of the human it was run for.
Where the keys and the coverage live
If your list is keyed to a provider's own vehicle identifiers rather than VINs, resolve them first: the crosswalk at apis.vin's sibling reconciles provider IDs to the VIN spine, and enrichment runs from there. And the full catalog of what any row can carry — every field, with its coverage, its license, and its price — lives at data.vin. Enrich is the append; data.vin is the record it appends from; apis.vin is how the machine reaches both.
The record, at its other addresses
- data.vinThe pillar door: every field a row can carry, cataloged — coverage, license, price, posted.
- apis.vinThe same enrichment by API and MCP — one key, batch or row at a time.
- vindex.vinRows keyed to a provider's IDs instead of VINs — the crosswalk maps them to the spine.
- values.vinOne row's numbers, read singly: the posted values for any VIN and their basis.
- vhr.vinOne row's history, read in full: what happened to any VIN, posted.
- all.vinEvery door, one record: all.vin/