Quickstart
This is the fastest path to a working integration.
Base URLs
- Local dev (default):
http://localhost:3000(orPORTenv) - Production:
https://api.taxlite.io
Get an API key
- Trial key (no login):
POST /api/keys/create-trialreturns atl_trial_...API key. - Signup (recommended): registering a user automatically provisions a
tl_trial_...key for the new company. - Live key: login to the Dashboard and regenerate your key there (when on a paid plan).
Public endpoint. Returns a trial API key (40 calls, 7 days).
curl -X POST https://api.taxlite.io/api/keys/create-trial
Calculate tax (checkout path)
Call /api/calculate during checkout to get the tax amount. The response is intentionally simple: rates are returned as integer *_rate_dbps only.
curl -X POST https://api.taxlite.io/api/calculate \
-H "Content-Type: application/json" \
-H "x-company-key: tl_live_your_api_key" \
-d '{
"state_code": "CO",
"city": "Denver",
"zip_code": "80202",
"address": "123 Main St",
"taxable_base_cents": 10000
}'
Example response (shape):
{
"taxable_base_cents": 10000,
"tax_amount_cents": 915,
"total_amount_cents": 10915,
"total_rate_dbps": 9150,
"state_rate_dbps": 2900,
"local_rate_dbps": 6250,
"confidence": 1,
"source": "POLYGON_DATABASE",
"api_log_id": "abc123...",
"calculation_id": "abc123...",
"expires_at": "2026-01-01T00:00:00.000Z"
}
Record a sale (ledger + reporting)
TaxLite does not auto-record. After payment succeeds, call /api/record-sale.
Send
api_log_id (alias: calculation_id) and let the server hydrate filing_payload from logs.
If
filing_payload is missing, TaxLite will still record the sale (no 500),
flag it for ops backfill, and it won’t appear in filing buckets/exports until fixed.
Recommended approach: call calculate, then pass the returned api_log_id into record-sale so the server can hydrate the quote details.
curl -X POST https://api.taxlite.io/api/calculate \
-H "Content-Type: application/json" \
-H "x-company-key: tl_live_your_api_key" \
-d '{
"state_code": "CO",
"city": "Denver",
"zip_code": "80202",
"address": "123 Main St",
"taxable_base_cents": 10000
}'
Authentication
TaxLite uses two auth modes.
API key auth (server-to-server)
Used for /api/calculate, /api/record-sale, and /api/report.
| Method | How to send it |
|---|---|
| Header | x-company-key: tl_live_... |
| Bearer | Authorization: Bearer tl_live_... |
| Query (GET only) | ?api_key=tl_live_... |
Session auth (Dashboard JWT)
Used for Dashboard endpoints like /api/reports/* and /api/reports/export.
POST /api/auth/login
Content-Type: application/json
{
"email": "you@company.com",
"password": "your_password"
}
Send the returned token on subsequent requests:
Authorization: Bearer <jwt_token>
POST /api/calculate
Compute the total sales tax rate and tax amount for a location.
Auth: API key
Request body
| Field | Description |
|---|---|
state_code (required) | 2-letter state code. |
taxable_base_cents (required) | Integer cents. Example: 10000 = $100.00. |
city (required*) | Either city or zip_code must be provided. |
zip_code (required*) | Either city or zip_code must be provided. |
address | Optional. If provided, PO Boxes are rejected. |
lat, lng | Optional. If provided, skips geocoding (faster and less dependent on address parsing). |
(required*) means “at least one of these is required.”
city or zip_code even if you provide lat/lng.
Response
Response shape (successful 200):
taxable_base_cents, tax_amount_cents, total_amount_cents,
total_rate_dbps, state_rate_dbps, local_rate_dbps,
api_log_id, calculation_id, expires_at.
Usually present: confidence, source (omitted if unknown).
*_rate_dbps: this is an integer “deci-bps” field (0.1 bps granularity). Convert to percent via percent = dbps / 1000.
Rate units (bps/dbps)
- bps = basis points.
100 bps = 1%. Example:915 bps = 9.15%. - dbps = deci-bps (0.1 bps) as an integer. Example:
9150 dbps = 915 bps.
You generally should not compute tax yourself: use tax_amount_cents from the API response.
POST /api/record-sale
Persist a sale (tax event) for reporting + exports.
Auth: API key
Preferred (best UX): api_log_id-only
If you already called /api/calculate, you should record the sale by sending only
api_log_id (alias: calculation_id). TaxLite will hydrate all filing context from logs.
Recommended fields
api_log_id(orcalculation_id)idempotency_key(prevents duplicate transactions on retries; unique per order/payment)event_id,payment_id(your identifiers)
- The linked
/api/calculateresponse must exist in logs (calculate logging is async). - If you also send
state_code/taxable_base_cents/total_tax_centsalongsideapi_log_id, they must match the logged quote (safety check). - If you call record-sale immediately after calculate, TaxLite retries briefly to wait for the log row. If still not found, retry record-sale.
Example (preferred)
{
"event_id": "order-123",
"payment_id": "pay-456",
"idempotency_key": "order-123",
"api_log_id": "abc123..."
}
Alias: you can also send calculation_id instead of api_log_id.
Manual fallback (no api_log_id)
This is an ops-only escape hatch.
If you did not call /api/calculate, you can still record manually by providing the core fields.
TaxLite will record money totals, but filing identifiers may be missing. This path triggers alerts so gaps get backfilled ASAP.
{
"event_id": "order-123",
"payment_id": "pay-456",
"idempotency_key": "order-123",
"state_code": "CO",
"taxable_base_cents": 10000,
"total_tax_cents": 915
}
Response
{
"status": "ok",
"tax_event_id": "uuid-or-null",
"filing_payload_missing": false,
"quote_mismatch": false,
"warnings": []
}
POST /api/refund-sale
Record a refund/return for a previously recorded sale.
Auth: API key
Mental model: TaxLite stores refunds as a negative mirror row in tax_events.
That means reports, rollups, and filing exports remain correct by summing events.
Required fields
original_tax_event_id: thetax_events.idof the sale you are refundingrefund_id: your refund identifier (used for idempotency; unique per company)refund_taxable_base_cents: refund base magnitude (positive integer)
Optional fields
refund_total_tax_cents: refund tax magnitude (positive integer). If omitted, TaxLite prorates from the original effective rate.apply_to_original_period: boolean (default false). If true, the refund is applied to the original sale month (restatement).refund_timestamp: ISO string or unix seconds/ms. Default: now.
- Retrying the same
refund_idreturns the existing refund event. - Reusing the same
refund_idfor a differentoriginal_tax_event_idis rejected.
Example
{
"original_tax_event_id": "uuid-of-sale",
"refund_id": "rf_123",
"refund_taxable_base_cents": 5000,
"refund_total_tax_cents": 412,
"apply_to_original_period": false
}
Response
{
"status": "ok",
"original_tax_event_id": "uuid-of-sale",
"refund_tax_event_id": "uuid-of-refund",
"refund_id": "rf_123"
}
Reporting
TaxLite supports a legacy state totals report and a filing buckets report.
Auth: API key. Returns state totals for a date range.
start=YYYY-MM-DD(optional)end=YYYY-MM-DD(optional)state=IL(optional)
Filing buckets
Filing buckets aggregate recorded sales into “buckets” derived from each transaction’s filing_payload (state-specific filing identifiers).
Auth: session JWT. Returns bucket totals for one state.
state=IL(required)start=YYYY-MM-DD(optional)end=YYYY-MM-DD(optional)filing_category=general_sales(optional, default:general_sales)
Buckets contain bucket_key, identifiers, and money totals. The identifier keys are state-specific.
Exports
Exports are generated from recorded sales (stored tax_events.filing_payload + money fields).
Auth: session JWT. Returns a file download (CSV/XLSX/NDJSON).
Filing export
{
"format": "csv",
"report_type": "filing_csv",
"state_code": "ALL",
"filing_category": "general_sales",
"start": "2025-12-01",
"end": "2025-12-31"
}
state_code supports "IL" for a single state, or "ALL"/"*" for an all-states export.
If no data exists, this endpoint returns 404 with {"error":"No filing_payload data found for export"}.
Errors
All errors return JSON:
{
"error": "message"
}
| Status | Meaning |
|---|---|
400 | Invalid input |
401 | Missing/invalid API key or session |
404 | Not found (or no data for export) |
429 | Rate limit / usage limit exceeded (when enabled) |
500 | Server error |
Best practices
- Recommended flow: calculate at checkout → charge customer → record-sale after payment succeeds.
- Record with
api_log_id: sendapi_log_idto/api/record-saleso TaxLite hydrates and stores the per-salefiling_payload(filing identifiers). - Provide
lat/lngwhen you have it: faster and less dependent on address parsing. - Retry strategy: on
429/5xx, retry with exponential backoff.