One GET estimates the sales or use tax on a vehicle purchase, plus title, registration and documentation fees. The structural work — and the reason to call this rather than multiply by a rate yourself — is the per-state trade-in rule: most states let a trade-in reduce the amount taxed and a few tax the full price regardless, which moves the tax by the trade-in value times the rate. It is an estimate and not tax advice, the rates are state-level only, and an unknown state returns null rather than a guess.
taxRate and taxRateBasis ("reference_table", "caller_supplied" or "unavailable"), tradeInReducesTaxableAmount, taxableBase (what is actually taxed after any trade-in credit and rebate), salesTax, the three fee fields — titleAndRegistration, documentationFee and feesTotal — total (tax plus fees, null when the tax could not be estimated), an echo of price and state, rateTableAsOf dating the table, source "carscrape" and the standard meta envelope whose coverage notes carry the not-tax-advice and state-level-only caveats in prose.
A state code resolves to two things in the reference table: a state-level motor-vehicle sales or use tax rate, and a boolean for whether that state lets a trade-in allowance reduce the taxable amount. The taxable base is the price minus the rebate, minus the trade-in value only where the state permits it, floored at zero. The rate is applied to that base to give the tax. Title, registration and documentation fees are added from national planning averages unless you replace them. Supplying tax_rate overrides the rate but keeps the state's trade-in rule, so a local surtax can be folded in without losing the structural correctness. An unknown state short-circuits: the rate, the taxable base and the tax all come back null, the fees still come back, and taxRateBasis records why.
Data source
A state-level reference table of motor-vehicle sales/use tax rates and trade-in credit rules, plus national planning averages for title, registration and documentation fees.
Route budget
5 s
Billed per
calculation, successful responses only
MCP tool
get_purchase_costs
Limits: what this endpoint will not do
THIS IS AN ESTIMATE, NOT TAX ADVICE, and the response says so in every payload. The rate table is state-level only: county, city and special-district surtaxes are excluded and are material in several states, so a figure here can be meaningfully below what a buyer actually pays. rateTableAsOf dates the table and rates change by legislation — treat a stale date as a reason to verify with the state revenue department or the selling dealer before quoting. An unknown or unsupported state returns a null taxRate and a null salesTax rather than a guessed rate, which is deliberate: a wrong tax number that looks confident is worse than an absent one. Title, registration and documentation fees are national planning averages rather than the filing state's actual schedule, and dealer doc fees in particular are capped by law in some states and unregulated in others, so replace them with real figures whenever you have them. Lease taxation is not modelled — several states tax leases on the monthly payment rather than the capitalized cost, and this endpoint prices a purchase. Private-party sales, out-of-state purchases and use-tax credits for tax already paid elsewhere are likewise out of scope. The endpoint is available now and successful responses use the published catalog rate. Standard plans include no SLA.
What teams build with it
Out-the-door pricing on a dealer or marketplace site
A deal sheet or out-the-door price calculator needs tax and fees on top of the sale price, and needs the trade-in handled correctly or the total is wrong by hundreds. Chain it with the loan endpoint — take total from here, roll it into fees there — and the financed amount reflects what the buyer will actually sign for.
State-aware pricing for a national marketplace
A multi-state marketplace showing the same vehicle to buyers in different states can show each one their own estimated tax rather than a single national figure that is wrong everywhere. Because unknown states return null instead of a guess, the UI can fall back to "check with your dealer" rather than displaying a confident wrong number.
Quantifying the trade-in tax benefit
A trade-in valuation flow can quantify the tax benefit of trading in versus selling privately: call twice, once with trade_in_value and once without, and the difference in salesTax is the credit the state grants. In a state with no trade-in credit the difference is zero, which is itself worth telling the seller.
Out-the-door questions in an AI assistant
An AI assistant asked "what will this cost out the door" can call the MCP tool and, because the tool description carries the not-tax-advice framing and the state-level-only caveat, relay the estimate with its limits attached instead of presenting it as a settled figure.
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([]);
export async function getPurchaseCosts(params: Record<string, string>, attempt = 0): Promise<GetPurchaseCostsResponse> {
const response = await fetch(
`${API}/v1/vehicles/purchase-costs?${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 GetPurchaseCostsResponse;
// 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 getPurchaseCosts(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. 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 calculation. The platform fee buys throughput and endpoint access; the unit price covers the data work. Worked at 250,000 successful calls a month so you can see where the plans cross over — the full price matrix is in the reference.
Car Sales Tax API pricing by plan
Plan
Platform fee
Rate limit
Per calculation
250,000 / month
Starter
$0 / mo
5 rps
$0.001
$250$250 usage + platform fee
Pro
$299 / mo
10 rps
$0.0007
$474$175 usage + platform fee
Scale
$599 / mo
50 rps
$0.0005
$724$125 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 does the trade-in rule matter more than the rate?
Because it is the part that actually moves the number. Most states let a trade-in allowance reduce the amount taxed, and a handful tax the full sale price regardless. On a $40,000 car with a $10,000 trade at a 6.25% rate, getting that rule wrong misstates the tax by $625 — far more than the error from a rate being a quarter point stale. tradeInReducesTaxableAmount and taxableBase both come back so you can show the buyer why the number is what it is.
What happens for a state you do not have a rate for?
taxRate and salesTax both come back null, and taxRateBasis is "unavailable". Nothing is guessed. Pass tax_rate with a rate you trust and the calculation proceeds — and if we know the state's trade-in rule, it is still applied to your rate, so an override does not lose the structural part.
Can I supply my own rate and fees?
Yes, and it is the recommended way to handle local surtaxes: pass the combined state-plus-local rate as tax_rate. taxRateBasis flips to "caller_supplied" so your own logs record that the figure was not ours. Similarly, title_and_registration and doc_fee replace the national planning averages with the filing state's real schedule or your dealership's actual fee.
Does it handle leases?
No. Several states tax leases on the monthly payment rather than the capitalized cost, and modelling that properly needs lease terms this endpoint does not take. Using a purchase estimate for a lease will overstate the tax substantially in those states, so do not — treat lease taxation as out of scope until it is explicitly supported.
What does it cost?
It makes no upstream call, so it is priced at the floor alongside the loan calculator: it shares Starter's 1,000-call monthly allowance, then $0.001 per call on Starter, $0.0007 on Pro and $0.0005 on Scale. The table only changes when legislation does, so cache by state and price band and you will make very few calls.
Where to go next
Most integrations chain two or three of these. The ones that pair with car sales tax api most often:
Auto Loan Calculator APIMonthly payment, total interest and the per-year interest split for an auto loan — including the negative trade-in equity most calculators get backwards.
Total Cost of Ownership APIFive-year cost of ownership across depreciation, fuel, upkeep, tax and financing — with every component labelled by how much of it we actually measured.
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.