API documentation
Two endpoints: a salvage market value estimate with the comparable sales behind it, and a retail (pre-loss) market value estimate. Base URL https://api.residualauto.com.
The full OpenAPI 3 schema is at api.residualauto.com/openapi.json, and interactive docs at api.residualauto.com/docs. To value a spreadsheet instead of calling the API, use bulk upload.
Authentication and usage
Send your key in the X-API-Key header on every request.
- What counts: each distinct vehicle once per calendar month, across the API, the lookup tool and bulk upload. A salvage and a retail value for the same vehicle count once. Failed requests don't count.
- Going over: we keep answering and never charge overage; the response carries a quota_note. After two months running over, requests return 429.
- Rate limit: per minute, per key; over it returns 429.
Salvage value
POST /v1/valuations — what the vehicle would realize at salvage auction, with the comparable sales behind the estimate.
curl -s https://api.residualauto.com/v1/valuations \
-H "X-API-Key: $RESIDUAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"vin": "4T1B11HK5KU211111",
"odometer": 78421,
"primary_damage": "Front end",
"title_group": "Salvage",
"condition_grade": "Runs and drives"
}' import requests
r = requests.post(
"https://api.residualauto.com/v1/valuations",
headers={"X-API-Key": API_KEY},
json={"vin": "4T1B11HK5KU211111", "odometer": 78421,
"primary_damage": "Front end", "title_group": "Salvage"},
timeout=30,
)
r.raise_for_status()
val = r.json()
print(val["estimate"], val["quantiles"]["p5"], val["quantiles"]["p95"]) Request rules
- Identity: send a vin, or year + make + model. With a VIN we decode year, make, model and specs server-side, and the decoded make and model are used. Without either, the request returns 422.
- Labels: text fields take the values listed by GET /v1/form-vocabulary, spelled exactly. A make or model that isn't listed returns 422; any other unlisted value is treated as not supplied.
- Listing context: damage, title, odometer, condition and keys are inputs the model runs on. If the vehicle is or was listed at auction, send what the listing shows.
- Unknown fields return 422, so typos fail loudly.
- Accepted but ignored: acv_estimate, retail_estimate, repair_cost, insurance_value, auction_location, sale_type and source. We record them with your request; they never affect the estimate.
Request fields
| Field | Type | Notes |
|---|---|---|
| vin | string | 11–17 characters |
| year | int | Required without a VIN |
| make | string | Required without a VIN, e.g. Toyota |
| model | string | Required without a VIN |
| trim | string | |
| odometer | number | Miles. A reading implausible for the model year is set aside; see odometer_check |
| primary_damage | string | e.g. Front end, Side, Water/flood |
| secondary_damage | string | |
| title_group | string | Salvage, Clean, Non-repairable |
| condition_grade | string | Runs and drives, Starts, Does not start |
| has_keys | bool | JSON true/false |
| state | string | Two-letter US state |
| fuel_type, drive_type, transmission, color, engine, body_style, engine_hp, displacement_l, engine_cylinders | — | Optional; send if known |
Full value lists: GET /v1/form-vocabulary (no key needed).
Response
Example (values illustrative):
{
"estimate": 6425.0,
"currency": "USD",
"quantiles": {"p5": 4410.0, "p10": 4980.0, "p25": 5720.0, "p50": 6425.0,
"p75": 7190.0, "p90": 8210.0, "p95": 8960.0},
"interval": {"p10": 4980.0, "p50": 6425.0, "p90": 8210.0},
"band_calibration": {"profile": "s4_lookup3", "resolution": "exact-match",
"ignored_inputs": null, "ignored_note": null, …},
"n_comparables": 15,
"comp_window": {"oldest_sale_date": "2025-11-19", "newest_sale_date": "2026-09-10",
"span_days": 295, "recency_days": 313},
"comps": [
{"year": "2019", "make": "Toyota", "model": "Camry", "trim": "LE",
"primary_damage": "Front end", "title_group": "Salvage", "odometer": "81250",
"sale_date": "2026-06-18", "realized_price": 6100.0, "vin_prefix": "4T1B11HK5KU",
"match_reason": [{"key": "model", "text": "same make & model"},
{"key": "damage", "text": "same damage type"}],
"same_model_recent": true, "vehicle_class": "passenger vehicle",
"body_type": "sedan", "similarity": 0.94}
],
"subject_class": "passenger vehicle",
"subject_body_type": "sedan",
"input_tier": "listing-grade",
"odometer_check": null,
"content_tier": "report",
"quota_note": null,
"lookup_token": "…",
"basis": "…", "accuracy_note": "…", "disclaimer": "…",
"computed_at": "2026-09-28T14:03:22Z",
"model": {"tag": "…", "trained_through": "…", "version_id": "…"}
} Response fields
The estimate and its range
| Field | Meaning |
|---|---|
| estimate | The market value estimate, USD. Equals quantiles.p50. |
| quantiles | The range around the estimate as seven price points, USD. p5–p95 is the wide range and p25–p75 the narrow one. How we measure explains how the ranges are measured. |
| interval | p10, p50, p90 from the same range. Kept for older integrations; use quantiles. |
| band_calibration | Which calibration produced the range. profile names it. resolution is exact-match when your inputs matched a calibration fit for exactly those inputs, degraded when some inputs were set aside to reach one (named in ignored_inputs and ignored_note), and pooled when none matched. |
The comparable sales
| Field | Meaning |
|---|---|
| n_comparables | How many comparable sales this response carries: the length of comps. A subscription or a Residual Report carries the 15 closest, fewer when fewer exist. |
| comp_window | When those sales happened: oldest_sale_date and newest_sale_date; span_days, the days between them; recency_days, the days from the oldest sale to your request. |
| comps | The sales themselves, in display order: your vehicle's make and model first, then the closest model year, then sales matching the damage and title you gave, then the nearest odometer reading. Each row carries the vehicle, its damage, title and odometer, sale_date and realized_price (USD). |
| comps[].vin_prefix | The first 11 characters of the sold vehicle's VIN, as the auction publishes it. |
| comps[].match_reason | What this sale shares with your vehicle, as a list of key (stable id, safe to branch on) and text (the wording we display). Only facts both vehicles carry are listed. |
| comps[].same_model_recent | true when the sale is the same make and model with a model year within 3 of yours. null means not computed, not false. |
| comps[].vehicle_class, body_type | The sold vehicle's class (passenger vehicle, motorcycle, commercial truck…) and body type (sedan, pickup, suv…). |
| comps[].similarity | Deprecated: an ordering score whose scale changes with each model release. Use match_reason. |
| comp_note | Set when some listed sales are a different or unknown vehicle type. |
Your vehicle and inputs
| Field | Meaning |
|---|---|
| subject_class, subject_body_type | Your vehicle's class and body type, on the same scale as the comps. |
| input_tier | listing-grade when you sent condition_grade, otherwise identity-only. |
| odometer_check | Set when the odometer reading you sent is implausible for the model year (under 100 miles at 3+ years old, over 500,000, or over 100,000 a year). The estimate was made without it; note says so in a sentence. |
| used_caller_context | Names any accepted-but-ignored field that entered the estimate. Always null today. |
Account, provenance and text
| Field | Meaning |
|---|---|
| content_tier | report: the full payload, comps included. content_note is set if anything was withheld. |
| quota_note | Set when your account is past its monthly vehicles; the request was still answered in full. |
| lookup_token | Send it back as the X-Lookup header to re-value the same vehicle with more or corrected details; every run counts as one vehicle. A different vehicle under the same token returns 409. |
| computed_at | When this answer was computed, UTC. A stored copy is a snapshot as of then. |
| model | The model that produced the answer: tag, trained_through (last sale date in its training data), version_id. |
| basis | What the estimate is a value of: a realized salvage-auction price. |
| accuracy_note | Which inputs the estimate used, in a sentence. |
| disclaimer | The disclaimer that goes with every estimate. |
Retail value (pre-loss)
POST /v1/acv — what the vehicle would sell for at retail, undamaged. Included with Pro and Practice, and counted with the same vehicle as /v1/valuations.
curl -s https://api.residualauto.com/v1/acv \
-H "X-API-Key: $RESIDUAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"vin": "4T1B11HK5KU211111", "odometer": 78421}' The retail estimate works from a year, make and model; it is more precise with an odometer reading, a trim or a 17-character vin. Damage and title fields are not accepted. A vehicle outside the retail data returns 422 with the reason.
| Field | Meaning |
|---|---|
| estimate, quantiles, interval | As in /v1/valuations, for the retail price. |
| bands | The ranges, keyed inner (p25–p75), mid (p10–p90) and outer (p5–p95). Each has low, high, its nominal_coverage, and the measured_coverage and measured_n it was checked at. |
| bands.*.claim_withheld | true when no coverage is stated for vehicles like this one: measured_coverage is then null and claim_withheld_reason says why. The range itself is still served. cell names the make-and-price group checked. |
| comps | Comparable retail sales, with n, the window_days_used they fall in, and per row the vehicle, mileage, price and sale_date. sale_basis is confirmed when the source published the sale and inferred when it's inferred from the listing ending. These are the comparable retail sales the model weighed for this estimate. |
| comp_note | The comps' count and window as a sentence, or why there are none. |
| basis, basis_note | The retail data the model was fit on, and a sentence stating it. |
| route | The inputs the estimate rests on: head names the route, described_as states those inputs as a phrase, and not_read lists any inputs sent that the route does not use. |
| quota_note, computed_at, model, disclaimer | As in /v1/valuations. |
Other endpoints
| Endpoint | Auth | Returns |
|---|---|---|
| GET /v1/form-vocabulary | none | Accepted values for every text field |
| GET /v1/health | none | Service status and model tag |
| GET /v1/model | key | Model tag, training cutoff, version id |
Errors
| Status | Meaning |
|---|---|
| 401 | Missing, invalid, or revoked API key |
| 403 | Retail value isn't included in your plan |
| 409 | An X-Lookup token sent with a different vehicle |
| 422 | No VIN or year+make+model, a make or model we don't list, an unknown field, or a vehicle the retail model can't value |
| 429 | Per-minute rate limit, or two months running over your monthly vehicles |
| 502 | VIN decode unavailable: retry, or send year/make/model |
| 503 | Service temporarily unavailable: retry later |
Pricing lists what each plan includes; the Enterprise card covers per-claim pricing and volume bands.
Terms of use
Responses are market value estimates from a statistical model, not appraisals, and every response carries the full disclaimer. Estimates and comps are licensed for your internal valuation workflows and client deliverables; bulk redistribution or resale of the underlying comp data requires a separate agreement.