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
Returns every tax rate of the store.
Admin API key (server-to-server). Issue via the dashboard at Settings → 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": "clx1a2b3c4d5e6f7g8h9",
"accountId": "clx1a2b3c4d5e6f7g8h9",
"storeId": "clx1a2b3c4d5e6f7g8h9",
"name": "California Sales Tax",
"country": "IL",
"region": "Ontario",
"postalCode": "6578301",
"rate": "8.25",
"taxType": "vat",
"isInclusive": true,
"priority": 1,
"stackable": true,
"appliesToShipping": true,
"exceptionCountries": [
"string"
],
"isActive": true,
"taxClassId": "clx1a2b3c4d5e6f7g8h9",
"effectiveFrom": "2019-08-24T14:15:22Z",
"effectiveTo": "2019-08-24T14:15:22Z",
"source": "preset:IL",
"sourceVerifiedAt": "2019-08-24T14:15:22Z",
"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"
}{
"statusCode": 429,
"code": "RATE_LIMITED",
"message": "Too many requests",
"details": {
"retryAfterSeconds": 12
},
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/store"
}Create a new tax rate
Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Header Parameters
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 Conflictwithcode: "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
GETreturns400withcode: "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.
length <= 255Display name shown on invoices and the storefront tax line. At most 100 characters.
length <= 100Tax rate as a percentage (e.g. 8.5 for 8.5%). Range 0 to 100.
0 <= value <= 100ISO 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.
State/province, as a code or a name. Normalized on save: QC, qc, CA-QC and Quebec are all stored as QC; text that cannot be resolved is kept as typed. Narrows the match within country.
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.
Tax category label (e.g. sales, vat, gst). Shown on invoices and used for reporting; does not affect calculation.
IGNORED by tax calculation. Stored and returned for backward compatibility only. Whether prices already include tax is decided by the store setting pricesIncludeTax (Settings, Tax), unless the region of the buyer sets its own taxInclusive override. It never varies per rate.
falseTiebreak 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.
Whether this rate participates in calculation. Defaults to true.
trueAdditional 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). At most 60.
items <= 60Charge 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.
falseWhether this rate is also charged on the delivery cost. Defaults to true: postage is taxed by every rate that applies to the order, as it always was. Set false to take THIS rate off postage while the items still pay it (Canada: GST on shipping, a province PST that does not apply to shipping). It only removes this one rate from postage; it never moves postage onto a different rate. A shipping rate whose own tax status is "none" still exempts postage from every rate.
trueTax class this rate applies to. Omit (or null) for the Standard rate that applies to products without an explicit class.
First instant this rate is charged (ISO 8601 date-time, UTC unless an offset is given). Leave empty for a rate that is already in force. A future date SCHEDULES the rate: it is stored now and starts charging at that instant. In force while effectiveFrom <= now < effectiveTo. To change a rate from a date, set the old rate's effectiveTo to that same instant FIRST, then create the new rate with this effectiveFrom: two rates on the same country, region and class may share a scope only when their date windows do not overlap.
First instant this rate is NO LONGER charged (ISO 8601 date-time). Leave empty for a rate with no end date. Must be later than effectiveFrom when both are set. The end instant itself is not charged (half-open window), so a successor rate can start at exactly this instant.
The date you last checked this rate against the tax authority (ISO 8601). A record for your own files: calculation never reads it. Country presets stamp the date they were verified.
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": "clx1a2b3c4d5e6f7g8h9",
"accountId": "clx1a2b3c4d5e6f7g8h9",
"storeId": "clx1a2b3c4d5e6f7g8h9",
"name": "California Sales Tax",
"country": "IL",
"region": "Ontario",
"postalCode": "6578301",
"rate": "8.25",
"taxType": "vat",
"isInclusive": true,
"priority": 1,
"stackable": true,
"appliesToShipping": true,
"exceptionCountries": [
"string"
],
"isActive": true,
"taxClassId": "clx1a2b3c4d5e6f7g8h9",
"effectiveFrom": "2019-08-24T14:15:22Z",
"effectiveTo": "2019-08-24T14:15:22Z",
"source": "preset:IL",
"sourceVerifiedAt": "2019-08-24T14:15:22Z",
"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"
}{
"statusCode": 429,
"code": "RATE_LIMITED",
"message": "Too many requests",
"details": {
"retryAfterSeconds": 12
},
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/store"
}Get a single tax rate by ID
Returns one tax rate of this store.
Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Tax Rate ID
curl -X GET "https://api.brainerce.com/api/v1/tax/rates/string"{
"id": "clx1a2b3c4d5e6f7g8h9",
"accountId": "clx1a2b3c4d5e6f7g8h9",
"storeId": "clx1a2b3c4d5e6f7g8h9",
"name": "California Sales Tax",
"country": "IL",
"region": "Ontario",
"postalCode": "6578301",
"rate": "8.25",
"taxType": "vat",
"isInclusive": true,
"priority": 1,
"stackable": true,
"appliesToShipping": true,
"exceptionCountries": [
"string"
],
"isActive": true,
"taxClassId": "clx1a2b3c4d5e6f7g8h9",
"effectiveFrom": "2019-08-24T14:15:22Z",
"effectiveTo": "2019-08-24T14:15:22Z",
"source": "preset:IL",
"sourceVerifiedAt": "2019-08-24T14:15:22Z",
"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}"
}{
"statusCode": 429,
"code": "RATE_LIMITED",
"message": "Too many requests",
"details": {
"retryAfterSeconds": 12
},
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/store"
}Update a tax rate
Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Tax Rate ID
Header Parameters
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 Conflictwithcode: "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
GETreturns400withcode: "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.
length <= 255Display name shown on invoices and the storefront tax line. At most 100 characters.
length <= 100Tax rate as a percentage (0 to 100).
0 <= value <= 100ISO 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.
State/province code or name, normalized on save (Quebec, qc, CA-QC → QC).
Exact postal code (case-insensitive, spaces and hyphens ignored). Prefixes and ranges are NOT supported.
Tax category label (e.g. sales, vat, gst).
IGNORED by tax calculation (backward compatibility only). Use the store setting pricesIncludeTax, or the taxInclusive override of a region.
Tiebreak between rates of EQUAL specificity: the LOWER number wins. More specific rates (postal > region > country) win regardless of priority.
Whether this rate participates in calculation.
Additional ISO country codes where this rate also applies. At most 60.
items <= 60Charge 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.
Whether this rate is also charged on the delivery cost. Omit to leave it as it is. false takes this one rate off postage while the items still pay it; true puts it back. It never moves postage onto a different rate. Changing it changes what future checkouts collect on shipping.
Tax class this rate applies to. Null/omit = Standard (applies to unclassed products).
First instant this rate is charged (ISO 8601 date-time). Send null to clear it (in force from the start); omit to keep it. A future date makes the rate scheduled. Must be earlier than effectiveTo when both end up set.
First instant this rate is NO LONGER charged (ISO 8601 date-time). Send null to remove the end date; omit to keep it. To retire a rate on a date while a successor takes over, set this to the successor's effectiveFrom.
The date you last checked this rate against the tax authority (ISO 8601). Send null to clear it. Changing rate without sending this clears it, because the old verification no longer describes the new number.
curl -X PATCH "https://api.brainerce.com/api/v1/tax/rates/string" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{}'{
"id": "clx1a2b3c4d5e6f7g8h9",
"accountId": "clx1a2b3c4d5e6f7g8h9",
"storeId": "clx1a2b3c4d5e6f7g8h9",
"name": "California Sales Tax",
"country": "IL",
"region": "Ontario",
"postalCode": "6578301",
"rate": "8.25",
"taxType": "vat",
"isInclusive": true,
"priority": 1,
"stackable": true,
"appliesToShipping": true,
"exceptionCountries": [
"string"
],
"isActive": true,
"taxClassId": "clx1a2b3c4d5e6f7g8h9",
"effectiveFrom": "2019-08-24T14:15:22Z",
"effectiveTo": "2019-08-24T14:15:22Z",
"source": "preset:IL",
"sourceVerifiedAt": "2019-08-24T14:15:22Z",
"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}"
}{
"statusCode": 429,
"code": "RATE_LIMITED",
"message": "Too many requests",
"details": {
"retryAfterSeconds": 12
},
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/store"
}Delete a tax rate
Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Tax Rate ID
Header Parameters
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 Conflictwithcode: "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
GETreturns400withcode: "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.
length <= 255curl -X DELETE "https://api.brainerce.com/api/v1/tax/rates/string" \ -H "Idempotency-Key: string"{
"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}"
}{
"statusCode": 429,
"code": "RATE_LIMITED",
"message": "Too many requests",
"details": {
"retryAfterSeconds": 12
},
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/store"
}