Skip to content

Powered by Grav CMS + Helios Theme

Tax

Tax zones, the rates hanging off them, the rate CSV, price preferences and the tax engines a zone can be bound to.

Tax zones and the rates hanging off them, the CSV that carries the whole rate table in and out, the preferences that say whether a region or a currency is priced inclusive of tax, and the engines a zone can be quoted by instead. See Tax.

List tax zones

GET /api/v1/kahunacart/tax/zones Permission kahunacart.settings

Every tax zone with its country, region and postcode criteria decoded, its priority, and the rates hanging off it. A zone is never returned without its rates, because a jurisdiction with no rate charges nothing. Rate percentages are decimal strings so figures like 25.5 reach the cents intact.

Create a tax zone

POST /api/v1/kahunacart/tax/zones Permission kahunacart.settings

Create a tax zone and its rates in one call. A name is required, and every rate needs a percentage. An empty criterion list matches everything, so a zone with no countries is the fallback zone. States are matched by ISO 3166-2 code or by name for the countries that publish a list, so "Colorado", "CO" and "US-CO" all match the same address.

Parameters

Name Type Description
name required string What the merchant calls this jurisdiction
countries optional array of string ISO country codes; empty matches every country
regions optional array of string States or provinces, as an ISO 3166-2 code or a name
postcodes optional array of string Postcodes or prefixes; empty matches every postcode
priority optional integer Higher priority zones win when several match
rates optional array of object The rates this zone charges. Keys: `rate`, `label`, `tax_class`, `apply_to_shipping`, `priority`, `translations`

Update a tax zone

PATCH /api/v1/kahunacart/tax/zones/{id} Permission kahunacart.settings

Change a zone's fields. Sending rates replaces the zone's whole set of rates and gives them new ids, so return each rate's translations unchanged if you want the overrides to survive. Omitting rates leaves them standing.

Parameters

Name Type Description
id required integer Tax zone id
name optional string
countries optional array of string
regions optional array of string
postcodes optional array of string
priority optional integer
rates optional array of object Replaces the whole set when present. Keys: `rate`, `label`, `tax_class`, `apply_to_shipping`, `priority`, `translations`

Delete a tax zone

DELETE /api/v1/kahunacart/tax/zones/{id} Permission kahunacart.settings

Delete a tax zone and its rates. Orders already placed keep the tax they were charged, because an order line carries its own snapshot.

Parameters

Name Type Description
id required integer Tax zone id

Import EU VAT presets

POST /api/v1/kahunacart/tax/presets/eu-vat Permission kahunacart.settings

Create a tax zone and rate for each EU member state at its standard VAT rate, and report how many were actually added. Idempotent by zone name, so calling it twice does not duplicate a jurisdiction and the count is what was added rather than what was offered.

List tax drivers

GET /api/v1/kahunacart/tax/engines Permission kahunacart.settings

Every tax engine an add-on registered, the store-level default engine code, whether the built-in zone and rate editors still govern anything, and the registered tax-id validators. KahunaCart ships no driver of its own, so engines is empty on a stock install and the connectors are add-ons; administration.state is full, partial or none, and none means the zone, class and rate routes answer 409.

Bind a tax driver to a region

PUT /api/v1/kahunacart/tax/engines/bind Permission kahunacart.settings

Attach a registered tax engine to one tax zone, so a store can quote a connector for one jurisdiction and its own rates for another from the same install. Send an empty engine to unbind. The store-level default every unbound zone falls back to is the tax.engine setting rather than anything this tool writes.

Parameters

Name Type Description
zone_id required integer The tax zone to bind
engine optional string A registered engine code, or an empty string to unbind

List price preferences

GET /api/v1/kahunacart/tax/price-preferences Permission kahunacart.settings

Whether prices contain tax, per region and per currency, plus the published resolution order and the store-wide tax.included at the bottom of it. A store selling to the UK and the US needs both answers at once, which one boolean cannot express.

Create a price preference

POST /api/v1/kahunacart/tax/price-preferences Permission kahunacart.settings

Say that one region or one currency is priced inclusive or exclusive of tax. attribute is region (the value is a tax zone id) or currency (the value is an ISO 4217 code). One answer per region per currency; creating a second for the same key is refused with 409.

Parameters

Name Type Description
attribute required string What the preference is written against
One of: region, currency
value required string A tax zone id for region, an ISO 4217 code for currency
is_tax_inclusive required boolean Whether prices in that scope contain tax
currency optional string The currency the row is for. Empty, which is the default for a currency preference and for a region preference on a store that sells in more than one currency, means every currency. A currency preference ("prices in EUR contain tax") matches on its value whatever this says. A region preference on a store with one currency defaults to the store currency, as it always did.

Update a price preference

PUT /api/v1/kahunacart/tax/price-preferences/{id} Permission kahunacart.settings

Change a preference's scope or its answer. Omitted fields are left standing.

Parameters

Name Type Description
id required integer Price preference id
attribute optional string
One of: region, currency
value optional string
is_tax_inclusive optional boolean
currency optional string The currency the row is for; empty is every currency. See create_price_preference

Delete a price preference

DELETE /api/v1/kahunacart/tax/price-preferences/{id} Permission kahunacart.settings

Remove a preference. The scope it covered falls back to the next level of the resolution order — the currency preference, then tax.included.

Parameters

Name Type Description
id required integer Price preference id

Import the tax rate table

POST /api/v1/kahunacart/tax/rates/import Permission kahunacart.settings

Import the 10-column rate table — country_code, state_code, postcode, city, rate, tax_rate_name, priority, compound, shipping, tax_class — as a csv string. Rows sharing a geography become one zone carrying several rates, which is what makes GST plus QST expressible. Idempotent on country, state, postcode, city, tax class and priority, so importing the same file twice updates rather than duplicates. A malformed row fails alone and comes back in errors with its line number.

Parameters

Name Type Description
csv required string The rate table as CSV text, header row optional

Export the tax rates as CSV

GET /api/v1/kahunacart/tax/rates/export Permission kahunacart.settings

The whole rate table as a text/csv file. No MCP tool: the answer is a document to edit, not a payload to read

Zone fields are name, countries, regions, postcodes, cities, priority and engine. A rate takes rate (a decimal string, required), label, tax_class, apply_to_shipping and priority. The preset import is idempotent by zone name, so imported is what was actually added.

The rate CSV is Woo's ten columns, because a merchant arriving from Woo has one of these files already. Import is idempotent on the geography, the class and the priority, so running the same file twice updates rather than doubles. A malformed row fails alone with its line number and every other row still imports. Rows sharing a geography fold into one zone carrying several rates, which is what makes a stacked GST and QST expressible at all, so write the most specific geography first. A round trip changes nothing: export, import the file back, export again, and the two files match byte for byte.

A price preference is an attribute of region or currency, the value it names, and is_tax_inclusive. Nothing else is accepted as an attribute, and a unique key over (attribute, value, currency) makes two rows that disagree impossible. Preferences sit in the middle of a published four-level resolution order: the product's own price_includes_tax first, then the region preference, then the currency preference, then the store-wide tax.included. The level that answered is recorded on the adjustment, so a receipt six months old can still say why it was inclusive.

A tax engine quotes tax for a region through its own service instead of the rate table. Binding is per zone rather than per store, so one install can quote a connector for the United States and its own EU VAT rates for Europe, and an empty engine on the bind route unbinds. KahunaCart ships the contract and no driver, so GET /kahunacart/tax/engines is empty on a stock install and lists whatever the tax add-on registered otherwise. A zone bound to an engine nobody registered falls back to the merchant's own rates rather than taking checkout down, and the read says which zones are in that state. When a bound engine has taken the built-in rate administration away, the four zone writes, the EU VAT preset and the CSV import all answer 409 naming the engine. The bind route is left out of that on purpose: it is the route that undoes a binding, and a store has to be able to unlock its own rate table.