Skip to main content
You can add tax to a public invoice or bill in two ways:
  1. Calculate on write: send auto_calculate_tax: "required" on create, replace, or patch. DualEntry calculates tax for every line and replaces any line tax you supplied before saving.
  2. Calculate, then create: call POST /public/v2/taxes/calculate/, map the result onto each line’s tax object, then create the record. Use this path for native VAT or GST (where you must select a rate), or when you need to preview tax before writing.
The DualEntry UI always calculates tax before saving. API clients that omit auto_calculate_tax keep caller-supplied line tax and must either send that tax themselves or use the calculate-then-create flow below.
With auto_calculate_tax: "required", DualEntry uses shipping_address, then billing_address, then the customer saved address for money-in records. For money-out records, it uses ship addresses and vendor/company saved addresses. Without that field, a shipping address on the invoice alone does not add tax. Pass the buyer address on the calculate call as transaction_buyer_address instead.

Prerequisites

Before you calculate tax or create a taxable record, confirm the following:
  • V2 API (/public/v2/...)
  • X-API-KEY header with your organization’s API key (authenticating your requests)
  • The company is set up for the correct tax regime (GST, VAT, or US sales tax)
  • The customer (or vendor) has is_taxable set to true when tax should apply

Let DualEntry calculate tax on write

Set auto_calculate_tax to "required" on the write payload. DualEntry calculates tax for every line and replaces any supplied line tax before saving. If calculation cannot complete, the request returns 422. Supported on: create (POST), replace (PUT), and patch (PATCH) for invoices, bills, cash sales, customer credits, direct expenses, purchase orders, sales orders, and vendor credits. Supported tax engines: native US sales tax (the DualEntry tax engine), and Avalara Sales Tax, VAT, and GST. Native VAT and GST return 422 because this field does not select a rate. Use calculate, then create for those.

Example: create an invoice with auto-calculated tax

The response includes calculated tax on each line. Rates and amounts depend on your tax setup; the shape looks like this:
When calculation succeeds, DualEntry overwrites any tax object you send on a line.

Address resolution

DualEntry picks tax addresses from the payload first, then from saved records, in this order:

When auto-calculate returns 422

These are the common causes of a 422 when auto_calculate_tax is "required", and how to fix each one:

Calculate, then create (manual path)

Use this path when you need to preview tax before writing, or when native VAT/GST requires selecting a rate from available_rates.

Step 1: Resolve IDs

Look up the company, customer or vendor, and item IDs the calculate call needs:

Step 2: Calculate tax

Send the resolved IDs and item lines to the calculate endpoint:
Required:
  • company_id
  • customer_id (money-in, e.g. invoices) or vendor_id (money-out, e.g. bills), not both
Usually also send:
  • items: each with item_id, quantity, and unit_price (item_id may be null for a vendor expense line)
  • transaction_buyer_address: the destination address, required for US sales tax
  • transaction_date and currency_code: required for US sales tax
  • supply_date: filters VAT/GST rates by their validity window
The response type is one of: For GST and VAT, the first calculate call often returns available_rates. Pick a rate, send it on the line as selected_gst_tax_rate_id or selected_vat_rate_id, then calculate again with that rate selected.

Step 3: Map the result onto each invoice line

Copy each field from the calculator response onto the matching field in the invoice line’s tax object: Also carry the line amount over: calculator unit_price → invoice line rate.

Step 4: Create the invoice

Send the mapped tax fields on each line when you create the invoice. Omit auto_calculate_tax so DualEntry keeps the tax you mapped:
Use the real values returned by your calculate call. The numbers above are examples only. You may still send shipping_address on the invoice for fulfillment; it does not replace the calculate step when auto_calculate_tax is omitted.

Canada GST example (two calculate calls)

GST and VAT regimes need a second calculate call once a rate is selected. Native VAT/GST cannot use auto_calculate_tax: "required". Use this flow instead. Call A: list available rates Calculate without a selected rate first to see what DualEntry offers for this address:
This returns type: "GST" with data.place_of_supply ("ON") and data.available_rates listing candidate rate IDs. Pick one (for example, the Ontario HST rate) from available_rates or from GET /public/v2/gst-tax-rates/. Call B: calculate again with the selected rate Add the chosen rate ID to the line and calculate again to get the final tax amounts:
Each returned line now has tax_code, tax_rate, tax_amount, and selected_gst_tax_rate_id set. Map those onto the invoice as in Step 3, then create the invoice using the values from call B.

US sales tax

With auto_calculate_tax: "required": send a complete buyer address on the invoice (or rely on the customer saved address), and DualEntry calculates and writes line tax on create, replace, or patch. With calculate-then-create:
  1. Send transaction_buyer_address (include region and postal_code), transaction_date, and currency_code on calculate.
  2. The response type is SALES_TAX.
  3. Copy tax_code, tax_rate, and tax_amount onto the line’s tax object.
  4. Create the invoice (omit auto_calculate_tax).
If the buyer address is missing on calculate, the response is type: "NONE" with a reason. Fix the address and calculate again before creating the invoice. With auto_calculate_tax: "required", a missing address returns 422 instead.

VAT

  1. The first calculate call returns available_rates.
  2. Send selected_vat_rate_id on the line and calculate again.
  3. Set the invoice line’s tax.vat_rate_id from the same selected_vat_rate_id.
  4. Create the invoice.
Native VAT cannot use auto_calculate_tax: "required". Avalara VAT can.

If calculate returns an error

The calculate endpoint returns errors for a few common mistakes: Fix the request and calculate again. A NONE result is not itself an error: it means DualEntry could not determine tax for this request, and the reason field says why. For errors when auto_calculate_tax is "required", see When auto-calculate returns 422.

Checklist before go-live

Calculate on write
  • Set auto_calculate_tax to "required" on create, replace, or patch
  • Provide a usable buyer or ship address, or a saved address on the customer, vendor, or company
  • Confirm the response shows items[].tax with the expected code, rate, and amount
  • For native VAT/GST, use calculate-then-create instead of this path
Calculate, then create
  • Call calculate before creating the invoice or bill
  • Pass the buyer/shipping address on calculate as transaction_buyer_address
  • For GST/VAT, select a rate from available_rates and calculate a second time
  • Copy every mapped tax field onto each line’s tax object
  • Omit auto_calculate_tax so DualEntry keeps the tax you mapped
  • Confirm the invoice response shows items[].tax with the expected code, rate, and amount
This guide covers calculate-on-write and calculate-then-create. Browse the individual V2 resources in the V2 API reference, including the calculate tax reference, GST rates, and product tax codes.
Last modified on October 28, 2026