Tax API
Every Tax action (7) and data model (4): endpoints, scopes, input, preconditions, events and examples.
Tax rates by country and product type, applied to every sale.
Module code tax · 7 actions · 4 data models · 4 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: tax (everything), tax:read (read only) or tax:<capability>.
Resources
| Resource | Table | List and read | POST | PATCH | DELETE |
|---|---|---|---|---|---|
tax_classes | tax_class | /api/v1/tax_classes | |||
tax_configuration_countries | tax_configuration_country | /api/v1/tax_configuration_countries | |||
tax_configurations | tax_configuration | /api/v1/tax_configurations | |||
tax_rates | tax_rate | /api/v1/tax_rates |
Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.
Actions
| Action | Needs | What it does |
|---|---|---|
tax.archive_class | tax:manage | Archive a tax class |
tax.remove_country_exception | tax:manage | Remove a country exception |
tax.remove_rate | tax:manage | Remove a tax rate |
tax.save_class | tax:manage | Save a tax class |
tax.save_configuration | tax:manage | Save tax configuration |
tax.set_country_exception | tax:manage | Set a country exception |
tax.set_rate | tax:manage | Set a tax rate |
tax.archive_class
Archive a tax class — Takes a class out of use without rewriting any document it already priced.
Needs tax:manage · changes data
https://app.getera.io/api/v1/tax_classes/{id}/actions/archive_class- Canonical address:
POST /api/v1/actions/tax.archive_class
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Tax class · id of tax_class |
archived | boolean | yes | Archived |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
tax_class_not_found | The class must exist. |
Events it publishes: tax.class_archived
What it writes: tax_class (update) — Archives or restores it
curl -X POST https://app.getera.io/api/v1/tax_classes/TAX_CLASSE_ID/actions/archive_class \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"archived":true}'{
"ok": true,
"result": "…",
"events": [
"tax.class_archived"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.taxClasses.archiveClass("TAX_CLASSE_ID", {
archived: true
})era tax_classes archive_class TAX_CLASSE_ID \
--archived true{
"name": "era__tax__archive_class",
"arguments": {
"id": "TAX_CLASSE_ID",
"archived": true
}
}Errors: 401 no or expired credential · 403 the credential does not hold tax:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 tax_class_not_found · 429 slow down
tax.remove_country_exception
Remove a country exception — The country falls back to the configuration it belongs to.
Needs tax:manage · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/tax_configuration_countries/{id}/actions/remove_country_exception- Canonical address:
POST /api/v1/actions/tax.remove_country_exception
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Exception · id of tax_configuration_country |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
tax_exception_not_found | The exception must exist. |
Events it publishes: tax.configuration_changed
What it writes: tax_configuration_country (delete) — Removes the exception
curl -X POST https://app.getera.io/api/v1/tax_configuration_countries/TAX_CONFIGURATION_COUNTRY_ID/actions/remove_country_exception \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"tax.configuration_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.taxConfigurationCountries.removeCountryException("TAX_CONFIGURATION_COUNTRY_ID")era tax_configuration_countries remove_country_exception TAX_CONFIGURATION_COUNTRY_ID{
"name": "era__tax__remove_country_exception",
"arguments": {
"id": "TAX_CONFIGURATION_COUNTRY_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold tax:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 tax_exception_not_found · 429 slow down
tax.remove_rate
Remove a tax rate — Removes one country rate. Documents already priced keep their snapshot.
Needs tax:manage · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/tax_rates/{id}/actions/remove_rate- Canonical address:
POST /api/v1/actions/tax.remove_rate
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Rate · id of tax_rate |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
tax_rate_not_found | The rate must exist. |
Events it publishes: tax.rate_changed
What it writes: tax_rate (delete) — Removes the rate
curl -X POST https://app.getera.io/api/v1/tax_rates/TAX_RATE_ID/actions/remove_rate \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"tax.rate_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.taxRates.removeRate("TAX_RATE_ID")era tax_rates remove_rate TAX_RATE_ID{
"name": "era__tax__remove_rate",
"arguments": {
"id": "TAX_RATE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold tax:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 tax_rate_not_found · 429 slow down
tax.save_class
Save a tax class — Creates or renames a tax class — the category a price is taxed under.
Needs tax:manage · changes data
https://app.getera.io/api/v1/tax_classes/{id}/actions/save_class- Canonical address:
POST /api/v1/actions/tax.save_class
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | Filled from the path {id} · Tax class · id of tax_class | |
name | string | yes | Name · max 255 |
description | string | Description · max 2000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
tax_class_not_found | The class being renamed must exist. |
tax_class_name_taken | Two tax classes cannot share a name. |
Events it publishes: tax.class_saved
What it writes: tax_class (upsert) — Writes the class
curl -X POST https://app.getera.io/api/v1/tax_classes/TAX_CLASSE_ID/actions/save_class \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name":"Name"}'{
"ok": true,
"result": "…",
"events": [
"tax.class_saved"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.taxClasses.saveClass("TAX_CLASSE_ID", {
name: "Name"
})era tax_classes save_class TAX_CLASSE_ID \
--name "Name"{
"name": "era__tax__save_class",
"arguments": {
"id": "TAX_CLASSE_ID",
"name": "Name"
}
}Errors: 401 no or expired credential · 403 the credential does not hold tax:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 tax_class_not_found, tax_class_name_taken · 429 slow down
tax.save_configuration
Save tax configuration — Whether and how tax is charged — for the workspace, or for one sales channel.
Needs tax:manage · changes data
https://app.getera.io/api/v1/tax_configurations/actions/save_configuration- Canonical address:
POST /api/v1/actions/tax.save_configuration
Input
| Field | Type | Required | Notes |
|---|---|---|---|
channelId | uuid | Channel | |
chargeTaxes | boolean | yes | Charge taxes |
calculationStrategy | string (enum) | Strategy · Allowed values: flat_rates, tax_app | |
displayGrossPrices | boolean | Show prices with tax | |
pricesEnteredWithTax | boolean | Prices are entered with tax | |
taxAppId | string | Tax app · max 256 | |
useWeightedTaxForShipping | boolean | Weighted tax for shipping |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
tax_app_id_required | The tax-app strategy needs the app that calculates it. |
Events it publishes: tax.configuration_changed
What it writes: tax_configuration (upsert) — Writes the configuration
curl -X POST https://app.getera.io/api/v1/tax_configurations/actions/save_configuration \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"chargeTaxes":true}'{
"ok": true,
"result": "…",
"events": [
"tax.configuration_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.taxConfigurations.saveConfiguration({
chargeTaxes: true
})era tax_configurations save_configuration \
--charge-taxes true{
"name": "era__tax__save_configuration",
"arguments": {
"chargeTaxes": true
}
}Errors: 401 no or expired credential · 403 the credential does not hold tax:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 tax_app_id_required · 429 slow down
tax.set_country_exception
Set a country exception — Overrides the configuration for one country — Saleor’s TaxConfigurationPerCountry.
Needs tax:manage · changes data
https://app.getera.io/api/v1/tax_configuration_countries/actions/set_country_exception- Canonical address:
POST /api/v1/actions/tax.set_country_exception
Input
| Field | Type | Required | Notes |
|---|---|---|---|
configurationId | uuid | yes | Configuration · id of tax_configuration |
country | string | yes | Country · max 2 |
chargeTaxes | boolean | yes | Charge taxes |
calculationStrategy | string (enum) | Strategy · Allowed values: flat_rates, tax_app | |
displayGrossPrices | boolean | Show prices with tax | |
taxAppId | string | Tax app · max 256 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invalid_country | The country must be an ISO-3166 two-letter code. |
tax_configuration_not_found | The configuration must exist. |
Events it publishes: tax.configuration_changed
What it writes: tax_configuration_country (upsert) — Writes the exception
curl -X POST https://app.getera.io/api/v1/tax_configuration_countries/actions/set_country_exception \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"configurationId":"TAX_CONFIGURATION_ID","country":"Country","chargeTaxes":true}'{
"ok": true,
"result": "…",
"events": [
"tax.configuration_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.taxConfigurationCountries.setCountryException({
configurationId: "TAX_CONFIGURATION_ID",
country: "Country",
chargeTaxes: true
})era tax_configuration_countries set_country_exception \
--configuration-id "TAX_CONFIGURATION_ID" \
--country "Country" \
--charge-taxes true{
"name": "era__tax__set_country_exception",
"arguments": {
"configurationId": "TAX_CONFIGURATION_ID",
"country": "Country",
"chargeTaxes": true
}
}Errors: 401 no or expired credential · 403 the credential does not hold tax:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_country, tax_configuration_not_found · 429 slow down
tax.set_rate
Set a tax rate — The rate for one country — for a class, or the country default when no class is given.
Needs tax:manage · changes data
https://app.getera.io/api/v1/tax_rates/actions/set_rate- Canonical address:
POST /api/v1/actions/tax.set_rate
Input
| Field | Type | Required | Notes |
|---|---|---|---|
taxClassId | uuid | Tax class · id of tax_class | |
country | string | yes | Country · max 2 |
rate | number | yes | Rate (%) · min 0 · max 100 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invalid_country | The country must be an ISO-3166 two-letter code. |
tax_class_not_found | The class must exist. |
Events it publishes: tax.rate_changed
What it writes: tax_rate (upsert) — Writes the rate
curl -X POST https://app.getera.io/api/v1/tax_rates/actions/set_rate \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"country":"Country","rate":1}'{
"ok": true,
"result": "…",
"events": [
"tax.rate_changed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.taxRates.setRate({
country: "Country",
rate: 1
})era tax_rates set_rate \
--country "Country" \
--rate 1{
"name": "era__tax__set_rate",
"arguments": {
"country": "Country",
"rate": 1
}
}Errors: 401 no or expired credential · 403 the credential does not hold tax:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_country, tax_class_not_found · 429 slow down
Data models
TaxClass
Table tax_class · Resource tax_classes
GET /api/v1/tax_classesGET /api/v1/tax_classes/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
name | string · text | |||
description | string · text | yes | ||
archived_at | string (date-time) · timestamptz | yes | ||
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK ((length(TRIM(BOTH FROM name)) > 0))
TaxConfigurationCountry
Table tax_configuration_country · Resource tax_configuration_countries
GET /api/v1/tax_configuration_countriesGET /api/v1/tax_configuration_countries/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
tax_configuration_id | string (uuid) · uuid | tax_configuration | ||
country | string · bpchar | max 2 | ||
charge_taxes | boolean · bool | |||
calculation_strategy | string · text | yes | ||
display_gross_prices | boolean · bool | |||
tax_app_id | string · text | yes | ||
use_weighted_tax_for_shipping | boolean · bool | |||
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Unique: tax_configuration_id, country
Checks:
CHECK (((calculation_strategy IS NULL) OR (calculation_strategy = ANY (ARRAY['flat_rates'::text, 'tax_app'::text]))))CHECK ((country ~ '^[A-Z]{2}$'::text))
TaxConfiguration
Table tax_configuration · Resource tax_configurations
GET /api/v1/tax_configurationsGET /api/v1/tax_configurations/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
channel_id | string (uuid) · uuid | yes | ||
charge_taxes | boolean · bool | |||
calculation_strategy | string · text | flat_rates, tax_app | ||
display_gross_prices | boolean · bool | |||
prices_entered_with_tax | boolean · bool | |||
tax_app_id | string · text | yes | ||
use_weighted_tax_for_shipping | boolean · bool | |||
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK (((calculation_strategy <> 'tax_app'::text) OR (tax_app_id IS NOT NULL)))
TaxRate
Table tax_rate · Resource tax_rates
GET /api/v1/tax_ratesGET /api/v1/tax_rates/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
tax_class_id | string (uuid) · uuid | yes | tax_class | |
country | string · bpchar | max 2 | ||
rate | string (decimal) · numeric | |||
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK ((country ~ '^[A-Z]{2}$'::text))CHECK (((rate >= (0)::numeric) AND (rate <= (100)::numeric)))
Events
Workflows and webhooks react to these events.
| Event | What happens | Payload |
|---|---|---|
tax.class_archived | A tax class was archived or restored. | id, archived |
tax.class_saved | A tax class was created or renamed. | id, name |
tax.configuration_changed | How tax is charged changed. | id, channelId, country |
tax.rate_changed | A country rate changed. | id, country, taxClassId, rate |