Recurring Charges
Recurring charges are everything on the invoice that is not a call: seats, DIDs, licences, support plans, concurrent-call capacity.
Configured under Plans → Recurring, with an optional cost per row so that margin covers seats and numbers as well as calls.

Basis
Every recurring row has a basis, which decides where its quantity comes from.
flat — a fixed fee with a description and quantity 1. Support plans, licence fees, anything not counted from the PBX. The description prints verbatim on the invoice.
entity — seats and PBX objects, counted from daily snapshots by subtype: extensions, hunt groups, auto attendants, agent groups, conference rooms, paging groups, mailboxes.
number — DIDs, matched by longest prefix against the tenant's captured
number list. A +61 3 row prices every Melbourne landline; a +61 4 row
prices mobiles differently.
Entity subtypes and wildcards
Subtypes can be written as wildcards. Note that ext:* matches only
ext:-prefixed subtypes. Hunt groups, agent groups and auto attendants are
not extensions, so they will report as unpriced unless you price them
explicitly.
An unpriced subtype with traffic blocks finalization. That is the guard against an invoice that silently omits half a customer's seats.
Frequency
monthly — every period, covering that period.
quarterly — every third month, counting from its billing month.
annual — only in the period whose month equals its billing month.
Quarterly and annual rows require a billing month. Without one the charge would never fire, so the API refuses to save it.
In advance, and it says so
Quarterly and annual charges bill in advance. An annual support plan on the September invoice covers September through August. The covered range is stored on the charge and printed on the invoice line, so the direction is never left to be inferred.
Usage stays retrospective. Monthly recurring stays concurrent. A single invoice can legitimately carry all three, and each line states its own period.
Frequency is part of the uniqueness key
Recurring charges are keyed on
{system, domain, basis, period, subtype, frequency}. A monthly seat fee and
an annual seat fee for the same subtype in the same month therefore cannot
collide and silently lose one.
Where quantities come from
Entity quantities are the highest count observed in that period's daily snapshots. Not the count on the last day, and not an average.
A customer who ran forty extensions for three weeks and then removed ten is billed for forty. That is the correct answer: they had forty.
Partial periods — a tenant onboarded mid-month — are flagged on the invoice line rather than silently prorated, so you can decide what to do about it.
Annual and quarterly quantities
The covered months are in the future and have no snapshots yet, so per-seat annual and quarterly charges use the highest count over the trailing window: the twelve or three months ending at the billing period. The window used is recorded on the charge.
Vodia licensing
Vodia PBX licences are charged until deleted, not until disabled. A disabled extension still holds its licence, so it still appears in the snapshot count and is still billed. This matches how Vodia licenses the PBX itself.
Concurrent-call capacity
Snapshots record the peak number of concurrent calls per domain. Six
snapshot passes run per day, four hours apart from SNAPSHOT_HOUR_UTC, so a
mid-afternoon concurrency peak is actually observed. A single overnight
sample would systematically understate it, and for capacity-based billing
that understates the bill.
Cost, and honest blanks
Every recurring row takes an optional cost. Leaving cost blank leaves margin blank — never a fabricated zero. This matches how call rating treats an unresolved cost plan: a missing number is reported as missing, not as free.
Not due is not unpriced
A rate that is simply not due this period — an annual charge in a non-billing month — is reported separately as not due. It is not a problem and does not block anything.
Only a subtype with no rate at any frequency blocks finalization.
Worked examples
Highest count observed
Extension counts through August for hq.northwind-example.com:
Aug 1-11: 38 extensions
Aug 12-25: 42 extensions <- peak
Aug 26-31: 40 extensions
Billed quantity: 42.
Not 40 (the count on the last day), and not 39.7 (the average). The customer had 42 extensions during the period, so they are billed for 42. A system that sampled month-end would have under-billed by two seats.
A complete recurring configuration
| Basis | Subtype / prefix | Frequency | Billing month | Rate | Cost |
|---|---|---|---|---|---|
| entity | ext:user | monthly | 12.00 | 4.50 | |
| entity | hunt | monthly | 5.00 | ||
| entity | attendant | monthly | 5.00 | ||
| number | 1206 | monthly | 1.50 | 0.60 | |
| flat | "Managed support plan" | monthly | 150.00 | ||
| flat | "Annual licence" | annual | September | 480.00 | 320.00 |
The hunt and attendant rows exist separately because ext:* does not
match them. Leaving them out produces an unpriced blocker at
finalization, which is the guard working — but it is easier to price them
now.
The hunt row has no cost, so its margin is blank rather than shown as 100%.
What lands on the invoice
For 42 extensions, 3 hunt groups, 0 attendants and 18 matching DIDs:
| Item | Quantity | Rate | Amount |
|---|---|---|---|
| Extensions | 42 | 12.00 | 504.00 |
| Hunt groups | 3 | 5.00 | 15.00 |
| DID numbers | 18 | 1.50 | 27.00 |
| Managed support plan | 1 | 150.00 | 150.00 |
| Recurring subtotal | 696.00 |
The attendant row is priced but has zero quantity, so it does not appear. A priced row with no instances is not a problem.
An annual charge, billed in advance
The "Annual licence" row above has a billing month of September. On the September invoice:
Annual licence
Covering 2026-09-01 to 2027-08-31 480.00
It appears on the September invoice only. On the October invoice it is reported as not due, which is not a blocker and not an error.
The covered range is printed on the line, so nobody has to infer whether an annual charge is retrospective or in advance.
Per-seat annual quantities
An annual per-seat charge cannot use the covered months, because they are in the future and have no snapshots. It uses the highest count over the trailing twelve months instead:
Sep 2025 - Aug 2026 peak: 44 extensions (reached in March)
Annual per-seat charge quantity: 44
Window recorded on the charge: 2025-09-01 to 2026-08-31
A quarterly charge uses the trailing three months on the same principle.
Partial periods
A tenant onboarded on 14 August:
Extensions 42 12.00 504.00 [partial period: 14 Aug - 31 Aug]
The line is flagged, not prorated. Vodia Billing does not guess whether your commercial terms prorate a part-month, so it bills the full amount and tells you the period was partial. Adjust with a credit if your terms require it.