TaxaFi API

v1US sales-tax calculation & filing

Overview

The TaxaFi API computes US sales and use tax for individual transactions and produces the state return a merchant must file. It is API-first and JSON over HTTPS. TaxaFi is the US system of record: it holds verified rates, jurisdiction boundaries, taxability rules, and every state return form, and returns both the computed figures and the assembled filing. The filing partner submits what TaxaFi returns.

Base URL

https://maxafi.com/taxafi/v1/{action}
# equivalent explicit form:
https://maxafi.com/taxafi/api/taxafi.php?action={action}

The current version is v1. The version is echoed on every response under meta.api_version.

Authentication

Every request except health requires an API key passed in the X-Api-Key header. Keys are prefixed tx_.

X-Api-Key: tx_1a2b3c4d5e6f...

Each key is bound to one merchant. Every authenticated call is automatically scoped to that merchant — there is no cross-merchant access. Keys may be restricted to a set of scopes (calculate, ingest, filing); a key with no scope list is unrestricted. Missing or invalid keys return 401; a key lacking a required scope returns 403.

Conventions

Response envelope

Every response is a JSON object with a boolean success, the endpoint payload, and a meta block.

{
  "success": true,
  // ...endpoint-specific fields...
  "meta": { "response_time_ms": 12, "api_version": "v1", "engine": "taxafi-engine/1.1.0" }
}

Money

Monetary fields are decimal dollars with two places: 100.00 means $100.00. Rates are decimal fractions: 0.0825 means 8.25%.

Idempotency

For calculate, send an X-Idempotency-Key header (or idempotency_key in the body) to make the recorded calculation idempotent — a retry with the same key returns the original result instead of recording a duplicate.

Verified data

Calculation and rate responses include data_verified. When false, at least one applied rate is unverified sample data and must not be used for real remittance; a warnings entry explains which.

Filing lifecycle

The typical integration uses these calls:

StepCallYou sendYou get
1. Price each salePOST calculatethe consumer ship-to address from the transaction, amount, categorycombined rate, jurisdiction breakdown, tax (recorded)
2. Find the returnPOST filing_formthe merchant address (or state)which form applies, its channel, and its field list
3. Prepare figuresPOST prepare_returnstate + periodperiod totals and per-category figures rolled up from the recorded sales
4. Build the filingPOST assemble_filingthe period (+ payment details)the ready-to-submit payload and name→value field map (figures auto-derived)

calculate scope: calculate

POST /taxafi/v1/calculate

Computes tax for one transaction. Send the consumer's address from the transaction as ship_to; that delivery address determines the jurisdictions and rate. Optionally send ship_from (the merchant origin) for origin-sourced states.

Request

FieldTypeNotes
amountnumberRequired. Transaction amount.
ship_toAddressConsumer delivery address: street, city, state, zip, plus4.
category_codestringProduct category (see categories). Defaults to the merchant default or GEN.
ship_fromAddressOptional merchant origin, used for origin-sourced states.
currencystringDefaults USD.
datestringYYYY-MM-DD; defaults to today. Selects effective-dated rates.
source_txn_refstringYour transaction id, stored on the immutable calculation record.

Example

# request
curl -X POST https://maxafi.com/taxafi/v1/calculate \
  -H "X-Api-Key: tx_..." -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: txn_88231" \
  -d '{
    "amount": 100.00,
    "category_code": "GEN",
    "ship_to": { "street": "1100 Congress Ave", "city": "Austin", "state": "TX", "zip": "78701" }
  }'

# response
{
  "success": true,
  "calculation": {
    "combined_rate": 0.0825,
    "subtotal": 100.00, "taxable_amount": 100.00, "tax_total": 8.25, "total": 108.25,
    "treatment": "taxable", "taxability_basis": "category_default",
    "resolution": { "confidence": "zip", "resolver": "zip5", "state": "TX" },
    "breakdown": [
      { "name": "Texas", "level": "state", "rate": 0.0625, "tax": 6.25, "verified": 1 },
      { "name": "Austin", "level": "city", "rate": 0.02, "tax": 2.00, "verified": 1 }
    ],
    "data_verified": true, "warnings": []
  },
  "meta": { "api_version": "v1", "...": "..." }
}

See the full field list under Data models → Calculation. Largest-remainder rounding guarantees the per-jurisdiction tax values sum exactly to tax_total.

rates

GET /taxafi/v1/rates?zip=&plus4=&state=&street=&city=

Resolves the combined rate for an address without a dollar amount — a rate lookup. Returns the resolved jurisdiction stack, the combined_rate, per-jurisdiction lines, confidence, and data_verified. Provide at minimum zip (add plus4 or street for rooftop accuracy).

jurisdictions

GET /taxafi/v1/jurisdictions?zip=
GET /taxafi/v1/jurisdictions?state=TX

With ?zip= (or a street address) it returns the resolved jurisdiction stack for that address with a confidence. With ?state= it lists all active jurisdictions in the state ordered state → county → city → special district.

categories

GET /taxafi/v1/categories

Returns the tax category taxonomy: code, parent_code, name, default_taxable, description. Use a category code as category_code on calculate.

classify

POST /taxafi/v1/classify

Maps a free-text product description to a category code using AI. Advisory only — intended to suggest a category for review, not to drive filing unattended.

{ "description": "wireless bluetooth headphones" }  // -> { "classification": { "code": "GEN", ... } }

obligations scope: filing

GET /taxafi/v1/obligations?as_of=&due_soon_days=

The merchant's filing calendar. For each of the merchant's registrations (state + assigned frequency) it returns the just-closed period (fileable now) and the in-progress period, each with its form, due date, and status. Returns are sorted by due date so what is due next is first.

Response

{
  "as_of": "2026-07-21",
  "obligations": [
    { "state": "AL", "frequency": "monthly", "form_code": "2100",
      "period": "2026-06", "period_start": "2026-06-01", "period_end": "2026-06-30",
      "due_date": "2026-07-20", "status": "overdue" },
    { "state": "AL", "frequency": "monthly", "form_code": "2100",
      "period": "2026-07", "due_date": "2026-08-20", "status": "upcoming" }
  ]
}

status is overdue, due_soon (within due_soon_days, default 14), upcoming, or filed. Query params: as_of (YYYY-MM-DD, defaults to today) and due_soon_days.

Due dates come from a per-state rule when one is on file, otherwise a national default (monthly by the 20th of the following month, etc.); weekends roll to the next business day. The rule.source on due_date tells you which was used. Per-state due days are still being filled in, so verify the exact day for a given state.

due_date

GET /taxafi/v1/due_date?state=&frequency=&period=

The due date for one return. period is YYYY-MM, YYYY-Qn, or YYYY.

{ "state": "AL", "frequency": "monthly", "period": "2026-06",
  "period_end": "2026-06-30", "due_date": "2026-07-20",
  "rule": { "due_day": 20, "due_month_offset": 1, "source": "default" } }

prepare_return scope: filing

POST /taxafi/v1/prepare_return

Rolls the merchant's recorded calculations for a state and period into return figures — the bridge between per-sale calculate and the period return. It aggregates period totals, a per-category breakdown, and a per-jurisdiction tax rollup, and returns a flat figures map keyed to match form fields.

Request

FieldTypeNotes
state or addressstring / AddressWhich state to summarize. An address is resolved to a state.
periodstringYYYY-MM, YYYY-Qn, or YYYY. Or send explicit period_start + period_end (YYYY-MM-DD).

Response

{
  "state": "TX", "period": { "start": "2026-04-01", "end": "2026-06-30" }, "transactions": 1842,
  "totals": { "gross_sales": 184320.00, "taxable_sales": 171200.00, "exempt_sales": 13120.00, "tax_total": 14124.00 },
  "by_category": [ { "category": "GEN", "gross": 160000.00, "taxable": 160000.00, "exempt": 0.00, "tax": 13200.00 } ],
  "by_jurisdiction": [ { "name": "Texas", "level": "state", "tax": 10700.00 } ],
  "figures": { "gross_sales": 184320.00, "taxable_sales": 171200.00, "tax_due": 14124.00,
               "gross:gen": 160000.00, "exempt:gen": 0.00, "...": "..." }
}

The figures keys use the same convention as form source_fields (gross_sales, taxable_sales, exempt_sales, tax_due, and per-category gross:<cat> / exempt:<cat> / taxable:<cat> / tax:<cat>), so they feed straight into assemble_filing. Categories here are TaxaFi category codes; mapping them to a state form's own buckets (for example Alabama's amusement / automotive columns) is a per-form step.

filing_form scope: filing

POST /taxafi/v1/filing_form

Resolves which return the merchant files and returns its structure. Send the merchant's address (TaxaFi derives the state) or an explicit state. The form is chosen from the merchant's filing frequency and filer type; SST-registered merchants resolve to the Simplified Electronic Return.

Request

FieldTypeNotes
addressAddressMerchant business address. Provide this or state.
statestringTwo-letter state, if you already know it.
filing_frequencystringmonthly | quarterly | annual. The frequency the state assigned the merchant.
filer_variantstringstandard (default) | ez | others.
sstbooleanTrue if the merchant is Streamlined-registered.

Response

{
  "state": "AL",
  "form": { "form_code": "2100", "form_name": "Alabama Sales Tax Return (Form 2100)",
            "filing_channel": "csv", "filing_frequency": "any", "filer_variant": "standard" },
  "format_spec": { "layout": "positional_csv", "delimiter": ",", "date_format": "MM/DD/YYYY" },
  "fields": [
    { "position": 1, "code": "account_id", "label": "Account ID",
      "value_source": "account", "source_field": "ador_account_number", "data_type": "text" },
    { "position": 8, "code": "gross_amusement", "label": "Gross: Amusement",
      "value_source": "figure", "source_field": "gross:amusement", "data_type": "currency" }
    // ...
  ]
}
Channels. filing_channel tells you how the return is submitted: csv (positional bulk upload), pdf (fillable form fields), xml (SST SER), portal, or efile. The fields structure is the same across channels — only the submission format differs. When a state has no return (e.g. Oregon), the response has form: null with an explanatory note.

assemble_filing scope: filing

POST /taxafi/v1/assemble_filing

Builds the filing for a period. TaxaFi selects the form, pulls each field from its source, formats per the form rules, and returns both the ready-to-submit payload and a field_map of name → value. You supply the period figures (TaxaFi supplies the form structure, formatting, and validation).

Request

FieldTypeNotes
state or addressstring / AddressWhich state to file. Address is resolved to a state.
periodstringYYYY-MM, YYYY-Qn, or YYYY. Or send an explicit period_end (YYYY-MM-DD).
figuresobjectOptional. Period figures keyed by the form's source_field, e.g. {"gross:amusement": 1500.00, "tax_due": 4231.55}. If omitted, they are auto-derived from the merchant's recorded calculations for the period (see prepare_return); anything you supply overrides the derived value.
paymentobjectRemittance details for banking fields: payment_type_code, routing_number, bank_account_number, bank_account_type, payment_amount.
accountobjectAccount identifiers (e.g. ador_account_number). Falls back to the merchant's registration number.

Response

{
  "form": { "form_code": "2100", "channel": "csv", "version": "2026" },
  "channel": "csv",
  "period_end": "2026-06-30",
  "payload": "0009988776,06/30/2026,E,062000019,0001234567,C,4231.55,1500.00,200.00,...",
  "field_map": { "account_id": "0009988776", "filing_period": "06/30/2026",
                 "gross_amusement": "1500.00", "...": "..." },
  "fields": [ { "position": 1, "code": "account_id", "formatted": "0009988776", "...": "..." } ],
  "warnings": []
}
payload vs field_map. For csv, payload is the ready positional line and field_map gives the same values by name. For pdf / xml / portal, payload equals field_map (name → value), which the channel step fills into the PDF fields, SER elements, or portal inputs.
When figures are omitted they are auto-derived from recorded calculations and mapped into the form's own category buckets via its category_map (e.g. Alabama admissions to the amusement column, everything else to All Other Sales). The PDF / SER serializers are still in progress; today the csv serializer is complete.

assemble_pdf scope: filing

POST /taxafi/v1/assemble_pdf

Produces the filled return PDF for states that file on paper or on a downloadable form. It assembles the filing (same inputs as assemble_filing), then applies the resulting field map to the blank PDF the state publishes. Two kinds of PDF are handled: forms with real fields are filled directly, and flat forms have their values drawn at the coordinates the spec defines.

Request

Same as assemble_filing: the state or merchant address, the period, and optional figures / payment / account overrides. Figures are auto-derived from recorded calculations when omitted.

Response

{
  "channel": "pdf",
  "form": { "form_code": "01-114", "form_name": "Texas Sales and Use Tax Return" },
  "file": { "filename": "return_20260721_144802_a1b2c3.pdf", "bytes": 253982,
             "blank": "tx_2025.pdf", "mode": "fill" },
  "fields_applied": 21, "unknown_fields": [],
  "field_map": { "Itm001": "184,320.00", "Itma15": "10,700.00", "...": "..." }
}

unknown_fields lists any spec field the blank PDF does not actually contain, which is how a form spec drifting from a re-issued state form gets caught.

Requires the state blank PDF to be installed on the server and the form spec to carry its real field names. Filer-supplied lines that TaxaFi does not compute (prepayment credits, timely-filing discounts, prior payments, penalty and interest) are left for the caller to supply.

assemble_ser scope: filing

POST /taxafi/v1/assemble_ser

Builds the Streamlined Simplified Electronic Return (SER) XML for a period. The ~24 Streamlined member states accept this one format, so a single call covers all of them — no per-state form needed. Figures are derived from the merchant's recorded calculations for the period (same rollup as prepare_return).

Request

FieldTypeNotes
state or addressstring / AddressThe Streamlined member state to file.
periodstringYYYY-MM, YYYY-Qn, or explicit period_start + period_end.
sst_idstringThe seller's Streamlined (SST) ID.
feinstringFederal EIN.
doc_typestringO original (default) or A amended.

Response

{
  "channel": "ser", "state": "IN",
  "period": { "start": "2026-06-01", "end": "2026-06-30" },
  "totals": { "gross_sales": 184320.00, "taxable_sales": 171200.00, "tax_total": 11984.00 },
  "schema_version": "SSTSER202409V01",
  "payload": "<SSTSimplifiedReturnTransmission>...</SSTSimplifiedReturnTransmission>"
}
The XML follows the SST SER structure (transmission header, return header with period / state FIPS / seller IDs / document type, and return data with amounts + a jurisdiction schedule). Validate against the official SSTGB-Schemas XSD before production, and submit through a certified web service (a CSP, or a self-certified service per state).

ingest scope: ingest

POST /taxafi/v1/ingest

Lands declared figures directly into the Tax Authority Ledger as signed entries (declared mode) — for reconciling externally computed tax rather than pricing individual transactions. Accepts one entry or an entries array. Each entry: stream (revenue|expense|adjustment), amount, period_year, period_month, and one of {authority_id, jurisdiction_id, state}.

ledger

GET /taxafi/v1/ledger?period_year=&period_month=

Returns the net position per authority/period for the scoped merchant: tax_collected, tax_expense, adjustments, net_due, and entry_count, grouped by authority and period. Optional period filters.

health

GET /taxafi/v1/health

No auth. Liveness plus engine version and table presence. Use for uptime checks.

{ "success": true, "status": "ok", "tables_present": { "...": true } }

Data models

Address

Used as ship_to (consumer), ship_from (merchant origin), and the merchant address on filing calls.

FieldTypeNotes
streetstringEnables rooftop / ZIP+4 accuracy where available.
citystring
statestringTwo-letter code.
zipstring5-digit ZIP.
plus4stringZIP+4 add-on for the most precise jurisdiction match.
countystringOptional county name. Disambiguates ZIPs that span more than one county, and resolves the jurisdiction when no ZIP match is found.
county_fipsstringOptional county FIPS code. Used the same way as county when you have the code.

Calculation

FieldTypeNotes
combined_ratenumberSum of applied jurisdiction rates (decimal fraction).
subtotal / taxable_amount / exempt_amountdecimal $Amount, taxed base, and exempt portion.
tax_total / totaldecimal $Tax due and grand total.
treatment / taxability_basisstringtaxable | exempt | reduced, and how it was decided.
merchant_exemptboolWhether a merchant exemption certificate applied.
sourcing / sourcing_usedstringorigin vs destination sourcing.
resolutionobjectconfidence (zip4|zip|county_fips|state|none), resolver, state.
breakdownarrayPer-jurisdiction name, level, rate, tax, source, verified.
data_verifiedboolFalse if any applied rate is unverified sample data.
warningsarrayHuman-readable cautions (unresolved address, state-only match, unverified data).

Form field

FieldNotes
positionOrder on the return (column index for CSV, line order for PDF).
code / labelField identifier and human label.
value_sourceWhere the value comes from: figure, payment, account, period, constant, formula, manual.
source_fieldThe specific key, interpreted per source (e.g. gross:amusement, routing_number).
data_type / format_ruleType and validation (mask, enum values, digit length, blank policy).

Errors

Errors return success: false with an error message and the HTTP status below.

StatusMeaning
400Malformed request.
401Missing or invalid API key.
403Key lacks the required scope.
404Unknown action, or address could not be resolved.
405Wrong method (e.g. GET on a POST-only action).
422Valid request but missing/invalid parameters.
500Internal error.
502Upstream AI error (classify).
{ "success": false, "error": "amount is required.", "meta": { "...": "..." } }

TaxaFi API v1 · © 2026 Dapit Financial Infrastructure · confidential