Valuation & ownership · Depreciation

Car Depreciation API

One GET returns a resale-value curve for a make and model: how much of original MSRP each model year still holds today, a smoothed retention curve across ages 0 through 12, and a single fitted annual decay rate. It takes exactly two parameters — make and model — and nothing else. That scope is deliberate and it is the first thing to check against your requirements: this answers how fast a Camry loses value, not what this particular Camry is worth today.

GET/v1/vehicles/depreciation5 s route budget

Get an API keyFull parameter reference

What comes back

Three things, plus echoes. annualDecay is the fitted rate of value loss as a fraction — 0.0652 means roughly 6.5% of remaining value is lost per year — and is null for models with no fitted curve. byModelYear is an array of observed rows, newest model year first, each carrying upstream snake_case keys: year, age, msrp, median_price, n_listings, retention, curve_beta and confidence. Those last two matter: n_listings tells you how thin a model year is, and confidence is a per-row score. curveByAge is the smoothed version — exactly 13 points, age 0 through 12, each { age, retention } — which is what you plot or index into. For the Toyota Camry the documented curve runs 0.979 at age 0, 0.915 at age 1, 0.699 at age 5 and 0.499 at age 10. Note the intentional asymmetry: the envelope is camelCase, the array elements keep upstream snake_case.

Request
curl -sS -G https://api.vehicles.dev/v1/vehicles/depreciation \
  -H "Authorization: Bearer $VEHICLES_API_KEY" \
  -d make=Toyota -d model=Camry
200 · arrays trimmed (real response has 12 model years and 13 curve points)
{
  "annualDecay": 0.0652,
  "byModelYear": [
    { "year": 2026, "age": 0, "msrp": 33058, "median_price": 34779, "n_listings": 4038, "retention": 0.9788, "curve_beta": -0.06739, "confidence": 0.82 },
    { "year": 2025, "age": 1, "msrp": 32263, "median_price": 29399, "n_listings": 3416, "retention": 0.9588, "curve_beta": -0.06739, "confidence": 0.82 },
    { "year": 2024, "age": 2, "msrp": 30014, "median_price": 26370, "n_listings": 1841, "retention": 0.9233, "curve_beta": -0.06739, "confidence": 0.82 },
    { "year": 2016, "age": 10, "msrp": 25715, "median_price": 13490, "n_listings": 78, "retention": 0.5116, "curve_beta": -0.06739, "confidence": 0.99 },
    { "year": 2015, "age": 11, "msrp": 27605, "median_price": 13694, "n_listings": 87, "retention": 0.4741, "curve_beta": -0.06739, "confidence": 0.98 }
  ],
  "curveByAge": [
    { "age": 0, "retention": 0.979 },
    { "age": 1, "retention": 0.915 },
    { "age": 2, "retention": 0.855 },
    { "age": 5, "retention": 0.699 },
    { "age": 10, "retention": 0.499 },
    { "age": 12, "retention": 0.436 }
  ],
  "make": "Toyota",
  "model": "Camry",
  "source": "carscrape"
}

The complete parameter table, the response-field dictionary and the per-endpoint error codes live in the reference: full parameter reference for /v1/vehicles/depreciation.

What it is actually built on

This is a precomputed table, not a live model call. For each make/model/year we take the median asking price from our scraped US dealer listings, normalize it to a standard 12,000 miles per year, apply a flat 5% asking-to-sale haircut, and divide by OEM MSRP — matched against the specific trim's MSRP where per-trim MSRP is known, and against the model-year median MSRP otherwise. Per model we then fit a sample-size-weighted log-linear curve, ln(retention) = a + b·age, with the slope forced negative, and blend thin model years toward that fit so a year with eight listings does not dictate the shape. annualDecay is derived from the fitted slope, and curveByAge extrapolates it across ages 0 to 12. The per-row confidence combines three things: sample size, the quality of the MSRP denominator (per-trim beats base-MSRP beats an estimated base-plus-premium), and whether a curve was fit at all. Live coverage today is 1,732 model-year rows across 223 make/model pairs and 28 makes, model years 2013 through 2026.

Data source
A precomputed depreciation table built from median scraped listing prices joined against model-year MSRP. Live coverage today is 1,732 model-year rows across 223 make/model pairs and 28 makes, model years 2013–2026.
Route budget
5 s
Billed per
model forecast, successful responses only
MCP tool
get_depreciation

Limits: what this endpoint will not do

Coverage is the main constraint and it is narrow: 223 make/model pairs across 28 makes, model years 2013–2026. Anything outside that set returns a non-retryable 404 depreciation_not_found, so build the 404 path first. Matching is exact and case-sensitive on both parameters — "Toyota" and "Camry" work, "toyota" and "camry" return 404, and there is no fuzzy matching or trim stripping, so you need canonical strings like "Grand Cherokee", "4Runner" and "S-Class". The curve is model-level: there is no VIN, year, mileage or trim parameter, and the same curve comes back regardless of the specific car. The inputs are asking prices from dealer inventory with a flat 5% haircut, not observed transaction prices, and there is no private-party or auction segment in the underlying store. The fit is a single-snapshot cross-section of current listings, so ages beyond the observed model years are extrapolated along the log-linear fit and age-period-cohort effects are not separated. Two null cases must be handled: some models have no fitted curve_beta, in which case annualDecay and curveByAge are both null while byModelYear is still populated; and byModelYear.retention can approach or exceed 1.0 for current model years where median asking price runs above base MSRP — the 2026 Camry row shows median_price 34,779 against msrp 33,058 — while curveByAge is capped at 1.0. Depreciation shares the free Starter plan's 1,000 monthly calls. After that shared allowance, curves cost $0.015 from the one-time $10 signup credit — about 666 additional curves — and return 402 insufficient_credits only when both are exhausted. The endpoint is available now and successful responses use the published catalog rate. Standard plans include no SLA.

What teams build with it

Setting lease residuals and loan LTV bands

A lessor, captive finance arm or auto lender indexes curveByAge at the term length — 36, 48 or 60 months maps to age 3, 4 and 5 — to derive a residual percentage per nameplate, then sanity-checks it against n_listings and confidence on the nearby observed model years before writing it into a rate card. Because the curve is model-level, this works as a portfolio-level prior, not as a per-contract residual.

True-cost-to-own calculators on automotive publishers

A car-shopping or comparison site that wants a defensible five-year cost figure combines three calls: depreciation for the value lost, the cost-of-ownership endpoint for EPA fuel spend, and market value for today's starting price. Depreciation is usually the largest line in that total, and this is the only one of the three where you can show the reader the underlying observations (msrp, median_price, n_listings) rather than a black-box number.

Fleet remarketing and cycle-out timing

A commercial fleet, rental operator or dealer group ranks the nameplates it holds by annualDecay to decide which units to cycle out first and where the curve flattens enough that holding longer stops costing much. The per-model-year rows let you check whether a specific vintage in your fleet is an outlier before you act on the fitted rate.

Programmatic depreciation pages

One call per make/model produces a full chart plus a table of observed model years, which is enough real data to support a genuine 2021 Toyota Camry depreciation page rather than a templated stub. Coverage is 223 make/model pairs today, so plan the page set against the covered list rather than against your full catalog — uncovered pairs return 404 and would otherwise generate empty pages.

Integrating it properly

The reference ships the bare curl. This is the shape worth deploying: a typed client that branches on the RFC 9457 code slug rather than the human-readable title, and retries only the codes this endpoint marks retryable.

TypeScript
const API = "https://api.vehicles.dev";

// Only these codes are marked retryable for this endpoint. Everything else is terminal.
const RETRYABLE = new Set(["depreciation_upstream_unavailable", "depreciation_upstream_error"]);

export async function getDepreciation(params: Record<string, string>, attempt = 0): Promise<GetDepreciationResponse> {
  const response = await fetch(
    `${API}/v1/vehicles/depreciation?${new URLSearchParams(params).toString()}`,
    {
      headers: {
        Accept: "application/json",
        Authorization: `Bearer ${process.env.VEHICLES_API_KEY ?? ""}`
      },
      // The route budget is 5 s; allow it plus headroom.
      signal: AbortSignal.timeout(10_000)
    }
  );

  if (response.ok) return (await response.json()) as GetDepreciationResponse;

  // Failures are application/problem+json. Branch on `code`, never on the human-readable title.
  const problem = (await response.json()) as { code: string; detail?: string };
  if (RETRYABLE.has(problem.code) && attempt < 3) {
    await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** attempt));
    return getDepreciation(params, attempt + 1);
  }
  throw new Error(`${response.status} ${problem.code}: ${problem.detail ?? ""}`);
}

How to check it is behaving

The route enforces a 5-second upstream budget, so a slow dependency returns a problem document instead of holding your request open. Exactly 2 codes are marked retryable here — depreciation_upstream_unavailable and depreciation_upstream_error — and everything else documented for this endpoint is terminal, so backoff on anything else just burns your rate limit. Billing is success-only: failed calls release their reservation, so you can measure error rates and hold-out accuracy against your own data without paying for the failures. Usage is visible per endpoint in the dashboard.

What this one endpoint costs

Priced per model forecast. The platform fee buys throughput and endpoint access; the unit price covers the data work. Worked at 50,000 successful calls a month so you can see where the plans cross over — the full price matrix is in the reference.

Car Depreciation API pricing by plan
PlanPlatform feeRate limitPer model forecast50,000 / month
Starter$0 / mo5 rps$0.015$750$750 usage + platform fee
Pro$299 / mo10 rps$0.015$1,049$750 usage + platform fee
Scale$599 / mo50 rps$0.01$1,099$500 usage + platform fee

Eligible for the Starter plan's included monthly calls; past those it draws on your credit balance. Data fees are prepaid from credits on every plan: when included calls and credits cannot cover a call you get 402 insufficient_credits rather than an overage bill. Published rates are active and apply only to successful responses. Standard plans include no SLA.

Questions engineers actually ask

Can I get a depreciation curve for a specific VIN, trim or mileage?

No. This endpoint accepts make and model only, and the response is identical for every car of that nameplate. For a number attached to a specific vehicle, use the market value endpoint, which takes year, mileage, trim and state. Decode a VIN first when those normalized inputs are not already available.

Why does "Toyota"/"Camry" work but "toyota"/"camry" return 404?

Both parameters are matched with exact, case-sensitive equality against the stored table. There is no normalization step, so a correct model sent with the wrong casing is indistinguishable from a model we do not cover — both are 404 depreciation_not_found. Store canonical Title-Case strings on your side rather than passing user input through.

How do I turn annualDecay into dollars?

annualDecay is a fraction of remaining value lost per year, so it compounds rather than running straight-line off MSRP: 0.0652 means each year keeps about 93.5% of what the car was worth at the start of that year. For a dollar figure, index curveByAge at the ages you care about and multiply the retention values by MSRP, or by a market-value estimate if you want to start from today's actual asking price.

Why is retention above 1.0 on the newest model year?

Because retention is median asking price divided by base MSRP, and for a current model year the median listing — often a higher trim, and often in a tight-supply market — can be above base MSRP. The 2026 Camry row shows a median price of $34,779 against an MSRP of $33,058. curveByAge caps retention at 1.0, but the raw byModelYear rows do not, which is intentional: the observation is the observation.

How fresh is the data, and is it live?

It is a precomputed snapshot derived from the scraped listings store, not a live computation per request, so the same call returns the same curve until the table is recomputed. We do not publish a fixed refresh cadence; each row exposes n_listings and confidence so you can judge how well supported a given model year is at the moment you read it.

What does it cost, and are 404s billable?

Depreciation shares Starter's 1,000-call monthly allowance, then costs $0.015 per successful curve from available credit. Pro is $0.015 and Scale is $0.01 — the same paid-plan rates as Market Value. Only successful responses bill: a 404 for an uncovered make/model releases the reservation and costs nothing, which means you can probe coverage for free. At 50,000 calls a month, Pro is $750 in data fees plus $299 and Scale is $500 plus $599, crossing over around 60,000 calls.

Where to go next

Most integrations chain two or three of these. The ones that pair with car depreciation api most often:

The reference entry for this endpoint — Depreciation in the API documentation — carries the parameter constraints, defaults, response fields and the full error table with retryable flags. Agents can reach the same endpoint as the get_depreciation tool on the vehicles-dev-mcp MCP server, and the machine-readable contract is published at https://api.vehicles.dev/openapi.json.

Browse the rest of the catalog on the Vehicle data APIs hub.