> ## Documentation Index
> Fetch the complete documentation index at: https://lmn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Vehicle history integration

> Integrate submission, polling, signed webhooks, partial results, credit handling, and the version-2 history contract.

This guide accompanies the [History endpoint reference](/endpoints/history-lookups). It describes the prepared version-2 contract after LMN enables it for your environment. The [complete response examples](/history-response-examples/completed) include full JSON bodies and downloads. This guide does not announce that multi-provider collection is live.

## Integration checklist

1. Confirm History API access, environment, and whether you are using fixtures, the saved example, or a real-data trial.
2. Send a VIN, or a plate with a stable idempotency key. Persist the returned lookup ID and original reference.
3. Use polling, signed webhooks, or both. Separate HTTP request failure from a retrieved lookup's failed processing status.
4. Preserve both primary `report` and supplemental `aggregate`, including on a failed lookup.
5. Display source coverage, conflicts, and unknown values. Do not turn unavailable information into a clean-history claim.
6. Handle stored version-1 webhooks alongside the new version-2 envelope. Remove dependencies on history PDFs.

## Run a client against sandbox

Download the [Node.js client module](/examples/history-lookups/client.mjs), save it as `client.mjs`, and use Node.js 22 or later:

```javascript theme={null}
import { lookupHistory } from './client.mjs';

const lookup = await lookupHistory({
  baseUrl: process.env.LMN_BASE_URL,
  apiKey: process.env.LMN_API_KEY,
  vin: 'KNAPM813BHK185630', // saved historical sandbox example; no provider call
  reference: 'history-example-001',
  onProgress: value => console.log(value.id, value.status),
});

// Keep supplemental data even if the primary lookup failed.
console.log({
  status: lookup.status,
  report: lookup.report,
  aggregate: lookup.aggregate ?? null,
  sources: lookup.sources ?? [],
  creditsCharged: lookup.credits_charged,
});
```

Set `LMN_BASE_URL=https://sandbox-api.lmnauto.com` and your sandbox key. The sample's response can still have supplemental sources marked `not_collected`; it tests API access and saved primary JSON, not current government/dealer collection.

The client polls every five seconds and allows six minutes of client-side waiting. That wait is a client policy, separate from the server's fixed five-minute content cutoff. A client timeout or cancellation does not cancel an accepted lookup. Save the ID from `onProgress`, then resume with GET. If POST's response was lost, reuse the same plate idempotency key/body. For a VIN, a repeat reuses the same job only while it is in flight: if it already finished, expanded collection creates a new lookup. Use GET-list, optionally bounded by `created_from`/`created_to`, and match the accepted reference and identifier to locate the original VIN job before submitting again. References are correlation values, not uniqueness constraints. Do not use a new plate key merely because a network response was lost.

For a new plate request, replace `vin` with `plate` and supply `idempotencyKey`. Keep the key stable for retries of that request. Real-data collection can consume a credit; the client deliberately never sends `fresh: true` automatically.

## Postman

Import the [dedicated History collection](/examples/history-lookups/history.postman_collection.json). It defaults to sandbox and a blank API key. Set `api_key`; the first request uses the free saved Sportage example. A successful POST stores `history_lookup_id` for the polling request. The plate template has a blank `history_plate` until you intentionally fill it and confirm your environment's billing mode.

The collection does not run itself or configure your webhook. Production needs a separately enabled key and an intentional base-URL change. The generic inventory/order collections remain separate.

## Store the response without flattening away evidence

| Store | Why |
| - | - |
| Lookup ID, original reference, requested identifier, status and versions | Correlate polling and events without assuming a later reference replaced the accepted request. |
| Primary report | Preserve the existing normalized CarHistory contract. |
| Aggregate facts and events | Retain supplemental facts, alternative values and context. |
| Sources and captured times | Show which source was available and when it was actually captured. |
| Credit fields | Record persisted settlement; a replay is not another debit. |
| Event ID and original signed body | Handle duplicate deliveries and retain the contract that was actually delivered. |

Use fact/value IDs to relate evidence within a frozen result. IDs are opaque; do not infer dates or business meaning from them, or assume they remain the same across distinct lookups. A `revision_id` is a source reference, not a public raw-data download.

### Scalars, sets, and conflicts

For a scalar fact, a single `values[]` member can carry several source references. If `conflict` is true, retain and label every alternative. Do not average conflicting odometer readings or sum duplicate claim totals.

For `cardinality: "set"`, multiple work items or part codes are legitimate members. Several providers can support the same value. Amounts retain the units indicated by the key: `*_won` and `*_krw` are Korean won; `*_raw` remains source-denominated text and must not be silently converted or added to a known-unit total.

### Events and uncertain matches

An event can be `verified`, `source_record`, or `unresolved`. `source_record` identifies an event within its source, not a verified match across providers. A shared `candidate_group_id` links uncertain correspondences; show them as possibly related. Date-only or same-month similarity does not prove identity.

Keep every event context's `date_precision`. A month is not the first day of that month, null mileage is not zero, and an unknown date can be an empty string with precision `unknown`.

### Partial result example

Open [completed-partial.json](/examples/history-lookups/completed-partial.json): the primary report is ready, some Car365 information is available, and Encar has no listing. Render the primary report and the aggregate; label source availability separately. The lookup remains `completed`, and the existing successful-lookup credit policy applies.

Open [failed-supplemental.json](/examples/history-lookups/failed-supplemental.json): the primary report was unavailable, but Car365 facts were ready by cutoff. Retain those facts, show the primary failure, and record `credits_charged: 0`. HTTP GET returns 200 for this stored failed lookup; it is not an HTTP 5xx error.

`aggregate.complete` concerns the observations admitted into consolidation. Check all three `sources` entries before describing coverage. Even a reconciled set of recorded facts is not a complete lifetime record or a vehicle-condition guarantee.

## Receive terminal events

1. Configure your existing environment-specific webhook endpoint with LMN.
2. Verify the signature over the raw request body before parsing, using [Signing and retries](/webhooks/signing-and-retries).
3. Deduplicate by event ID. Enqueue application processing durably, then acknowledge with `2xx`; keep delivery handling fast.
4. Route `history_lookup.completed` and `history_lookup.failed` to the same history-result storage path.

```javascript theme={null}
import { readHistoryEvent } from './client.mjs';

// Run only after signature verification and your event-ID deduplication.
const result = readHistoryEvent(verifiedEvent);
if (result) {
  await saveHistoryResult(result.lookupId, {
    status: result.status,
    report: result.report,
    aggregate: result.aggregate,
    sources: result.sources,
    contractVersion: result.version,
  });
}
```

`verifiedEvent` and `saveHistoryResult` are your verified event object and persistence function. This snippet does not replace signature verification or durable deduplication.

One terminal business event can have several network deliveries. Reusing a job, retrying a delivery, or explicitly replaying an event does not create a second credit debit. Five minutes limits result content, not webhook network arrival time.

Full downloadable [completed](/examples/history-lookups/completed-webhook.json), [failed](/examples/history-lookups/failed-webhook.json), and [legacy](/examples/history-lookups/legacy-completed-webhook.json) event bodies can be fed to `readHistoryEvent` offline, without any API or webhook call.

## Cutover and replay checklist

| Surface | After coordinated version-2 cutover |
| - | - |
| Current POST / GET-one / GET-list | Version-2 envelope with `history_lookup_schema_version`, `aggregate`, and `sources`. Earlier jobs can show supplemental sources as `not_collected`. |
| New terminal events | Contract in effect when the result is finalized. |
| Stored earlier events and explicit replay | Original body remains unchanged; absence of `data.history_lookup_schema_version` means version 1. |
| Primary report | `report.schema_version` remains 1. |
| History PDFs | No PDF field or new PDF artifact. Existing authorized legacy links only retain access to already stored objects. |

This is an in-place `/v1/history-lookups` contract cutover, not an unannounced `/v2` route. Accept unknown added fields, keep version-1 event handling, and test null report/aggregate values. Do not overwrite the archived body of an old event with a current GET response just because both use the same lookup ID.

## Acceptance cases for your integration

* VIN in flight: a repeat returns the same ID and no extra event.
* Plate retry: same key/body returns the original job; changed body returns 422.
* Completed with partial supplemental coverage: useful data remains visible and one successful charge policy applies.
* Failed primary with ready supplemental facts: aggregate remains visible and no credit is deducted.
* All source data unavailable: null aggregate stays unknown, not a clean result.
* Equal facts from several providers: preserve all provenance without summing repeated values.
* Conflicting scalar or uncertain event: retain alternatives and uncertainty.
* Seven-day source reuse versus 30-day credit entitlement: do not treat either as the other's expiry.
* Duplicate or legacy webhook delivery: keep one business result and do not assume its body matches current GET.
* Client timeout: resume a known ID; a plate's stable key recovers its original request, while a VIN job that has already finished must be located through recent lookups before intentionally starting another.

The response downloads are explicitly synthetic offline fixtures, generated through the implementation. Controlled live-source acceptance is a separate LMN release gate.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.