The figures behind every calculator on this site, served as typed JSON. All 50 states: how each treats pension income, its retirement system and benefit formula, what members contribute, what they earn on average, and whether they are covered by Social Security.
No key, no sign-up, no rate limit. Static JSON on a CDN, readable from the browser.
Nevada is the record worth starting with, because it is the one a flat number gets wrong. Teachers there have nothing deducted from their pay — the employer contributes the full 36.75% instead, in exchange for a correspondingly lower base salary. A single employee_contribution: 36.75 would state the opposite of what happens.
{
"api_version": 1,
"license": "https://creativecommons.org/licenses/by/4.0/",
"attribution": "Teacher Retirement Tools — https://www.retirement-tax.com",
"schema": "https://www.retirement-tax.com/data/v1/schema.json",
"data_updated": "2026-07-28",
"state": {
"slug": "nevada",
"name": "Nevada",
"abbr": "NV",
"pension_system": {
"name": "Public Employees' Retirement System of Nevada",
"acronym": "NVPERS"
},
"supplemental_account_type": "403(b)",
"benefit_formula": "AMC × 2.5% × years (2.25% if enrolled on/after 7/1/2015; 2.67% for service after 7/1/2001)",
"employee_contribution": {
"rate_pct": {
"min": 0.0,
"max": 0.0
},
"employer_pays_pct": 36.75,
"display": "0% deducted (employer pays 36.75%)"
},
"avg_teacher_salary_usd": 74812,
"social_security": "not_covered",
"state_income_tax": false,
"pension_income_exempt": false,
"pension_tax_rate_pct": 0.0,
"pension_tax_condition": null
}
}
A range, not a figure. Six states are not a single percentage: New York and Washington tier or elect theirs, so min and max differ. Where a rate is fixed, both are equal.
Present only where the employer pays the member’s share outright. This field exists because Nevada needs it; in the other 49 states it is null.
null means no condition is recorded — not that none exists. Where a value is present it names the cliff, age tier or credit inside the rate, which is the part a single number cannot show.
The service index: every endpoint below as an absolute URL, plus the licence and these docs. The base URL is what most people paste first, so it answers rather than 404s.
All 50 records in one response, with a metadata envelope. Around 60 KB — small enough to fetch once and filter client-side.
One state. slug is the lower-case hyphenated state name, the same identifier used in this site’s page paths — nevada, new-york, rhode-island. An unknown slug returns 404 with a JSON body, not an HTML page.
JSON Schema (draft 2020-12) for a state record. Generated from the same declaration that builds the records, so it cannot describe a field the API does not return.
OpenAPI 3.1. Import it into Postman, Bruno, or an SDK generator.
curl -s https://www.retirement-tax.com/data/v1/states.json \
| jq '.states[] | select(.pension_income_exempt) | .name'
# states where teachers are not covered by Social Security
curl -s https://www.retirement-tax.com/data/v1/states.json \
| jq -r '.states[] | select(.social_security=="not_covered") | .abbr'
Every value is a JSON type a machine can use directly, or null. Nothing here is formatted for display — no currency symbols, no "Yes" where a boolean belongs.
| Field | Type | Meaning |
|---|---|---|
slug | string | Stable identifier and the key for /data/v1/states/{slug}.json. |
name | string | State name in full. |
abbr | string | USPS two-letter abbreviation. |
pension_system | object | The retirement system covering public-school teachers. |
• name | string | null | Full legal name. |
• acronym | string | null | Common short form. |
supplemental_account_type | string | null | Tax-deferred account teachers in this state normally use alongside the pension. |
benefit_formula | string | null | How the annuity is calculated, as published by the plan. Prose, deliberately: multipliers and averaging periods differ by tier and hire date, and a single machine-readable formula would be wrong for most members of most states. |
employee_contribution | object | null | What the member contributes. A range rather than a single figure because several states tier it by salary or let the member elect it. |
• rate_pct | object | null | Percent of salary deducted from the member. min == max where the rate is fixed. null where the published wording could not be read as a number. |
• employer_pays_pct | number | null | Present only where the employer pays the member's share outright. Nevada is the case this exists for: the deduction is 0% because the employer pays 36.75%, in exchange for a correspondingly lower base salary. |
• display | string | The published wording, unmodified. |
avg_teacher_salary_usd | integer | null | Average public-school teacher salary, US dollars, whole units. |
social_security | "covered" | "not_covered" | "partial" | null | Whether teaching employment is covered by Social Security. 'partial' means coverage varies by district within the state. |
state_income_tax | boolean | null | Whether the state levies a personal income tax at all. |
pension_income_exempt | boolean | null | Whether pension income is fully exempt from that tax at the modelled income. Exemptions that phase out with income are false here and explained in pension_tax_condition. |
pension_tax_rate_pct | number | null | EFFECTIVE state income-tax rate on an $80,000 teacher pension, single filer, age 65, after the state's standard retirement exclusion. This is NOT the top marginal rate, and comparing it against a published marginal-rate table will disagree for most states by construction. |
pension_tax_condition | string | null | The allowance, cliff or credit inside pension_tax_rate_pct, where one applies. null means no condition is recorded, not that none exists. |
v1, fields are only ever added. Nothing is removed, renamed, or changes type. Write your parser to ignore fields it does not recognise and it will keep working./data/v2/. Both serve side by side.Deprecation and Sunset response headers (RFC 9745, RFC 8594) for the whole period.data_updated and revision change only when a figure changes — not when the site is redeployed. Responses carry an ETag; send If-None-Match and expect 304.For a spreadsheet or a one-off analysis, the flat files are easier than the API. They stay published and are not going away.
In Google Sheets: =IMPORTDATA("https://www.retirement-tax.com/data/teacher-pension-tax-by-state.csv"). Prefer the API for anything a program reads — the flat files keep their display formatting for people.
Want the rates on your own page instead? One script tag, no request back to us at load time: see the embed widget →
pension_tax_rate_pct is not a state’s top marginal rate. It is the effective rate on an $80,000 teacher pension — single filer, age 65, after the state’s standard retirement exclusion. A state whose top bracket reads 13.3% does not tax that pension at 13.3%, and comparing top marginal rates across states — which many published tables do — systematically overstates the difference between them.
Exemptions are handled first: where a state exempts public pension income outright, the rate is zero regardless of its income tax. Full derivation is on the Methodology page.
Found something wrong? Tell us — corrections with a primary source get applied fast, and a dataset people correct is worth more than one nobody reads.