State income tax calculator
With brackets of 2%, 4%, 6% and 8%, a taxable income of 80,000 owes 3,800 in state tax, an effective 4.75% even though the last dollar pays 6%. You supply the table, so any state or year works. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Income
- 80,000
- Tax
- 3,800
- Effective rate percent
- 4.75
- Marginal rate percent
- 6
- Bracket count
- 4
State income tax is almost never a single percent of every dollar. Residents, payroll teams, and planners who search for a state tax calculator need the tax owed from a caller-supplied schedule rather than a table frozen for one named state and one tax year. This calculator accepts taxable income and a bracket table with rates, walks the staircase, and returns the tax owed together with the effective rate on the whole income. Zero taxable income is valid and yields zero tax and a zero effective rate.
What a state tax bracket table means and how tax is stacked
State income tax is assessed on taxable income after that state's deductions, using a staircase of statutory rates rather than a single flat percent. A typical table lists an upper bound for each slice and the percent that applies only to dollars that sit inside that slice. The first ten thousand dollars might be taxed at two percent, the next thirty thousand at four percent, and every dollar above that at six or eight percent.
A filer with eighty thousand dollars of taxable income does not pay the top percent on the entire eighty thousand. The first slice remains at two percent, the second slice at four percent, and only the remainder at the higher bands. That stacked identity is the whole regular rate schedule for a progressive state: there is no hidden surcharge unless you put one in the table, and there is no flat override unless you send a single row.
This calculator encodes that identity as tax equals the sum over slices of slice width times rate divided by one hundred, then reports the effective rate as tax divided by income when income is positive and as zero when income is zero. Zero taxable income is a valid input and yields zero tax. You supply the table yourself, so the same engine can represent New York, North Carolina, a historical year, or a what-if reform without waiting for a hardcoded statute.
The last row is the top bracket: its rate applies to all remaining taxable income even if you also wrote a finite upper bound. A negative rate on any row is rejected, because a statutory percent cannot be less than zero.
Where a generic state tax calculator earns its keep
Households comparing two states, two tax years, or a proposed reform can send the same taxable income twice with different bracket tables and read the tax owed, the last-dollar rate, and the effective rate without waiting for a named-state landing to be published. Payroll and compensation teams that already know taxable wages and the current statutory schedule can quote an annual state-tax line for offer letters and year-end estimates, then layer federal withholding, FICA, and credits in their own engine.
Exam-prep generators and consumer-math classrooms use the same contract so a student who multiplies the whole income by the top percent is marked wrong, because progressive state tax is stacked, not flat. This landing targets people searching for a state tax calculator, a state income tax calculator, or tax owed from a bracket table; it is not a sales-tax tool, not a federal 1040 estimator, not a California-only or Illinois-only statute, and not a credit optimizer.
Prefer this capability when you already have taxable income and a schedule of rates and want the staircase applied.
What you can do with it
Any-state tax from a published bracket table
Households and advisors paste this year's statutory slices and taxable income to display state tax owed, last-dollar rate, and effective rate before credits.
Compare two states or two reform tables
Send the same taxable income with two different bracket tables and compare stacked tax without waiting for a named-state calculator to be hardcoded.
FAQ
How is state income tax computed here?
The calculator walks your bracket table in order, taxing each slice of taxable income at that row's percent, with the last row as the open top band. The result is the tax owed, rounded to cents, plus the effective rate on the whole income.
Are the rates percents or decimals?
Percents. Send 4 for four percent, 6.5 for 6.5 percent. Do not send 0.04 unless you truly mean four hundredths of a percent.
What happens if a bracket rate is negative?
A statutory bracket rate cannot be less than zero, so the calculator never invents a refund. Failed validation is not billed.
Is the input gross pay or taxable income?
Taxable annual income after that state's deductions, not gross wages. The engine does not subtract a standard deduction or apply credits; supply income already reduced to the taxable amount, plus the table you want applied.
What does the state tax calculator cost?
The browser tool is free and runs locally.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/fin/state-tax \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"income":80000,"brackets":[{"up_to":10000,"rate":2},{"up_to":40000,"rate":4},{"up_to":80000,"rate":6},{"rate":8}]}'const res = await fetch("https://api.kit.forhosting.com/fin/state-tax", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"income": 80000,
"brackets": [
{
"up_to": 10000,
"rate": 2
},
{
"up_to": 40000,
"rate": 4
},
{
"up_to": 80000,
"rate": 6
},
{
"rate": 8
}
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/fin/state-tax",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"income": 80000,
"brackets": [
{
"up_to": 10000,
"rate": 2
},
{
"up_to": 40000,
"rate": 4
},
{
"up_to": 80000,
"rate": 6
},
{
"rate": 8
}
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/fin/state-tax", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"income":80000,"brackets":[{"up_to":10000,"rate":2},{"up_to":40000,"rate":4},{"up_to":80000,"rate":6},{"rate":8}]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"income":80000,"brackets":[{"up_to":10000,"rate":2},{"up_to":40000,"rate":4},{"up_to":80000,"rate":6},{"rate":8}]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/fin/state-tax", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"income": 80000,
"brackets": [
{
"up_to": 10000,
"rate": 2
},
{
"up_to": 40000,
"rate": 4
},
{
"up_to": 80000,
"rate": 6
},
{
"rate": 8
}
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "fin.state_tax",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_abs | 1000000000000 |
max_items | 50 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Most states stack a table of brackets: the first slice of taxable income is taxed at one statutory rate, the next slice at a higher rate, and every remaining dollar at the top rate. A negative bracket rate is rejected with invalid_input instead of inventing a refund.
What a state tax bracket table means and how tax is stacked
Negative taxable income is rejected with invalid_input rather than a negative tax that would look like a refund.
Input contract, response shape, and error behaviour
Rates above one hundred percent are rejected. Failed validation is never charged. Prefer numbers over currency-formatted strings. Send a JSON body with a required income field and a required brackets field. income (aliases taxable_income, taxable, amount) is state taxable annual income in currency units after that state's deductions, not gross wages and not federal taxable income if those differ. It may arrive as a JSON number or as a numeric string that matches a strict finite pattern without dollar signs or thousands separators. Absolute value must stay within one trillion so the product remains finite in IEEE double precision on the Worker and in the browser. A negative income is rejected with invalid_input; zero is allowed and yields tax zero and an effective rate of zero. brackets (aliases table, rates, schedule) is a non-empty list of at most fifty rows. Each row needs rate (aliases percent, tax_rate) as a percent such as 4 for four percent, and may include up_to (aliases to, max, ceiling) as the slice ceiling. Omit up_to on the top open-ended bracket. Only the last row may omit up_to; duplicate ceilings are rejected. A negative bracket rate is the hard error this landing exists to enforce: invalid_input naming the row, never a refund. On success the response echoes income rounded to cents, returns tax as two-decimal money, reports effective_rate_percent as tax divided by income or zero when income is zero, reports marginal_rate_percent on the last dollar that was taxed, reports bracket_count, and restates the formula. Pricing is a flat $0.002 per successful request with unit item. Pair a classic eighty-thousand-dollar case against a four-row table with a higher-income case that reaches the top band and a deliberately negative rate to cover the hard invalid_input path in your own tests.
Where a generic state tax calculator earns its keep
Personal-finance apps and notebook scripts that need a deterministic state tax oracle can pin eighty thousand dollars against a known four-row table to a published tax, a one-hundred-thousand-dollar case to the top band, and a deliberately negative rate to the hard invalid_input path. If you need a frozen Franchise Tax Board table or a single Illinois flat percent, use a named-state endpoint instead. Failed tasks that return invalid_input do not consume prepaid balance, which keeps exploratory scripts cheap when developers deliberately send a negative bracket rate. Round-trip checks are easy: recompute each slice by hand, confirm the published tax within two-decimal rounding, then confirm that tax divided by income matches effective_rate_percent at the published $0.002 automation rate.
Integration use cases
Payroll estimates and golden tests
Compensation tools and QA suites pin known taxable incomes to a deterministic tax, and reject a negative bracket rate with invalid_input.