Avalara AvaTax setup guide

Connect Avalara AvaTax to an application: credentials, company code, tax code mapping, customer exemptions, address validation, customs duty and import tax (CDIT), exemption certificates (ECM) and the call log.

Avalara AvaTax is a cloud-based service that calculates transaction tax in real time and files it. This page is the setup guide: what to enter, where, and how to tell that it worked.

There is nothing to install. AvaTax is connected from inside the dashboard, per application and per environment, and no deployment is involved.

What Throttle calls, and when

WhenThe AvaTax call
Tax on a cart, a checkout, an orderCreateTransaction, type SalesOrder — a read-only quote that never reaches a filed return
Tax on a paid orderCreateTransaction, type SalesInvoice, committed, with the order number as the document code
A refundRefundTransaction against the committed document
A cancellationVoidTransaction
An address checkResolveAddress (US and Canada)
Tax-code search in the dashboardListTaxCodes
Company lookup during setupQueryCompanies
Credential check on savePing, then QueryCompanies

Quotes and filed documents are different document types, which is why turning off document recording still leaves tax calculating.

Before you begin

From Avalara you need an account id, a license key, and at least one company on the account with nexus configured in the jurisdictions you sell into.

In Throttle you need the admin or developer application role. finance and viewer cannot connect a provider.

A company with no nexus is the most common setup surprise. AvaTax returns $0.00 tax for every address until nexus exists. Throttle checks for this when you connect and shows it as a warning on the connection, rather than letting you find out from a zero-tax order.

Connect to AvaTax

1. Confirm which environment you are in. The AvaTax service URL is not a field you type — it follows the workspace environment you are working in. Any non-production environment reaches sandbox-rest.avatax.com; the production environment reaches rest.avatax.com. To connect production credentials, switch the environment selector in the top bar to production first. The connect dialog states the resolved environment as read-only text, so you can see which AvaTax host your credentials are about to be sent to before you type them.

The Connect dialog, showing the resolved AvaTax environment as read-only text 2. Go to App → Tax → Tax providers → Connect new → avalara. Enter:

FieldNotes
Account IDProvided when your AvaTax account was activated
License KeyProvided when your AvaTax account was activated. Masked in the dashboard and never returned by the API — responses replace credential values with placeholders, so the UI can show that a credential is set without exposing it
Company CodeChosen in step 3, from the companies on your account

Credentials are held per workspace, application and environment. A sandbox connection and a production connection are separate records and never share a key.

The Connect dialog with Account ID and License Key filled in, before the company lookup runs. The Account ID is redacted in this screenshot.

3. Look up your company. With the account id and license key filled, Look up calls QueryCompanies and the company field becomes a picker showing each company code and name. If the account holds exactly one company it is selected for you. The field stays free text if the lookup fails, so a provider outage slows setup down instead of blocking it. The lookup runs on the credentials as typed and stores nothing — nothing is saved until you save the connection.

The company field after Look up: a picker listing the companies on the AvaTax account, with a count below it

4. Save. Saving verifies: it runs Ping, then QueryCompanies, and tells you which of four things happened.

ResultMeaning
ConnectedCredentials are valid and the company exists
Connected, with a warningUsable, but something was worth saying: the company has no nexus, its base currency differs from the application’s, or the company list could not be read so neither could be checked (re-verify — that one usually clears)
Invalid credentialsPing reported not-authenticated, or the call returned 401
Invalid configurationThat company code is not on the account — the message names the codes that are

The saved connection row: provider, environment, status and last-verified time 5. Switch tax to the provider. On App → Tax → Setup, set tax mode to App-based (third-party provider) and press Save. Tax mode is the only calculation switch; there is no second one to fall out of sync with it.

Save publishes: it takes effect immediately for every checkout in that environment. There is no second button to press, and nothing you saved is left sitting as an unpublished draft.

App → Tax → Setup with tax mode set to App-based

Document recording

Document recording, on the Tax providers page, decides whether Throttle writes to your AvaTax account at all.

  • On (the default) — a paid order is committed as a SalesInvoice and appears in your filed returns. Refunds and cancellations follow it.
  • Off — quote only. Tax still calculates on every cart and order, because quotes were always the read-only SalesOrder type, but nothing is committed, refunded or voided in AvaTax.

Turn it off if you file from another system and want Throttle only for the numbers. It is per application and versioned with the rest of the published config.

The document code is the order number. AvaTax requires it to be unique within the company, while Throttle order numbers are unique within an application — so two applications filing into one AvaTax company can produce the same code for two different orders. Throttle checks the order total before it reuses an existing document, and refuses the filing rather than binding your order to someone else’s document, which is what would later be refunded against. The document shows as failed with both totals named. The fix is either a distinct order-number prefix per application, or a separate AvaTax company per application.

The Document recording toggle on the Tax providers page

Map tax codes to items

Throttle does not store a product catalogue — line data arrives in your cart or checkout payload — so an item’s AvaTax tax code resolves at three levels, most specific first.

  1. Per line item. The payload carries taxCategory on the line. This is the normal path when your own catalogue already knows its tax codes. You can also set it by hand with no integration work — every line on a quote has a Tax code field, in New quote and in the working-revision editor, which writes the same taxCategory.
  2. Application default. Default item tax code, under Tax codes in the Avalara settings card on the Tax providers page, covers any line with no taxCategory. Shipping tax code beside it does the same for shipping — typically FR020100. Both fields search ListTaxCodes, so you pick from Avalara’s catalogue rather than typing a code from memory.
  3. Neither set. The amount is still sent and AvaTax decides taxability without a code.

The full list of AvaTax system tax codes is at taxcode.avatax.avalara.com.

If you keep an item catalogue in AvaTax, that wins. Throttle sends your SKU as itemCode on every line, so an item registered under Items on your AvaTax company resolves its own tax code there — and it takes precedence over both the code on the line and the application default. Verified against the AvaTax sandbox: an item registered as NT taxed at 0.00 even when the line was sent with P0000000. Newly created items take up to a minute to index before that happens. This is the mapping we’d point a larger catalogue at: it lives in one place, and every system that sends the same SKU agrees.

The Avalara settings card on the Tax providers page, searching AvaTax tax codes live

File by outlet: reporting location

Some jurisdictions require a return per outlet — the store, branch or warehouse a sale is reported from. Reporting location, directly under the tax codes in the Avalara settings card, sets the AvaTax reportingLocationCode for the application.

The field searches the Company Locations registered on your AvaTax company, so the code you pick is one AvaTax already recognizes. A code that is not on that list is flagged before you save, because AvaTax rejects an unregistered location when the document is created — that is at commit time, after the buyer has been charged, not when you save the setting.

Leave it blank if the application is a single location with no special outlet filing. Blank is a valid answer; Throttle omits the field entirely rather than sending an empty value.

The same code goes on the quote as well as the filed document. In an outlet-filing jurisdiction the location can change the rate, so pricing from one location while filing from another would charge the buyer one number and remit another.

The Reporting location field, listing the AvaTax company's registered locations

Selling into the EU: VAT

AvaTax handles VAT through the same calls as sales tax, but four things only matter once you cross a border. Each is optional — none of them affects a domestic US sale.

Who moves the goods

Transport responsibility, behind Cross-border in the Avalara settings card, records who is responsible for moving the physical goods: Buyer, Seller, Third party (for seller), Third party (for buyer), or None. In several EU regimes that answer decides where a cross-border supply is taxed, so it is an input to the rate rather than a note on the paperwork.

The setting is the application’s default. Override it for a single order by setting transport on the order’s metadata:

{ "metadata": { "transport": "ThirdPartyForBuyer" } }

Only those five values are accepted; anything else is dropped rather than sent, because AvaTax rejects the whole transaction on an unrecognized parameter value — at commit time, that is after the buyer has paid. Leave it blank on domestic sales. Blank means the parameter is not sent at all.

The same value goes on the quote and on the filed document, so the rate the buyer saw is the rate that is filed.

Overriding an application default on one order

Four of the application-level tax answers can be overridden for a single transaction, by putting the key on the cart’s metadata (the order copies it, which is what keeps the quote and the filed document agreeing):

KeyOverridesUse it when
taxCategory (per line)Default item tax codeThe line is not general tangible goods
shippingTaxCodeShipping tax codeThis order ships by a different means — FR020100 (common carrier) and FR010000 (your own vehicle) are different codes, and taxable in different states
taxTriggerTax triggerThis order should be taxed against a different address — drop-ship, a gift, a digital line in a physical basket
transportTransport responsibilityThis cross-border order moves on different terms
{ "metadata": { "shippingTaxCode": "FR010000", "taxTrigger": "billing_address" } }

That shipping code is worth a concrete example, because the difference is not cosmetic. On a $25 delivery into California, FR020100 is exempt and FR010000 is taxed $2.15 — the same order, the same address, a $2.15 difference decided by which truck it went on. Pick the code from the search field rather than typing one: AvaTax does not reject a freight code it does not recognize. It silently falls back to the general goods code and taxes the line, so a typo here is wrong in your returns rather than loud at checkout.

taxTrigger accepts only shipping_address, billing_address and customer_address. It moves the ship-to address and nothing else: an order cannot switch taxation on or off. If the application’s tax trigger is disabled, it stays disabled; if it is enabled, an order cannot disable it. Whether you tax at all is a published-config answer you can see on the Setup tab, not something a payload decides. Any unrecognized value in either key is dropped rather than sent.

Prices that already include tax

Whether prices include tax is normally an application-wide answer — Tax pricing on App → Tax → Setup, either tax_exclusive or tax_inclusive. A single line can disagree with it in either direction by carrying taxIncluded in its own metadata:

{ "name": "Ticket", "unitPrice": 12000, "metadata": { "taxIncluded": true } }

true means the amount already contains tax and AvaTax should carve it out; false means it does not, even on a tax-inclusive application. Omit the key and the line follows the application setting. The quote and the committed document read the same key, so a line quoted gross is never filed net.

The VAT code AvaTax returned

Every tax line Throttle stores keeps three values straight from the engine alongside our own normalized type:

  • vatCode — AvaTax’s own code for the VAT that applied, such as IE_S_L_230_C. Present on VAT jurisdictions; absent on US sales tax, which has no VAT code.
  • taxName — the engine’s name for the tax, such as Standard.
  • rateTypeCode — its rate classification: S standard, R reduced, Z zero.

They appear on the calculation response, on GET /api/v1/carts/{cartId} under taxLines[], and are persisted with the cart’s tax lines — so the record shows which VAT actually applied rather than only that VAT applied. The full AvaTax request and response for any calculation is also readable under App → Tax → Provider calls.

AvaTax’s own wording on the invoice

When AvaTax returns messaging about a transaction — a reverse-charge notice, an intra-community supply note — Throttle stores it verbatim on the calculation, carries it to the order, and prints it on the customer invoice. It is readable as providerMessages on a calculation response and as taxProviderMessages on the cart and the order. AvaTax does not return this as plain text: it returns one envelope listing every candidate message and which lines each applied to, so Throttle expands it into the applicable text itself — Article 138.1 - Directive 2006/112 EC, not the envelope. In several member states the engine’s own wording is what makes the invoice compliant, so it is reproduced rather than paraphrased, and it prints alongside Throttle’s own reverse-charge line, not instead of it. Nothing is shown when AvaTax returns no messaging.

Customs duty and import tax (CDIT)

On a cross-border sale, AvaTax can return the import duty and import VAT alongside the ordinary tax, so the buyer sees a landed cost rather than a bill at the border. Throttle displays them apart from tax, files them on the document, and records what the border actually charged when it differs.

What has to be true first

RequirementWhereIf it is missing
AvaLandedCost on the accountAvalaraNo duty is returned at all
Seller is importer of record, on the destination’s customs-duty nexusAvaTax → Settings → Where you collect taxDuty is ESTIMATED, not charged — see below
A genuinely cross-border saleThe orderAvaTax refuses the customs document outright

The nexus flag is what decides whether duty is charged. Not the subscription, and not anything on the Throttle side. With AvaLandedCost live but the destination nexus’s isSellerImporterOfRecord unset, a US → IE sale of 950.00 returns totalTax: 0, no charged duty line, and a message saying the buyer is the importer of record and will pay at the border. Set it and the same request returns the duty and the import VAT as charged tax. The document cannot override the nexus in either direction — sending isSellerImporterOfRecord: true on the transaction is echoed back as false while the nexus says false. AvaTax caches nexus configuration, so allow about a minute after changing it.

Cross-border means crossing a customs frontier, not crossing a border. Goods moving between EU member states are inside a customs union, so there is no customs document to file. AvaTax says so plainly — “Customs document type valid only for cross border transactions. Goods moving between EU Member States are within a customs union.” A Germany → Netherlands sale can carry VAT but never duty; a US → Ireland sale carries both.

Classify your items

Customs duty is rated from a Harmonized System code, not a tax code, so classification is its own mapping. App → Tax → Classifications holds one row per item code:

FieldNotes
Item codeYour own SKU. Matched against the line’s referenceId — see the warning below
Destination countryBlank means every destination. A country-specific row overrides the blank one, field by field
HS code6 to 10 digits, no dots or spaces. 6204.42.00 is rejected; 6204420000 is accepted
Product categoryFull breadcrumb path, > separated; multiple paths separated by ;
Country of manufactureOnly sent alongside a preferred-programme claim — see the warning below
Weight and volumeNetWeight, GrossWeight, ShippingWeight, NetVolume, which some tariff lines require to rate

A row needs an HS code or a product category; one with neither classifies nothing and is refused.

The mapping is keyed on itemCode, which comes from the cart line’s referenceId. A line sent without one produces no item code, so the Classifications table is never consulted and the mapping silently has no effect. If you send line items through the API, send referenceId.

Country of manufacture, on its own, removes the duty. Measured against the live engine: a US → IE line went from 95.00 duty and 240.35 import VAT to no duty at all the moment a country of manufacture was added, with every other field unchanged — and the same happened with IsPreferredProgram: false alongside it. Presence is enough. Throttle therefore sends it only paired with a preferred-programme claim, in both directions. Fill the field if you claim a trade preference; leave it blank otherwise.

When there is no HS code

Avalara’s own “estimate taxes” scenario has no tariff code on the line, and AvaTax estimates one. It estimates from the line DESCRIPTION — not from the product category, and not from the SKU. Measured US → IE at 950.00:

Line descriptionCategory sentDuty returned
Leather shoes, outer sole rubbernone8%
Leather handbag, women'snone3%
Very long cotton dressClothing > For Women > Dresses12%
Very long cotton dressnone12%
Cuban Links (a product NAME)Man > Jewelry ; Gold > Bracelet0.00

Two things follow. The category does not pick the tariff line — the clothing figure is identical with it and without it — and a product name classifies as nothing. Send a description of the goods, not a model name.

Declare who clears the goods

App → Tax → Tax providers → Avalara settings → Cross-border carries the declaration that rides on the document:

ControlSent asNotes
Transport responsibilityTransportA closed value set — an unrecognised value fails the whole transaction
Importer of recorddocument flagDeclares who clears the goods. It does not decide what AvaTax charges
Delivery termsDeliveryTermsEleven Incoterms, not the two the badge prose names
Buyer’s agentIsBuyersAgent
Default product categoryline parameterApplied to lines with no category of their own
Fiscal representationIsCustomerRegisteredThroughFiscalRep, IsRegisteredThroughFiscalRepThree states, not a checkbox — Not answered sends nothing, which is a different claim from No

What the buyer sees

The order summary shows import fees apart from tax, in Avalara’s own order:

Subtotal
Shipping
Import fees          <- Show breakdown
  Duty
  VAT
Total

Duty is aggregated into one line for the buyer, because a summary with two duty rows at different rates is a worse summary. The per-rate detail is on the wire and in App → Tax → Provider calls: a two-line order returns two LandedCost/ImportDuty rows at their own rates plus one Output/O row. The buyer sees what they owe; the filing keeps the detail per rate.

De minimis is not a message. Below the threshold AvaTax changes the duty basis rather than announcing anything: Ireland charges a flat amount under €150 against an ad valorem rate at or above it, and every product tested returns the same flat charge whatever its tariff line. Do not look for a notice — read the figures.

Engine messaging follows the same logic. AvaTax emits a customs message when the duty is an estimate the buyer will pay at the border, and says nothing when the seller is the importer of record and the duty is genuinely charged. A charged duty needs no notice.

Record what the border actually charged

The sales invoice files the estimate made at the time of sale. Parcels clear at something else — exchange rates move, classifications get corrected, and a parcel can cross a de minimis threshold on a different day.

On App → Tax → Documents, a committed SalesInvoice for a cross-border order carries a File customs control. Enter the duty and the import tax from the shipper’s receipt and Throttle files a CustomsInvoice against the same order, under its own document code, and shows the difference beside the sales invoice rather than reconciling it away.

The receipt gives one total, so Throttle apportions it across every line by value — which is how an ad valorem duty was assessed in the first place — with the largest line absorbing the rounding remainder so the filed total equals the receipt to the cent. AvaTax requires a line-level override on every line of a customs document, freight included, and refuses the filing when any line lacks one.

Identify a customer’s exemption

Open Customers → a customer. The Tax exemption block holds two values, both sent to AvaTax on every calculation for that customer:

  • Entity/use code — picked from Avalara’s own catalogue, which Throttle loads live. If the catalogue cannot be reached the field accepts the letter code directly, so setup is never blocked. These are Avalara’s codes: A federal government, B state government, G resale, J direct pay permit, and so on through R non-resident (Canada).
  • Exemption certificate number — for merchants not using Avalara ECMS.

A buyer’s VAT number is carried from the cart at checkout and sent as well, which is what drives reverse charge on cross-border EU B2B.

AvaTax decides exemption, per jurisdiction. Setting these values does not zero the tax by itself — Throttle never overrides tax on its own authority. It passes the inputs and files what AvaTax returns.

The customer code Throttle sends is the customer’s own code, falling back to throttle-guest for a guest checkout. That is the value AvaTax joins exemption certificates on, so certificates managed in AvaTax match a stable code.

The Tax exemption card on a customer, with the AvaTax entity/use code list

Exemption certificates (ECM)

Avalara’s Exemption Certificate Management holds a buyer’s certificates and decides, per jurisdiction, whether they are exempt. Throttle mirrors the customer into ECM, requests certificates on their behalf, and shows what ECM holds — on the same AvaTax account, with no second credential.

Turn it on

App → Tax → Tax providers → Avalara settings → Exemption certificates. It is off by default, deliberately: mirroring a customer to Avalara discloses buyer data to a third party, and that should be a decision rather than a side effect of connecting a tax provider.

Then press Test ECM connection. It answers one of three ways:

ResultMeaning
Connected. Avalara returned N exemption jurisdictions.ECM is usable on this company
Your credentials work, but this company is not provisionedCertCapture is not switched on for the company. It is self-service — requestCertificateSetup provisions it in a minute or two
An errorCredentials or connectivity

This is deliberately not the tax connection’s own verify. That call proves the AvaTax credentials, which are already good on any account that calculates tax, so it answers “yes” for an account that cannot use ECM at all. The ECM test reads a real ECM catalogue instead.

The company is addressed by numeric id, which Throttle resolves once when you connect and stores, rather than spending a lookup per request.

The customer mirror

Once ECM is on, a customer is mirrored into CertCapture automatically — on create, on edit, and whenever their address is added, changed or removed. The mirror never blocks the write that triggered it: a slow or failing Avalara cannot fail a signup, and what went wrong is recorded on the customer instead.

ECM requires a complete addressline1, city, region, postalCode and country, verified field by field against the live API. A customer missing any one of them cannot be mirrored at all.

Region is AvaTax’s code, not the name. ECM answers region: "Dublin" with “Field ‘region’ has an invalid length.” — it wants D. Throttle resolves this for you against AvaTax’s own region list for the country, so either the name or the code is accepted and anything unrecognised is passed through for AvaTax to judge rather than guessed at.

When a mirror does fail, the customer’s Exemption certificates panel shows the reason Avalara gave. AvaTax’s own answer for an unmirrored customer is “Customer not found.”, which is true and useless — it names the symptom rather than the cause — so Throttle shows the recorded sync error in preference to it.

Request a certificate

On the customer, Exemption certificates → Request a certificate sends a CertExpress invitation, with the recipient pre-filled from the customer’s email. The buyer follows it to Avalara’s hosted flow and uploads their certificate there; it is linked to the customer record automatically.

Exposure zone and exempt reason are chosen from Avalara’s live catalogues. Note that the zones this account exposes are US states and Canadian provinces — a certificate is scoped to a jurisdiction, so a buyer outside that set has no zone to be exempt in.

Retrieve, view and revoke

Reload the customer and each certificate is listed with its exempt state, exempt reason and expiry.

  • View opens the certificate PDF in the browser, where the browser’s own viewer provides print and save. It is streamed through Throttle rather than linked straight to Avalara, because fetching it needs the merchant’s Avalara credentials and those must never reach a browser.
  • Remove unlinks the certificate from this customer. It does not delete it — a certificate can be linked to several customers, and deleting removes it from all of them.

Everything is logged

Every ECM call — the mirror, the catalogues, the invitation, the retrieval — appears in App → Tax → Provider calls with its full request and response body and the round-trip duration, retrievable by the merchant and retained for 90 days. Filter to ecm to see them apart from the tax traffic.

The buyer’s purchase order number

A B2B buyer’s own PO number rides onto the filed AvaTax document as purchaseOrderNo, so the transaction Avalara holds matches the paperwork the buyer’s auditor holds. It is reporting only — it never changes the tax.

Throttle reads it from the order’s metadata, checking purchaseOrderNo first and then buyerReference. You do not have to set anything for the quotes flow: the RFQ form already asks the buyer for their reference, and it is carried from the quote to the cart to the order. For orders created through the API, set either key yourself:

{ "metadata": { "purchaseOrderNo": "PO-1001" } }

AvaTax caps the field at 50 characters and rejects the whole transaction when it is longer, so Throttle trims to 50 rather than failing a commit after the buyer has paid.

Validate an address

Addresses are checked against AvaTax in two places: your ship-from origin, on App → Tax → Setup → Verify origin, and the buyer’s address at checkout, before payment. Throttle calls ResolveAddress and shows the address AvaTax resolved — at checkout the buyer can apply it as typed by AvaTax. The origin form collects city, state, postal and country, which is what tax sourcing needs, so AvaTax resolves the jurisdiction and still notes that it could not match a street line; Throttle shows the resolved place alongside that note rather than calling a correct origin unresolvable.

ResolveAddress covers the United States and Canada. Throttle applies that filter itself: an address anywhere else is returned unvalidated rather than being sent to AvaTax and rejected. An address that cannot be resolved is reported as unresolved, never as a hard failure — blocking a sale because validation was unavailable is worse than accepting an unvalidated address.

Turning address validation off

The buyer’s address is validated automatically, when they press Continue. If you do not want that, clear Check addresses with the tax provider on Shipping and tax → Tax providers and save. No address is then sent to AvaTax for validation from either checkout or the origin check on that page, and the verdict everywhere becomes the same “not validated” you already get for a country AvaTax does not cover. Tax is still calculated, on the address as typed.

The switch is per application and versions with the published configuration, so turning it off in a sandbox environment leaves production untouched.

Verify origin on App → Tax → Setup, showing the address AvaTax resolved

Review what was sent

Two pages show the traffic, without a support ticket.

  • App → Tax → Provider calls — every AvaTax call: type, duration, outcome, and the provider’s error verbatim when one fails. Logging is on by default and can be turned off from the same page. Rows are kept for 90 days.
  • App → Tax → Documents — the document ledger: what was committed, refunded or voided, and against which order.

Tax provider calls: the call counters and one order address validation, quote and commit

Tax documents: the committed document ledger, per order Throttle does not embed a link to the Avalara AvaTax Admin Console; sign in to Avalara directly for filing and returns.

If you build your own checkout

The dashboard and the hosted checkout already handle everything below. Building directly against the API means handling four things the HTTP status code does not tell you.

A calculation can fail inside a 200. POST /api/v1/shipping-tax/carts/{cartId}/calculate answers 200 whether or not AvaTax answered. Read data.status and data.errors, not the status code alone:

data.statusWhat it means
estimatedA cart estimate. Check data.warnings for provider_fallback — AvaTax was unreachable and Throttle’s own rules answered instead, so the figure is an estimate in a stronger sense than usual
finalLocked checkout totals, calculated by AvaTax
failedNothing was calculated, and data.errors says why. provider_unavailable is the strict fallback policy declining to guess

A blocked sale is a 409, on both conversion rails. With the tax fallback policy on Strict — the default — a checkout_final that AvaTax could not answer makes the order impossible to create:

POST /api/v1/carts/{cartId}/finalize           → 409 tax_calculation_failed
POST /api/v1/checkout-sessions/{id}/complete   → 409 tax_calculation_failed

Both refuse before anything is captured, so a buyer is never charged against a guessed rate. Cart estimates keep answering throughout — only the committed sale is held — and the same cart converts at the real rate once the connection recovers. Handle the 409 by telling the buyer to retry rather than by falling back to your own number.

Line identifiers reach AvaTax; per-line tax codes travel as metadata. There is no dedicated tax-code field on a line item. Send the AvaTax code as metadata.taxCategory on the line — the same key the override table above uses. The line’s merchant identifier is sent separately, as AvaTax’s itemCode: from referenceId on a cart line and from sku on a quote line (a quote carries its sku across to referenceId when it converts). That is worth knowing because a code registered against that item in AvaTax’s own Item catalogue takes precedence over the taxCode on the line — if a line is taxed as something you did not ask for, look in the catalogue before looking at your payload.

Choosing a shipping method does not recalculate tax. POST /api/v1/carts/{cartId}/shipping records the rate you pass it and returns the cart with that freight in the total, but the tax on that response is the tax from the last calculation — which ran before the cart had any freight on it. Selecting a method is deliberately not a tax event: it would put an AvaTax call behind every click in a shipping picker. Recalculate before you show the buyer a total, passing the method you are pricing:

POST /api/v1/shipping-tax/carts/{cartId}/calculate
{ "kind": "cart_estimate", "selectedShippingMethodId": "ground" }

calculate prices and taxes the freight in the same call, so it is the one that has to run last. The hosted checkout recalculates on every shipping-method Continue for exactly this reason.

Before you go live

  • A non-production environment is a real rehearsal. Separate connection, separate credentials, AvaTax sandbox. Publishing there has no effect on production.
  • Nexus decides the tax, not Throttle. Zero tax on a real address almost always means missing nexus on the AvaTax company.
  • Calculation failures are not silent. By default, if AvaTax cannot be reached during final checkout, the checkout is blocked rather than completing at the wrong tax. It is a per-application setting, and the reason to keep it strict is that an undertaxed order is a liability you find at filing time.
  • Refunds depend on the commit. A refund is a RefundTransaction against the committed document, never a hand-built return, so the tax refunded is the tax that was filed.
  • Duty depends on the nexus flag, not on the subscription. If you sell cross-border, check that the destination country’s customs-duty nexus has the seller marked as importer of record. Without it AvaTax returns the duty as an estimate the buyer pays at the border, and charges nothing — which looks like working software right up until the buyer is surprised at delivery.
  • Classification needs an item code to match on. The HS mapping is keyed on the cart line’s referenceId. Lines sent without one fall back to AvaTax estimating from the description, which is a reasonable answer but not the one you configured.
  • Mirroring customers to Avalara is a disclosure. Exemption certificates are off by default for that reason. Turn them on deliberately, and remember that ECM refuses a customer whose address is incomplete.

Last updated September 16, 2026