5. Sales Tax Calculations
The tax estimation endpoint (POST /tax-estimations) prices sales tax before you take payment. It is the core of the Level 2 (L2) tax engine, enabled once transaction sync (L1) is in place. Rates reflect your registrations, your product taxability, your customer's exemptions, and the address you are shipping to. This guide covers when to call it, how to shape the request, and how to use what comes back.
Understanding Tax Estimates
A tax estimate is a real-time calculation with no record behind it. Each estimate:
- Prices tax against your current registrations
- Applies the taxability rules for each product
- Honors customer exemptions
- Resolves rates for the destination jurisdiction
- Returns a per-line-item tax breakdown
Nothing is stored and an estimate cannot be retrieved afterwards, so you can call the endpoint as often as customers change their cart or their address. To price the same cart again, send the same request again.
Estimates do not sync transactions: POST /tax-estimations calculates tax; it does not record a sale. For full compliance (L2), sync the transaction separately through POST /transactions once payment is confirmed. Kintsugi determines nexus from those synced transactions, which is what makes accurate calculation possible in the first place, even when Kintsugi is not handling your filing and remittance.
When to Calculate Tax
Calculate during checkout or billing, after address entry and before payment processing. Common integration points:
- Shopping cart pages: When customers review their order
- Checkout flows: Once the shipping address is entered
- Subscription billing: When pricing a recurring charge
- Quote generation: When quoting a total to a customer
Validate the address first: Run addresses through address validation before calculating. The estimate validates addresses too, and one that cannot be validated returns 400, so checking early lets you correct it while the customer is still on the page.
Tax Estimate Request Structure
An estimate request mirrors the shape of a transaction sync request, with its lines in transactionItems.
The estimate is computed for exactly one organization. If your key can reach more than one, send an Organization-Id, Connection-Id, or Entity-Id header to choose it; without one, the request is rejected with 400.
Required Fields
externalId: Your identifier for the transaction being priced, echoed on the responsedate: When the transaction takes place. The rates in force on this date are the ones appliedcurrency: ISO 4217 currency code, for exampleUSDaddresses: At least aSHIP_TOorBILL_TOaddresstransactionItems: The line items to price. At least one
Optional Fields
customer: The buyer. SendexternalIdto match a customer on file and pick up their exemptions and tax registrations. Sendnull, or leave it out, when you have no buyer to attribute the sale tosimulateActiveRegistration: Settrueto price the transaction as though you were registered in the destination jurisdiction. Defaults tofalse
Transaction Items
Each line item requires:
externalId: Your identifier for the line, returned on the matching response line so you can attribute each tax amountamount: Total for the line after discounts, as a decimal string
And should carry:
externalProductId: The product to price, one you have already createdquantity: Defaults to"1"exempt: Settrueto treat this specific line as exempt regardless of the rules. Defaults tofalsedate: Only when the line's date differs from the transaction date. Leave it out to use the transaction date, which is almost always correct
Classifying an item inline: Instead of an externalProductId, a line can name a productCategory and productSubcategory pair, optionally with productName and productDescription. The line is priced under that classification without creating a product. Send one or the other, not both, and note that an unrecognized pair returns 400. Pre-built catalogs keep classification consistent, so treat inline classification as a fallback rather than the default path.
An externalProductId is unique only within a connection. When the same ID exists in more than one of your connections, send a Connection-Id header to price against that connection's product; without one, the ambiguous ID returns 400.
Addresses
Every address entry needs a type of SHIP_TO, BILL_TO, SHIP_FROM, or BILL_FROM, plus street1, street2, city, county, state, postalCode, and country (ISO 3166-1 alpha-2). A single fullAddress string can stand in for the structured fields, and isUnincorporated: true marks an address outside any city limit so city-level rates are not applied.
Tax is sourced to the SHIP_TO address when one is supplied, and to BILL_TO otherwise.
Addresses are validated as part of the estimate. An address that cannot be validated returns 400, one in a country Kintsugi does not cover returns 422, and an address-validation outage returns 503.
Tax Estimate Response
The response echoes your request and adds the calculation.
At the top level:
totalTaxAmount: Total tax due on the transactiontaxableAmount: Total across all lines that tax was charged ontaxRate: Combined effective rate across the transaction, as a fraction of the taxable amounthasActiveRegistration: Whether an active registration covers the destination jurisdictionaddresses: The addresses as validated, each with astatus.VERIFIEDandPARTIALLY_VERIFIEDare the only statuses an estimate is priced from
On each entry in transactionItems:
taxAmount: Tax due on that linetaxableAmount: The portion of the line that tax was charged ontaxRate: Combined rate applied to the lineexemptandexemptReason: Whether the line was exempt, and whyproductCode,productCategory, andproductSubcategory: The tax code the line was priced undertaxItems: The tax applied per jurisdiction, each with aname,rate,amount,exemptflag, andexemptReason
Amounts come back as strings: Monetary values and rates are returned as decimal strings, with amounts at 2 places and rates at 9, for example "8.25" and "0.082500000". Parse them with a decimal-safe type rather than a float so cents do not drift.
Using Tax Amounts
Take the figures from the response to:
- Show tax to customers during checkout
- Calculate the final total
- Carry tax amounts into the transaction you sync later
- Sanity-check the calculation before you charge
Keep the estimate: Store the response so the transaction you sync afterward carries the same tax amounts the customer saw. Send each line's charged tax on that line's taxAmountImported when you call POST /transactions. Your records and your receipts then agree.
Tax Calculation Workflow
The Three Calls
Each one gates the next. A bad address gives a wrong rate; an unrecorded sale never reaches your filings.
Checkout Sequence
Solid step numbers are the Kintsugi calls. Everything else happens in your storefront.
Nexus and Tax Calculation
Kintsugi calculates tax where you hold an active registration. The hasActiveRegistration flag on the response tells you which side of that line the transaction fell.
When Tax Is Calculated
Tax applies when:
- An active registration covers the customer's jurisdiction
- The product is taxable there
- No valid exemption covers the sale
When Tax Is Zero
Tax is zero when:
- No active registration covers that jurisdiction.
hasActiveRegistrationis thenfalseand every amount is zero - The product is exempt there
- A valid customer or line-level exemption applies
In the exempt cases, exemptReason on the line item tells you which rule zeroed it out, for example PRODUCT, CUSTOMER, TRANSACTION, WHOLESALE, or REGION.
Preview a registration before you make it: Set simulateActiveRegistration: true to see what the transaction would be taxed at if you were registered in the destination. Leave it false to see what you owe today.
Customer Exemptions
To have an exempt customer's status applied, reference the customer on the estimate:
{
"externalId": "txn-1001",
"date": "2026-07-28T12:00:00Z",
"currency": "USD",
"customer": {
"externalId": "cust-1001",
"name": "Acme Corp"
},
"addresses": [
{
"type": "BILL_TO",
"street1": "123 Main St",
"city": "Austin",
"state": "TX",
"postalCode": "78701",
"country": "US"
}
],
"transactionItems": [
{
"externalId": "item-1",
"externalProductId": "sku-1001",
"quantity": "2",
"amount": "100.00"
}
]
}
When the customer's externalId matches a customer Kintsugi holds, that customer's exemptions and tax registrations are applied to the estimate. Only an ACTIVE exemption is applied by tax calculation. For one-off exemptions that are not tied to a customer record, set exempt: true on the relevant line items instead.
See the Product & Customer Records guide for how to create exempt customers.
Error Handling
Every error comes back in one envelope, { "code", "message", "requestId", "errors": [...] }. Common failures when calculating tax:
- Product cannot be priced: Send a known
externalProductId, or a validproductCategoryandproductSubcategorypair. An unrecognized pair returns400 - Ambiguous product: An
externalProductIdthat exists in more than one of your connections returns400without aConnection-Idheader - Invalid address: An address that cannot be validated returns
400; one in a country Kintsugi does not cover returns422 - No organization chosen: A key that reaches more than one organization must send a selector header, or gets
400 - Missing required fields:
externalId,date,currency,addresses, andtransactionItemsare all required, and so areexternalIdandamounton every line - Missing or invalid API key: Every request needs a valid
Api-Keyheader, or it returns401 - Service unavailable or rate limited:
503and429are worth retrying with exponential backoff
See the Error Handling guide for detailed strategies.
Best Practices
Request Structure
- Use consistent external IDs: Keep one identifier across the estimate and the transaction that follows it
- Validate addresses first: Verified addresses resolve to the right jurisdiction
- Reference products precisely:
externalProductIdvalues must match your product records - Send complete line items: Every item needs an
externalIdand anamount
Response Handling
- Store the response: Carry the same tax amounts into transaction sync
- Handle zero tax: No registration and valid exemptions are normal outcomes, not errors
- Show the breakdown: Surface
taxItemsso customers and your support team can see how tax was composed - Parse decimals safely: Amounts and rates arrive as strings
Performance
- Cache short-lived estimates: Reuse a result while the cart and address are unchanged
- Debounce address input: Wait for typing to settle before calling
- Fail gracefully: Decide in advance what checkout shows if an estimate fails
- Watch your call volume: Track usage so you see rate pressure before your customers do
Integration Patterns
E-Commerce Checkout
- Customer adds items to the cart
- Customer enters a shipping address
- Validate the address
- Calculate tax with
POST /tax-estimations - Display the total with tax
- Process payment
- Sync the transaction with those tax amounts
Subscription Billing
- Customer selects a plan
- Customer provides a billing address
- Calculate tax for the first billing cycle
- Store the tax amount for recurring charges
- Recalculate when the address changes or the subscription renews
Multi-Step Checkout
- Calculate tax once the shipping address step is complete
- Recalculate when the customer changes address
- Recalculate when the customer changes the cart
- Update the displayed total after each recalculation
Reading the Tax Breakdown
Each line item's taxItems array shows the tax applied per jurisdiction, for example a state tax and a county tax, each with its own name, rate, and amount. Together with the line's taxRate and taxAmount, and the transaction-level totalTaxAmount, that gives you a complete picture of the calculation.
Use it to:
- Show customers how their tax was composed
- Produce receipts and invoices with real tax detail
- Debug unexpected results against a specific rate component
- Reconcile totals before you charge
Next Steps
With tax calculation integrated:
-
Sync transactions: After payment, sync the sale with the tax amounts from the estimate. See the Syncing Transaction Records guide.
-
Handle errors: Build the failure path before you need it. See the Error Handling guide.
-
Tune performance: Cache and debounce to cut calls and keep checkout fast.
For endpoint-level detail, see: