Federal tax / Library
The federal tax engine as a library
The engine behind POST /api/federal-tax is also a library you run inside your own application (libfedtax 1.0.0, engine 1.3.0, tax year 2026): the same C code, 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. It is the sibling of the state tax engine library.
One engine, two doors
The JSON entry point (qcft_compute_json) is the API's own request parser, field table, engine call and response
builder, compiled unchanged: for any request it returns the bytes the API returns, including the 400 body of a refusal. The
schema and rules documents (GET /api/federal-tax/schema and /rules) are there too, byte for byte.
The typed entry point (qcft_compute) takes a C struct and returns the result's figures; it runs the API's own
parser and evaluator on the request, so a refused typed request carries the API's message.
What you get
| Artefact | Size | For |
|---|---|---|
libfedtax-1.0.0-linux-x86_64.tar.gz | 261 KB | C/C++ and any language with a C FFI: libfedtax.so.1, libfedtax.a, the header, a CMake package and pkg-config |
quantcalc-fedtax-1.0.0.tgz | 99 KB | npm: WebAssembly, ESM glue and TypeScript types (Node 18 or later, browsers, workers) |
quantcalc_fedtax-1.0.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | 122 KB | Python 3.9 or later: one wheel for every CPython version, typed |
libfedtax-1.0.0-src.tar.gz | 178 KB | the C source drop, when the licence includes source |
Thin bindings for Go, Rust, Java, .NET and Swift call the JSON entry point of the same shared library. Sizes measured on the 1.0.0 release build (engine 1.3.0) of 2026-09-28; every artefact is rebuilt byte for byte from the same sources, with SHA-256 checksums and a manifest of the pinned toolchains.
Without the citations
Each full response carries the official sources of every step on the return, with their checked quotes. A build without them
answers every request as the full library does, minus rules.sources and rules.unsourced (the blocks
includeSources: false leaves out); every figure is the same. The sources stay published on the
sources page.
| Artefact | Size | For |
|---|---|---|
libfedtax-nosources-1.0.0-linux-x86_64.tar.gz | 168 KB | the C library without the citations |
quantcalc-fedtax-nosources-1.0.0.tgz | 75 KB | npm @quantcalc/fedtax-nosources |
quantcalc_fedtax_nosources-1.0.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | 85 KB | Python distribution quantcalc-fedtax-nosources (import name quantcalc_fedtax) |
libfedtax-nosources-1.0.0-src.tar.gz | 178 KB | the source drop, built without the citations |
Size budgets are enforced on every release build (bytes, measured and budget):
| Binary | Full | Without citations |
|---|---|---|
| libfedtax.so.1, stripped | 370,688 (budget 445,000) | 194,560 (budget 229,000) |
| libfedtax.a | 514,608 (budget 613,000) | 260,088 (budget 310,000) |
| fedtax.wasm, raw | 229,436 (budget 274,000) | 146,295 (budget 175,000) |
| npm WebAssembly + glue + wrapper + layout, gzipped | 91,413 (budget 111,000) | 65,770 (budget 81,000) |
The typed request
The typed request is generated from the API's field table, so it has one member per request field and nothing else. When the API gains a field, a regeneration and a rebuild add it at the end of the struct, and the build fails until that is done. The offsets below are the library's binary interface: they never move within a major version.
| Kind | C type | Not sent |
|---|---|---|
| one of the listed strings | int32_t (index) | QCFT_ENUM_ABSENT |
| whole number | int32_t | QCFT_INT_ABSENT |
| boolean | int32_t | QCFT_BOOL_ABSENT |
| amount | double | QCFT_ABSENT |
| list of {year, taxable, nontaxable} | _count + qcft_roth_conversion[16] | count QCFT_LIST_ABSENT |
| up to 4 amounts | _count + double[4] | count QCFT_LIST_ABSENT |
| up to 4 dates | _count + int32_t[4] (YYYYMMDD) | count QCFT_LIST_ABSENT |
| JSON field | C member | Kind | Offset |
|---|---|---|---|
filingStatus | filing_status | one of the listed strings: single, mfj, married_filing_jointly, mfs, married_filing_separately, hoh, head_of_household, qss, qualifying_surviving_spouse | 8 |
age | age | whole number | 12 |
spouseAge | spouse_age | whole number | 16 |
mfsLivedApart | mfs_lived_apart | boolean | 20 |
iraDistributions | ira_distributions | amount | 24 |
rothConversion | roth_conversion | amount | 32 |
pension | pension | amount | 40 |
otherOrdinaryIncome | other_ordinary_income | amount | 48 |
shortTermGains | short_term_gains | amount | 56 |
longTermGains | long_term_gains | amount | 64 |
socialSecurityGross | social_security_gross | amount | 72 |
taxExemptInterest | tax_exempt_interest | amount | 80 |
includeSources | include_sources | boolean | 88 |
birthMonth | birth_month | whole number | 92 |
blindCount | blind_count | whole number | 96 |
saltIncomeOrSalesTax | salt_income_or_sales_tax | amount | 104 |
saltRealEstateTax | salt_real_estate_tax | amount | 112 |
saltPersonalPropertyTax | salt_personal_property_tax | amount | 120 |
otherTaxes | other_taxes | amount | 128 |
mortgageInterest | mortgage_interest | amount | 136 |
investmentInterest | investment_interest | amount | 144 |
charityCash | charity_cash | amount | 152 |
charityCashDaf | charity_cash_daf | amount | 160 |
charityCapitalGainProperty | charity_capital_gain_property | amount | 168 |
medicalExpenses | medical_expenses | amount | 176 |
otherItemized | other_itemized | amount | 184 |
itemizeMode | itemize_mode | one of the listed strings: auto, itemize, standard | 192 |
mfsSpouseItemizes | mfs_spouse_itemizes | boolean | 196 |
employerPlanDistribution | employer_plan_distribution | amount | 200 |
separationYearAge | separation_year_age | whole number | 208 |
publicSafetyEmployee | public_safety_employee | boolean | 212 |
seppDistribution | sepp_distribution | amount | 216 |
disabled | disabled | boolean | 224 |
iraBasis | ira_basis | amount | 232 |
iraYearEndValue | ira_year_end_value | amount | 240 |
rothDistribution | roth_distribution | amount | 248 |
rothContributions | roth_contributions | amount | 256 |
rothFirstContributionYear | roth_first_contribution_year | whole number | 264 |
rothConversions | roth_conversions | list of {year, taxable, nontaxable} | 268 |
iraBalancePriorYearEnd | ira_balance_prior_year_end | amount | 656 |
spouseSoleBeneficiary | spouse_sole_beneficiary | boolean | 664 |
beneficiarySpouseAge | beneficiary_spouse_age | whole number | 668 |
rmdShortfall | rmd_shortfall | amount | 672 |
rmdShortfallCorrected | rmd_shortfall_corrected | boolean | 680 |
priorYearTax | prior_year_tax | amount | 688 |
priorYearAgi | prior_year_agi | amount | 696 |
priorYearFullYearReturn | prior_year_full_year_return | boolean | 704 |
withholding | withholding | amount | 712 |
estimatedPayments | estimated_payments | up to 4 amounts | 720 |
estimatedPaymentDates | estimated_payment_dates | up to 4 dates | 760 |
taxableInterest | taxable_interest | amount | 784 |
ordinaryDividends | ordinary_dividends | amount | 792 |
qualifiedDividends | qualified_dividends | amount | 800 |
capitalLossRules | capital_loss_rules | boolean | 808 |
capitalLossCarryoverShortTerm | capital_loss_carryover_short_term | amount | 816 |
capitalLossCarryoverLongTerm | capital_loss_carryover_long_term | amount | 824 |
With the state engine
The state tax library (libstatetax 1.6.0) carries this engine: its JSON entry point answers
computeFederal: true as POST /api/state-tax does, with the federal engine
supplying the federal figures the state rules read. It is one binary, so the two engines are always the same version.
The same answer, checked on every build
A parity gate runs on every build. On 2026-09-28 (engine 1.3.0) it sent a generated corpus of 16,551 requests (13,478 computed, 3,073 refused: every filing status and age gate, every one of the 56 request fields at and past its limits, extended returns, every refusal the API makes, malformed bodies) through four surfaces: the C library, the WebAssembly package, the Python package and the API's own request handler. All four must produce the same bytes for every request, and the 11,300 requests the typed entry point can express must give the same document through it. The build also runs the whole test suite under address, undefined-behaviour and thread sanitizers, fuzzes both entry points, and checks that the binary exports only its own functions.
Performance
Measured 2026-09-28 (engine 1.3.0) on Intel Core i7-12800H, one core; 1,547,861 typed calls a second on 8 threads.
| Surface | Call | Calls a second | Microseconds a call |
|---|---|---|---|
| C | typed (qcft_compute) | 278,528 | 3.6 |
| C | JSON, includeSources false | 36,864 | 27.1 |
| C | JSON, the full document with its sources | 7,339 | 136.3 |
| C | JSON, an extended return with its sources | 3,584 | 279.0 |
| WebAssembly (Node) | typed (compute) | 153,271 | 6.5 |
| WebAssembly (Node) | JSON, includeSources false | 20,089 | 49.8 |
| WebAssembly (Node) | JSON, the full document | 3,906 | 256.0 |
| Python | typed (compute) | 61,052 | 16.4 |
| Python | typed, compute_batch of 1,000 | 69,433 | 14.4 |
| Python | JSON, the full document | 6,462 | 154.8 |
Most of a JSON call is building and printing the document, and the sources are most of the document: send
includeSources: false, or use the typed entry point, when a response does not need them.
Versions and updates
The library version follows semantic versioning (additions are minor versions; a break is a new major version with a new
shared-library name); the engine version, reported as engineVersion, moves whenever a rule, rate or threshold moves;
the tax year is the law the build encodes.
Licence
The library is licensed commercially. Ask at [email protected].