One GET returns EPA fuel and emissions figures for a year/make/model: annual fuel cost, five-year fuel cost, combined MPG, fuel type and tailpipe CO2. It is fuel and operating cost only — maintenance, insurance, tires, taxes, fees and depreciation are not included, which is why a fixed disclaimer string ships in every single response. If you need total cost of ownership, this is the running-cost half; pair it with the depreciation endpoint for the value-loss half, which is usually the larger number.
A flat, EPA-shaped object. annualFuelCostUsd (2350 for a 2021 Camry) and fiveYearFuelCostUsd (11750) are the headline figures, combinedMpg is EPA's combined city/highway number truncated to an integer, co2GramsPerMile is tailpipe grams per mile, and fuelType is EPA's label — "Regular", "Premium" or "Electricity". Two fields do the honest work: config names the exact EPA trim configuration that was priced, for example "Auto (S8), 6 cyl, 3.5 L", and trimsAvailable tells you how many configurations exist for that year/make/model. Your make, model and year come back echoed verbatim and unnormalized. Every numeric field is nullable, because EPA occasionally omits them, and note is a fixed disclaimer present on every response.
Unlike our valuation and depreciation endpoints, this one is not backed by scraped data. Each request makes two live calls to EPA's fueleconomy.gov: one to resolve your year/make/model into the list of trim configurations EPA holds, and a second to fetch the figures for the first configuration in that list. Nothing is cached in between, and the route runs against a 15-second budget. Because EPA performs the lookup, make and model matching is case-insensitive here — a genuine difference from the depreciation endpoint, which is exact and case-sensitive — but your values are echoed back exactly as you sent them rather than normalized. annualFuelCostUsd is EPA's own estimate at 15,000 miles a year against current national average fuel prices, and fiveYearFuelCostUsd is simply that figure multiplied by five.
Data source
Two live calls to EPA fueleconomy.gov per request: one to resolve year/make/model to trim configurations, then one for the first configuration returned. Not scraped data, and not cached.
Route budget
15 s
Billed per
model estimate, successful responses only
MCP tool
get_ownership_costs
Limits: what this endpoint will not do
Fuel only. There is no maintenance schedule, no insurance premium, no tire or tax or registration estimate, and no depreciation — the fixed note string in every response says so, and you should surface an equivalent caveat in your UI. The figures describe EPA's first trim configuration only: when trimsAvailable is greater than 1, treat the numbers as one configuration's, not the model's. For the 2021 Camry, EPA resolves the V6 ("Auto (S8), 6 cyl, 3.5 L"), not the best-selling four-cylinder. The five-year figure is arithmetic, not a forecast: annual × 5, with no inflation, discounting or fuel-price projection. Error behaviour is counterintuitive and worth coding for: EPA coverage starts at 1984, and an unknown model or a pre-1984 year makes EPA return an empty body, which surfaces as a 503 ownership_upstream_error rather than a clean 404. The 404 ownership_not_found only fires in the rarer case where EPA returns a well-formed menu with zero options. So a 503 here is more often bad input than an outage — do not blind-retry it. Latency is upstream-bound by design, since it is two live round trips with no caching. There is also no VIN parameter and no state-level fuel pricing; figures use EPA's national assumptions. Ownership Costs shares the free Starter plan's 1,000 monthly calls. After that shared allowance, successful calls cost $0.045 from the one-time $10 signup credit — about 222 additional calls — 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
EV-versus-gas cost comparison in a shopping tool
A comparison site, dealer EV landing page or OEM cost-to-switch calculator makes two calls and subtracts the annual fuel costs. Handle the EV shape explicitly: combinedMpg becomes MPGe, fuelType becomes "Electricity", and co2GramsPerMile is 0 because it is a tailpipe measure — so present the CO2 delta as tailpipe-only rather than implying a lifecycle comparison.
Fleet and corporate mobility procurement screening
A fleet manager scoring candidate vehicles gets a consistent, citable fuel-cost baseline across every nameplate on the shortlist. EPA's annual figure assumes 15,000 miles a year, so rescale linearly to your own duty cycle (annualFuelCostUsd × yourAnnualMiles ÷ 15,000). Where trimsAvailable is greater than 1, pull the spec sheet separately, because you are being priced on one configuration.
Tailpipe emissions reporting for rental and mobility operators
A car-sharing, rental or delivery operator multiplies co2GramsPerMile by miles driven per unit to produce a fleet-level tailpipe estimate for internal or customer-facing reporting. It is a tailpipe figure, not a well-to-wheel or lifecycle figure, so it is defensible for a Scope 1 style tailpipe number and not for a full carbon accounting claim.
Listing-detail enrichment on a marketplace
Attaching MPG and annual fuel cost to every vehicle detail page is a cheap trust signal, but this endpoint makes two live EPA round trips per call and is not cached by us. Cache aggressively on your side keyed by year/make/model — the figures only move when EPA's data or national fuel prices move — because at $0.045 per call on Pro this is the most expensive of the three valuation-and-ownership endpoints.
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(["ownership_upstream_unavailable", "ownership_upstream_error"]);
export async function getOwnershipCosts(params: Record<string, string>, attempt = 0): Promise<GetOwnershipCostsResponse> {
const response = await fetch(
`${API}/v1/vehicles/ownership-costs?${new URLSearchParams(params).toString()}`,
{
headers: {
Accept: "application/json",
Authorization: `Bearer ${process.env.VEHICLES_API_KEY ?? ""}`
},
// The route budget is 15 s; allow it plus headroom.
signal: AbortSignal.timeout(20_000)
}
);
if (response.ok) return (await response.json()) as GetOwnershipCostsResponse;
// 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 getOwnershipCosts(params, attempt + 1);
}
throw new Error(`${response.status} ${problem.code}: ${problem.detail ?? ""}`);
}
How to check it is behaving
The route enforces a 15-second upstream budget, so a slow dependency returns a problem document instead of holding your request open. Exactly 2 codes are marked retryable here — ownership_upstream_unavailable and ownership_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 estimate. 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.
Cost of Ownership API pricing by plan
Plan
Platform fee
Rate limit
Per model estimate
50,000 / month
Starter
$0 / mo
5 rps
$0.045
$2,250$2,250 usage + platform fee
Pro
$299 / mo
10 rps
$0.045
$2,549$2,250 usage + platform fee
Scale
$599 / mo
50 rps
$0.035
$2,349$1,750 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
Why did an unknown model return 503 instead of 404?
Because EPA returns an empty body rather than an error for a model it does not recognise, and an empty upstream body surfaces as ownership_upstream_error (503, marked retryable). The practical consequence: a 503 from this endpoint is more likely to mean your model string is wrong than that EPA is down. Validate the name against EPA's naming — models are sometimes verbose, like "F150 Pickup 2WD" — before retrying, and cap retries on identical inputs.
Does this include maintenance, insurance or depreciation?
No, and that is deliberate rather than a gap we are hiding — the fixed disclaimer ships on every response. For a fuller total-cost-of-ownership figure, add the depreciation endpoint for value loss by age, and treat insurance and maintenance as inputs you source elsewhere. Value loss is typically the largest single line in a five-year ownership total, so a fuel-only figure presented as "cost of ownership" will mislead your users.
Which trim's numbers am I actually getting?
The first configuration EPA returns for that year/make/model. config names it in human-readable form and trimsAvailable tells you how many exist. If trimsAvailable is 1 the answer is unambiguous; if it is higher, the returned MPG and fuel cost describe one powertrain and can differ substantially from the volume trim. There is no parameter to select a different configuration today.
Can I change the 15,000 miles-per-year assumption?
Not via a parameter — EPA's annual cost is computed at 15,000 miles a year at current national average fuel prices. The figure is linear in miles, so scale it yourself: annualFuelCostUsd × yourAnnualMiles ÷ 15,000. The same applies to CO2, which is already expressed per mile and so needs no assumption at all.
How does it behave for electric vehicles?
combinedMpg is reported as MPGe, fuelType comes back as "Electricity", co2GramsPerMile is 0 because it measures tailpipe emissions, and the fuel-cost fields carry EPA's electricity-cost estimate rather than a gasoline cost. If you are comparing an EV against a gas model, say explicitly in your UI that the CO2 comparison is tailpipe-only.
What does it cost, and how do I keep the bill down?
Ownership Costs shares Starter's 1,000-call monthly allowance, then costs $0.045 per successful call from available credit. Pro is $0.045 and Scale is $0.035 — roughly three times the Market Value and Depreciation rates because each call makes two live upstream round trips. At 50,000 calls a month, Pro is $2,250 plus $299 and Scale is $1,750 plus $599, so Scale wins above roughly 30,000 calls a month. The bigger lever is caching: results are keyed entirely by year/make/model and change only when EPA's data or fuel prices do, so a cache on your side removes most of the volume. Failed calls, including the 503s above, are not billed.
Where to go next
Most integrations chain two or three of these. The ones that pair with cost of ownership api most often:
Car Depreciation APIMSRP retention by model year, a smoothed age 0–12 retention curve and one fitted annual decay rate, for 223 covered make/model pairs.
Vehicle Valuation APIAn ML-predicted current asking price in whole USD for a year/make/model, with the model's 3.7% median holdout error reported on every response.
Vehicle Specs APIThe 20-field NHTSA vPIC factory sheet for a VIN — seats, horsepower, displacement, GVWR class, plant country — read live on every request and never cached.