API ReferenceEndpointsTax

Tax

Tax rates are configured per region and applied to orders at checkout. Use these endpoints to manage rate definitions and inspect tax breakdowns on orders.

List tax rates for the store

GET
/v1/tax/rates
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

curl -X GET "https://api.brainerce.com/api/v1/tax/rates"
[
  {
    "id": "string",
    "accountId": "string",
    "storeId": "string",
    "name": "California Sales Tax",
    "country": "string",
    "region": "string",
    "postalCode": "string",
    "rate": "8.25",
    "taxType": "string",
    "isInclusive": true,
    "priority": 0,
    "stackable": true,
    "exceptionCountries": [
      "string"
    ],
    "isActive": true,
    "taxClassId": "string",
    "createdAt": "2019-08-24T14:15:22Z",
    "updatedAt": "2019-08-24T14:15:22Z"
  }
]

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates"
}

Create a new tax rate

Supports Idempotency-Key header for safe retries.

POST
/v1/tax/rates
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Header Parameters

Idempotency-Key?string

Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.

  • Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
  • Reusing a key with a different body returns 409 Conflict with code: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response.
  • Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
  • Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
  • Sending this header on a GET returns 400 with code: "IDEMPOTENCY_KEY_NOT_SUPPORTED" — GETs are already idempotent.
  • Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.

See /docs/api/idempotency.

Lengthlength <= 255
namestring

Display name shown on invoices and the storefront tax line.

ratenumber

Tax rate as a percentage (e.g. 8.5 for 8.5%). Range 0 to 100.

Range0 <= value <= 100
countrystring

ISO 3166-1 alpha-2 country code this rate applies to. REQUIRED — there is no global fallback rate. TaxRate.country is NOT NULL in the schema, so a country-less rate cannot be stored at all; omitting this is a 400, not a catch-all. A rate that must cover several countries names one here and lists the rest in exceptionCountries.

region?string

State/province code (ISO 3166-2 subdivision part, e.g. CA for California). Narrows the match within country.

postalCode?string

Exact postal code, compared case-insensitively with spaces and hyphens ignored. Wildcards, prefixes (941*) and ranges (94100-94199) are NOT supported, and match nothing. Narrows the match within region.

taxType?string

Tax category label (e.g. sales, vat, gst). Shown on invoices and used for reporting; does not affect calculation.

isInclusive?boolean

When true, prices are tax-inclusive (VAT-style): the rate is backed out of the displayed price instead of added on top.

Defaultfalse
priority?number

Tiebreak between rates of EQUAL specificity: the LOWER number wins. Specificity is decided first (a postal-code rate beats a region rate, which beats a country-wide rate), so priority only matters between rates scoped the same way.

isActive?boolean

Whether this rate participates in calculation. Defaults to true.

Defaulttrue
exceptionCountries?array<string>

Additional ISO country codes where this rate also applies (in addition to the same-country rule). Useful for trade blocs that share a tax (e.g. EU reverse-charge schemes).

stackable?boolean

Charge this rate TOGETHER WITH the other stackable rates that match the same address, instead of the single most specific one. This is how Canada works: GST 5% (country-level) + PST/QST (province-level), both stackable, both charged on the same pre-tax base — the buyer sees two tax lines. Leave false (the default) and the historic rule applies: the most specific matching rate wins alone and every other match is discarded. An HST province is a single non-stackable province row, so it wins at 13% without adding GST. Stacking is additive, never tax-on-tax, and never crosses a tax class: a class-specific rate replaces the Standard rates rather than adding to them, so a class that needs GST + reduced QST needs both rows entered in that class.

Defaultfalse
taxClassId?string

Tax class this rate applies to. Omit (or null) for the Standard rate that applies to products without an explicit class.

curl -X POST "https://api.brainerce.com/api/v1/tax/rates" \  -H "Idempotency-Key: string" \  -H "Content-Type: application/json" \  -d '{    "name": "California Sales Tax",    "rate": 8.5,    "country": "US"  }'
{
  "id": "string",
  "accountId": "string",
  "storeId": "string",
  "name": "California Sales Tax",
  "country": "string",
  "region": "string",
  "postalCode": "string",
  "rate": "8.25",
  "taxType": "string",
  "isInclusive": true,
  "priority": 0,
  "stackable": true,
  "exceptionCountries": [
    "string"
  ],
  "isActive": true,
  "taxClassId": "string",
  "createdAt": "2019-08-24T14:15:22Z",
  "updatedAt": "2019-08-24T14:15:22Z"
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates"
}

Get a single tax rate by ID

GET
/v1/tax/rates/{rateId}
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Path Parameters

rateIdstring

Tax Rate ID

curl -X GET "https://api.brainerce.com/api/v1/tax/rates/string"
{
  "id": "string",
  "accountId": "string",
  "storeId": "string",
  "name": "California Sales Tax",
  "country": "string",
  "region": "string",
  "postalCode": "string",
  "rate": "8.25",
  "taxType": "string",
  "isInclusive": true,
  "priority": 0,
  "stackable": true,
  "exceptionCountries": [
    "string"
  ],
  "isActive": true,
  "taxClassId": "string",
  "createdAt": "2019-08-24T14:15:22Z",
  "updatedAt": "2019-08-24T14:15:22Z"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates/{rateId}"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates/{rateId}"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates/{rateId}"
}

Update a tax rate

Supports Idempotency-Key header for safe retries.

PATCH
/v1/tax/rates/{rateId}
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Path Parameters

rateIdstring

Tax Rate ID

Header Parameters

Idempotency-Key?string

Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.

  • Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
  • Reusing a key with a different body returns 409 Conflict with code: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response.
  • Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
  • Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
  • Sending this header on a GET returns 400 with code: "IDEMPOTENCY_KEY_NOT_SUPPORTED" — GETs are already idempotent.
  • Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.

See /docs/api/idempotency.

Lengthlength <= 255
name?string

Display name shown on invoices and the storefront tax line.

rate?number

Tax rate as a percentage (0 to 100).

Range0 <= value <= 100
country?string

ISO 3166-1 alpha-2 country code. Optional only in the PATCH sense: omit it and the rate keeps the country it already has. It cannot be cleared. TaxRate.country is NOT NULL, there is no global fallback rate, and sending null is rejected. A rate covering several countries names one here and lists the rest in exceptionCountries.

region?string

State/province subdivision code.

postalCode?string

Exact postal code (case-insensitive, spaces and hyphens ignored). Prefixes and ranges are NOT supported.

taxType?string

Tax category label (e.g. sales, vat, gst).

isInclusive?boolean

Treat prices as tax-inclusive (VAT-style).

priority?number

Tiebreak between rates of EQUAL specificity: the LOWER number wins. More specific rates (postal > region > country) win regardless of priority.

isActive?boolean

Whether this rate participates in calculation.

exceptionCountries?array<string>

Additional ISO country codes where this rate also applies.

stackable?boolean

Charge this rate together with the other stackable rates matching the same address (Canada GST + PST/QST) instead of the single most specific one. Flipping an existing rate to true changes what future checkouts collect — check the other rates on the store first, because rates that used to be mutually exclusive will start being charged together.

taxClassId?string

Tax class this rate applies to. Null/omit = Standard (applies to unclassed products).

curl -X PATCH "https://api.brainerce.com/api/v1/tax/rates/string" \  -H "Idempotency-Key: string" \  -H "Content-Type: application/json" \  -d '{}'
{
  "id": "string",
  "accountId": "string",
  "storeId": "string",
  "name": "California Sales Tax",
  "country": "string",
  "region": "string",
  "postalCode": "string",
  "rate": "8.25",
  "taxType": "string",
  "isInclusive": true,
  "priority": 0,
  "stackable": true,
  "exceptionCountries": [
    "string"
  ],
  "isActive": true,
  "taxClassId": "string",
  "createdAt": "2019-08-24T14:15:22Z",
  "updatedAt": "2019-08-24T14:15:22Z"
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates/{rateId}"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates/{rateId}"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates/{rateId}"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates/{rateId}"
}

Delete a tax rate

Supports Idempotency-Key header for safe retries.

DELETE
/v1/tax/rates/{rateId}
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Path Parameters

rateIdstring

Tax Rate ID

Header Parameters

Idempotency-Key?string

Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.

  • Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
  • Reusing a key with a different body returns 409 Conflict with code: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response.
  • Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
  • Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
  • Sending this header on a GET returns 400 with code: "IDEMPOTENCY_KEY_NOT_SUPPORTED" — GETs are already idempotent.
  • Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.

See /docs/api/idempotency.

Lengthlength <= 255
curl -X DELETE "https://api.brainerce.com/api/v1/tax/rates/string" \  -H "Idempotency-Key: string"
Empty

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates/{rateId}"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates/{rateId}"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/tax/rates/{rateId}"
}

On this page

No Headings