Sales tax
Flint calculates and records tax as part of an order. The order owns the taxable base and the final money arithmetic. Each taxable line item and charge records its own tax, and the order's total lands in pricing_amounts.tax_money.
There are two calculation modes. Automatic has Flint calculate US sales tax from a tax location through your account's tax connection, added on top of your prices. External takes normalized jurisdiction components from your own tax engine and supports additive or inclusive percentage tax plus order-scoped flat taxes such as retail delivery fees.
Do not model an external tax result as an ordinary order charge. A charge does not participate in tax reversals on refunds or in tax reports.
Connect automatic tax#
Automatic tax calculates through a tax connection. Connect it in the Dashboard's tax settings with your business address and the states where you are registered to collect tax, and Flint sets up a Quaderno account that supplies the rates. Each sandbox has its own tax connection, so connect automatic tax in each sandbox where you test it.
Without an active connection, automatic tax is refused rather than calculated as zero. Turning tax on for an order or checkout session, creating one while tax is on by default, or changing an order that already has automatic tax fails with AUTOMATIC_TAX_CONNECTION_REQUIRED on tax.enabled. Turning on tax.default_enabled in your settings fails the same way on that field. Orders with tax turned off and orders using external tax need no connection.
Enable tax#
Set the mode when you create or update the order:
{
"tax": {
"enabled": true,
"calculation": {
"mode": "automatic",
"price_mode": "additive"
}
}
}
Switching modes recalculates the order from the inputs you supplied. It does not stack external tax on top of a previous automatic result.
Then state taxability on every ad-hoc line item and charge. tax.taxable is required once tax is enabled: a line item without it fails with ORDER_LINE_ITEM_TAX_INPUT_REQUIRED, and a charge without it fails with ORDER_CHARGE_TAX_INPUT_REQUIRED.
{
"name": "Prepared lunch",
"quantity": 1,
"unit_price_money": { "amount": 1800, "currency": "USD" },
"tax": {
"taxable": true,
"line_item_tax_category": "prepared_food",
"calculation": {
"mode": "automatic",
"price_mode": "additive"
}
}
}
A line item sourced from the catalog works differently. When you pass variant_id or bundle_id, taxability is frozen from that variant or bundle at the time the line is added, and sending a tax object alongside it is rejected as read-only. A catalog record that does not set taxability resolves to taxable. Set taxability on the catalog record, not on the order.
A line item or charge can set its own calculation, but its mode must match the order's or the request fails with TAX_MODE_MISMATCH. Automatic tax is added on top of the price, so its price_mode must be additive: inclusive on the order, a line item, or a charge fails with AUTOMATIC_TAX_INCLUSIVE_PRICING_UNSUPPORTED. Use external tax for tax-inclusive prices. In external mode the price mode that applies is always the one on the line item or charge. The order-level price_mode is not a default: a line item that omits calculation is calculated additive even when the order says inclusive, so state it per line item and per charge.
line_item_tax_category records the item's intended tax classification. It is optional on a taxable line item and falls back to the default tax category in your merchant tax settings, then to general. Line items and charges take different category sets (Tips & fees covers the charge list), and an unrecognized value fails with INVALID_TAX_CATEGORY. Automatic tax charges general, physical_goods, and prepared_food at the generally taxable rate where the order is delivered, and maps digital_goods, software, saas, services, and professional_services to the tax connection's codes for them. It rejects food, clothing, medical_goods, and admission with INVALID_TAX_CATEGORY because whether they are taxed depends on the state; use external tax for them. tax_transactions_itemized_v1 reports each item's flint_tax_category.
Line items and charges with taxable: false owe no tax, and automatic tax leaves them out of the calculation. Taxable shipping_fee and delivery_fee charges are taxed at the generally taxable rate where the order goes. Any other taxable charge, such as a service_fee or a paid pickup's fulfillment_fee, fails with AUTOMATIC_TAX_CHARGE_UNSUPPORTED: mark it not taxable, or use external tax.
Supply external tax components#
In external mode, every economic line item and charge needs its own tax.calculation. Omitting it fails with TAX_CALCULATION_REQUIRED; supplying one with an empty components array fails with EXTERNAL_TAX_COMPONENTS_REQUIRED. Automatic mode is the mirror image: components there fail with AUTOMATIC_TAX_COMPONENTS_FORBIDDEN. Percentages are whole-percent units with at most four decimal places, so 6.5 means 6.5%.
{
"tax": {
"enabled": true,
"calculation": { "mode": "external", "price_mode": "additive" }
},
"line_items": [
{
"name": "Tax-included item",
"quantity": 1,
"unit_price_money": { "amount": 1065, "currency": "USD" },
"tax": {
"taxable": true,
"calculation": {
"mode": "external",
"price_mode": "inclusive",
"components": [
{
"jurisdiction": {
"code": "US-WA",
"country": "US",
"state": "WA",
"level": "state",
"name": "Washington"
},
"tax_type": "sales_tax",
"calculation_type": "percentage",
"percent": 6.5
}
]
}
}
}
]
}
Every jurisdiction subfield is required at every level, including state, or the component fails with INVALID_TAX_JURISDICTION. Components must be unique by jurisdiction code, tax type, and calculation type (DUPLICATE_TAX_COMPONENT). Percentage components must use sales_tax or use_tax (INVALID_TAX_TYPE) and carry percent from 0 through 100 with at most four decimal places (INVALID_TAX_PERCENTAGE).
Percentage tax currently applies to the full eligible amount after discounts. Flint does not yet represent threshold rules that tax only part of an otherwise taxable line. Do not encode a partial exemption as a lower effective percentage because that would misstate both the rate and taxable basis.
Inclusive components are derived back to a pre-tax basis and then reproduced from the published percentage. Some combinations of amount and multiple jurisdictions have no basis that reproduces every row exactly; the request is rejected with ORDER_TAX_RATE_UNAVAILABLE rather than publishing rows that do not add up. Splitting inclusive pricing across many jurisdictions on a small line item is where you will hit this.
Order-Scoped flat tax#
A flat tax, such as a state retail delivery fee, applies once to the order rather than to a line. It goes in the order's own tax.calculation.components:
{
"tax": {
"enabled": true,
"calculation": {
"mode": "external",
"price_mode": "additive",
"components": [
{
"jurisdiction": {
"code": "US-CO",
"country": "US",
"state": "CO",
"level": "state",
"name": "Colorado"
},
"tax_type": "retail_delivery_fee",
"calculation_type": "flat_amount",
"flat_money": { "amount": 29, "currency": "USD" }
}
]
}
}
}
Order-scoped components must be flat_amount (ORDER_PERCENTAGE_TAX_FORBIDDEN), the order's price_mode must be additive (FLAT_TAX_PRICE_MODE_INVALID), and flat_money must be positive and in the order currency (INVALID_FLAT_TAX). retail_delivery_fee is the only tax_type a flat component accepts; anything else fails with INVALID_TAX_TYPE. Flat components are order-scoped only; putting one on a line item or charge fails with FLAT_TAX_SCOPE_INVALID.
Flint applies the flat row once while the order still has taxable economics. You own the legal trigger and the amount; Flint does not carry statutory fee tables.
Supply a tax location#
Automatic tax needs a location before the order can be paid.
For shipment or local-delivery orders, delivery_destination is the tax location. It takes precedence over tax.location and over customer or merchant addresses. If the order has no delivery destination, an active taxable shipment's recipient address is used instead. When a taxable shipment has no address, the order update succeeds with tax.status: requires_location. Supply tax.location in a later update. When taxable shipments go to different destinations, the update succeeds with tax.status: incomplete. One order gets one tax location.
When a checkout's delivery selection picks up every item at one store, the store is the tax location, so a buyer who picks up needs no ZIP. tax.location has address_source: location, the store's location_id, and its address, and a ZIP the buyer already gave does not change it. A store whose address has no country or postal code cannot be taxed, and choosing it fails with AUTOMATIC_TAX_ORIGIN_ADDRESS_REQUIRED, which names the location to fix.
Automatic tax needs one seller origin and one buyer destination for all of an order's taxable items. A selection that picks up at more than one store, or ships from more than one location, fails with AUTOMATIC_TAX_MULTIPLE_ORIGINS. A selection that ships or delivers some items and picks up others fails with AUTOMATIC_TAX_MULTIPLE_DESTINATIONS, or with AUTOMATIC_TAX_MULTIPLE_ORIGINS when those items leave from different locations. Use external tax for those orders.
Without a shipping destination, Flint falls back to the customer's address, billing before shipping. Your own business address is not used as the buyer's location, so an order with no other location waits in requires_location. Set delivery_destination with tax in the same order update when the order ships to an address that is not already on the order:
curl -X PATCH https://api.withflintpay.com/v1/orders/ord_123 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Flint-Version: 2026-09-07" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: tax-ord-123-v1" \
-d '{
"delivery_destination": {
"address": {
"line1": "200 Spectrum Center Drive",
"city": "Irvine",
"state": "CA",
"postal_code": "92618",
"country": "US"
}
},
"tax": {
"enabled": true,
"calculation": {"mode": "automatic", "price_mode": "additive"}
}
}'
For an order without a shipping destination, send a buyer-provided address as tax.location:
{
"tax": {
"enabled": true,
"calculation": {"mode": "automatic", "price_mode": "additive"},
"location": {
"address_source": "provided",
"address_type": "billing_address",
"address": {
"line1": "200 Spectrum Center Drive",
"city": "Irvine",
"state": "CA",
"postal_code": "92618",
"country": "US"
}
}
}
}
Automatic tax can calculate from a US ZIP code alone. A ZIP code that is not five digits or ZIP+4 is rejected with ORDER_TAX_LOCATION_INVALID on the postal code before tax is calculated. When the ZIP code alone cannot be placed, the order update succeeds with tax.status: requires_location; send the full street address in a later update. A full address whose ZIP code is in a different state than its state is rejected with ORDER_TAX_LOCATION_INVALID on the postal code, and an address that cannot be placed at all is rejected on the address. These rejections are not retryable: send a corrected address. A location the connection has no rate for is rejected with ORDER_TAX_RATE_UNAVAILABLE, which names the location and is not retryable. A state where you have no tax registration calculates zero tax with taxability_reason: not_taxable. When a request is rejected, error.param names the field for tax-location errors, for example tax.location.address.postal_code, and error.remediation.missing_or_invalid_fields lists them for line-item and charge tax errors.
Automatic tax is US-only. A non-US country is rejected rather than calculated as zero, with ORDER_TAX_LOCATION_INVALID when it comes from the order and ORDER_TAX_UNSUPPORTED_COUNTRY when it comes from a customer address Flint fell back to.
Automatic tax does not accept tax-exempt customers: an order for a Customer with tax_exempt: true fails with AUTOMATIC_TAX_EXEMPTION_UNSUPPORTED. Use external tax to supply exemption treatment; with external tax, the customer's orders resolve to status: exempt.
Automatic tax requires one payment for the full order balance, taken immediately or billed on an invoice. Use external tax for partial or split payments and delayed capture. A requested tip is not taxed: adding, changing, or clearing one leaves the calculated tax as it is. See Tax on an invoice for what an invoice adds.
Read the result#
tax.status describes readiness:
not_required: tax is disabled on the order.requires_location: the order needs a usable tax location.calculated: tax was calculated, including a valid zero-tax result. An order with tax enabled and nothing taxable on it lands here withtaxability_reason: not_taxable, not innot_required.exempt: the customer is tax-exempt.incomplete: calculation could not complete.
tax.taxability_reason says why the result came out the way it did:
taxability_reason on order taxAn incomplete result also carries a normalized failure_reason. Today the only value Flint sets is calculation_unavailable. Flint never returns an upstream message or identifier here. A mutation that leaves Flint unable to calculate tax, including one with taxable shipments going to different destinations, still succeeds and returns this status. Invalid tax input is rejected instead.
The order's tax on a read is not the same object you sent. It returns enabled, status, mode, taxability_reason, failure_reason, location, exemption, available_location_inputs, and tax_breakdowns. The price mode and any external-tax components you supplied are inputs; read them back off the breakdown rows. Automatic tax results do not expose tax_breakdowns; reconcile them through tax_transactions_itemized_v1.
Tax on an invoice#
An invoice bills an order, so tax is calculated on that order. Issuing the invoice recalculates tax and freezes the result into the billing snapshot. From then on the frozen amount is what the customer owes, and ORDER_TAX_NOT_READY blocks issue and collection while a tax-enabled order is still calculating.
Automatic tax collects an invoice in one payment for the full balance:
- Issuing with more than one schedule entry, or with an entry for less than the invoice total, fails with
AUTOMATIC_TAX_INVOICE_INSTALLMENTS_UNSUPPORTED. Send a single full-balance entry or omitschedule_entries. - Recording a manual payment for less than the outstanding balance fails with
AUTOMATIC_TAX_PARTIAL_PAYMENT_UNSUPPORTED.
Use external tax for a deposit, installments, or a balance you expect to collect in pieces. External tax puts no payment restriction on an invoice.
Editing the backing order after issue leaves the frozen calculation behind, and collection fails with INVOICE_TAX_SNAPSHOT_STALE. An invoice that carries an automatic calculation with no frozen evidence fails with INVOICE_TAX_SNAPSHOT_MISSING. Both clear the same way: void the invoice and issue it again, which recalculates tax at the new numbers.
A late fee is assessed on the outstanding balance and is not taxed. The issued line items, tax, and original total stay fixed.
Reconcile jurisdictions#
An order calculated with external tax carries tax_breakdowns, one row per taxing authority and taxable component:
{
"tax_breakdown_id": "txb_01J00000000000000000000000",
"jurisdiction": {
"code": "US-WA",
"country": "US",
"state": "WA",
"level": "state",
"name": "Washington"
},
"tax_type": "sales_tax",
"calculation_type": "percentage",
"price_mode": "additive",
"percent": 6.5,
"taxable_money": { "amount": 3700, "currency": "USD" },
"tax_money": { "amount": 241, "currency": "USD" },
"taxability_reason": "standard_rated"
}
jurisdiction.code is the code you supplied, returned verbatim. Orders calculated with automatic tax before it required a tax connection, which have automatic_profile: standard, also carry breakdowns whose codes Flint minted: use those for equality and grouping, display jurisdiction.name, and never parse them. Each row rounds half-up in minor units, and the published percentage reproduces tax_money from taxable_money.
For inclusive pricing the entered amount does not change: taxable_money is the derived pre-tax basis and tax_money is already inside the price. Additive tax is added to the order total. One order can mix inclusive lines, additive charges, and flat order tax.
Automatic tax keeps the tax connection's combined result instead of inventing authority rows, so its orders carry no breakdowns. Use tax_transactions_itemized_v1 for those calculations and their refund reversals.
Refund tax#
By default a line-item or charge refund reverses percentage tax proportionally with the economics being refunded. For external tax, Flint allocates the reversal back to the original tax_breakdown_id values using exact minor-unit arithmetic. Charge reversals appear in the refund's top-level tax_breakdown_refunds carrying order_charge_id. For automatic tax, the transaction report links each reversal to its frozen calculation.
Override a line item's tax reversal#
Overriding tax needs the commerce.refunds.tax_overrides.write scope on your API key. Without it the refund is rejected, and the gate is wider than tax_refund_mode alone: tax_money, tax_breakdown_refunds, tax_adjustment_reason, and tax_adjustment_audit on any line item all trip it.
tax_refund_mode is a line-item field. There is no order-level or charge-level equivalent, so charge tax always reverses proportionally.
{
"reason": "requested_by_customer",
"order_id": "ord_123",
"line_items": [
{
"order_line_item_id": "oli_123",
"quantity": 1,
"tax_refund_mode": "explicit",
"tax_money": { "amount": 100, "currency": "USD" },
"tax_adjustment_reason": { "code": "jurisdiction_specific_rule" },
"tax_adjustment_audit": {
"actor_id": "usr_123",
"actor_type": "user",
"source": "support_console"
},
"tax_breakdown_refunds": [
{ "tax_breakdown_id": "txb_123", "tax_money": { "amount": 100, "currency": "USD" } }
]
}
]
}
Each tax_breakdown_refunds entry takes exactly tax_breakdown_id and tax_money. It is not optional garnish: in explicit mode a line item with jurisdiction breakdowns must supply it (TAX_BREAKDOWN_REFUNDS_REQUIRED), the entries must sum exactly to the line's tax_money (TAX_BREAKDOWN_REFUND_TOTAL_MISMATCH), and each breakdown may appear once (DUPLICATE_TAX_BREAKDOWN_REFUND). In automatic mode supplying it fails with TAX_BREAKDOWN_REFUNDS_REQUIRE_EXPLICIT_MODE.
The reason and audit are siblings on the line item, required in explicit mode and rejected in automatic mode. Reason codes are customer_agreement, jurisdiction_specific_rule, merchant_policy, customer_request, restocking_policy, manual_tax_correction, and other, which requires a description.
The explicit amount must be strictly less than the automatic reversal. Matching it returns EXPLICIT_TAX_REFUND_NOOP; exceeding it returns TAX_REFUND_EXCEEDS_AUTOMATIC.
Flat order tax on a refund#
An order-scoped flat tax refund must cover its full remaining amount. A full order refund includes every remaining flat row automatically. If a mixed-tender refund credits a gift card but its processor allocation fails, only the failed allocation's tax capacity becomes refundable again. A subsequent request can refund that full remaining amount without repeating the gift card credit. For a partial refund, name the row in the request's top-level tax_breakdown_refunds[] with its full remaining tax_money; a partial amount returns FLAT_TAX_REFUND_MUST_BE_FULL, and naming a row that is not an order-scoped flat row returns TAX_BREAKDOWN_NOT_ORDER_SCOPED_FLAT.
See Refunds for the rest of the refund request.
Reports and filing#
For external tax, tax_itemized_v1 gives one row per order tax breakdown and tax_summarized_v1 totals taxable sales, collected tax, refunded tax, and payable tax by jurisdiction and tax type. These reports fail with data_unavailable when the interval contains nonzero automatic tax, which has no authority breakdowns.
For automatic tax, use tax_transactions_itemized_v1. It exports each frozen provider calculation collected with an order and each successful refund reversal. Pending and failed refund executions do not appear. All tax reports are asynchronous CSV reports: see Reports.
Flint calculates and reports tax. It does not determine nexus, register your business, file returns, or remit tax. Confirm your obligations with a qualified tax professional.
Errors#
Setting up tax on an order:
Supplying external components:
Resolving a tax location:
Issuing and collecting an invoice:
Overriding tax on a refund:
Next steps#
- Refunds: the rest of the refund request that tax reversal sits inside.
- Tips & fees: taxing delivery, service, and handling charges.
- Reports: tax report types and how to download them.
- Orders API reference: every tax field on the order.
- Invoicing: issuing, collecting, and following up on an invoice.
