State tax docs / Library
The state tax engine as a library
The engine behind POST /api/state-tax is also available as a library you run inside your own application: all 50 states and the District of Columbia, the same rules, the same citations, and the same output, byte for byte. No network call, no per-request latency, and no household data leaving your system.
One engine, the same answer
The library is compiled from the same C sources as the API, and its JSON entry point uses the API's own request evaluator. Every build is checked by a parity gate: a generated corpus of 6,814 requests (every state, every filing status, incomes from zero to tens of millions, every age threshold the engine uses, and deliberately invalid requests) is run through the C library, the WebAssembly package, the Python package and the production API handler, and all four must return identical bytes for every request.
One build setting matters more than it looks: the library is always compiled without fused multiply-add. When we compiled the same sources with it allowed, 268 of the 6,814 results changed in their last digits. The build pins the setting and the test suite rejects a binary that contains a fused instruction, so a figure from the library and a figure from the API always agree.
What you receive
| Package | For | Size |
|---|---|---|
| C library (shared + static), header, CMake and pkg-config files | C, C++, and any language with a C foreign-function interface | 99 KB download |
| npm package (WebAssembly + TypeScript types) | Browsers, Node.js 18+, web workers | 33 KB gzipped module |
| Python wheel | CPython 3.9 and later, one wheel for all versions | 48 KB |
| Examples for Go, Rust, Java 22, .NET 8 and Swift | Calling the C library from those languages | source |
The Linux build targets the manylinux2014 baseline (glibc 2.17), so one binary loads on mainstream Linux distributions from 2014 on. Other platforms (macOS, Windows, ARM64, mobile) are built on request. Every release is reproducible: rebuilding it from the same sources produces the same bytes, and ships with SHA-256 checksums.
Using it
JavaScript or TypeScript, in the browser or in Node:
import loadStateTax from '@quantcalc/statetax';
const st = await loadStateTax();
const r = st.compute({
state: 'NY', filingStatus: 'mfj', age: 68,
ordinaryIncome: 92000, socialSecurity: 34000, capitalGains: 15000,
});
r.tax; // the same figure the API returns
r.rules.socialSecurityRule; // and the rules that produced it
Python:
import quantcalc_statetax as st
doc = st.compute({"state": "NY", "filingStatus": "mfj", "age": 68,
"ordinaryIncome": 92000, "socialSecurity": 34000})
C:
qcst_request req;
qcst_request_init(&req);
snprintf(req.state, sizeof req.state, "NY");
req.filing_status = QCST_FILING_MFJ;
req.age = 68;
req.ordinary_income = 92000;
req.social_security = 34000;
qcst_result res; qcst_result_init(&res);
qcst_error err; qcst_error_init(&err);
if (qcst_compute(&req, &res, &err) == QCST_OK)
printf("%s: $%.2f\n", res.state_name, res.tax);
Each language gets two ways in. The JSON interface takes the API's
request and returns the API's response, including the full rules block. The
typed interface takes a struct or object and returns the headline figures
without any JSON, for tight loops such as a Monte Carlo projection.
Request and response
The fields are exactly those of the HTTP API, documented at /docs/state-tax-api/. The per-spouse ages and the income types (wages, IRA distributions, private, public and military pensions, Roth conversions) are explained in couples and income types. A field you do not supply is treated as absent, not as zero, exactly as when it is missing from a JSON request.
Errors
Every call returns a status code: OK, INVALID_ARGUMENT (a
programming error such as a missing pointer), INVALID_JSON,
INVALID_REQUEST (for example an unknown state, a negative amount, or an age
outside 0–120), UNSUPPORTED (such as a tax year the build does not
carry), OUT_OF_MEMORY or INTERNAL. A rejected request comes
with the API's own message, and the JSON interface also returns the API's exact
{"error": "…"} body, so your application can relay it unchanged.
Threads
Every function is safe to call from any number of threads at once, with no locking on
your side. The rate tables are built once, on first use or when you call
qcst_init(), and are read-only afterwards; there is no other shared state.
The test suite proves this under ThreadSanitizer, including sixteen threads racing to
make the very first call. The Python package releases the interpreter lock during each
call, so a thread pool computes in parallel.
Versions and the annual update
| Number | Example | Changes when |
|---|---|---|
| Library | 1.0.0 | The programming interface changes. Within version 1, nothing is ever removed, so updates are drop-in replacements. |
| Engine | 2026.1.0 | Any rule, rate or threshold changes: tax year, rule revision, fix. |
| Tax year | 2026 | The law the engine encodes. |
All three can be read at run time, along with a fingerprint of the exact rule sources compiled in. Each new tax year ships as a new engine in a drop-in release; corrections within a year ship as patch releases whose changelog names the states and rules affected. A request can state the tax year it expects, and the library refuses to answer with a different year's law. How each update is monitored, gated and released is described in the update cycle, and how correctness is established, state by state, in verification.
Performance
Measured on one core of an Intel Core i7-12800H laptop (Linux):
| Interface | Calculations per second |
|---|---|
| C, typed | about 5 million (0.2 µs each) |
| C, JSON in and out | about 250,000 |
| WebAssembly (Node), typed | about 710,000 |
| WebAssembly (Node), JSON | about 92,000 |
| Python, batch | about 300,000 |
A 10,000-path, 40-year Monte Carlo projection needs 400,000 state-tax evaluations; through the typed C interface that is under a tenth of a second on one core. Loading the WebAssembly module takes about 5 milliseconds.
Security and privacy
- The library is a pure calculation: it opens no files or network connections, reads no environment variables and sends no telemetry.
- The WebAssembly module needs no
eval. It runs under a strict Content Security Policy that adds only'wasm-unsafe-eval'. - The test suite, including a fuzzer that fed one million malformed requests to the JSON interface, runs clean under AddressSanitizer and UndefinedBehaviorSanitizer.
- Only the documented functions are exported, so the library cannot clash with other code in your application, including your own copy of the JSON parser it uses internally.
Licensing
The library is licensed commercially, for embedding in a named product. Get in touch at [email protected] with your platform and how your application will be deployed (server, desktop, mobile or browser), and we will send an evaluation build.