> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dualentry.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Withhold and Remit Tax Through the API

> Apply withholding tax to bills, direct expenses, and vendor credits through the Public API, then list open withheld amounts and remit them to a tax agency.

Withholding tax (WHT) is tax you deduct from a vendor's payment and pay to a tax agency on the vendor's behalf. Through the Public API, you can apply withholding tax to bill and direct expense lines, reverse it with vendor credit lines, set vendor defaults, and remit the withheld amounts to a tax agency.

The fields on bills, direct expenses, vendor credits, recurring bills, and vendors are available on V1, V2, and V3-alpha. The withholding tax rates, open items, and remittances resources are available on V2 and V3-alpha only. For how withholding tax is calculated and posted, see [Withholding Tax](https://docs.dualentry.com/accountants/core-financials/tax-management/withholding-tax.md).

> [!WARNING]
>
> On a bill, direct expense, or vendor credit with withholding tax, the header `amount` is the amount payable to the vendor, net of withholding tax. A 1,000.00 bill with 10% withholding tax returns `amount` `"900.00"` and `withholding_tax_total` `"100.00"`. If your integration reads `amount` as the gross bill total, add `withholding_tax_total` to it.

## Prerequisites

Before you apply withholding tax through the API, confirm the following:

* **API key**: Your requests send your organization's API key in the `X-API-KEY` header. See [authenticating your requests](https://docs.dualentry.com/developers/guides/api/api-keys/how-to-authenticate-your-requests.md).
* **Withholding tax rates**: The rates are set up in the app under **Configuration → Tax Setup → Withholding Rates**. The API reads rates but can't create them. See [How to Set Up Withholding Tax](https://docs.dualentry.com/accountants/core-financials/tax-management/set-up-withholding-tax.md).
* **Company address**: The company has an address with a country. A rate applies only to records of a company in the rate's country.
* **Accounts and vendors**: You have a liability account (type `other_current_liability` or `long_term_liability`) for withheld tax, and a vendor with `vendor_type` `tax_agency` for the tax authority.

## Look up withholding tax rates

Withholding tax rates are read-only in the API. List them to find the `id` to send as `withholding_tax_rate_id` on a line. The `active_on` filter returns only the rates that are valid on a date, which is the record date you plan to use.

```bash theme={null}
curl "https://api.dualentry.com/public/v2/withholding-tax-rates/?country_code=IN&is_active=true&active_on=2026-11-09" \
  -H "X-API-KEY: YOUR_API_KEY"
```

Each rate in the response looks like this. The `rate` is a fraction, so `0.100000` is 10%.

```json theme={null}
{
  "id": 7,
  "country_code": "IN",
  "name": "Professional services 10%",
  "rate": "0.100000",
  "valid_from": "2026-04-01",
  "valid_to": null,
  "is_active": true,
  "is_system": false,
  "description": null
}
```

The withholding tax rate endpoints are listed below.

| Endpoint | Description |
| - | - |
| `GET /public/v2/withholding-tax-rates/` | List rates. Filters: `country_code` (repeatable), `is_active`, `active_on`, `limit`, `offset`. |
| `GET /public/v2/withholding-tax-rates/{withholding_tax_rate_id}/` | Get one rate. Returns `404` for a rate in another organization. |

## Set a vendor's withholding tax defaults

A vendor's withholding fields are writable on create, `PUT`, and `PATCH`. On `PUT` and `PATCH`, an omitted field keeps its stored value, and `null` clears an ID field. The following request turns on withholding tax for a vendor and sets its defaults:

```bash theme={null}
curl -X PATCH https://api.dualentry.com/public/v2/vendors/42/ \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "is_withholding_tax_applicable": true,
    "default_withholding_tax_rate_id": 7,
    "default_withholding_tax_payable_account_id": 210,
    "withholding_tax_agency_id": 99
  }'
```

The API doesn't copy the vendor's default rate onto lines: the app suggests it, but an API client must send `withholding_tax_rate_id` on each line. The default payable account is used when a bill or direct expense with withholding tax has no `withholding_tax_payable_account_id`.

The vendor withholding tax fields are listed below.

| Field | Type | Description |
| - | - | - |
| `is_withholding_tax_applicable` | boolean | Shows that withholding tax applies to the vendor. Defaults to `false`. It doesn't apply withholding tax by itself. |
| `default_withholding_tax_rate_id` | integer or `null` | The rate the app suggests for the vendor's lines. |
| `default_withholding_tax_rate` | object or `null` | Read-only. The default rate's `id`, `name`, `country_code`, and `rate`. |
| `default_withholding_tax_payable_account_id` | integer or `null` | The fallback liability account for bills and direct expenses. |
| `withholding_tax_agency_id` | integer or `null` | The vendor of type `tax_agency` that receives the withheld amounts. Use it as `tax_agency_id` on a remittance. |

## Create a bill with withholding tax

To withhold tax on a bill or direct expense line, send `withholding_tax_rate_id` on the line. DualEntry calculates the withheld amount as the line's net amount (before VAT, GST, or sales tax) multiplied by the rate, rounded to two decimal places. The following request creates a posted bill with one 1,000.00 line at 10%:

```bash theme={null}
curl -X POST https://api.dualentry.com/public/v2/bills/ \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "company_id": 1,
    "vendor_id": 42,
    "date": "2026-11-09",
    "due_date": "2026-12-09",
    "currency_iso_4217_code": "INR",
    "exchange_rate": "1.0",
    "record_status": "posted",
    "withholding_tax_payable_account_id": 210,
    "expenses": [
      {
        "position": 0,
        "account_number": 6000,
        "expense_description": "Consulting fee",
        "rate": "1000.00",
        "quantity": "1.000000",
        "withholding_tax_rate_id": 7
      }
    ]
  }'
```

The response includes the withholding tax on the header and on each line:

```json theme={null}
{
  "amount": "900.00",
  "withholding_tax_total": "100.00",
  "withholding_tax_payable_account_id": 210,
  "expenses": [
    {
      "id": 555,
      "withholding_tax_rate_id": 7,
      "withholding_tax": {
        "rate_id": 7,
        "name": "Professional services 10%",
        "rate": "0.100000",
        "amount": "100.00"
      },
      "payable_amount": "900.00"
    }
  ]
}
```

If you omit `withholding_tax_payable_account_id`, or send `null`, DualEntry uses the vendor's `default_withholding_tax_payable_account_id`. A record with withholding tax needs one of the two. Direct expenses take the same fields on their `items` lines. Recurring bill templates accept them in `record_payload`, and each bill the template creates gets the withholding tax.

## Update withholding tax on a record

On an update, a line keeps its withholding tax unless you send `withholding_tax_rate_id` for it. DualEntry matches each line you send to its stored line by the line `id`, then reads `withholding_tax_rate_id` as follows:

* **Field omitted**: The line keeps its stored rate. This stops clients that don't know about withholding tax from removing it.
* **`null`**: The line's withholding tax is removed.
* **A rate ID**: The line uses the new rate.
* **New line**: A line sent with no `id` is a new line, with only the withholding tax you send.

The header `withholding_tax_payable_account_id` works the same way: omit it to keep the stored account. Bills support this on `PUT` and `PATCH`, and direct expenses on `PUT`. Vendor credits have no header payable account.

A bill `PATCH` that sends `expenses` or `items` replaces that whole list, so include every line you want to keep. The following request removes the withholding tax from line 555:

```bash theme={null}
curl -X PATCH https://api.dualentry.com/public/v2/bills/1234/ \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "expenses": [
      {
        "id": 555,
        "position": 0,
        "account_number": 6000,
        "expense_description": "Consulting fee",
        "rate": "1000.00",
        "quantity": "1.000000",
        "withholding_tax_rate_id": null
      }
    ]
  }'
```

## Reverse withholding tax with a vendor credit

A vendor credit line reverses withholding tax by linking to a bill line with `credited_bill_line_id`. The line takes the bill line's rate and payable account, so vendor credits have no `withholding_tax_rate_id` or header payable account. The reversed amount is the bill line's rate multiplied by the credit line's net amount, up to the bill line's withholding tax that isn't already reversed.

For example, a vendor credit line of 300.00 with `"credited_bill_line_id": 555` reverses 30.00 of a 10% bill line. The vendor credit returns `amount` `"270.00"`, `withholding_tax_total` `"30.00"`, and a `credited_bill_line` object with the bill line's `id`, `bill_id`, `bill_number`, and `position`.

The bill line must be on a posted bill with the same vendor, company, and currency. On a `PUT`, a line that omits `credited_bill_line_id` keeps its link, and `null` removes the link and the withholding tax.

## Remit withheld tax to a tax agency

A withholding remittance records a payment to a tax agency for open withheld amounts. A withheld amount is open when its record is posted and it isn't on a draft or posted remittance. Remit in two steps.

First, list the open withheld amounts for a company. Amounts are in the company's base currency, and vendor credit amounts are negative.

```bash theme={null}
curl "https://api.dualentry.com/public/v2/withholding-tax/open-items/?company_id=1&tax_agency_id=99&liability_account_id=210" \
  -H "X-API-KEY: YOUR_API_KEY"
```

The response lists each open withheld amount:

```json theme={null}
{
  "items": [
    {
      "withheld_entry_id": 9001,
      "record_type": "bill",
      "record_id": 812,
      "record_number": 1234,
      "record_code": "BI-001234",
      "vendor": { "id": 42, "name": "Example Consulting Ltd" },
      "date": "2026-11-09",
      "rate_name": "Professional services 10%",
      "rate": "0.100000",
      "net_amount": "1000.00",
      "withheld_amount": "100.00"
    }
  ],
  "count": 1
}
```

Then create the remittance with the `withheld_entry_id` values you're paying. Each amount is remitted in full.

```bash theme={null}
curl -X POST https://api.dualentry.com/public/v2/withholding-tax-remittances/ \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "date": "2026-11-30",
    "company_id": 1,
    "tax_agency_id": 99,
    "bank_account_id": 100,
    "liability_account_id": 210,
    "reference_number": "REM-1",
    "lines": [{ "withheld_entry_id": 9001 }]
  }'
```

A remittance created through the API is posted. It debits the liability account and credits the bank account for the `total`, and locks the source lines. The request body fields are listed below.

| Field | Type | Required | Description |
| - | - | - | - |
| `date` | date | Yes | The date you paid the tax agency |
| `company_id` | integer | Yes | The company that withheld the tax |
| `tax_agency_id` | integer | Yes | A vendor with `vendor_type` `tax_agency` |
| `bank_account_id` | integer | Yes | The bank or credit card account that paid, in the company's base currency |
| `liability_account_id` | integer | Yes | The withholding tax payable account the withheld amounts are on |
| `reference_number` | string or `null` | No | Up to 128 characters |
| `lines` | array | Yes | Objects with a `withheld_entry_id` from the open items list |

The withholding tax endpoints are listed below. The remittances resource has no update or archive endpoint: archive a remittance in the app.

| Endpoint | Description |
| - | - |
| `GET /public/v2/withholding-tax/open-items/` | List open withheld amounts. `company_id` is required. Optional filters: `liability_account_id`, `tax_agency_id`, `withholding_tax_rate_id`, `date_from`, `date_to`, `limit` (up to 100), `offset`. |
| `GET /public/v2/withholding-tax-remittances/` | List remittances. Filters include `company_id`, `tax_agency_id`, `liability_account_id`, `start_date`, `end_date`, and `record_status`. |
| `GET /public/v2/withholding-tax-remittances/{record_number}/` | Get a remittance by its `number`, with its `lines`. |
| `POST /public/v2/withholding-tax-remittances/` | Create a posted remittance. |

The same endpoints are available under `/public/v3-alpha/`.

## Permissions

Each endpoint needs a permission on the API key's role. The open items list and remittance create need permission to create vendor payments, the same as remittances in the app.

| Endpoint | Permission |
| - | - |
| Withholding tax rates (list, get) | Tax calculation: view |
| Open items (list) | Vendor payments: create |
| Remittances (list, get) | Vendor payments: view |
| Remittances (create) | Vendor payments: create |

A key without the permission gets `403`. Withholding tax fields on bills, direct expenses, vendor credits, and vendors use those resources' existing permissions. For an `open-items` request with a `company_id` outside the key's access, the API returns `200` with an empty `items` list.

An API key carries the permissions of the roles you choose when you create it. An integration that only reads withholding tax, such as one that exports remittances, needs the view permissions. An integration that also remits needs vendor payment create. If a call returns `403`, add a role with the missing permission to the key, or create a new key with the right roles. The API can't archive a remittance, so a user with permission to archive vendor payments must do it in the app. See [How to Give an Integration Access to DualEntry](https://docs.dualentry.com/developers/guides/api/api-keys/how-to-give-an-integration-access.md).

## Errors

Withholding tax validation errors return `422`. The most common messages are listed below.

| Message | Cause |
| - | - |
| `Withholding tax rate not found or not valid on the record date.` | The rate doesn't exist, is inactive, or isn't valid on the record date. |
| `Withholding tax rate country does not match the company country.` | The rate's `country_code` isn't the company's country, or the company has no address. |
| `A withholding tax payable account is required when a line has withholding tax.` | No `withholding_tax_payable_account_id` and no vendor default. |
| `Withholding tax is not allowed with an intercompany vendor.` | The vendor is intercompany. |
| `Withholding tax is not allowed on a discount line.` | The line is a discount item line. |
| `The withholding tax of this line is on remittance {remittance}. Archive the remittance first.` | The update changes or removes a line whose withholding tax is on a remittance. |
| `The linked bill line has no withholding tax.` | The `credited_bill_line_id` points to a line without withholding tax. |
| `The payee must be a tax agency vendor.` | The remittance `tax_agency_id` isn't a vendor with `vendor_type` `tax_agency`. |
| `Withheld amount {id} is on a record that is not posted.` | A remittance line points to a draft record. |
| `Withheld amount {id} is on remittance {remittance}. Archive that remittance first.` | A remittance line is already on another remittance. |
| `The remittance total must be above 0.` | Vendor credit amounts cancel out the rest of the remittance. |

For the error response format, see [Errors](https://docs.dualentry.com/developers/guides/api/core-concepts/errors.md).

## Related pages

* [Withholding Tax](https://docs.dualentry.com/accountants/core-financials/tax-management/withholding-tax.md): how withholding tax works in the app
* [How to Calculate Tax Before Creating an Invoice or Bill](https://docs.dualentry.com/developers/guides/api/building-blocks/calculate-tax-before-invoicing.md): VAT, GST, and sales tax on write
* [Pagination](https://docs.dualentry.com/developers/guides/api/core-concepts/pagination.md): `limit` and `offset` on list endpoints


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.