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. External takes normalized jurisdiction components from your own tax engine. Both support additive and inclusive percentage tax; external mode also supports 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,
"inventory_requirement": "not_tracked",
"unit_price_money": { "amount": 1800, "currency": "USD" },
"tax": {
"taxable": true,
"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.
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 does not yet vary the rate by category, and current tax reports do not include the category.
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,
"inventory_requirement": "not_tracked",
"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.
Shipped taxable goods decide the location outright. If the order has an active taxable shipment with a recipient address, that address is the tax location and a location you supply is ignored. A taxable shipment with no address yet fails with ORDER_TAX_LOCATION_REQUIRED on the fulfillments field, and you can recover by supplying a location. Two taxable shipments going to different destinations fail with ORDER_TAX_MULTIPLE_LOCATIONS_UNSUPPORTED; one order gets one tax location.
Without a shipping destination, Flint falls back in order: a location you provide, the customer's address (billing before shipping), then the merchant's own address. You can set one directly:
curl -X PATCH https://api.withflintpay.com/v1/orders/ord_123/tax \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: tax-ord-123-v1" \
-d '{
"tax_location": {
"address_source": "provided",
"address_type": "shipping_address",
"address": {
"line1": "200 Spectrum Center Drive",
"city": "Irvine",
"state": "CA",
"postal_code": "92618",
"country": "US"
}
}
}'
address_type is one of postal_code, billing_address, shipping_address, or tax_address. How much address detail you need depends on the address itself: a postal_code that spans several jurisdictions fails with ORDER_TAX_FULL_ADDRESS_REQUIRED and needs a full street address instead. 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.
Automatic tax is US-only. A non-US country is rejected outright rather than calculated as zero, with ORDER_TAX_LOCATION_INVALID when you send it on the request and ORDER_TAX_UNSUPPORTED_COUNTRY when it comes from a customer or merchant address Flint fell back to.
Tax-exempt customers are marked on the Customer, not on the order: set tax_exempt: true on the customer and orders for that customer resolve to status: exempt.
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:
| Value | Meaning |
|---|---|
standard_rated | Tax was calculated at the jurisdiction's ordinary rate. |
not_taxable | The jurisdiction does not tax this, or nothing on the order is taxable. |
customer_exempt | The customer is tax-exempt. |
tax_disabled | Tax is turned off on the order. |
no_jurisdiction | The location resolved to no taxing authority. |
location_required | No usable tax location yet. |
An 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. The problems you might expect to surface as a status instead reject the request: an unusable location and an underivable inclusive basis are errors on the call that caused them, not a stored result to read back.
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 components you supplied are inputs; read them back off the breakdown rows.
Reconcile Jurisdictions#
A 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 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. Flint rejects any nonzero automatic tax result that cannot be attributed to specific authorities, so a charged amount never exists without the lineage required by refunds and tax reports.
Refund Tax#
By default a line-item or charge refund reverses percentage tax proportionally with the economics being refunded. Flint allocates the reversal back to the original tax_breakdown_id values using exact minor-unit arithmetic, so tax reports keep the authority lineage. Charge reversals appear in the refund's top-level tax_breakdown_refunds carrying order_charge_id.
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#
tax_itemized_v1 gives one row per order tax breakdown; tax_summarized_v1 totals taxable sales, collected tax, refunded tax, and payable tax by jurisdiction and tax type. Refunded tax is recognized when each payment-level refund execution succeeds. Pending and failed executions do not reduce tax payable. Both are asynchronous CSV reports: see Reports. A report fails with data_unavailable when an order or successful refund in the interval is missing the authority evidence a complete result needs.
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:
| Code | Cause |
|---|---|
ORDER_LINE_ITEM_TAX_INPUT_REQUIRED | Tax is enabled and an ad-hoc line item omitted tax.taxable. |
ORDER_CHARGE_TAX_INPUT_REQUIRED | Tax is enabled and a charge omitted tax.taxable. |
INVALID_TAX_MODE | mode is not automatic or external. |
TAX_MODE_MISMATCH | A line item or charge mode differs from the order's. |
INVALID_TAX_PRICE_MODE | price_mode is not additive or inclusive. |
INVALID_TAX_CATEGORY | The category is not valid for that scope. |
Supplying external components:
| Code | Cause |
|---|---|
TAX_CALCULATION_REQUIRED | External mode and an economic line item or charge has no tax.calculation. |
EXTERNAL_TAX_COMPONENTS_REQUIRED | External mode and components is empty on a line item or charge. |
AUTOMATIC_TAX_COMPONENTS_FORBIDDEN | Components were supplied in automatic mode. |
INVALID_TAX_JURISDICTION | A component is missing code, country, state, level, or name. |
DUPLICATE_TAX_COMPONENT | Two components share a jurisdiction code, tax type, and calculation type. |
INVALID_TAX_PERCENTAGE | Percentage is below 0, above 100, or exceeds four decimal places. |
INVALID_TAX_TYPE | Percentage components must use sales_tax or use_tax; flat components must use retail_delivery_fee. |
INVALID_TAX_CALCULATION_TYPE | calculation_type is not percentage or flat_amount. |
ORDER_PERCENTAGE_TAX_FORBIDDEN | An order-scoped component used percentage. |
FLAT_TAX_SCOPE_INVALID | A flat_amount component was put on a line item or charge. |
FLAT_TAX_PRICE_MODE_INVALID | The order's price_mode is not additive alongside a flat component. |
INVALID_FLAT_TAX | flat_money is not positive or is not in the order currency. |
ORDER_TAX_RATE_UNAVAILABLE | No pre-tax basis reproduces every inclusive row exactly. |
Resolving a tax location:
| Code | Cause |
|---|---|
ORDER_TAX_LOCATION_REQUIRED | No usable location, or a taxable shipment has no address yet. |
ORDER_TAX_FULL_ADDRESS_REQUIRED | A postal code alone spans several jurisdictions. |
ORDER_TAX_LOCATION_INVALID | The location you sent is malformed or not a US address. |
ORDER_TAX_UNSUPPORTED_COUNTRY | A resolved fallback address is outside the US. |
ORDER_TAX_NO_JURISDICTIONS | The address resolved to no taxing authority. |
ORDER_TAX_MULTIPLE_LOCATIONS_UNSUPPORTED | Taxable shipments go to more than one destination. |
ORDER_TAX_NOT_UPDATABLE_AFTER_PAYMENT | The order has already been paid. |
Overriding tax on a refund:
| Code | Cause |
|---|---|
INVALID_TAX_REFUND_MODE | tax_refund_mode is not automatic or explicit. |
TAX_MONEY_REQUIRED | Explicit mode without a tax_money. |
TAX_MONEY_REQUIRES_EXPLICIT_MODE | tax_money sent in automatic mode. |
TAX_ADJUSTMENT_REASON_REQUIRED | Explicit mode without tax_adjustment_reason. |
TAX_ADJUSTMENT_AUDIT_REQUIRED | Explicit mode without tax_adjustment_audit. |
TAX_ADJUSTMENT_REQUIRES_EXPLICIT_MODE | Reason or audit sent in automatic mode. |
TAX_BREAKDOWN_REFUNDS_REQUIRED | Explicit mode on a line item that has jurisdiction breakdowns. |
TAX_BREAKDOWN_REFUNDS_REQUIRE_EXPLICIT_MODE | tax_breakdown_refunds sent in automatic mode. |
TAX_BREAKDOWN_REFUND_TOTAL_MISMATCH | Entries do not sum to the line item's tax_money. |
DUPLICATE_TAX_BREAKDOWN_REFUND | The same tax_breakdown_id appears twice. |
TAX_BREAKDOWN_LINE_ITEM_MISMATCH | The breakdown does not belong to that line item. |
TAX_BREAKDOWN_REFUND_EXCEEDS_REMAINING | More than the breakdown's unrefunded tax. |
EXPLICIT_TAX_REFUND_NOOP | The explicit amount equals the automatic reversal. |
TAX_REFUND_EXCEEDS_AUTOMATIC | The explicit amount exceeds the automatic reversal. |
FLAT_TAX_REFUND_MUST_BE_FULL | An order-scoped flat row was partially refunded. |
TAX_BREAKDOWN_NOT_ORDER_SCOPED_FLAT | A top-level entry names a row that is not order-scoped flat. |
Next Steps#
- Refunds: the rest of the refund request that tax reversal sits inside.
- Tips & Fees: taxing delivery, service, and handling charges.
- Reports: the two tax reports and how to download them.
- Orders API reference: every tax field on the order.
