Rate Plans and Sheets
A rate plan is a set of destinations with prices. Tenants get a sell plan (what the customer pays) and optionally a cost plan (what you pay).

Rate sheet format
CSV, parsed by header name. Column order is free and unrecognised columns are ignored, so you can upload a carrier sheet with extra columns untouched.
| Column | Required | Default | Notes |
|---|---|---|---|
destination | yes | The label printed on the invoice, e.g. "Australia Mobile" | |
prefix | yes | Digits, or * for a tagged catch-all | |
charge_type | yes | per_minute, per_call or free | |
rate | yes | Currency units; per minute, or flat for per_call | |
connect_fee | no | 0 | Charged once per answered call |
increment_initial | no | 60 | Seconds in the first billing increment |
increment_step | no | 60 | Seconds per increment thereafter |
min_duration | no | 0 | Calls shorter than this are not charged |
included | no | false | On a sell-side free row, marks a plan inclusion |
high_risk | no | false | Arms the fraud detectors for this destination |
Matching
Calls match on longest prefix. A sheet with 61, 614 and 6141 prices
an Australian mobile against 6141 if present, 614 otherwise, 61 as a
last resort.
* is a catch-all. Use it deliberately: a * row priced above zero means no
call is ever unrated, which sounds safe but hides a missing destination.
Increments
increment_initial and increment_step are how carriers actually bill.
30/6 means a 31-second call bills as 36 seconds. 60/60 means every call
rounds up to the next whole minute. 1/1 is per-second.
Increments are the single most common source of disagreement when reconciling against another billing system. Check them first.
The sanity ceiling
Rate sheet import refuses any row above RATE_SANITY_CEILING currency units
per minute, default 10. It exists to catch a sheet uploaded in cents as if it
were dollars, which is a hundredfold overbill that otherwise looks like
perfectly valid data.
Raise it in .env if you legitimately sell destinations above that price.
Uploading
Upload runs as a dry run first. The result reports:
- Rows parsed, rows rejected, and why each was rejected
- Destinations added, changed and removed
- The largest price movements, so a decimal-point error is visible
Only after reviewing the dry run do you commit.
An upload supersedes the whole sheet. Rating history is never rewritten, so an invoice issued last month can always be reproduced exactly as it was issued, even after the sheet under it has changed.
Inclusion groups
An inclusion group is an allowance: a set of destinations, a number of included minutes, and an overflow rate for minutes beyond the allowance.
Configured on the rate plan:
- Label — printed on the invoice, e.g. "1300 numbers"
- Destinations — which rate rows the allowance covers
- Allowance minutes — included minutes per period
- Overflow rate — per-minute price beyond the allowance
- Alert threshold — raise an alert when the tenant crosses this much of the allowance
- Scope — per customer, or per extension
Overflow uses the group's overflow rate only. The destination's own connect fee is not applied to overflow minutes.
The invoice prints an inclusion table showing the allowance, what was used and what overflowed.
Recurring rates
Plans → Recurring prices the non-call items. Each row has a basis and a frequency. See Recurring Charges.
Catalogue
Catalogue is a hardware and one-off price list: handsets, licences, installation fees, anything you sell as a one-time charge. Items are picked from the catalogue when raising a one-time charge on a tenant, so the price and description are consistent across invoices.
One-time charges must be approved before they appear on an invoice. An unapproved charge blocks finalization rather than being quietly dropped.
Rate Test
Rate Test answers "what would this number cost this tenant". Enter a number, a tenant and a duration, and it reports the matched row on both the sell and cost sides, the increments applied, connect fee, and final price.
Use it before the first invoice, and use it every time you upload a sheet.
Worked examples
A complete minimal rate sheet
destination,prefix,charge_type,rate,connect_fee,increment_initial,increment_step,included,high_risk
US National,1,per_minute,0.012,0,60,60,,
US Toll Free,1800,free,0,0,0,0,true,
US Premium,1900,per_minute,2.50,0.75,60,60,,true
United Kingdom,44,per_minute,0.035,0,30,6,,
UK Mobile,4477,per_minute,0.14,0,30,6,,
Catch-all,*,per_minute,0.50,0,60,60,,true
What the dry run tells you
Dry run: Business Standard USD
Rows parsed: 6
Rows rejected: 0
Destinations: +6 added, 0 changed, 0 removed
Largest rate: 2.50/min (US Premium) — under ceiling of 10.00
A rejected row is reported with its line number and the reason. Common ones:
a charge_type that is not one of the three permitted values, a non-numeric
rate, a missing prefix, or a rate above RATE_SANITY_CEILING.
Longest prefix in practice
Given the sheet above, a call to +44 7700 900123:
Candidate rows: 44 (United Kingdom), 4477 (UK Mobile)
Matched: 4477 — longer prefix wins
Rate: 0.14/min
Remove the 4477 row and the same call bills at 0.035. That is a factor of
four, and it is why a missing mobile row is worth finding on a dry run rather
than on an invoice.
Increments, worked
A 1 minute 43 second call is 103 seconds of talk time.
| Increments | Billed seconds | Working |
|---|---|---|
60/60 | 120 | rounds up to the next whole minute |
30/6 | 108 | 30 + ceil(73/6) x 6 = 30 + 78 |
1/1 | 103 | per-second |
At 0.035/min those are 0.07, 0.06 and 0.06. At scale the difference
between 60/60 and 1/1 on short calls is substantial: a thousand
30-second calls bill as 1,000 minutes on 60/60 and 500 on 1/1.
Connect fee, worked
A 45-second premium call at 2.50/min with a 0.75 connect fee, on 60/60:
45s rounds to 60s = 1.00 min
1.00 x 2.50 = 2.50
connect fee 0.75
-----
3.25
The connect fee is charged once, only on an answered call, and in addition to the per-minute amount.
An inclusion group, worked
| Label | "US Toll Free" |
| Destinations | US Toll Free (1800) |
| Allowance | 500 minutes per period |
| Overflow rate | 0.008 per minute |
| Alert at | 400 minutes |
With 640 minutes used:
Allowance 500.00 min included 0.00
Overflow 140.00 min x 0.008 1.12
Overflow uses the group's overflow rate only. The destination's own connect fee is not applied to overflow minutes.
About the catch-all row
A * row priced at 0.50 means no call is ever unrated. That sounds safe,
and it is a trap: an unrated call blocks finalization and makes you look
at it, whereas a catch-all silently prices a missing destination at a number
you invented.
Price the catch-all deliberately high and mark it high_risk, so a call
landing on it is both expensive enough to notice and loud enough to alert. Or
omit it and let unrated calls block the invoice.
→ Full arithmetic for a complete invoice: Worked Example