Maintain the tax rate matrix

Add and end-date rates without ever rewriting what last quarter was taxed at.

Owned by Admin · 8 steps · about 18 minutes

Why this exists

The rate matrix is a small table with a strict discipline: for each jurisdiction and category there is at most one rate in force at any instant, and a rate is immutable once created. You may end-date it and you may deactivate it. You may not change its percentage, its jurisdiction or what it applies to.

That rule exists because of what happens downstream. Every taxable sale snapshots the rate onto its accrual row at the moment of the sale. Last quarter's numbers are therefore made of last quarter's rates, permanently, and a rate change today cannot retroactively alter a filing you have already posted to an agency. If rates were editable, every historical report would be a guess about what the rate had been when the report was run.

So a rate change is always two operations, not one: end-date the outgoing rate, create the incoming one from that instant. The no-overlap rule per jurisdiction and category is enforced, so you cannot accidentally have two rates racing each other.

Two distinctions to keep straight. Inclusive versus exclusive: an exclusive rate adds on top of the price, an inclusive one is carved out of it. The municipal admissions rate here is inclusive, which is why a ticket price is the price and the tax comes out of it rather than being added at the till. And point-of-sale versus filing: sales categories accrue at the till and need a liability account, while income estimate rates are filing-time only and never touch a sale.

Before you start

  • An admin session.
  • The exact date and time the new rate takes effect, in UTC. Rates are time-bounded, not dated by day.
  • The agency the rate is collected for — the code must already exist.

Practise with

PersonaEmailPasswordNote
adminadmin@club.test admin123the only persona that can read or write the rate matrix

Steps 1–8 — Admin

their manual →
  1. 1
    Open the rate matrix. Read the current rates and the history separately — the history is where end-dated rates live, and it is not clutter.
    Expected result Rates by jurisdiction and category with their effective windows, agencies and whether they are inclusive.
    Watch out for Reference rates ship in the schema itself and exist in every fresh database. They are real rates, not placeholders, and they have stable ids.
  2. 2
    Pull the same matrix as JSON, optionally filtered to a category, a jurisdiction, or the rates active on a particular date.
    Expected result The filtered rate rows.
    Watch out for Asking what was active on a past date is the honest way to answer 'what did we charge in March'. Do not infer it from today's matrix.
  3. 3
    End-date the outgoing rate: set its effective-to to the instant the new rate begins.
    /api/tax/rates/{rate_id} PATCH tax
    Expected result The rate is updated and an audit row records the end-dating.
    Watch out for Only the end date and the active flag may be patched. Send anything else — the percentage, the jurisdiction, the account — and you get 422 immutable_field naming exactly what you tried to change.
  4. 4
    Create the replacement rate: jurisdiction, category, agency, rate in basis points, inclusive or exclusive, when it applies and, for point-of-sale rates, which liability account it collects into.
    /api/tax/rates POST tax
    Expected result 201 with the new rate.
    Watch out for The no-overlap rule will refuse a rate whose window collides with an existing one for the same jurisdiction and category — that refusal usually means you forgot to end-date the old one. Point-of-sale rates must name a liability account; income estimate rates must be filing-time and never do.
  5. 5
    Quote the new rate before you trust it: give it a category and an amount and read the lines it produces.
    Expected result Gross, net and one line per matched jurisdiction with its own rate and tax.
    Watch out for Exclusive lines add to the gross; inclusive lines carve out of the net. CORRECTED 2026-08-30: a gross equal to the amount you passed does NOT mean every matched rate was inclusive — it also happens when NOTHING matched, which is exactly how the mistyped effective date in the next step shows up. Measured on the seeded matrix: a ticket today quotes gross 10000, net 9524, one line; the same ticket quoted before any rate exists quotes gross 10000, net 10000, zero lines. Read the LINES, not the gross — and note that an inclusive rate always moves the net, so a net equal to the amount is the same warning said twice.
  6. 6
    When a quote surprises you, ask which rows matched and why, for a category at an instant.
    Expected result The resolution trace: the candidate rates and the ones selected.
    Watch out for Zero matched rates is legal — the sale simply proceeds untaxed. That is the correct behaviour and it is also exactly how a mistyped effective date shows up.
  7. 7
    Understand the consequence you cannot see from this page: the rate in force at the moment of a sale is copied onto that sale's accrual row. Correcting a rate today does not correct yesterday's accruals.
    Expected result A clear model of why the matrix is append-and-end-date rather than editable.
    Watch out for If a wrong rate was genuinely charged, that is a refund-and-rebill question or a reconciliation adjustment, not a rate edit. Editing the rate would silently rewrite history and fix nobody's money.
  8. 8
    Check the agencies and their mailing addresses, since a rate collects for one of them and a filing posts an envelope to it.
    Expected result The four agency records with their addresses.
    Watch out for An unknown agency code on a new rate is a 422. Add or correct the agency first — a rate collecting for an agency nobody can post to is a filing that cannot be mailed.