Skip to content

Tax engine

How Ownrecord decides which tax applies to a sale, and why it stops a sale rather than guess.

The tax engine answers one question for every sale: which tax applies, at what rate, and what the invoice must say. It's a pure calculation. Given the sale, the tax rules valid on the date of the sale and your entity's registration state, it always returns the same answer, with a trace of every rule it used.

What goes in

  • The sale: the selling entity and its establishment, the line items (amount, currency, whether prices include tax, and each product's tax category), and the tax point, the date that decides which rules apply.
  • The customer: business or consumer, any tax IDs and whether they're valid, and the location evidence: billing address, IP address country, card country and other signals that show where the customer is.
  • The rules: rates, regimes and their evidence and supply rules, as valid at the tax point. A regime is a set of rules for one jurisdiction and seller situation, for example the EU One-Stop Shop for sellers established in the EU (EU_OSS_UNION) or Texas sales tax (US_SALES_TAX:TX).
  • Your registration state: for each regime, whether your entity is registered, in a grace period, below a threshold or blocked. See Registrations.

How it decides

  1. Classify the customer. A customer with a valid tax ID, or another verified business identifier where the regime accepts one, is a business (B2B). Everyone else is a consumer (B2C).
  2. Locate the customer from the evidence. For a business, the country of its validated tax ID wins.
  3. Choose the regime from your establishment, the customer's location and the customer type: your home country's domestic rules, the EU One-Stop Shop, reverse charge for cross-border business sales, a US state's sales tax, or a country's scheme for foreign sellers.
  4. Confirm the location with that regime's evidence rules. Consumer sales need enough evidence that agrees.
  5. Find the place of supply for each line from the regime's rules for its tax category.
  6. Check your registration for the regime. This decides whether to charge tax, apply reverse charge, use the below-threshold rule, treat the sale as out of scope, or block it.
  7. Pick the rate valid at the tax point for the jurisdiction and tax category.
  8. Compute the tax with exact integer arithmetic, from net prices or out of gross prices, rounding per line or per invoice as the regime requires.
  9. Return a quote: lines, totals, the decision, the notes the invoice needs (such as reverse-charge wording) and a rule trace with the version of every rule used.

Reverse charge means the business customer accounts for the VAT instead of you. You invoice without VAT, and the invoice says why.

It fails closed

If the engine can't determine the tax with confidence, a new sale doesn't go ahead. The customer sees a clear message, or can join a waitlist for that country. The quote says why:

ReasonMeaning
evidence_conflictThe location evidence disagrees, and the regime requires a review.
unsupported_tax_categoryThere's no supply rule for this product's tax category in that regime.
registration_requiredYour entity must register before selling to consumers there.
rate_unavailableThere's no verified rate for that jurisdiction and category on that date.
jurisdiction_blockedThere are no usable rules for where the customer is.

Ownrecord never guesses a rate or a rule.

  • A category's own rate wins. If a product category has its own rate that can't be used in this mode, for example because it hasn't been reviewed yet, the sale is blocked with rate_unavailable. It isn't charged the country's standard rate. The standard rate applies only to categories with no rate of their own.
  • Territories with their own country code, such as Åland (AX), Monaco (MC) or the Isle of Man (IM), are taxed under another country's rules or lie outside its VAT area. Until the engine supports them, a customer located there, by location evidence or by a business customer's tax ID, is blocked with jurisdiction_blocked. They're never treated as countries without tax.
  • Rules the knowledge base can't express yet are recorded with their effect. A rate is refused when such a rule could make the tax too low, or when it hasn't been classified yet. A rule that could only overcharge in an edge case, such as a territory outside the VAT area, doesn't block the rate. The quote's rule trace lists it.

Quotes API

The engine checkout uses is open to you: tax.quotes.create (POST /api/v1/tax/quotes, MCP tool tax_quotes_create) quotes a sale without making it, for your own checkout or a price preview. It acts on one brand, in the mode of your key, and needs the checkout:write permission.

What you send. The customer (what they declare, their tax IDs, their location evidence), the lines (amounts in integer micros of one currency, whole minor units, with taxBehavior and taxCategoryId), and optionally the tax point (default: now) and the context (default quote; renewal, topup and refund are for customers you already sell to, see Registrations).

JSON
{
  "customer": {
    "type": "business",
    "taxIds": [{ "type": "eu_vat", "value": "ATU12345678", "country": "AT" }],
    "evidence": [{ "type": "billing_address", "country": "AT" }]
  },
  "lines": [
    {
      "amount": { "amount": "100000000", "currency": "EUR" },
      "taxBehavior": "exclusive",
      "taxCategoryId": "saas"
    }
  ]
}

What happens. Each tax ID is checked with its register (VIES for EU VAT IDs, HMRC for UK VAT numbers, the Swiss UID register), with your entity's own VAT ID as requester so the answer carries a consultation number. Answers are reused for 24 hours. Then the engine runs with the rules valid on the tax point's date for your mode, your entity's registrations and threshold counters (all brands combined) and your brand's policies.

What you get. A quote (tq_…) with the decision, every line with its rate, treatment and the regime the tax is due under, the totals, the invoice notes, the registration changes the sale would trigger (intents), the rule trace and the version of every rule used. A quote changes nothing else: no registration moves and no threshold counter grows until a sale is recorded. Checkout charges a quote for 30 minutes (validUntil), and tax.quotes.get returns it later, exactly as it was, with the rates it used, so you can explain any number on an invoice.

Blocked sales are errors. If the engine can't tax the sale with confidence, you get no quote but an HTTP 422 error of type tax_blocked, and nothing is stored. Its code is the reason from the table above (or jurisdiction_blocked when there are no usable rules for this seller there), and details holds what you need to act:

Codedetails (besides regime, location, customer_type, knowledge and rule_trace)
registration_requiredregime_name, registration (state, reason, armed), deadline, prerequisites, registration_portal_url
evidence_conflictcountries_seen, evidence, evidence_rule (items required, accepted types, conflict policy)
unsupported_tax_categorylines: the lines and tax categories without a supported rule
rate_unavailablelines
jurisdiction_blockedknowledge.excluded_records: records the mode can't use

Test mode and dry runs. In test mode a quote may rely on rules that aren't reviewed yet. It then has unverifiedRules: true and the label "Unverified rules"; live mode refuses the same sale. A dry run (Ownrecord-Dry-Run: true, or dry_run: true in MCP) returns the quote without storing it, and simulates the tax-ID checks instead of asking the registers.

Rules are data

Rates, thresholds, grace periods and evidence rules aren't written into code. They're versioned records with effective dates and official sources, maintained by Ownrecord and shared by every organization. When a rate changes, the new record takes effect on its date, and every quote records which version it used.

The knowledge base and record statuses

The rules come from the Jurisdiction Knowledge Base, Ownrecord's own research: one folder per jurisdiction, with a source for every value. Every change is reviewed in Ownrecord's repository and reaches the platform with the next release. Nobody edits the rules in the database, and organizations only read them.

Every record has a status that shows how far it has been checked:

StatusMeaning
researchedBuilt from official sources, not yet independently checked.
cross_checkedRe-derived from independent sources by a second review.
reviewedChecked against the sources by Ownrecord's tax team.
verifiedConfirmed by a tax adviser, whose name and date are recorded.
retiredNo longer in the knowledge base. Kept so older quotes can be explained.

Which records a quote may use depends on the mode:

  • Live mode uses only reviewed and verified records. For the launch jurisdictions (Tier 1: the EU rules and member-state rates, Croatia, the UK, Switzerland, Norway and the US states), it uses only verified records.
  • Test mode uses every record except retired ones. A quote that relies on a record that isn't reviewed yet says so (unverifiedRules), and checkout and the console label it "unverified rules".

So a jurisdiction can work in test mode before it's ready for live sales. If live mode has no usable rate or rule for a sale, the engine doesn't guess: the sale is blocked with a reason, as described above.

Tax rules are a tool, not advice

You're responsible for your tax compliance. Every rule shows its sources and when it was last verified.

Reading the knowledge

The knowledge is read-only and the same for every organization. Any member, API key or agent of your organization can read it:

WhatRESTMCP tool
The loaded version and its record countsGET /api/v1/tax/knowledgeknowledge_state_get
Jurisdictions and their coverageGET /api/v1/tax/knowledge/jurisdictionsknowledge_jurisdictions_list
RatesGET /api/v1/tax/knowledge/ratesknowledge_rates_list
Regimes, one regime with its sourcesGET /api/v1/tax/knowledge/regimesknowledge_regimes_list, knowledge_regimes_get
Playbooks: how to register, file and payGET /api/v1/tax/knowledge/playbooksknowledge_playbooks_list, knowledge_playbooks_get

Records come back as they were compiled from the knowledge base. Rates are in parts per million as a string ("250000" is 25%), amounts in integer micros with their currency. Every record has a meta object: its status, confidence, open questions, the sources it cites, when it's next due for review (nextReviewBy), and the file and git commit it came from.

Coverage levels and tiers

A jurisdiction is covered to a level, and each level adds what Ownrecord can do there:

LevelWhat it adds
L0 knownThe jurisdiction is recognized; sales are out of scope.
L1 taxRates and thresholds: correct quotes and registration states.
L2 registrationFull registration rules and a registration playbook.
L3 filingFiling, invoicing and e-invoicing rules.
L4 connectorA documented tax authority API.

Jurisdictions are researched in tiers. Tier 1 is what Ownrecord launches with: the EU rules and member-state rates, Croatia, the UK, Switzerland, Norway and the US states. Tier 2 adds likely establishment countries and larger markets, Tier 3 the rest. Each jurisdiction's coverage level, target and tier are in the jurisdictions list.

Keeping the rules current

Rules change, so every record carries a review date: 6 months for Tier 1, 12 for Tier 2 and 18 for Tier 3. On top of that, Ownrecord sweeps rates and thresholds before 1 January and 1 July and re-researches Tier 1 every quarter.

Research happens in research waves: sets of research runs, one jurisdiction each, which Ownrecord's tax team runs in Claude Code sessions outside the platform. A wave ends as one reviewed change to the knowledge base. Nothing a wave finds reaches live mode by itself: a record is used for live sales only after a person reviews it, and for Tier 1 only after a tax adviser verifies it. Every reviewed change reaches the platform with the next release, and the version in use is the commit of the loaded knowledge.

Ownrecord's platform console tracks this work: a coverage map, a review queue for open questions, changed sources and due research, and a research schedule that says when a recurring wave or an overdue review is due. These tools are for Ownrecord's staff; they aren't part of your organization's API.

Exact money

All amounts are integers. The API expresses money in micros, millionths of the currency's major unit, so €12.50 is 12500000. Rounding to cents (or the currency's smallest unit) happens only at defined points, such as invoice line totals and tax amounts.