Skip to main content

Per-operator SMS rates

A single per-country SMS rate is only an average of the operators inside that country, and carriers do not cost the same. When your traffic concentrates on one network, the country row misprices every message to it — in both directions. This guide covers the per-operator rate decks that fix that: how a send resolves its operator, how the resolver walks the rate chain, how you maintain the SMS deck yourself by CSV, and how to prove which tier priced any given send. The pricing model behind this page is Pricing and rate resolution. Read that page first if you are new to pricing_source and the override precedence; this guide is the operator-level, day-to-day companion to it.

1. The two resolution chains: voice vs SMS

Every priced send resolves an operator for the destination first, then reads the rate that operator carries. Voice and SMS build that operator identity differently:
  • SMS — country + MCC/MNC → operator → rate. The Mobile Country Code (MCC, 3 digits) and Mobile Network Code (MNC, 2–3 digits) together identify one operator, e.g. 432 + 11 = 432011, Hamrah-e-Avval (MCI) in Iran. On the send path the MCC/MNC comes from the HLR lookup of the destination number. On the preflight that prices and debits before the HLR responds, the operator is resolved from the GSMA dial-code deck instead: the longest prefix of the destination number that the deck knows (a number starting 98912 resolves to MCI) supplies the MCC/MNC early. A dial-code miss never fails a send — the price simply falls back to the country row.
  • Voice — country + dial code → operator → rate. Voice destinations resolve by longest-prefix match of the E.164 number against the per-prefix rate deck: the most specific stored prefix wins (+98912 beats +98, +98 beats the country row). The per-prefix deck is the voice analogue of the MCC/MNC deck.
Both chains end the same way: once the operator (or prefix) is known, the resolver reads the most specific rate row that exists for it, and walks down the chain in section 2 when a row is missing. Which rule finally produced the number you were charged is stamped on the message record and shown on the rate card, so the chain is auditable after the fact — see section 5.

2. How the resolver walks the chain

Every send, voice or SMS, resolves its price through one shared per-send resolver, so the card you read and the wallet debit always agree. For an SMS send carrying a resolved MCC/MNC, the resolver consults these tiers in order and stops at the first match:
  1. Your per-network override — a fixed rate you published for one operator from Billing → MCCMNC Overrides. Applied verbatim: no markup on top. This is the strongest tier and beats every markup-based rule.
  2. The published per-operator rate — the operator’s wholesale cost in the operator deck, times markup (your organization’s markup when one is set, otherwise the platform default). This is the tier that makes “price by operator, not by country” real for every operator the deck covers.
  3. Your organization’s country row — a rate row scoped to your organization for that country and channel.
  4. The platform country row — the default per-country rate for the channel.
  5. The wildcard row — the channel’s catch-all when no country row exists.
  6. The per-channel hard default — the last-resort floor used when no row at all exists (for example $0.02 per SMS segment). A send priced here is a signal that a rate row is missing, not a negotiated price.
Each missing row falls through to the next tier, so the consequence of a gap is always “billed at the next, coarser grain”: a missing operator row bills the country row, a missing country row bills the wildcard, and a missing wildcard bills the hard default. The country row, when it is the only row, must be built from the country’s most expensive operator or some network bills below cost — which is exactly why per-operator rows matter for the networks that carry your traffic (see section 7). The same precedence labels the rate card: GET /api/v1/pricing/mccmnc-rates returns each operator row with a pricing_source of absolute_override (tier 1), org_markup / default_markup (tiers 2–4), or null when no published rate exists at all. A null row reads Contact sales on the card — it is not a zero price, and nothing is billable from it. Voice sends run the identical walk with the prefix deck in place of the MCC/MNC deck.

3. Maintain the per-operator deck: bulk CSV import

The tenant-owned surface for operator-level rates is Billing → MCCMNC Overrides at /billing/mccmnc-overrides, role-gated to owner and admin. Single rows are created and edited through the two-step form (edit fields, then review with a mandatory reason); bulk work goes through Import CSV in the page header. The import dialog parses the file in your browser, shows a confirmation step with the affected scope (row count, distinct organizations, and a per-row preview), and only applies rows after you click Apply. The file needs a header row; columns are case-insensitive:
  • organization_id (or org_id) — the organization the rate applies to. Owners and admins may only target their own organization.
  • mcc — exactly 3 digits; mnc — 2 or 3 digits. The pair is normalized to the canonical zero-padded network code (432 + 11 → 432011), the same key the resolver looks up, so 11 and 011 land on the same row.
  • rate_per_unit (or rate) — the customer-facing price per unit as a decimal, in currency (ISO-4217, defaults to USD). The rate must clear the network’s current wholesale cost; a rate below cost is rejected so a deck typo can never sell traffic below what the carrier charges.
  • effective_from / effective_to — optional effective window; a future-dated row is not applied until it starts, an expired row stops applying.
  • notes — free text carried on the row for your own bookkeeping.
Per-row semantics are upsert-by-key with an explicit conflict: each row is applied as a create keyed on (organization, network); a row whose pair already carries an override is rejected with a per-row error naming the existing row, so an import never silently overwrites a negotiated rate — you edit the existing row (or export, tweak, re-import the rest) on purpose. Failed rows are reported line-by-line with the exact reason while valid rows in the same file still apply. Export CSV downloads the currently filtered rows in the same shape, which makes the export → tweak → re-import round-trip clean. The form and the deck resolve the operator name and country from the network code for you (the create dialog’s autocomplete lists networks per country), so you only ever maintain codes and rates, never names.

4. Maintain the per-prefix deck (voice)

The voice side of the chain — the per-prefix deck that longest-prefix-match reads — is maintained in the platform’s voice-destinations console, not in your dashboard: per-prefix voice rates are a platform-owned deck, and there is no tenant upload for it. What you own on the voice side is exactly what you own on SMS: your organization’s overrides and markups, and the diagnosis below when a destination prices wrong. The walk itself (most-specific prefix first, then country, then wildcard, then the per-channel floor) is the same ladder described in section 2 and modeled in Pricing and rate resolution; the rate card for what your voice destinations currently resolve to is on Rate cards.

5. Diagnosing a mis-priced send

When a bill line looks wrong, work backwards from the record to the missing row:
  1. Read the CDR / message record. The ledger row and the message record carry the attribution: which rate row or override produced the charge, the wholesale cost, the markup applied, and whether a per-operator rate actually matched for the destination. A send whose record says no operator rate matched was priced on the country ladder or a coarser tier.
  2. Read the rate card for that network. GET /api/v1/pricing/mccmnc-rates (the same rows Billing shows) labels each operator with its pricing_source. Compare the label on the row for your destination’s MCC/MNC with the tier you expected.
  3. Locate the gap. pricing_source: null means the deck has no row for that operator — the send priced on the country row or wildcard. A country-labeled charge where you expected an operator rate means your override or the operator row is missing or outside its effective window. A hard-default charge (the per-channel floor) means even the country row is missing.
  4. Close the gap on the tier you own. Publish the missing operator rate from Billing → MCCMNC Overrides (form or CSV). The write invalidates the pricing cache immediately, so the next priced send resolves on the new row — there is no cache TTL to wait out. For a gap on a platform-owned tier (a missing country or prefix row), the diagnosis above is exactly the evidence to bring to support.
For call-level detail on voice, the CDR export walkthrough in CDR usage export and billing reconciliation shows where the per-call rated amount and its breakdown land.

6. Rate resets after an override, and the audit trail

An override holds until you change or delete it. Deleting an override reverts that network to the resolved price from that point on — operator cost times markup, or the country ladder when no operator row exists — and the confirmation dialog states that revert before you confirm. Every create, edit, import, and delete is written to the pricing change log with the actor, the timestamp, the before and after rate, and the reason you entered at review; the tenant-facing change history (GET /api/v1/pricing/changelog) exposes that trail, so any rate your finance team questions can be traced to the change that set it. Because each write invalidates the resolver cache, “reset” means the next send, not the next cache expiry.

7. Worked example: Iran MCI 0.2099 vs the 0.2757 country row

Iran’s country row must cover its most expensive network, RighTel, at a cost of 0.2757 per message. The two large networks, MCI (Hamrah-e-Avval, 432011) and Irancell, cost 0.2099. With only the country row published, every MCI message is priced off 0.2757 — a 31% overcharge on the network that carries most of the traffic, with the margin silently subsidizing the marginal one. With the operator deck in place, the send path resolves +98 912 … to MCI before pricing (via the dial-code deck on preflight, via HLR on the send path) and prices the operator row instead: The same arithmetic runs in reverse for a tenant whose contract quotes a fixed per-message price on one network: publish that number as a verbatim override from Billing → MCCMNC Overrides and the resolver applies it exactly, markup excluded (tier 1 in section 2). After either change, verify on the rate card that the row for 432011 now shows the expected pricing_source and your_rate — the card labels the rule the wallet bills under, so a correct card row is a correct charge.