# Edara API v3: Taxes

Read the [guide](../guide.md) first: authentication, paging, errors, rate limits and the shared `PagedResult`, `BatchResult` and `*Reference` shapes.

- `GET /v3/taxes`: Lists taxes.
- `GET /v3/taxes/{id}`: Gets a tax by id.
- `POST /v3/taxes`: Creates a tax.
- `PUT /v3/taxes/{id}`: Updates a tax.
- `DELETE /v3/taxes/{id}`: Deletes a tax.
- `GET /v3/taxes/einvoice-tax-types`: Lists e-invoice tax types.

## Endpoints

### `GET /v3/taxes`: Lists taxes.

Operation `GetTaxes` · permission `read:tax`

**Query string**: `GetTaxesQuery`

| field | type | default | validation | description |
|---|---|---|---|---|
| `offset` | `int?` | 0 | >= 0 | Number of items to skip before returning results. Defaults to 0. |
| `limit` | `int?` | 100 | 1..1000 (out of range → 400) | Maximum number of items to return. Defaults to 100, maximum 1000. |
| `name` | `string` |  | Must(name => !string.IsNullOrWhiteSpace(name)) .When(x => x.Name != null) | The tax name filter. |
| `rate` | `decimal?` |  | GreaterThanOrEqualTo(0m) .When(x => x.Rate.HasValue) | The tax rate filter. |
| `scope` | `TaxScope?` |  | Must(BeValidScope) .When(x => x.Scope.HasValue) | The scope filter. |

**Responses**: OK `PagedResult<TaxResponse>`: Paged list of taxes.

**Notes**

- `name` matches any part of the tax name. `rate` must match exactly, compared to four decimal places.
- `scope` set to `Any` does not return every tax. It returns only taxes that have no scope. Omit `scope` to get taxes of every scope.
- Inactive taxes are included, so check `active` if you need only active ones.
- Results are ordered by id. On an empty page, including when `offset` is past the end, `totalCount` is 0 instead of the real total.
- An account reference that cannot be resolved is returned as null.

---

### `GET /v3/taxes/{id}`: Gets a tax by id.

Operation `GetTaxById` · permission `read:tax`

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `id` | route | `int` |  | The tax id. |

**Responses**: OK `TaxResponse`: The tax.

**Notes**

- Inactive taxes are returned. An unknown id returns `404`.
- A tax that has no scope is returned with `scope` set to `Any`.

---

### `POST /v3/taxes`: Creates a tax.

Operation `CreateTax` · permission `create:tax`

**Body**: `TaxUpsertRequest`

| field | type | default | validation | description |
|---|---|---|---|---|
| `name` | `string` |  | NotEmpty() | The tax name. |
| `rate` | `decimal` |  | InclusiveBetween(0m, 100m) | The tax rate percentage. |
| `eInvoiceTaxTypeId` | `int?` |  | GreaterThan(0) .When(x => x.EInvoiceTaxTypeId.HasValue) | The e-invoice tax type identifier. |
| `scope` | `TaxScope?` |  | Must(BeValidPersistedScope) .When(x => x.Scope.HasValue) | The tax scope. |
| `salesAccountId` | `int?` |  | GreaterThan(0) .When(x => x.SalesAccountId.HasValue) | The sales account identifier. |
| `salesWithholdingAccountId` | `int?` |  | GreaterThan(0) .When(x => x.SalesWithholdingAccountId.HasValue) | The sales withholding account identifier. |
| `purchaseAccountId` | `int?` |  | GreaterThan(0) .When(x => x.PurchaseAccountId.HasValue) | The purchase account identifier. |
| `active` | `bool` |  |  | Whether the tax is active. |
| _(object rule)_ | | | RuleFor(x => x) .Must(HaveAtLeastOneAccount) .WithMessage("At least one tax account must be supplied.") | |

**Responses**: Created `TaxResponse`: Tax created.

**Business errors** (HTTP 409, match on `errorCode`):

- `EInvoice_KSA_TaxRateCantBeGreaterThanZeroWithCategoryType`: The KSA e-invoice category requires a zero tax rate.
- `EInvoice_KSA_TaxRateCantBeZeroWithCategoryType`: The KSA e-invoice category requires a non-zero tax rate.
- `NameAlreadyExists`: Another tax already uses the same name.
- `NameContainsInvalidSpecialCharacters`: The tax name contains unsupported special characters.
- `TaxCannotSetBothTaxAndWithholdingTaxAccounts`: The tax cannot use both tax and withholding accounts.
- `TaxCannotUseDifferentPurchaseTaxAccount`: PurchaseAccountId is already set for this tax setup. Repro: save the same tax with a different PurchaseAccountId.
- `TaxCannotUseDifferentSalesTaxAccount`: SalesAccountId is already set for this tax setup. Repro: save the same tax with a different SalesAccountId.
- `TaxCannotUseDifferentSalesWithholdingAccount`: SalesWithholdingAccountId is already set for this tax setup. Repro: save the same tax with a different SalesWithholdingAccountId.
- `TaxInvalidEInvoiceType`: The tax does not have an e-invoice tax type that matches the active country configuration.
- `TaxPurchaseAndSalesAccountMustMatch`: When both SalesAccountId and PurchaseAccountId are provided, they must point to the same account.

**Notes**

- All taxes in an organization must share one sales account, one sales withholding account, and one purchase account. Using a different account from the one already in use returns `409` with an `errorCode` that starts with `TaxCannotUseDifferent`.
- The sales account and the purchase account must be the same account. Otherwise the request returns `409` `TaxPurchaseAndSalesAccountMustMatch`.
- You cannot set a tax or purchase account together with a withholding account on the same tax. This returns `409` `TaxCannotSetBothTaxAndWithholdingTaxAccounts`.
- The tax name must be unique. A duplicate returns `409` `NameAlreadyExists`. The name is also checked for special characters.
- When the organization is connected to Egypt e-invoicing or e-receipts, `eInvoiceTaxTypeId` is required and must be an Egypt tax type. When the organization has connected to KSA e-invoicing before, it must be a KSA tax type. Otherwise the request returns `409` `TaxInvalidEInvoiceType`. This depends on the organization's setup, so handle the error in your code.
- Omitting `scope` is the same as sending `Any`. The tax is stored with no scope.
- If you omit `active`, the tax is created inactive. Send `active` as true to create an active tax.

---

### `PUT /v3/taxes/{id}`: Updates a tax.

Operation `UpdateTax` · permission `update:tax`

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `id` | route | `int` |  | The tax id. |

**Body**: `TaxUpsertRequest`

| field | type | default | validation | description |
|---|---|---|---|---|
| `name` | `string` |  | NotEmpty() | The tax name. |
| `rate` | `decimal` |  | InclusiveBetween(0m, 100m) | The tax rate percentage. |
| `eInvoiceTaxTypeId` | `int?` |  | GreaterThan(0) .When(x => x.EInvoiceTaxTypeId.HasValue) | The e-invoice tax type identifier. |
| `scope` | `TaxScope?` |  | Must(BeValidPersistedScope) .When(x => x.Scope.HasValue) | The tax scope. |
| `salesAccountId` | `int?` |  | GreaterThan(0) .When(x => x.SalesAccountId.HasValue) | The sales account identifier. |
| `salesWithholdingAccountId` | `int?` |  | GreaterThan(0) .When(x => x.SalesWithholdingAccountId.HasValue) | The sales withholding account identifier. |
| `purchaseAccountId` | `int?` |  | GreaterThan(0) .When(x => x.PurchaseAccountId.HasValue) | The purchase account identifier. |
| `active` | `bool` |  |  | Whether the tax is active. |
| _(object rule)_ | | | RuleFor(x => x) .Must(HaveAtLeastOneAccount) .WithMessage("At least one tax account must be supplied.") | |

**Responses**: OK `TaxResponse`: The updated tax.

**Business errors** (HTTP 409, match on `errorCode`):

- `EInvoice_KSA_TaxRateCantBeGreaterThanZeroWithCategoryType`: The KSA e-invoice category requires a zero tax rate.
- `EInvoice_KSA_TaxRateCantBeZeroWithCategoryType`: The KSA e-invoice category requires a non-zero tax rate.
- `NameAlreadyExists`: Another tax already uses the same name.
- `NameContainsInvalidSpecialCharacters`: The tax name contains unsupported special characters.
- `TaxCannotDeactivateRelatedToMasterData`: The tax is attached to master data and cannot be deactivated.
- `TaxCannotDeleteOrUpdateRelatedToTransactions`: The tax is referenced by posted transactions or documents.
- `TaxCannotSetBothTaxAndWithholdingTaxAccounts`: The tax cannot use both tax and withholding accounts.
- `TaxCannotUseDifferentPurchaseTaxAccount`: PurchaseAccountId is already set for this tax setup. Repro: save the same tax with a different PurchaseAccountId.
- `TaxCannotUseDifferentSalesTaxAccount`: SalesAccountId is already set for this tax setup. Repro: save the same tax with a different SalesAccountId.
- `TaxCannotUseDifferentSalesWithholdingAccount`: SalesWithholdingAccountId is already set for this tax setup. Repro: save the same tax with a different SalesWithholdingAccountId.
- `TaxInUseCannotDeactivateOrUpdate`: The tax is already used and cannot be deactivated or changed.
- `TaxInvalidEInvoiceType`: The tax does not have an e-invoice tax type that matches the active country configuration.
- `TaxPurchaseAndSalesAccountMustMatch`: When both SalesAccountId and PurchaseAccountId are provided, they must point to the same account.

**Notes**

- An unknown id returns `404`.
- This is a full replace. Account ids you omit are cleared, and omitting `active` deactivates the tax.
- If the tax is used in transactions, any change other than `active` returns `409` `TaxCannotDeleteOrUpdateRelatedToTransactions`.
- If the tax is used in master data, a request that changes only `active`, to deactivate or to reactivate, returns `409` `TaxCannotDeactivateRelatedToMasterData`.
- `TaxInUseCannotDeactivateOrUpdate` is listed but is not returned in practice.
- The same rules as POST /v3/taxes apply: shared accounts across the organization, a unique name, and the e-invoice tax type.

---

### `DELETE /v3/taxes/{id}`: Deletes a tax.

Operation `DeleteTax` · permission `delete:tax`

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `id` | route | `int` |  | The tax id. |

**Responses**: NoContent: (not declared; read from the method body)

**Business errors** (HTTP 409, match on `errorCode`):

- `ItemCannotDeleteItInUse`: The tax is referenced by other records.
- `TaxCannotDeleteOrUpdateRelatedToTransactions`: The tax is referenced by posted transactions or documents.

**Notes**

- An unknown id returns `404`.
- A tax used in transactions cannot be deleted. The request returns `409` `TaxCannotDeleteOrUpdateRelatedToTransactions`.
- The delete is permanent. If the tax is still referenced, for example by a stock item, the request returns `409` `ItemCannotDeleteItInUse`.

---

### `GET /v3/taxes/einvoice-tax-types`: Lists e-invoice tax types.

Operation `GetEInvoiceTaxTypes` · permission `read:tax`

**Query string**: `GetEInvoiceTaxTypesQuery`

| field | type | default | validation | description |
|---|---|---|---|---|
| `country` | `string` |  | NotEmpty() | The country filter. |

**Responses**: OK `IReadOnlyCollection<EInvoiceTaxTypeResponse>`: List of e-invoice tax types.

**Notes**

- `country` must match a country name exactly, for example `Egypt` or `KSA`. Partial names do not match.
- An unknown `country` returns `200` with an empty list, not `404`. The list is not paged.

## Types

### `AccountReference`

Lightweight account reference (id + code + name).

| field | type | description |
|---|---|---|
| `id` | `int` | The account id. |
| `code` | `string` | The account code. |
| `name` | `string` | The account name. |

### `EInvoiceTaxTypeResponse`

Response describing an e-invoice tax type.

| field | type | description |
|---|---|---|
| `id` | `int` | The tax type identifier. |
| `description` | `string` | The tax type description. |
| `type` | `string` | The tax type. |
| `code` | `string` | The external-system code. |
| `country` | `string` | The country the tax type applies to. |

### `TaxResponse`

Response describing a tax.

| field | type | description |
|---|---|---|
| `id` | `int` | The tax identifier. |
| `name` | `string` | The tax name. |
| `rate` | `decimal` | The tax rate percentage. |
| `eInvoiceTaxTypeId` | `int?` | The e-invoice tax type identifier. |
| `scope` | `TaxScope` | The tax scope. |
| `salesAccount` | `AccountReference` | The sales account reference. |
| `salesWithholdingAccount` | `AccountReference` | The sales withholding account reference. |
| `purchaseAccount` | `AccountReference` | The purchase account reference. |
| `active` | `bool` | Whether the tax is active. |

### `TaxScope`

Tax scopes exposed by the v3 API.

Values (sent/returned as the name): `Any`=0 (Any scope.), `StockItem`=1 (Stock items.), `ServiceItem`=2 (Service items.)
