Tax classes
Tax classes define the sales-tax rates your shop charges. Each class is a named bundle of one or more rate components (e.g. a state rate plus a county rate), can be marked tax-inclusive, and can be assigned to a single location for multi-location shops. Exactly one class per tenant is the default, it’s what invoices and estimates pick up when nothing more specific applies.
Rates are stored as exact integer parts-per-million (rate_ppm, so 6.625% = 66250) and echoed as a derived decimal percent (rate_percent). You can supply either form when writing.
The tax class object
Section titled “The tax class object”{ "object": "tax_class", "id": 3, "name": "NJ Sales Tax", "label": "NJ Sales Tax", "inclusive": false, "is_default": true, "active": true, "location_id": null, "components": [ { "id": 7, "name": "State", "jurisdiction": "NJ", "rate_ppm": 66250, "rate_percent": 6.625, "compound": false, "ordinal": 0 } ], "created_at": "2026-05-01T12:00:00.000Z", "updated_at": "2026-06-10T09:30:00.000Z"}| Field | Type | Description |
|---|---|---|
id | integer | Unique tax class ID |
name | string | Class name |
label | string|null | Display label (defaults to name) |
inclusive | boolean | true when prices already include this tax |
is_default | boolean | Exactly one default class exists per tenant |
active | boolean | false = soft-deactivated (kept for historical invoices) |
location_id | integer|null | null = tenant-wide; otherwise assigned to that location |
components | array | Ordered rate components (see below) |
created_at | string|null | ISO-8601 creation timestamp |
updated_at | string|null | ISO-8601 last-updated timestamp |
Component fields
Section titled “Component fields”| Field | Type | Description |
|---|---|---|
id | integer | Component ID |
name | string | Component name (e.g. "State") |
jurisdiction | string|null | Jurisdiction label |
rate_ppm | integer | Exact integer rate in parts-per-million (6.625% = 66250) |
rate_percent | number | Derived decimal percent (rate_ppm / 10000) |
compound | boolean | Applies on top of prior components’ tax |
ordinal | integer | Application order |
List tax classes
Section titled “List tax classes”GET /api/v1/tax_classesReturns the tenant’s complete tax-class catalog (not paginated, a settings-style list). Inactive classes are included by default so historical/deactivated classes stay discoverable; filter with active. The default class sorts first.
Scope required: tax.read
Query parameters
Section titled “Query parameters”| Parameter | Type | Description |
|---|---|---|
active | boolean | Filter by active state (true = active only, false = deactivated only) |
curl "https://app.benchkey.com/api/v1/tax_classes?active=true" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Node.js
Section titled “Node.js”const res = await fetch( "https://app.benchkey.com/api/v1/tax_classes?active=true", { headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } });const { data } = await res.json();Response
Section titled “Response”{ "object": "list", "data": [ { "object": "tax_class", "id": 3, "name": "NJ Sales Tax", "is_default": true } ], "has_more": false, "next_cursor": null}Additional resource fields are omitted from this example.
Retrieve a tax class
Section titled “Retrieve a tax class”GET /api/v1/tax_classes/:idReturns a single tax class with its components.
Scope required: tax.read
curl "https://app.benchkey.com/api/v1/tax_classes/3" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Response
Section titled “Response”Returns the tax class object. Returns 404 if no tax class with that ID exists.
Create a tax class
Section titled “Create a tax class”POST /api/v1/tax_classesCreates a named tax class with one or more rate components. Each component takes rate_percent (decimal, e.g. 6.625) or rate_ppm (integer parts-per-million, e.g. 66250), not both; omitting both makes a 0% (exempt) component.
The tenant’s first tenant-scoped class is forced to be the default; is_default: true transactionally unseats the previous default. A location_id assigns the class to one location, at most one active class per location; conflicts return 409.
Scope required: tax.write
Send an Idempotency-Key header to make retries safe. See Idempotency.
Request body
Section titled “Request body”| Field | Required | Type | Description |
|---|---|---|---|
name | yes | string | Class name (max 200 characters) |
components | yes | array | One or more component objects (see below) |
label | no | string|null | Display label, max 200 characters (defaults to name) |
inclusive | no | boolean | Prices already include this tax (default: false) |
is_default | no | boolean | Promote this class to tenant default (default: false) |
location_id | no | integer|null | Assign the class to one location (immutable after create). null/omitted = tenant-wide |
Component input fields
Section titled “Component input fields”| Field | Required | Type | Description |
|---|---|---|---|
name | yes | string | Component name (max 200 characters) |
jurisdiction | no | string|null | Jurisdiction label (max 200 characters) |
rate_percent | no | number | Decimal percent, 0–100 (e.g. 6.625). Mutually exclusive with rate_ppm |
rate_ppm | no | integer | Integer parts-per-million, 0–1,000,000 (e.g. 66250). Mutually exclusive with rate_percent |
compound | no | boolean | Applies on top of prior components’ tax (default: false) |
ordinal | no | integer | Application order (defaults to array position) |
curl -X POST https://app.benchkey.com/api/v1/tax_classes \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: taxclass-create-$(uuidgen)" \ -d '{ "name": "NJ Sales Tax", "components": [ { "name": "State", "jurisdiction": "NJ", "rate_percent": 6.625 } ] }'Node.js
Section titled “Node.js”const res = await fetch("https://app.benchkey.com/api/v1/tax_classes", { method: "POST", headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa", "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID(), }, body: JSON.stringify({ name: "NJ Sales Tax", components: [{ name: "State", jurisdiction: "NJ", rate_percent: 6.625 }], }),});const taxClass = await res.json(); // HTTP 201Response
Section titled “Response”Returns the tax class object with HTTP 201.
Update a tax class
Section titled “Update a tax class”PATCH /api/v1/tax_classes/:idPartial update: omitted fields are left untouched. components, when present, replaces the full component list.
is_default: true promotes this class (unseating the previous default). The default flag cannot be dropped directly, and the default class cannot be deactivated (409, promote another class first). location_id is immutable and not accepted here.
Scope required: tax.write
Request body
Section titled “Request body”| Field | Type | Description |
|---|---|---|
name | string | Class name (max 200 characters) |
label | string|null | Display label (max 200 characters) |
inclusive | boolean | Prices already include this tax |
active | boolean | true re-activates a deactivated class |
is_default | boolean | true promotes this class to tenant default |
components | array | Replaces the full component list (same fields as create, min 1 item) |
curl -X PATCH "https://app.benchkey.com/api/v1/tax_classes/3" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \ -H "Content-Type: application/json" \ -d '{ "label": "New Jersey Sales Tax", "inclusive": false }'Response
Section titled “Response”Returns the updated tax class object.
Deactivate a tax class
Section titled “Deactivate a tax class”DELETE /api/v1/tax_classes/:idSoft-deactivates the class (active: false) and returns the updated object. The row is never hard-deleted, historical invoices and estimates reference it, and it can be re-activated via PATCH with active: true.
Deactivating the default class returns 409 (set another default first). Deactivating an already-inactive class is an idempotent no-op.
Scope required: tax.write
curl -X DELETE "https://app.benchkey.com/api/v1/tax_classes/3" \ -H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"Response
Section titled “Response”Returns the deactivated tax class object (active: false) with HTTP 200.
Error codes
Section titled “Error codes”| HTTP status | Code | Meaning |
|---|---|---|
400 | invalid_body | Request body is not a JSON object |
400 | unknown_field | Body contains a field this endpoint doesn’t accept (e.g. location_id on PATCH) |
400 | missing_field | A required field is absent (name, components) |
400 | invalid_field | A field value is invalid, e.g. both rate_percent and rate_ppm supplied on one component, a rate out of range, or a non-string name |
400 | invalid_query | A list filter parameter was supplied as an array or object instead of a single scalar value |
400 | invalid_param | The :id is not a valid integer |
404 | not_found | No tax class with that ID exists for this tenant |
409 | conflict | The location already has an active class, the class is the tenant default and cannot be deactivated, or the default flag would be dropped without a replacement |
422 | create_failed | The tax class could not be created |
403 | insufficient_scope | API key lacks tax.read or tax.write |
See Errors for the full error envelope format.