Tax
DayZero charges sales tax on invoices from the business's default tax rate. Tax rates are the percentages, by jurisdiction, and where the collected tax is booked. Tax codes mark product variants as taxable or not. This guide covers both, how tax is worked out when an invoice is finalized, and the tax fields on invoices.
The Tax Settings page is switched off in the DayZero app for now and new businesses start with no rates or codes, but these endpoints work. A business with no default rate charges no sales tax.
All requests need Authorization: Bearer dz_… and x-business-id. GET needs read access, POST and PUT write access, DELETE delete access. Successful responses are wrapped as {"success": true, "data": …}; the examples below show data.
Tax rates
Create a tax rate
POST /api/v1/tax/rates returns 201.
curl -X POST "https://api.ondayzero.com/api/v1/tax/rates" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "California Sales Tax",
"rate": 0.0725,
"tax_type": "sales",
"country": "US",
"state": "CA",
"is_default": true
}'{
"id": "019a0d41-2b7c-7e19-8f3a-4c6d9e0b1a22",
"business_id": "019a0b11-4e6f-7a20-8c3d-5b9e2a7f1d44",
"name": "California Sales Tax",
"rate": 0.0725,
"rate_percent": 7.25,
"tax_type": "sales",
"country": "US",
"state": "CA",
"county": null,
"city": null,
"tax_liability_ledger_id": null,
"is_default": true,
"is_active": true,
"created_at": "2026-10-14T15:20:03Z",
"updated_at": "2026-10-14T15:20:03Z"
}
rateis a decimal fraction, not a percentage:0.0725= 7.25%. Up to six decimal places are kept, so0.08875stays 8.875%.
| Field | Type | Description |
|---|---|---|
name |
string | Display name (required) |
rate |
number | Decimal rate from 0 to 1 (required) |
tax_type |
string | sales (default), use, vat, gst, hst, pst |
country / state / county / city |
string | Jurisdiction; country defaults to US |
tax_liability_ledger_id |
UUID | Ledger the collected tax is booked to. Must be a ledger of this business; Sales Tax Payable is used when omitted |
is_default |
boolean | Make this the rate invoices are charged; clears the previous default |
List tax rates
curl "https://api.ondayzero.com/api/v1/tax/rates?country=US&state=CA" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID"| Parameter | Type | Description |
|---|---|---|
active_only |
boolean | Only return active rates (default true). Ignored when country is set |
country |
string | Filter by country code; returns active rates only |
state |
string | Filter by state or region; only applied together with country |
data is {"items": [...], "total": N, "has_next": false, "has_prev": false}: the whole list in one page, sorted by name. GET /api/v1/tax/rates/{rate_id} returns one rate.
Get the default rate
curl "https://api.ondayzero.com/api/v1/tax/rates/default" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID"Returns 404 when no active rate is the default.
Update a tax rate
PUT /api/v1/tax/rates/{rate_id} takes the create fields plus is_active, all optional. Send only what changes.
curl -X PUT "https://api.ondayzero.com/api/v1/tax/rates/{rate_id}" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID" \
-H "Content-Type: application/json" \
-d '{"rate": 0.0775, "tax_liability_ledger_id": "019a0b13-9c21-7d0e-8b4f-1e2d3c4b5a60"}'nullforname,rate,tax_typeorcountryleaves the field as it is."is_active": falsealso clearsis_default, so the business has no default until you pick another.- A new rate applies to invoices finalized afterwards; invoices already finalized keep their tax.
Tax codes
Tax codes classify product variants: a variant whose code has is_taxable: false is left out of sales tax. Codes don't attach to customers; to charge no tax on one invoice, use tax_exempt.
Seed defaults
Loads TAXABLE (taxable), EXEMPT, NONTAXABLE and RESALE (not taxable) when the business has no codes yet. If it has any code, active or not, nothing is created and the existing codes are returned. Returns 200.
curl -X POST "https://api.ondayzero.com/api/v1/tax/codes/seed-defaults" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID"Create a tax code
curl -X POST "https://api.ondayzero.com/api/v1/tax/codes" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID" \
-H "Content-Type: application/json" \
-d '{
"code": "FOOD",
"name": "Unprepared food",
"description": "Groceries exempt from state sales tax",
"is_taxable": false
}'code and name are required, description is optional and is_taxable defaults to true. A code the business already has returns 409.
GET /api/v1/tax/codes?active_only=true lists codes sorted by code, in the same one-page shape as the rate list. GET /api/v1/tax/codes/{code_id} returns one. PUT /api/v1/tax/codes/{code_id} updates code, name, description, is_taxable and is_active; renaming to a code that already exists returns 409.
Assign a code to a product variant
Send tax_code_id when you create or update a variant (null clears it, which leaves the variant taxable). The code must belong to the business, otherwise 404. Variant responses return tax_code_id.
Deactivating rates and codes
curl -X DELETE "https://api.ondayzero.com/api/v1/tax/rates/{rate_id}" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID"DELETE /api/v1/tax/codes/{code_id} works the same way. Both deactivate (is_active: false) rather than delete: tax lines on existing invoices keep pointing at the rate. Deactivating a rate also clears is_default. Reactivate with PUT … {"is_active": true}.
If an integration import (QuickBooks, Shopify, Square …) created duplicate rates or codes, POST /api/v1/tax/dedupe-imported deactivates the duplicates and returns rates_deactivated and codes_deactivated.
AI rate suggestions
curl -X POST "https://api.ondayzero.com/api/v1/tax/suggest-rates" \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID"No body. Suggestions are based on the business's existing rates and come back as suggestions, each with jurisdiction, rate (a percentage here, unlike the decimal rate on /tax/rates), code and reason. Nothing is created; POST /api/v1/tax/rates the ones you want after checking them.
How tax is applied
Tax is worked out once, when an invoice is finalized (PUT /api/v1/invoices/{invoice_id}/finalize, or a bulk finalize). A draft carries no tax.
- If the business has no active default rate, no tax is added.
- The taxable amount is the sum of the taxable lines:
- A Products & Services catalog line is taxable only when the item's
taxableflag istrue(it defaults tofalse). - An inventory variant line is taxable unless the variant's tax code has
is_taxable: false. - A custom line is taxable.
- A negative custom line (a discount or credit) lowers the taxable amount in proportion to the taxable share of the invoice; a negative item line lowers it only when that item is taxable. The taxable amount never goes below
0.
- A Products & Services catalog line is taxable only when the item's
- Tax = taxable amount × rate, rounded to the nearest cent (half up). It is saved as one tax line (rate, taxable amount, tax amount) and the invoice
totalbecomes the lines plus the tax. - The finalization journal entry debits Accounts Receivable for the total, credits Sales for the lines and credits the rate's
tax_liability_ledger_id(Sales Tax Payable when none) for the tax. Voiding reverses all three.
Tax already on an invoice is not recalculated when rates change. Contract invoices carry no default-rate tax: the contract booking posts their total.
Stripe invoices
Before Stripe finalizes an invoice, DayZero adds the tax to the Stripe draft as its own invoice item, in the invoice's currency, so the customer is charged exactly the amount DayZero books. Retrying a failed finalize replaces that item instead of adding another.
- If the Stripe invoice uses Stripe's automatic tax, DayZero adds nothing and books Stripe's own figures, even when Stripe's tax is
0. - If Stripe's final total still differs from the lines plus DayZero's tax, DayZero drops its tax line, books the total Stripe charged, and logs an error.
Exempting one invoice
Send "tax_exempt": true on POST /api/v1/invoices, or on PUT /api/v1/invoices/{invoice_id} while the invoice is a draft (false charges the default rate again; leave it out to keep the current setting).
- The exemption is recorded against the business's default rate. With no default rate there is nothing to exempt: nothing is stored and the response returns
"tax_exempt": false. - Changing it on a finalized invoice returns
400(BUSINESS_061). Sending the value the invoice already has is accepted.
Tax fields on invoice responses
| Field | Description |
|---|---|
subtotal |
Sum of the lines before tax, in cents |
tax_total |
Tax charged, in cents; 0 when none; null when the response did not load the tax lines |
tax_lines |
One entry per rate: tax_rate_id, name, rate_percent (e.g. 7.25), taxable_amount, tax_amount |
tax_exempt |
true when the invoice is marked to charge no sales tax |
The invoice PDF lists the tax per rate between the subtotal and the total.
Errors
| Status | Code | When |
|---|---|---|
400 |
GEN_002 |
Validation: missing name/rate, rate outside 0–1, missing code |
400 |
BUSINESS_061 |
Changing tax_exempt on an invoice that is no longer a draft |
404 |
NOT_FOUND_001 |
Rate or code not found or in another business; no default rate (/rates/default); a variant tax_code_id from another business |
404 |
NOT_FOUND_014 |
tax_liability_ledger_id is not a ledger of this business |
409 |
CONFLICT_020 |
The business already has that tax code |
401 / 403 |
— | Missing or invalid token, or the key lacks the permission for the method |