Skip to main content

What-if pricing — the rate preview over real usage

Pricing decisions fail on one of two axes: either the proposed rate set is wrong, or it is right on paper but wrong for your specific traffic mix. Devotel Orbit’s what-if pricing simulator exists to close the second failure: it replays the usage events your organization already recorded through a candidate rate card and tells you what those exact messages and minutes would have cost. The traffic is held fixed; only the rates move. This page builds the model — what a usage lane is, what a candidate card is, how the two meet — so the step-by-step guide has an anchor and the neighboring preview surfaces stay in their own lanes.

What the preview answers — and what it never does

The simulator answers one question: projected spend, and the signed delta, if a candidate rate set were applied to your historical usage. Nothing else moves:
  • It is not a traffic-mover. A preview emits no send, touches no provider, and routes nothing. The usage it reads is already recorded; the replay is a report over your own history.
  • It is not a rater. No charge is computed against your wallet, no invoice line appears, no pricing override changes. Applying a rate for real is a different surface — Pricing and rate resolution — that owns the precedence rules a live send is actually charged under.
  • It is your data, not a catalog. The baseline comes from your own recorded usage under your own live rates, resolved with the same pricing ladder your invoice uses. A tenant with no recorded traffic gets an all-zero simulation, not an error.
That posture — compute a projection, mutate nothing — is shared with the volume-tier and margin/payout previews, and it is what makes the simulator safe to run as often as a negotiation demands.

The usage-lane taxonomy

Everything starts from the usage records your organization accumulates as it sends — each send logs its event type, destination, direction, and quantity. A single preview call aggregates those records into lanes — the simulator’s unit of comparison. A lane is:
  • event_type maps to a billing channel: messaging usage is recorded as <channel>_sent (sms_sent, whatsapp_sent), voice usage as voice_minute. Meters a per-unit rate card cannot re-price — recording, embeddings, AI-agent conversations — are skipped entirely.
  • country_code is the normalized upper-case ISO alpha-2 destination, with a missing value grouping under the * wildcard.
  • direction collapses inbound spellings (mo, inbound, in) to mo; everything else is mt (outbound).
  • sub_type carries finer partitions such as WhatsApp’s marketing / utility; empty values group under null.
Quantities are summed per lane and ranked, so the largest lanes arrive first.

The lane cap and the overflow rule

One simulation replays at most 200 distinct lanes (WHATIF_MAX_LANES), cutting by descending quantity. If your traffic mix produces more lanes than that, the tail is not silently lost — it is excluded by rank, which is exactly why the guide tells you to read the lane list before the totals: lanes outside the cut never reach the response, so their cost is in neither the current nor the candidate total. A preview over a wide country mix is a projection of your top 200 lanes, and the totals are honest about that scope.

Candidate rates — the partial override

The request body carries a candidate_card: an array of rate rows with per-lane scope. Each row pairs a lane key (channel, country_code, direction, sub_type) with a price:
  • rate_per_unit — the wholesale base, USD per unit (per message, per voice minute). Values are USD — not cents — at this layer; the simulator converts to cents with sub-cent precision internally.
  • markup_bps — optional proportional markup in basis points (500 = 5%), layered on the base.
  • markup_fixed_cents — optional flat per-unit markup in cents.
  • absolute_price_cents — optional fixed per-unit price in cents; when set, it replaces the base-plus-markup math entirely.
The card is a partial override: a lane no row matches keeps its current cost untouched and reports candidate_matched: false with zero delta. You model only the lanes you intend to change, and the simulation total moves only for those. When several rows could price one lane, the same most-specific-wins ladder your live rate resolution uses decides: an org-independent exact country beats the * wildcard; an exact sub-type beats a sub-type-agnostic row; direction must match (mt rows never re-price mo lanes). Ties resolve to the first row in array order. Because the simulator reuses the live resolver’s naming and math, a candidate card you like can be expressed later as real overrides without the semantic drift two different calculators would hand you.

Reading the response — lanes, then totals

Every preview response has the same shape: window_days, your echoed label, a lanes array, and a rolled-up totals object. Per lane you get the quantity, both per-unit prices and both totals, the candidate_matched coverage flag, and the signed delta_cents / delta_percent (null when the current cost is zero but the candidate is not). All money is USD cents; unit prices carry sub-cent precision, totals are whole cents rounded up once per lane — the never-under-bill convention your invoice also uses. The totals roll the lanes up: current_total_cents, candidate_total_cents, the signed delta_cents and delta_percent, plus two coverage counters that matter more than the delta does: lane_count and matched_lane_count. A card that matches 1 of 40 lanes gives you a projection about one lane — delta_percent on the totals is only meaningful once its coverage is confirmed against the lane count. The dashboard surface — Billing → What-if pricing simulator — renders this same comparison interactively (a per-channel editable rate plus a global ±% slider, rolled up to one row per channel). Both the dashboard and POST /api/v1/billing/whatif-pricing/preview are gated to the owner, admin, or billing role, because the reply exposes your real per-channel spend.

The boundary — previews never mutate billing

A what-if preview is a read with arithmetic attached. It writes no row, emits no event, moves no money, and leaves your live rates, overrides, and invoice untouched no matter what you model. A candidate rate takes effect only when it is installed as a real rate override — at which point Pricing and rate resolution owns the precedence and the audit trail. The simulator’s job ends at the projection; resolution’s job starts there.

Which preview do I open?

Orbit ships several read-only “what would this cost” surfaces, and they answer different questions. Anchor them: Decision box:
  • Negotiating a rate card, plan, or volume tier against your own real traffic → what-if pricing.
  • Projecting margin or partner payout economics → the billing pricing previews.
  • Asking which route a specific send would take → the LCR route quote.

Worked sample — a candidate US SMS rate against 30 days

Back-test a negotiated SMS rate for outbound US traffic against your last 30 days:
The replay reports each affected lane plus the roll-up; the delta fields carry the decision:
Read it top-down: matched_lane_count: 1 of lane_count: 1 says the card covered the whole scope of this window’s US MT SMS lane, and delta_cents: -513 is what the candidate rate set would have saved on that traffic — 3,420 messages re-priced from 27.36to27.36 to 22.23. A negative delta green-lights the rate; a positive one prices the trade-off before you sign.

See also