KintsugiKintsugi

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.

POST /tax-estimations
-H "Api-Key: ***"
-H "Api-Version: 2026-07-21"
{
"externalId": "txn-1001",
"date": "2026-07-28T12:00:00Z",
"currency": "USD",
"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"
}
]
}

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 response
  • date: When the transaction takes place. The rates in force on this date are the ones applied
  • currency: ISO 4217 currency code, for example USD
  • addresses: At least a SHIP_TO or BILL_TO address
  • transactionItems: The line items to price. At least one

Optional Fields

  • customer: The buyer. Send externalId to match a customer on file and pick up their exemptions and tax registrations. Send null, or leave it out, when you have no buyer to attribute the sale to
  • simulateActiveRegistration: Set true to price the transaction as though you were registered in the destination jurisdiction. Defaults to false

Transaction Items

Each line item requires:

  • externalId: Your identifier for the line, returned on the matching response line so you can attribute each tax amount
  • amount: Total for the line after discounts, as a decimal string

And should carry:

  • externalProductId: The product to price, one you have already created
  • quantity: Defaults to "1"
  • exempt: Set true to treat this specific line as exempt regardless of the rules. Defaults to false
  • date: 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 transaction
  • taxableAmount: Total across all lines that tax was charged on
  • taxRate: Combined effective rate across the transaction, as a fraction of the taxable amount
  • hasActiveRegistration: Whether an active registration covers the destination jurisdiction
  • addresses: The addresses as validated, each with a status. VERIFIED and PARTIALLY_VERIFIED are the only statuses an estimate is priced from

On each entry in transactionItems:

  • taxAmount: Tax due on that line
  • taxableAmount: The portion of the line that tax was charged on
  • taxRate: Combined rate applied to the line
  • exempt and exemptReason: Whether the line was exempt, and why
  • productCode, productCategory, and productSubcategory: The tax code the line was priced under
  • taxItems: The tax applied per jurisdiction, each with a name, rate, amount, exempt flag, and exemptReason

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.

CALL 1
Validate the address
Destination decides the rate, down to the local jurisdiction. Recommended rather than required, and it fills in fields such as county when it can.
/addresses/validate
CALL 2
Quote the tax
Returns amounts and the jurisdictions they belong to. Nothing is recorded.
/tax-estimations
CALL 3
Record the sale
Only after payment succeeds, carrying the tax you quoted.
/transactions

Checkout Sequence

Solid step numbers are the Kintsugi calls. Everything else happens in your storefront.

Cart assembled, address entered
Your storefront. No Kintsugi call yet.
Validate the address
POST/addresses/validateRecommended
Returns standardizedAddress, the standardized and enriched version of what you sent. enrichedFields names the fields validation added or corrected, and verificationStatus says whether the address verified. An unverified address can resolve to the wrong jurisdiction.
Assemble the estimate request
externalIddatecurrencyaddressestransactionItems
Each line needs an existing product record or an inline productCategory and productSubcategory pair.
Calculate tax
POST/tax-estimationsReturns a quote
RETURNSTax amounts broken out by the jurisdictions that levy them, on each line item's taxItems. A quote only: nothing is stored, and there is no estimate ID to track.
Show the total with tax
Display the quoted amount before the customer pays, not after.
Process payment
DECLINED →Send the customer back to the cart. The estimate was never recorded, so there is nothing to reverse in Kintsugi.
Sync the transaction with the tax amounts
POST/transactionsOnly after payment succeeds
Send the tax you actually charged on each line's taxAmountImported. See Syncing Transaction Records.
Order complete
The sale counts toward nexus and appears in filings.
An estimate is not a record. If step 7 never runs, the sale is invisible to nexus and filings even though the customer was charged tax.

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. hasActiveRegistration is then false and 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 valid productCategory and productSubcategory pair. An unrecognized pair returns 400
  • Ambiguous product: An externalProductId that exists in more than one of your connections returns 400 without a Connection-Id header
  • Invalid address: An address that cannot be validated returns 400; one in a country Kintsugi does not cover returns 422
  • 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, and transactionItems are all required, and so are externalId and amount on every line
  • Missing or invalid API key: Every request needs a valid Api-Key header, or it returns 401
  • Service unavailable or rate limited: 503 and 429 are 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: externalProductId values must match your product records
  • Send complete line items: Every item needs an externalId and an amount

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 taxItems so 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

  1. Customer adds items to the cart
  2. Customer enters a shipping address
  3. Validate the address
  4. Calculate tax with POST /tax-estimations
  5. Display the total with tax
  6. Process payment
  7. Sync the transaction with those tax amounts

Subscription Billing

  1. Customer selects a plan
  2. Customer provides a billing address
  3. Calculate tax for the first billing cycle
  4. Store the tax amount for recurring charges
  5. Recalculate when the address changes or the subscription renews

Multi-Step Checkout

  1. Calculate tax once the shipping address step is complete
  2. Recalculate when the customer changes address
  3. Recalculate when the customer changes the cart
  4. 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:

  1. Sync transactions: After payment, sync the sale with the tax amounts from the estimate. See the Syncing Transaction Records guide.

  2. Handle errors: Build the failure path before you need it. See the Error Handling guide.

  3. Tune performance: Cache and debounce to cut calls and keep checkout fast.

For endpoint-level detail, see: