estimatetax
2026 · REST · No key required

Tax calculation API

The same deterministic engine the site runs on, as an endpoint. Federal, all 51 state jurisdictions and the local layer, with the source and verification date for every figure in the response.

Endpoint

GET or POST /api/v1/calculate

curl "https://estimatetax.net/api/v1/calculate?income=85000&state=NY&city=new-york-city"

Parameters

NameTypeRequiredNotes
incomenumberYesAnnual gross wage income. 0 to 100,000,000.
filingStatusstringNosingle (default), married_jointly, married_separately, head_of_household.
statestringNoTwo-letter code. Omit for federal only. All 51 supported.
citystringNoSlug of a local jurisdiction, where the state has one. Requires state.
preTaxDeductionsnumberNo401(k), HSA, health premiums. Reduces the base for income tax, not FICA.
selfEmploymentIncomenumberNoNet profit. Adds self-employment tax and its deduction.

Response

{
  "taxYear": 2026,
  "input": { "income": 85000, "filingStatus": "single", "state": "NY", "city": "new-york-city" },
  "federal": {
    "adjustedGrossIncome": 85000,
    "deduction": 16100,
    "taxableIncome": 68900,
    "incomeTax": 9870,
    "socialSecurity": 5270,
    "medicare": 1232.5,
    "totalFica": 6502.5,
    "marginalRate": 0.22,
    "brackets": [ { "rate": 0.1, "taxableInBracket": 12400, "tax": 1240 }, ... ]
  },
  "state": { "code": "NY", "name": "New York", "tax": 3993, "system": "graduated" },
  "local": { "jurisdiction": "New York City", "tax": 2859.52, "rate": 0.0371, "base": "city_taxable" },
  "totals": { "totalTax": 23225.02, "netIncome": 61774.98, "effectiveRate": 0.2732 },
  "sources": {
    "federal": { "citation": "IRS Rev. Proc. 2025-32, § 3.01, Tables 1-4", "verified": "2026-08-31" },
    "state":   { "authority": "New York Department of Revenue", "checked": "2026-09-01" },
    "local":   { "document": "NYS Form IT-201-I", "checked": "..." }
  }
}

What it computes

Federal income tax with the full bracket breakdown, FICA including the wage base and the additional Medicare tax, self-employment tax where applicable, state income tax for all 51 jurisdictions, and local income tax for 3,672 jurisdictions across 7 states.

It is deterministic. The same inputs always produce the same output, there is no model involved anywhere in the path, and nothing is estimated or sampled — which is why responses are cacheable and why the endpoint costs nothing to run at volume.

Every response carries a `sources` object naming the document each figure came from and the date it was verified. That is the part we would argue makes the data worth integrating rather than merely available: if you display our figures to your users, you can display their provenance too.

It also carries a `disclaimer` field, because an API that returns tax figures without one is handing an integrator a liability they did not ask for.

Limits, and what is not promised

60 requests per minute per address. No key, no registration, no quota beyond that. Exceeding it returns 429 with a plain message.

Cacheable. Responses set a 24-hour cache header with a week of stale-while-revalidate. Tax figures change on legislative calendars, not by the second, and caching is encouraged rather than merely tolerated.

CORS is open, so the endpoint can be called from a browser as well as from a server.

No availability commitment. This is the open endpoint. If you need guaranteed availability, higher volumes or a contractual arrangement, that is what the business page is for.

No versioning surprises. The path carries `/v1/`. Breaking changes go to a new version rather than to this one, and any introduction of keys, quotas or pricing will be announced here with a date and a transition period.

What the endpoint does not model

It assumes the standard deduction unless `preTaxDeductions` is supplied. It does not model itemised deductions, capital gains, credits, retirement income, rental income or the alternative minimum tax.

The specialist calculators on this site handle several of those, and they are not currently exposed through the API. If one of them is what you need, the business page is the route.

It does not model partial-year residency, income earned across state lines, or reciprocity agreements. Those depend on facts an endpoint cannot know from a salary and a state code.

Where a state figure has not been read off a primary source, the `sources` object still names what it was read from — so the response tells you the provenance rather than implying uniform verification.

Errors, and what the endpoint will not do

Every numeric field is validated before anything is computed, and an invalid request returns a 400 naming the parameter and what it expected. It will not return a 500, and it will not return a plausible-looking wrong number for input it could not parse.

That is a deliberate choice with history behind it. An earlier endpoint on this site validated two of nine numeric fields and then called formatting functions on the rest — so a request missing one field crashed the function instead of explaining itself. It is on the corrections page.

`400` with a message for a malformed request or an unknown state code. `429` with a plain message when the rate limit is hit. `204` for a CORS preflight. Anything else is a bug and worth reporting.

The endpoint returns the same figures the website shows for the same inputs, because it runs the same engine — not a parallel implementation that could drift. If a page and the API ever disagree, that is a bug rather than a difference in scope.

Notes for integrating it

Cache aggressively. Tax figures change on legislative calendars. Caching a response for a day costs you nothing in accuracy and removes almost all of your traffic.

Display the provenance. The `sources` object exists so that your users can see where a figure came from. Passing that through is what turns a number in your product into a number your support team can defend.

Show the disclaimer. The response includes one. An estimate presented in a product without any indication that it is an estimate creates a problem for you rather than for us.

Handle a null `state` and a null `local`. Omitting the state parameter is valid and returns federal only; requesting a city in a state without a local income tax returns `local: null` rather than an error.

Do not derive rates from the response and store them. The figures are correct on the day of the request and carry a verification date for exactly that reason. Storing a derived rate table recreates the staleness problem this whole site exists to argue against.

Why every response carries its sources

Most tax APIs return numbers. This one returns numbers with the document each came from and the date it was checked, and that is the design decision we would most defend.

The reason is what we found auditing this category: the arithmetic almost never fails and the data often does. A rate entered once and never revisited produces an internally consistent, confidently returned, wrong answer — and nothing in a plain JSON number reveals it.

For an integrator that is a support problem waiting to happen. When a user disputes a figure in your product, the useful response is the document and the date rather than a promise that the vendor keeps things updated.

It is also a check on us. Publishing the provenance in every response means an out-of-date figure is visible to anyone who looks, which is a considerably stronger discipline than an internal process nobody can inspect.

What is not exposed yet

The specialist engines are not on the API. Self-employment tax with QBI, capital gains with correct stacking, the provisional income rule for Social Security, rental income with depreciation and passive-loss limits, the refundable credits and the estimated tax safe harbour all exist and are tested — they are simply not endpoints.

Property tax is the other one. All 3,143 counties are loaded as effective rates computed from tax actually paid, and there is no endpoint for looking one up.

Which of these gets exposed next is driven by what people ask for rather than by a roadmap decided in advance. If one of them is what you need, say so through the contact page and it moves up.

What will not change is the shape: deterministic, source-attributed, cacheable, and honest about what it does not model. An endpoint that returns a confident number for a case the engine did not compute is the specific failure this whole site is built against.

Where to go next

Questions

Do I need an API key?
Not currently. The endpoint is open with a 60-requests-per-minute limit per address. If keys are introduced, it will be announced on this page with a date and a transition period rather than applied without notice.
Is it free?
The open endpoint is free within the rate limit and carries no availability commitment. Higher volumes, guaranteed availability or a commercial arrangement are what the business page is for.
Can I call it from the browser?
Yes — CORS is open and GET is supported, so a query string call from client-side JavaScript works. POST with a JSON body is also supported for server-side use.
How current are the figures?
Every response includes the source document and verification date for each figure used. Federal figures come from IRS Rev. Proc. 2025-32, § 3.01, Tables 1-4; state figures from each state's own department of revenue. The changelog records every change.
Can I cache responses?
Yes, and you should. The endpoint sets a 24-hour cache header with a week of stale-while-revalidate. Tax figures change on legislative calendars, and re-requesting an identical calculation is wasteful rather than prohibited.
What happens if I send something invalid?
A 400 with a message naming the parameter and what it expected — not a 500 and not a silently wrong answer. Every numeric field is validated before anything is computed.