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 tax from a tax location. Standard automatic tax supports US additive and inclusive percentage tax. Quaderno-connected automatic tax supports approved US and Canadian registrations with additive pricing. 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.
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": "inclusive"
}
}
}
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 choose its own price_mode, but its mode must match the order's or the request fails with TAX_MODE_MISMATCH. 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. Standard automatic tax does not vary the rate by category, and its jurisdiction reports do not include the category. Quaderno-connected automatic tax maps supported Flint categories to provider tax codes and includes flint_tax_category in tax_transactions_itemized_v1.
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",
"percentage": 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 a percentage from 0 through 100 (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.
Without a shipping destination, Flint falls back to the customer's address, billing before shipping, then the merchant's own address. 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"
}
}
}
}
How much address detail you need depends on the address itself. When a postal code spans several jurisdictions and Flint cannot use another location fallback, the order update succeeds with tax.status: requires_location. Send a full street address in a later update. After Flint exhausts the usable location fallbacks, a location with no taxing authority produces a calculated zero-tax result with taxability_reason: no_jurisdiction. When a request is rejected, error.param names the field for tax-location errors, and error.remediation.missing_or_invalid_fields lists them for line-item and charge tax errors.
Standard 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 or merchant address Flint fell back to. Quaderno-connected automatic tax accepts US and Canadian tax locations covered by the merchant's configured registrations.
Standard automatic tax supports tax-exempt customers. Set tax_exempt: true on the Customer and its orders resolve to status: exempt. Quaderno-connected automatic tax does not accept exempt customers; use external tax when you need to supply exemption treatment.
Quaderno-connected automatic tax requires one immediate payment for the full order balance. Use external tax for subscription orders, partial or split payments, and delayed capture.
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 taxstandard_ratednot_taxablecustomer_exempttax_disabledno_jurisdictionlocation_requiredAn 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. Quaderno-connected results do not expose tax_breakdowns; reconcile them through tax_transactions_itemized_v1.
Reconcile jurisdictions#
A standard automatic or external calculated order 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",
"percentage": 6.5,
"taxable_money": { "amount": 3700, "currency": "USD" },
"tax_money": { "amount": 241, "currency": "USD" },
"taxability_reason": "standard_rated"
}
In standard automatic mode jurisdiction.code is a key Flint mints: use it for equality and grouping, display jurisdiction.name, and never parse it. In external mode it is the code you supplied, returned verbatim. 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.
Breakdowns are absent for a genuine zero-tax result with no taxing jurisdiction. Standard automatic tax rejects a nonzero result that cannot be attributed to specific authorities. Quaderno-connected tax preserves the combined provider result instead of inventing authority rows; use tax_transactions_itemized_v1 for that calculation and its refund reversals.
Refund tax#
By default a line-item or charge refund reverses percentage tax proportionally with the economics being refunded. For standard automatic and 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 Quaderno-connected 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 reverses whole or not at all. A full order refund includes every remaining flat row automatically. 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 standard automatic and 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 a nonzero Quaderno-connected result lacks the authority breakdowns they require.
For Quaderno-connected 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:
tax.taxable.tax.taxable.mode is not automatic or external.mode differs from the order's.price_mode is not additive or inclusive.Supplying external components:
tax.calculation.components is empty on a line item or charge.code, country, state, level, or name.sales_tax or use_tax; flat components must use retail_delivery_fee.calculation_type is not percentage or flat_amount.percentage.flat_amount component was put on a line item or charge.price_mode is not additive alongside a flat component.flat_money is not positive or is not in the order currency.Resolving a tax location:
Overriding tax on a refund:
tax_refund_mode is not automatic or explicit.TAX_MONEY_REQUIREDtax_money.TAX_MONEY_REQUIRES_EXPLICIT_MODEtax_money sent in automatic mode.TAX_ADJUSTMENT_REASON_REQUIREDtax_adjustment_reason.TAX_ADJUSTMENT_AUDIT_REQUIREDtax_adjustment_audit.TAX_ADJUSTMENT_REQUIRES_EXPLICIT_MODETAX_BREAKDOWN_REFUNDS_REQUIREDTAX_BREAKDOWN_REFUNDS_REQUIRE_EXPLICIT_MODEtax_breakdown_refunds sent in automatic mode.TAX_BREAKDOWN_REFUND_TOTAL_MISMATCHtax_money.tax_breakdown_id appears twice.TAX_BREAKDOWN_LINE_ITEM_MISMATCHTAX_BREAKDOWN_REFUND_EXCEEDS_REMAININGEXPLICIT_TAX_REFUND_NOOPTAX_REFUND_EXCEEDS_AUTOMATICNext 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.
