Tax zones belong to a market and use read_markets / write_markets. A zone may cover only countries assigned to that market. Geography is validated and overlap is rejected, so an address resolves to one specific zone or the optional default zone. Rates owned by a deleted zone are removed only after the confirmation handshake reports the impact.
Match an order to a jurisdiction#
A tax zone describes where a set of rates applies. Its countries must belong to the market, and subdivisions use the platform's canonical country-subdivision codes. Specific zones are matched before the optional default zone. If no zone matches and no default exists, Tringify returns zero zone tax and marks the calculation as unmatched.
Tringify checks tax ID formats and check digits only. It does not verify registrations or exemptions with tax authorities. You are responsible for your tax settings and for approving exempt customers. A tax ID never grants an exemption. An online customer must sign in to the customer account you marked tax_exempt, and the matched tax zone must have an enabled exemption with effect exempt. Otherwise an entered tax ID is recorded with invoice_only and tax is charged, even in an exempt zone. With effect invoice_only, tax is always charged. Staff can explicitly approve an exemption for an individual order. Orders record the applied source as customer_flag, staff_order or none. Saved IDs must pass the zone type, format and optional pattern rules; invalid saved IDs are ignored without blocking checkout. Shopper-entered IDs are sent with quotes and receive field errors. EU VAT uses national published checksum rules; Czech nine-digit birth numbers and Latvian personal numbers receive structural checks only. Canadian BN uses MOD10. Order edits recalculate the identity type, applied exemption source and GSTIN state mismatch with the tax totals.
Create a zone#
// POST /api/2025-01/markets/{marketId}/tax-zones
{
"name": "California",
"description": "California shipping addresses",
"countries": ["US"],
"subdivisions": ["US-CA"],
"is_default": false
}
Use the available-countries endpoint when building geography controls. It returns market countries together with their subdivision choices and existing zone claims. Business-tax exemption settings belong to the zone because the applicable treatment depends on the jurisdiction that matched the order. Tringify assigns priority itself, in creation order; zones never overlap, so it does not change which zone an address matches.
Endpoints#
Get tax zone limits
Fetch tax-zone validation limits.
Returns the live tax-zone limits — the per-market cap and the name, description, and search length caps (all counted in characters), plus list pagination bounds. Read this instead of hardcoding numbers.
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Get available countries
Fetch the market's countries with their zone-claim status.
Returns every country in the market stamped with its claim status across the market's zones: fully_claimed (a country fallback already exists, named in claimed_by_zone) and claimed_subdivisions (subdivisions other zones already took). Both can be present for one country. A country fallback does not prevent creating a zone for its unclaimed subdivisions; it only prevents another zone from covering that country whole, that is, listing it with none of its subdivisions. Build zone pickers from this — the server's verdict is the only source of what is still claimable. Pass exclude_zone_id when editing a zone so its own claims don't count against it.
Path parameters
| Name | Type | Description |
|---|---|---|
marketId
required
|
uuid |
Market id. |
Query parameters
| Name | Type | Description |
|---|---|---|
exclude_zone_id
|
uuid |
Zone id whose own claims to ignore — pass the zone being edited. |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Errors
-
400STORE_API_INVALID_REQUEST_BODYmarketIdmust be a valid UUID. -
404MARKET_NOT_FOUNDMarket does not exist in this store.
List tax zones
List a market's tax zones.
Returns paginated tax zones, searchable by name. Each zone carries rate_count — how many rate lines it holds — and is_default marks the market's catch-all zone if one exists. A market is not required to have a default zone: addresses no zone covers are sold with zero tax, and the tax calculation reports tax_zone_unmatched. Pagination and search bounds come from the limits endpoint.
Path parameters
| Name | Type | Description |
|---|---|---|
marketId
required
|
uuid |
Market id. |
Query parameters
| Name | Type | Description |
|---|---|---|
page
|
integer |
Page number. |
per_page
|
integer |
Page size (service-capped). |
search
|
string |
Search by tax zone name. |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Errors
-
400STORE_API_INVALID_REQUEST_BODYmarketIdmust be a valid UUID. -
404MARKET_NOT_FOUNDMarket does not exist in this store.
Create a tax zone
Create a tax zone in a market.
Creates a tax zone. name is required and unique within the market. A regular zone needs at least one country or subdivision; every country must belong to the market, every subdivision must be a known ISO subdivision belonging to a selected country, and duplicate subdivision claims or duplicate whole-country claims are rejected. A whole-country zone with an empty subdivisions array can coexist with state-specific zones: state matches win, then the country zone covers the rest. For example, a zone with countries: ["IN"] and subdivisions: ["IN-UP"] handles Uttar Pradesh; a second zone with countries: ["IN"] and subdivisions: [] handles the rest of India. Only the matching zone’s rates apply. One zone can list whole countries and subdivisions of other countries together: each listed country with at least one listed subdivision is claimed only for those subdivisions, and a listed country with none of its subdivisions listed is claimed whole, except subdivisions other zones claim. So countries: ["IN", "US"] with subdivisions: ["IN-UP"] covers Uttar Pradesh and all of the US. Addresses are matched with country and state trimmed and case-insensitive, and a state matches a subdivision zone whether the address gives its ISO code with or without the country (UP, up, IN-UP) or its full name (Uttar Pradesh). When a country has subdivision zones and the address has no state, or one that isn't recognised, tax is not taken from the country zone: the tax calculation reports tax_zone_unmatched: true with tax_zone_needs_state: true and reports unresolved tax until a recognised state is given. Operational order creation is refused with ORDER_TAX_ZONE_NEEDS_STATE and details.tax_zone_needs_state: true; it cannot create a payable zero-tax order. A recognised state that no subdivision zone covers uses the whole-country zone. Setting is_default instead creates the market's catch-all zone for unmatched addresses. The optional tax_exemption object sets the business treatment: exempt removes tax only for a buyer marked tax_exempt by the merchant, and invoice_only keeps tax and marks the order for a business invoice when such a buyer, or any buyer who enters a tax ID, checks out. Fires tax_zones/create.
Path parameters
| Name | Type | Description |
|---|---|---|
marketId
required
|
uuid |
Market id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Body parameters
| Name | Type | Description |
|---|---|---|
name
required
|
string |
Tax zone name — unique within the market. |
description
|
string |
Internal description. |
countries
|
array |
ISO country codes belonging to the market. A listed country with none of its subdivisions listed is covered whole, except subdivisions assigned to other zones; a listed country with listed subdivisions is covered only for those subdivisions. Only one zone may cover a country whole, but it can coexist with subdivision zones. Empty countries and subdivisions are only valid for the default zone. |
subdivisions
|
array |
ISO subdivision codes (e.g. IN-KA). Each must belong to a selected country and not be claimed by another zone. Listing a subdivision limits the zone to the listed subdivisions of that country only; other listed countries are still covered whole. |
is_default
|
boolean |
Make this the market's catch-all zone for addresses no other zone covers. The default zone must have no countries or subdivisions; promoting one demotes the previous default in the same write. |
tax_exemption
|
object |
Business tax exemption for this zone: |
Errors
-
400STORE_API_INVALID_REQUEST_BODYmarketIdmust be a valid UUID. -
400TAX_ID_ZONE_TYPE_MISMATCHThe type fits no zone country (no market country for a default zone). Existing mismatches are returned on reads without modifying stored settings.
-
400TAX_ID_PATTERN_INVALIDCustom RE2 pattern is invalid or exceeds 256 UTF-8 bytes.
-
400TAX_ID_INVALIDCustom example does not match the normalized full-value pattern.
-
400TAX_ZONE_NAME_REQUIREDName is empty after normalization.
-
400TAX_ZONE_NAME_TOO_LONGName exceeds the limit.
-
400TAX_ZONE_DESCRIPTION_TOO_LONGDescription exceeds the limit.
-
400TAX_ZONE_LIMIT_REACHEDThe market's tax zone cap has been reached.
-
400ZONE_NEEDS_REGIONA regular zone must include at least one country or subdivision.
-
400ZONE_DEFAULT_NO_REGIONSThe default zone must have no countries or subdivisions — it covers all remaining regions.
-
400ZONE_COUNTRY_INVALIDA country is not valid for this zone (unknown code or not in the market).
-
400ZONE_SUBDIVISION_INVALIDA subdivision code is unknown.
-
400ZONE_SUBDIVISION_NEEDS_COUNTRYSubdivisions require their country to be selected in the zone.
-
400ZONE_SUBDIVISION_NOT_IN_COUNTRYA subdivision belongs to a country not selected in this zone.
-
400ZONE_COUNTRY_TAKENA country this zone covers whole (none of its subdivisions listed) is already covered whole by another zone.
-
400ZONE_SUBDIVISION_TAKENA subdivision is already assigned to another zone.
-
400MARKET_TAX_EXEMPTION_TYPE_INVALIDTax_exemption.type is not a supported tax ID kind.
-
400MARKET_TAX_EXEMPTION_EFFECT_INVALIDTax_exemption.effect must be exempt or invoice_only.
-
400MARKET_CHECKOUT_TEXT_TOO_LONGTax_exemption.checkout_text exceeds the limit.
-
400MARKET_CUSTOM_LABEL_REQUIREDTax_exemption.custom_label is required when type is custom.
-
400MARKET_CUSTOM_LABEL_TOO_LONGTax_exemption.custom_label exceeds the limit.
-
400MARKET_CUSTOM_PLACEHOLDER_TOO_LONGTax_exemption.custom_placeholder exceeds the limit.
-
404MARKET_NOT_FOUNDMarket does not exist in this store.
-
409TAX_ZONE_NAME_DUPLICATEA tax zone with this name already exists in the market.
Get a tax zone
Fetch one tax zone.
Returns the tax zone.
Path parameters
| Name | Type | Description |
|---|---|---|
marketId
required
|
uuid |
Market id. |
taxZoneId
required
|
uuid |
Tax zone id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Errors
-
400STORE_API_INVALID_REQUEST_BODYmarketIdmust be a valid UUID. -
404MARKET_NOT_FOUNDMarket does not exist in this store.
-
404NOT_FOUNDTax zone does not exist in this market.
Update a tax zone
Update a tax zone.
Updates the zone's name, description, regions, default flag, and business tax exemption. Region rules match create: regular zones need at least one country or subdivision, all regions must be claimable, and the default zone must have none. Promoting a zone to default demotes the previous default in the same write. Fires tax_zones/update.
Path parameters
| Name | Type | Description |
|---|---|---|
marketId
required
|
uuid |
Market id. |
taxZoneId
required
|
uuid |
Tax zone id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Body parameters
| Name | Type | Description |
|---|---|---|
name
required
|
string |
Tax zone name — unique within the market. |
description
|
string |
Internal description. |
countries
|
array |
ISO country codes belonging to the market. A listed country with none of its subdivisions listed is covered whole, except subdivisions assigned to other zones; a listed country with listed subdivisions is covered only for those subdivisions. Only one zone may cover a country whole, but it can coexist with subdivision zones. Empty countries and subdivisions are only valid for the default zone. |
subdivisions
|
array |
ISO subdivision codes (e.g. IN-KA). Each must belong to a selected country and not be claimed by another zone. Listing a subdivision limits the zone to the listed subdivisions of that country only; other listed countries are still covered whole. |
is_default
|
boolean |
Make this the market's catch-all zone for addresses no other zone covers. The default zone must have no countries or subdivisions; promoting one demotes the previous default in the same write. |
tax_exemption
|
object |
Business tax exemption for this zone: |
Errors
-
400STORE_API_INVALID_REQUEST_BODYmarketIdmust be a valid UUID. -
400TAX_ID_ZONE_TYPE_MISMATCHThe type fits no zone country (no market country for a default zone). Existing mismatches are returned on reads without modifying stored settings.
-
400TAX_ID_PATTERN_INVALIDCustom RE2 pattern is invalid or exceeds 256 UTF-8 bytes.
-
400TAX_ID_INVALIDCustom example does not match the normalized full-value pattern.
-
400TAX_ZONE_NAME_REQUIREDName is empty after normalization.
-
400TAX_ZONE_NAME_TOO_LONGName exceeds the limit.
-
400TAX_ZONE_DESCRIPTION_TOO_LONGDescription exceeds the limit.
-
400TAX_ZONE_LIMIT_REACHEDThe market's tax zone cap has been reached.
-
400ZONE_NEEDS_REGIONA regular zone must include at least one country or subdivision.
-
400ZONE_DEFAULT_NO_REGIONSThe default zone must have no countries or subdivisions — it covers all remaining regions.
-
400ZONE_COUNTRY_INVALIDA country is not valid for this zone (unknown code or not in the market).
-
400ZONE_SUBDIVISION_INVALIDA subdivision code is unknown.
-
400ZONE_SUBDIVISION_NEEDS_COUNTRYSubdivisions require their country to be selected in the zone.
-
400ZONE_SUBDIVISION_NOT_IN_COUNTRYA subdivision belongs to a country not selected in this zone.
-
400ZONE_COUNTRY_TAKENA country this zone covers whole (none of its subdivisions listed) is already covered whole by another zone.
-
400ZONE_SUBDIVISION_TAKENA subdivision is already assigned to another zone.
-
400MARKET_TAX_EXEMPTION_TYPE_INVALIDTax_exemption.type is not a supported tax ID kind.
-
400MARKET_TAX_EXEMPTION_EFFECT_INVALIDTax_exemption.effect must be exempt or invoice_only.
-
400MARKET_CHECKOUT_TEXT_TOO_LONGTax_exemption.checkout_text exceeds the limit.
-
400MARKET_CUSTOM_LABEL_REQUIREDTax_exemption.custom_label is required when type is custom.
-
400MARKET_CUSTOM_LABEL_TOO_LONGTax_exemption.custom_label exceeds the limit.
-
400MARKET_CUSTOM_PLACEHOLDER_TOO_LONGTax_exemption.custom_placeholder exceeds the limit.
-
404MARKET_NOT_FOUNDMarket does not exist in this store.
-
409TAX_ZONE_NAME_DUPLICATEA tax zone with this name already exists in the market.
-
404NOT_FOUNDTax zone does not exist in this market.
Delete a tax zone
Delete a tax zone.
Deletes the tax zone. If it still has rate lines, the first call returns CONFIRMATION_REQUIRED; retry with confirmed and the returned confirmation_token. The token is for that number of rate lines: if lines are added before the retry, the zone asks for confirmation again. Cascaded rate lines do not fire individual tax_rates/delete events, so re-sync rates after the tax_zones/delete event. The default zone can't be deleted while it is the default. After deletion, addresses are matched again: any remaining state-specific zone wins, then a whole-country zone, then the default zone. If none matches, no tax is applied.
Path parameters
| Name | Type | Description |
|---|---|---|
marketId
required
|
uuid |
Market id. |
taxZoneId
required
|
uuid |
Tax zone id. |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Body parameters
| Name | Type | Description |
|---|---|---|
confirmed
|
boolean |
On a confirmation retry, set this to true and send the returned confirmation_token. Omit it on the first request. |
confirmation_token
|
string |
Token from the CONFIRMATION_REQUIRED response. Omit it on the first request and resend the exact same id selection when using it. |
Errors
-
400STORE_API_INVALID_REQUEST_BODYmarketIdmust be a valid UUID. -
409CONFIRMATION_REQUIREDThe zone still has rate lines. Retry with confirmed and the returned confirmation_token.
-
400TAX_ZONE_DEFAULT_DELETE_LOCKEDThe default zone can't be deleted while it is the default.
-
404MARKET_NOT_FOUNDMarket does not exist in this store.
-
404NOT_FOUNDTax zone does not exist in this market.
Webhook events#
Tax-zone events are published for every write, and the saved change and its event become visible together. Making a zone the default also publishes an update for the zone that stops being the default. Update payloads contain the complete saved geography and exemption configuration; delete payloads retain the removed zone's identity.
Tax zone created
Subscribe to receive the saved tax zone after it is created in a market.
Payload fields
| Field | Type | Description |
|---|---|---|
tax_zone.id |
string |
Always present
The stable tax zone id. |
tax_zone.market_id |
string |
Selectable in the Payload builder
The market where this zone is matched. |
tax_zone.name |
string |
Selectable in the Payload builder
The merchant-facing tax zone name. |
tax_zone.description |
string |
Selectable in the Payload builder
The optional explanation of the jurisdiction this zone covers. |
tax_zone.countries |
array |
Selectable in the Payload builder
ISO 3166-1 alpha-2 country codes claimed by the zone. |
tax_zone.subdivisions |
array |
Selectable in the Payload builder
Canonical country-subdivision codes claimed by the zone. |
tax_zone.is_default |
boolean |
Selectable in the Payload builder
Whether this zone catches addresses not matched by a more specific zone. |
tax_zone.priority |
integer |
Selectable in the Payload builder
The order in which the zone was created, assigned by Tringify (newer zones are higher). Zones never overlap, so it does not change which zone an address matches. |
tax_zone.tax_exemption.type_mismatch |
boolean |
Selectable in the Payload builder |
tax_zone.tax_exemption.custom_pattern |
string |
Selectable in the Payload builder |
tax_zone.tax_exemption.custom_example |
string |
Selectable in the Payload builder |
tax_zone.tax_exemption.translations |
object |
Selectable in the Payload builder |
tax_zone.tax_exemption.enabled |
boolean |
Selectable in the Payload builder
Whether this zone's business tax treatment is on. |
tax_zone.tax_exemption.type |
string |
Selectable in the Payload builder
The exemption configuration type used by this zone. |
tax_zone.tax_exemption.effect |
string |
Selectable in the Payload builder
The business tax treatment: exempt removes tax for a buyer verified as tax exempt; invoice_only keeps tax and marks the order for a business invoice for such a buyer or any buyer who enters a tax ID. |
tax_zone.tax_exemption.checkout_text |
string |
Selectable in the Payload builder
The customer-facing guidance shown when exemption details are requested. |
tax_zone.tax_exemption.custom_label |
string |
Selectable in the Payload builder
The custom customer-facing label for the exemption field. |
tax_zone.tax_exemption.custom_placeholder |
string |
Selectable in the Payload builder
The custom placeholder shown in the exemption field. |
tax_zone.created_at |
string |
Selectable in the Payload builder
When the tax zone was created. |
tax_zone.updated_at |
string |
Selectable in the Payload builder
When the tax zone last changed. |
Tax zone updated
Subscribe to receive the current tax zone after its geography, priority, default status, or exemption settings change.
Payload fields
| Field | Type | Description |
|---|---|---|
tax_zone.id |
string |
Always present
The stable tax zone id. |
tax_zone.market_id |
string |
Selectable in the Payload builder
The market where this zone is matched. |
tax_zone.name |
string |
Selectable in the Payload builder
The merchant-facing tax zone name. |
tax_zone.description |
string |
Selectable in the Payload builder
The optional explanation of the jurisdiction this zone covers. |
tax_zone.countries |
array |
Selectable in the Payload builder
ISO 3166-1 alpha-2 country codes claimed by the zone. |
tax_zone.subdivisions |
array |
Selectable in the Payload builder
Canonical country-subdivision codes claimed by the zone. |
tax_zone.is_default |
boolean |
Selectable in the Payload builder
Whether this zone catches addresses not matched by a more specific zone. |
tax_zone.priority |
integer |
Selectable in the Payload builder
The order in which the zone was created, assigned by Tringify (newer zones are higher). Zones never overlap, so it does not change which zone an address matches. |
tax_zone.tax_exemption.type_mismatch |
boolean |
Selectable in the Payload builder |
tax_zone.tax_exemption.custom_pattern |
string |
Selectable in the Payload builder |
tax_zone.tax_exemption.custom_example |
string |
Selectable in the Payload builder |
tax_zone.tax_exemption.translations |
object |
Selectable in the Payload builder |
tax_zone.tax_exemption.enabled |
boolean |
Selectable in the Payload builder
Whether this zone's business tax treatment is on. |
tax_zone.tax_exemption.type |
string |
Selectable in the Payload builder
The exemption configuration type used by this zone. |
tax_zone.tax_exemption.effect |
string |
Selectable in the Payload builder
The business tax treatment: exempt removes tax for a buyer verified as tax exempt; invoice_only keeps tax and marks the order for a business invoice for such a buyer or any buyer who enters a tax ID. |
tax_zone.tax_exemption.checkout_text |
string |
Selectable in the Payload builder
The customer-facing guidance shown when exemption details are requested. |
tax_zone.tax_exemption.custom_label |
string |
Selectable in the Payload builder
The custom customer-facing label for the exemption field. |
tax_zone.tax_exemption.custom_placeholder |
string |
Selectable in the Payload builder
The custom placeholder shown in the exemption field. |
tax_zone.created_at |
string |
Selectable in the Payload builder
When the tax zone was created. |
tax_zone.updated_at |
string |
Selectable in the Payload builder
When the tax zone last changed. |
Tax zone deleted
Subscribe to remove a deleted tax zone from your integration. Its rate lines are removed with it without separate tax_rates/delete events, so use the zone id to refresh affected rates.
Payload fields
| Field | Type | Description |
|---|---|---|
tax_zone.id |
string |
Always present
The stable tax zone id. |
tax_zone.market_id |
string |
Selectable in the Payload builder
The market where this zone is matched. |
tax_zone.name |
string |
Selectable in the Payload builder
The merchant-facing tax zone name. |