# Tax Automation with the Accounting API

Sales tax, VAT, and GST handling is one of the messiest parts of any accounting integration. Rates differ per jurisdiction, codes differ per connector, and the same purchase produces a different journal in QuickBooks than it does in Xero or NetSuite. This guide walks through how to pull tax rates and codes from a connected accounting system, attach them to invoice and bill line items, and post the resulting tax liability journal entries using Apideck's [Accounting API](/apis/accounting/reference).

## Why a Unified API

Tax setup lives in the customer's accounting system. The integration job is to read what is already configured, reference it correctly on outgoing transactions, and let the downstream system compute and post the liability. A unified API removes the connector-specific paperwork around that flow.

- No bespoke code per connector to list tax codes, VAT codes, or AST rates.
- One shape for `tax_rate` references on invoice and bill line items.
- One model for tax liability journal entries that maps cleanly onto downstream postings.
- Webhook events on tax rate changes so cached rates do not drift.

## Resource mapping

Tax rates in the unified model normalise to the connector's tax catalogue. Liability journal entries normalise to the connector's general journal.

| Connector | Tax rate object | Journal entry object |
| --- | --- | --- |
| QuickBooks | `TaxRate` (or AST agency rate) | `JournalEntry` |
| Xero | `TaxRate` (`TaxType` code) | `ManualJournal` |
| NetSuite | `TaxCode` / `TaxGroup` | `Journal` |
| Sage Intacct | `Tax Detail` | `GL Journal Entry` |
| Exact Online | `VAT Code` | `Journal Entry` |
| Workday | `Tax Code` | `Journal` |
| Zoho Books | `Tax` | `Journal` |
| Stripe | `Tax Rate` | n/a |

## Walkthrough

The flow has four steps: fetch the existing tax rates, reference them on invoice line items, reference them on bill line items, and post a tax liability journal at period close.

### 1. Pull the connector's tax rates

Start by listing the tax rates configured on the connection. These represent the only valid IDs that can be referenced on transactions. Send this to [`GET /accounting/tax-rates`](/apis/accounting/reference#operation/taxRatesAll).

```http
GET /accounting/tax-rates HTTP/1.1
Host: unify.apideck.com
Authorization: Bearer <APIDECK_API_KEY>
x-apideck-app-id: dWN0c3Rfb...
x-apideck-consumer-id: cnsmr_01H8X9Y2A3B4C5D6E7
x-apideck-service-id: xero
```

A typical response item looks like this. Store `id`, `code`, and `effective_tax_rate` so the front end can label them and the backend can attach them to outgoing transactions.

```json
{
  "id": "tx_01H8X9Y2A3B4C5D6E7F8G9H0",
  "code": "OUTPUT2",
  "name": "20% (VAT on Income)",
  "description": "Standard rate VAT on sales",
  "effective_tax_rate": 20.0,
  "total_tax_rate": 20.0,
  "tax_payable_account_id": "led_acct_01H8X9Y2A3B4C5D6E7F8G9HXX",
  "tax_remitted_account_id": "led_acct_01H8X9Y2A3B4C5D6E7F8G9HYY",
  "status": "active"
}
```

If the connector supports it (see the coverage table below), new rates can be created with [`POST /accounting/tax-rates`](/apis/accounting/reference#operation/taxRatesAdd). Most production integrations only need read access, since the customer maintains tax setup in their accounting system.

### 2. Attach a tax rate to an invoice line item

Reference the tax rate by ID on each line. The downstream system computes `tax_amount` from the line `unit_price`, `quantity`, and the rate. Send this to [`POST /accounting/invoices`](/apis/accounting/reference#operation/invoicesAdd).

```json
{
  "invoice_number": "INV-2025-00471",
  "customer": {
    "id": "cust_01H8X9Y2A3B4C5D6E7F8G9CUST"
  },
  "invoice_date": "2025-03-14",
  "due_date": "2025-04-13",
  "currency": "GBP",
  "line_items": [
    {
      "description": "Implementation services, March 2025",
      "quantity": 12,
      "unit_price": 150.0,
      "ledger_account": {
        "id": "led_acct_01H8X9Y2A3B4C5D6E7F8G9SALES"
      },
      "tax_rate": {
        "id": "tx_01H8X9Y2A3B4C5D6E7F8G9H0",
        "code": "OUTPUT2"
      }
    }
  ],
  "sub_total": 1800.0,
  "total_tax": 360.0,
  "total": 2160.0,
  "status": "authorised"
}
```

### 3. Attach a tax rate to a bill line item

The same `tax_rate` shape works on bills. Use the input-side rate code where the connector distinguishes input from output VAT (Xero `INPUT2`, Exact Online `IB`, NetSuite tax codes flagged as purchase). Send this to [`POST /accounting/bills`](/apis/accounting/reference#operation/billsAdd).

```json
{
  "bill_number": "VEND-887412",
  "supplier": {
    "id": "sup_01H8X9Y2A3B4C5D6E7F8G9SUPP"
  },
  "bill_date": "2025-03-04",
  "due_date": "2025-04-03",
  "currency": "GBP",
  "line_items": [
    {
      "description": "Cloud hosting, March 2025",
      "quantity": 1,
      "unit_price": 480.0,
      "ledger_account": {
        "id": "led_acct_01H8X9Y2A3B4C5D6E7F8G9HOST"
      },
      "tax_rate": {
        "id": "tx_01H8X9Y2A3B4C5D6E7F8G9INPUT",
        "code": "INPUT2"
      }
    }
  ],
  "sub_total": 480.0,
  "total_tax": 96.0,
  "total": 576.0,
  "status": "authorised"
}
```

### 4. Post a tax liability journal entry

At period close, post a manual journal that moves the net VAT or sales tax position to the remittance account. The `tax_payable_account_id` returned on the tax rate in step 1 is the account to debit when net tax is owed. Send this to [`POST /accounting/journal-entries`](/apis/accounting/reference#operation/journalEntriesAdd).

```json
{
  "title": "VAT remittance, Q1 2025",
  "memo": "Net VAT owed for the quarter ending 2025-03-31",
  "posted_at": "2025-04-07T00:00:00.000Z",
  "currency": "GBP",
  "journal_symbol": "GJ",
  "line_items": [
    {
      "description": "Clear output VAT control",
      "type": "debit",
      "total_amount": 12480.0,
      "ledger_account": {
        "id": "led_acct_01H8X9Y2A3B4C5D6E7F8G9HXX"
      }
    },
    {
      "description": "Reclaim input VAT control",
      "type": "credit",
      "total_amount": 4180.0,
      "ledger_account": {
        "id": "led_acct_01H8X9Y2A3B4C5D6E7F8G9INPUT"
      }
    },
    {
      "description": "Bank transfer to HMRC",
      "type": "credit",
      "total_amount": 8300.0,
      "ledger_account": {
        "id": "led_acct_01H8X9Y2A3B4C5D6E7F8G9BANK"
      }
    }
  ]
}
```

>
> Tax rate IDs are stable per connection but not portable across connections. Do not cache them in shared application state. Re-list rates per consumer, and refresh on tax-rate change events from the webhook stream.

## Connector-specific behavior

The note column reflects what is exposed today through the unified API. "Read-only" means tax rates can be listed and referenced on transactions but cannot be created or updated through Apideck. "Not in coverage" means the tax-rates endpoint is not exposed for that connector, and tax must be applied by referencing percentages or codes directly in the line item or by relying on downstream computation. Always verify gaps in the live coverage matrix before depending on a write path.

| Connector | Notes |
| --- | --- |
| Access Financials | Tax rates are read-only. Bills are not in coverage; reference existing rate IDs on invoice and credit-note line items and manage tax setup in Access. |
| Acumatica | Tax rates are read-only. Manage tax categories and zones in Acumatica and reference them on invoice and bill lines. |
| Banqup | Tax rates are not in coverage. Invoices and customers are read-only and suppliers are not in coverage, so use Banqup as the system of record for tax-bearing data. |
| Campfire | Tax rates are not in coverage. Use the journal-entries endpoint, which is read+write on Campfire, to record tax liability postings directly. |
| Clearbooks UK | Tax rates are not in coverage. Invoices, bills, and credit notes are all read-only, so use Clearbooks as the system of record. |
| Digits | Tax rates are not in coverage. Reporting-style connector. Pull computed totals back rather than pushing tax-bearing transactions. |
| DualEntry | Tax rates are not in coverage. Provide tax-inclusive totals on invoice and bill line items and let the connector reconcile. |
| Exact Online | Tax rates are read-only. Reference VAT codes by ID on invoice and bill line items. |
| Exact Online NL | Tax rates are read-only. Dutch BTW codes are configured per administration; reference by ID. |
| Exact Online UK | Tax rates are read-only. Bills and credit notes are read-only as well, so write paths for tax-bearing transactions are limited to invoices and journal entries. |
| FreeAgent | Tax rates are not in coverage. Bills and journal entries are read-only; tax-bearing writes flow through invoices and credit notes, both of which are read+write. |
| FreshBooks | Full read and write on tax rates. Standard mapping on invoices and bills. Expenses are not in coverage on FreshBooks, and journal entries are read-only. |
| Intuit Enterprise Suite | Full read and write on tax rates. Supports departments, locations, and tracking categories on tax-bearing transactions. |
| KashFlow | Tax rates are read-only. Invoices and journal entries are read-only as well, and bills are not in coverage. KashFlow is effectively the system of record. |
| Microsoft Dynamics 365 Business Central | Tax rates are read-only. VAT posting groups must be configured in Business Central before they can be referenced. |
| Moneybird | Tax rates are read-only. Reference Dutch BTW codes by ID. Tracking categories are read+write. |
| MRI Software | Tax rates are read-only. Invoices are not in coverage on MRI Software, so the tax-rate references shown in this guide apply to journal entries and supplier-side records rather than AR invoices. Verify against the live coverage matrix before depending on any write path. |
| MYOB | Full read and write on tax rates. Bills and journal entries are not in coverage on MYOB, so tax workflows run through invoices and payments only. |
| MYOB Acumatica | Tax rates are read-only. Reference tax categories on line items. |
| NetSuite | Full read and write on tax rates. Subsidiaries (read+write) and departments and locations (read-only) can all be referenced on tax-bearing transactions. |
| Odoo | Tax rates are read-only. Odoo computes tax from the tax IDs assigned to each line; reference IDs returned from the tax-rates endpoint. |
| Pennylane | Tax rate support is limited. Prefer reading existing rates and avoid creating new ones through the API. |
| Procountor (FI) | Tax rates are read-only. Reference Finnish VAT codes on line items. |
| QuickBooks | Full read and write on tax rates. US files using Automated Sales Tax compute tax centrally; do not create custom rates on AST files. See subsection below. |
| Rillet | Tax rates are read-only. Standard mapping on invoices, bills, and journal entries. |
| Sage Business Cloud Accounting | Tax rates are read-only. Reference tax codes on invoice and bill line items. Journal entries are read-only on this connector. |
| Sage Intacct | Tax rates are read-only. Tax solutions and tax details are configured at the company level. |
| Sage Intacct REST | Tax rates are not in coverage on this connector variant, and most other tax-relevant resources are not exposed either. Use the classic Sage Intacct connector for tax workflows. |
| Stripe | Full read and write on tax rates via Stripe Tax. Bills and journal entries are not in coverage; tax workflows are invoice-driven. |
| Visma Netvisor | Tax rates are not in coverage. Reference Finnish VAT percentages directly on line items, and note that journal entries are read-only here. |
| Wave | Tax rates are read-only. Bills are not in coverage; tax workflows run through invoices and ledger accounts. |
| Workday | Full read and write on tax rates. Subsidiaries and departments are read-only and can be referenced on tax-bearing transactions. |
| Xero | Full read and write on tax rates. Each line item must reference a `TaxType` code that exists on the Xero organisation. See subsection below. |
| Yuki | Tax rates are read-only. Bills are read-only and credit notes are not in coverage, so tax-bearing writes flow through invoices and journal entries. Tracking categories are read+write. |
| Zoho Books | Full read and write on tax rates. Multi-organization setups require selecting the correct organization context per request. |

### QuickBooks

QuickBooks Online files in the United States use Automated Sales Tax (AST). On AST files, tax is computed by Intuit based on customer address and product taxability, not by referencing a fixed rate. Listing tax rates still works and returns the agency-level rates QuickBooks tracks internally, but creating custom rates on an AST file is rejected by the downstream API. For non-US locales (UK, Canada, Australia) the standard `TaxRate` model applies and full read and write is available.

### Xero

Xero distinguishes input tax (`INPUT`, `INPUT2`, `RRINPUT`) from output tax (`OUTPUT`, `OUTPUT2`, `RROUTPUT`). The unified `tax_rate.code` field maps to the Xero `TaxType` code. Tax rates created via the unified API become custom tax rates on the Xero organisation and are immediately referenceable.

### NetSuite

NetSuite supports both `TaxCode` (single jurisdiction) and `TaxGroup` (combined jurisdictions). The unified tax-rates endpoint returns both. On multi-subsidiary accounts the rate set is filtered by subsidiary, so include the subsidiary on the parent transaction and ensure the referenced tax rate belongs to that subsidiary.

### Stripe

Stripe exposes `Tax Rate` objects through Stripe Tax. These are the same rates used by Stripe Invoicing and Stripe Billing. Because Stripe does not maintain a general ledger, the journal-entry step in the walkthrough does not apply; reconcile Stripe tax totals into the destination general ledger via a separate accounting connection.

## Next steps

- [Mark invoices and bills as paid](/guides/mark-invoices-as-paid) once tax-inclusive totals have settled
- [Handling bills and expenses](/guides/expenses-bills) for the broader AP workflow
- [Tax Rates reference](/apis/accounting/reference#tag/Tax-Rates)
- [Journal Entries reference](/apis/accounting/reference#tag/Journal-Entries)
