Taxes
A tax rule decides the rate applied to an invoice line. Kontier picks the rule at billing time from three things: where the customer is, what kind of customer they are, and what is being sold.
Rules are versioned and dated, because a VAT rate that changed on 1 January must not retroactively change last year's invoices.
What a rule matches on
Three fields are required: jurisdiction_country, tax_type (VAT,
SALES_TAX, GST or CUSTOM) and rate. Everything else narrows when the
rule applies.
| Field | Narrows by |
|---|---|
jurisdiction_country | Country. Required |
jurisdiction_region | State or province, where country is not enough |
applies_to_customer_types | BUSINESS, CONSUMER, UNKNOWN |
applies_to_supply_types | DOMESTIC, INTRA_EU_B2B, INTRA_EU_B2C, EXPORT |
applies_to_product_categories | The product's tax category |
rate is a decimal string holding a fraction between 0 and 1: 19% is
"0.19", never "19" and never a float.
Supply type
Supply type is derived from where you are, where the customer is, and whether they are a business. It is what makes cross-border VAT work.
| Supply type | Situation |
|---|---|
DOMESTIC | Same country. Normal local rate |
INTRA_EU_B2B | EU business in another member state. Reverse charge: you charge 0% and they account for it |
INTRA_EU_B2C | EU consumer in another member state. Charged at their country's rate |
EXPORT | Outside the EU. Usually zero-rated |
This is why customer type matters so much. A business with a
validated VAT number in another EU state is INTRA_EU_B2B and pays no VAT to
you. The same company recorded as CONSUMER is charged full local VAT, and the
invoice is wrong.
Reverse charge depends on a validated VAT number
INTRA_EU_B2B requires the customer to actually be a business with a valid VAT
identification number. Validate it rather than trusting what was typed into a
signup form.
Stacking and order
Two fields handle jurisdictions that apply more than one tax.
apply_order sets the sequence. is_compound decides whether a rule is
calculated on the subtotal, or on the subtotal plus the taxes already applied
before it. Compound tax is how some Canadian provinces work, and getting the
order wrong produces a number that is close enough to look right.
Whether an amount already contains the tax is not a property of the tax rule
but of the price: a price with price_basis: "inclusive" is billed as
the stated amount, and the invoice derives the net and the tax out of it. An
inclusive CHF 100 at 19% is CHF 84.03 net plus CHF 15.97 tax, not CHF 119.
Versioning and coverage
Tax rules are versioned like plans. POST /v1/tax-rules creates a
rule as a draft. PUT /v1/tax-rules/{id} publishes nothing: a billing-field
edit (rate, jurisdiction, tax type, applicability) to a live rule stages on its
working draft, so what is being invoiced does not move under the request, while
a metadata-only edit lands live. GET /v1/tax-rules/{id}/draft shows the staged
rule and DELETE /v1/tax-rules/{id}/draft discards it.
The day a rate takes effect is decided by the publish, not by a field on the
rule: POST /v1/changes with operation catalog.publish_version,
subject_kind: "tax_rule", and "when": "at" with the wall-clock at and its
zone, so a rate change takes effect on the right day rather than the day
somebody remembered. A past at is a deliberate backdated correction: it
rewrites the tax timeline for periods already invoiced. The live version's
window shows as head_effective_from / head_effective_to on the rule, and
is_expired turns true once it has closed. See Versioning.
requires_continuous_coverage refuses to publish a version that leaves a gap in
the timeline, which is worth enabling: a gap means an invoice lands on a date
with no applicable rule.
Terms and API names
| Term | In the API |
|---|---|
| a tax rule | /v1/tax-rules |
| the rate | rate, a decimal string |
| country / region | jurisdiction_country / jurisdiction_region |
| what a rule applies to | the three applies_to_* arrays |
| a product's tax category | matched by applies_to_product_categories |
| the org's own tax identity | /v1/organization/tax-profile |
Where taxes go next
- Every invoice line carries its rate and the decision behind it, pinned at finalisation so it never drifts.
- Verifications validate a VAT number, which is what unlocks reverse charge.
- Credit notes reverse tax at the rate the original line used, not today's rate.
Reference
| Topic | When you need it |
|---|---|
| Customers | Type and address, the inputs to supply type |
| Invoices | Where the chosen rate is recorded |
| Exchange rates | The other thing pinned at finalisation |
| Tax rules API | Every field, plus versions and timeline |