Open-source US sales tax calculation API. Free, self-hostable, contributor-driven. Apache 2.0.
OpenSalesTax answers one question for any US transaction: how much sales tax should I charge? It uses free public data from the Streamlined Sales Tax (SST) project plus per-state contributor modules to cover the messy reality of US sales tax (~13,000 taxing jurisdictions, every state with its own quirks).
Live demo: demo.opensalestax.org · Live API: api.opensalestax.org
Pre-loaded PostgreSQL database dumps ship with every release tag, so a fresh install can be live without spending ~50 minutes fetching SST data and loading every state by hand.
pip install opensalestax
# 1. Point at any empty PostgreSQL database
export OPENSALESTAX_DATABASE_URL="postgresql+asyncpg://USER:PASSWORD@HOST:5432/opensalestax"
# 2. Apply the schema
alembic -c $(python -c "import opensalestax, pathlib, os; print(os.path.join(pathlib.Path(opensalestax.__file__).parent.parent.parent, 'alembic.ini'))") upgrade head
# 3. Restore the latest pre-built dump (all 52 jurisdictions)
opensalestax data restore
# 4. Serve the API
opensalestax serve --port 8080That's it. opensalestax data restore downloads
opensalestax-dump-<latest-tag>-postgres.sql.gz from the GitHub
release, validates that the dump's schema matches the migration head
you just applied, then pipes it through psql. A new install is
ready to answer real US sales-tax queries in well under two minutes.
Pin a specific version:
opensalestax data restore --release v0.23.0Restore from a local file (useful for air-gapped installs):
opensalestax data restore --file ./opensalestax-dump-v0.23.0-postgres.sql.gzThe dump is regenerated on every release tag by the
Build data dump workflow.
It is data-only (no schema, no API keys); the consumer's own
alembic upgrade head is the source of truth for the schema.
MariaDB users: the bundled dump is PostgreSQL COPY format;
fall back to the manual data fetch + data load workflow
described under "Refresh from source" below.
You need Docker + Docker Compose.
git clone https://github.com/ejosterberg/open-sales-tax.git
cd open-sales-tax
# Bring up API + PostgreSQL (or use --profile mariadb for MariaDB)
docker compose --profile postgres up -d
# Apply migrations + restore the latest published dump
docker compose run --rm api alembic upgrade head
docker compose run --rm api opensalestax data restore
# Hit the API
curl http://localhost:8080/v1/health
curl http://localhost:8080/v1/states | jq '.states[] | select(.tier > 0)'
# Calculate sales tax on a $100 general purchase in Minneapolis
curl -X POST http://localhost:8080/v1/calculate \
-H 'Content-Type: application/json' \
-d '{
"address": {"zip5": "55401"},
"line_items": [{"amount": "100.00", "category": "general"}]
}'Visit http://localhost:8080/v1/docs for the auto-generated Swagger UI.
All 52 US sales-tax jurisdictions (50 states + DC + Puerto Rico) are
tier-1 maintained -- meaning each ships a per-state module with
a taxability matrix and is exercised by the regression tests. The
five no-sales-tax states (AK, DE, MT, NH, OR) are correctly modeled
with has_sales_tax=False.
Per-locality coverage breakdown:
| Coverage type | States | How |
|---|---|---|
| Full SST data (rates + boundaries from quarterly file) | 24 SST member states | AR, GA, IA, IN, KS, KY, MI, MN, NE, NC, ND, NJ, NV, OH, OK, RI, SD, TN, UT, VT, WA, WI, WV, WY |
| Per-county + per-city seeded from state DOR | 16 non-SST self-seeded | AZ, CA, FL, NY, TX, IL, PA, MO, MS, SC, VA, AL, NM, HI (with county surcharges), PR (with municipal SUT), CT (flat statewide) |
| Statewide flat rate (no locals to model) | 3 | DC, MD, MA |
| Tier-1 no-sales-tax | 5 | AK, DE, MT, NH, OR |
| Pending SubJurisdiction Protocol architectural work | 2 | CO (home-rule cities), LA (parishes) |
407 ZIPs validated against published state DOR rates on every release (the live regression test). The grid spans every state with locals; CI auto-rebuilds the pre-loaded data dump on every release tag.
The pre-built dump is rebuilt on every release tag and pinned to the SST quarterly file current at release time. If you need data fresher than the latest tag -- typically because a state DOR has published mid-quarter rate changes -- bypass the bundled dump and load directly from the upstream sources:
docker compose run --rm api opensalestax data fetch \
--state MN --version 2026Q2FEB18
docker compose run --rm api opensalestax data load \
--state MN --version 2026Q2FEB18The API now returns Minnesota's actual SST rates (state base 6.875% plus any local additions) for any covered ZIP. See docs/data-refresh.md for the full fetch / load / status / purge workflow.
This is the only path supported on MariaDB -- the bundled release dump is PostgreSQL COPY format.
Auto-generated OpenAPI 3.x:
- Spec:
GET /v1/openapi.json - Swagger UI:
GET /v1/docs(interactive "Try it out") - ReDoc:
GET /v1/redoc(read-optimized)
Endpoints:
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/health |
Liveness + DB connectivity |
| GET | /v1/states |
Coverage tier for all 52 jurisdictions |
| GET | /v1/rates?zip5=&zip4= |
Active jurisdictional rate stack |
| POST | /v1/calculate |
Tax decomposition for line items |
See docs/api.md for request/response examples.
A live instance runs at api.opensalestax.org.
Open /v1/docs in a browser for an interactive Swagger UI with
"Try it out" buttons that prefill realistic request bodies.
The demo site has click-to-run calculators for Minneapolis, Dallas, San Francisco, and NYC.
Or try these curl recipes:
curl -s https://api.opensalestax.org/v1/health
# {"status":"ok","version":"0.54.0","database_connected":true}curl -s https://api.opensalestax.org/v1/states \
| jq '.states[] | select(.tier == 1) | .abbrev'curl -s -X POST https://api.opensalestax.org/v1/calculate \
-H 'Content-Type: application/json' \
-d '{
"address": {"zip5": "55401"},
"line_items": [
{"amount": "100.00", "category": "general"},
{"amount": "50.00", "category": "clothing"}
]
}' | jqThe response includes per-line jurisdictions[] with name, type,
rate_pct, and tax (dollar amount). The line's tax equals the
sum of its jurisdictions' tax values exactly -- accounting callers
can reconcile state/county/city/district splits.
curl -s 'https://api.opensalestax.org/v1/rates?zip5=55401' | jq# A $75 clothing item is exempt during the August holiday in Texas
curl -s -X POST https://api.opensalestax.org/v1/calculate \
-H 'Content-Type: application/json' \
-d '{
"address": {"zip5": "75201"},
"line_items": [{"amount": "75.00", "category": "clothing"}]
}' | jq '.lines[0].note'Yes please! See CONTRIBUTING.md.
The architectural keystone is the per-state contributor pattern: every state is a Python module implementing a small Protocol. Maintainers are listed per-state in MAINTAINERS.md.
To add or improve your state's module, see docs/state-modules.md.
Apache License 2.0. DCO sign-off
(git commit -s) required on every commit.
Built on free public data:
- Streamlined Sales Tax Project rates and boundary files (24 member states)
- US Census TIGER/Line shapefiles (planned for Phase 4)
- State Department of Revenue publications (per-state)
We deliberately do not ingest paid feeds (Avalara, TaxJar, Vertex, Sovos, TaxCloud). See constitution §3.
Active development. Latest stable: see the releases page. Production self-hosting is viable today for every state listed in the coverage table above.
Recent releases ship via the Build data dump
workflow that pre-loads every state's data into a PostgreSQL dump and
attaches it as a release asset, so a fresh install can call
opensalestax data restore and be live in under two minutes.