Rate cards
The subaccounts guide prices a child either on the flatreseller_margin_pct or on an inline rate deck (per-channel/destination markup cells). Both approaches inline the pricing on the child itself. A rate card is the next step up: a named, re-usable price book you draft once on your parent organization and assign to any number of subaccounts — the same pricing, draftable once and never re-entered, survives deployments and re-provisioning.
Full endpoint catalog: Subaccounts API.
What a rate card is
A rate card is a named pricing template your parent org keeps in a library. Each card carries a stable id (rc_...), a human label, and one or both of the two cell families:
- Lane cells — candidate per-lane rates captured from the what-if pricing preview (
POST /billing/whatif-pricing/preview), kept verbatim. A lane is a channel/country/direction pair, such as “WhatsApp to Nigeria.” - Deck cells — channel[, destination] →
margin_pctmarkup cells, the same shape an inline rate deck uses.
Dashboard walkthrough
You manage rate cards on the parent organization’s Settings → Subaccounts → Rate cards tab (/settings/subaccounts/[id]). The page has two parts: the parent’s reusable card library, and the per-subaccount assignment.
Build the library
- Open Settings → Subaccounts, then pick any child subaccount and open its Pricing tab. The top card is Rate cards — your reusable library.
- Click New rate card. Give it a name (for example, “West Africa Gold”). The dashboard slugifies the name into a card id like
rc_west_africa_gold. - Add markup cells: a channel (
sms,whatsapp,voice, …), an optional destination (NG,GB,US, …), and a margin percent (0–100). Leave destination blank to cover every destination on that channel. - Click Save card. The card is appended to your library and the table shows its id, name, and cell counts.
- Delete a card from the library with the row’s trash icon. Subaccounts already assigned to that card then show a Stale link badge on their Pricing tab and fall back to their flat reseller margin until you re-assign a different card.
Assign a card to a subaccount
Still on the child’s Pricing tab, below the library card:- Find Assigned rate card.
- Choose a saved card from the dropdown, or choose None — inline deck to price the child from its own inline rate deck (or the flat margin when no deck exists).
- The assignment is stored immediately. From the next send onward, the child resolves against the selected card’s cells.
400 BAD_REQUEST.
Rate-card lifecycle: create, version, persist, assign
The library lives on the parent org. You replace it full-set withPUT /api/v1/subaccounts/rate-cards and read it with GET /api/v1/subaccounts/rate-cards. To version a card, PUT the full library again with the updated entry — the validator dedupes by id (last write wins), sorts by name, and returns the normalized library it actually stored, so the response is always the source of truth.
Because the PUT is full-set, the workflow for versioning is: GET the library, edit one card in the array, PUT the whole array back. An empty array clears the library.
Margin in a deck cell is a markup percent over your wholesale cost — the same margin_pct semantics as the inline rate deck (0–100%). Draft it in one place and reuse it for every subaccount on that plan tier.
Assignment
Point a subaccount at a saved card once:400 BAD_REQUEST. To un-assign, send rate_card_id: null and the child falls back to its flat margin or its own inline deck. Read the current assignment back on GET /api/v1/subaccounts/{id}/rate-deck, whose response carries both the inline deck and the assigned_rate_card_id so the dashboard shows which named card the child prices against.
Limits
- Library cap: a parent org can store at most 50 named rate cards.
- Per-card cell cap: each card can hold at most 200 total cells (
lane_cells+deck_cellscombined). That is the same cap as a subaccount’s inline rate deck. - Inline-deck limits: when you assign a card with deck cells, those cells are resolved exactly like an inline deck, so the same rate-deck limits and matching rules that apply to
PUT /api/v1/subaccounts/:id/rate-deckapply here. A card cell is achannel/destination/margin_pcttuple, the same shape as an inline deck entry.
400 BAD_REQUEST when either cap is exceeded or when every entry in a non-empty payload is invalid.
Safe concurrent versioning of the full-set PUT
PUT /api/v1/subaccounts/rate-cards is a read-modify-write surface. The body you send becomes the entire library, so two admins editing at the same time can overwrite each other:
- Admin A GETs the library.
- Admin B GETs the same library.
- Admin A adds a card and PUTs.
- Admin B edits a different card and PUTs — Admin B’s payload does not contain Admin A’s new card, so Admin A’s card disappears.
- serialize edits within your team, or
- always start from a fresh GET immediately before the PUT, and retry if a recent change from another session is missing.
id (last write wins) and sorts by name, so the response order may differ from your request order.
Validation error catalog
PUT /api/v1/subaccounts/rate-cards normalizes every card silently: malformed entries are dropped rather than persisted, and the remaining cards are returned. A 400 BAD_REQUEST is returned only in these cases:
Deduping is by
id last write wins: if the payload contains two cards with the same id, the second one replaces the first. The final library is sorted by name ascending, then by id ascending.
PUT /api/v1/subaccounts/:id/assigned-rate-card returns 400 BAD_REQUEST when the supplied rate_card_id is not in the parent’s current library. Send null to clear the link.
Migration path: flat margin → inline deck → assigned rate card
Most reseller pricing evolves in three stages. You can move a subaccount forward or backward at any time without rewriting history.- Flat margin only — set
reseller_margin_pcton the subaccount. Every usage line prices at wholesale × (1 +reseller_margin_pct/ 100). This is the simplest starting point. - Inline rate deck — add per-channel/destination markup cells with
PUT /api/v1/subaccounts/:id/rate-deck. The deck overrides the flat margin for matching lanes; anything not covered still falls back to the flat margin. - Assigned rate card — build a named card in the parent’s library and assign it to the subaccount. The card’s deck cells are used in place of the inline deck. The inline deck stays stored on the child, but is ignored while an assigned card is active.
rate_card_id: null) and the child immediately falls back to its inline deck or flat margin. To move a deck from one child to many children, convert the inline deck into a rate card, save it to the library, then assign it to each child.
Precedence summary
When a subaccount sends traffic, the markup is resolved in this order:- Assigned rate card — if the child has
assigned_rate_card_idpointing to a card that still exists in the parent’s library, the card’s deck cells supply the markup. - Inline rate deck — if no assigned card is active, the child’s own
reseller_rate_deckcells supply the markup. - Flat reseller margin — if neither card nor deck covers the lane, the child’s
reseller_margin_pctis used.
channel + destination cell beats a channel-only cell, which beats the flat margin. See Pricing and rate resolution for the full precedence model.
Impact and retroactive semantics
A rate card is a pointer, not a copy. Updating or deleting a card in the parent’s library changes pricing for every subaccount currently assigned to it.- Which children are affected: only direct children of the parent org that have the card assigned via
assigned_rate_card_id. Sibling parent orgs cannot see or assign your cards. - When the change takes effect: at the next send. Rate resolution reads the current card at send time, so there is no batch delay.
- Past usage: already-sent traffic is not re-rated. The usage statement for a past period reflects the card (or deck, or flat margin) that was active when the traffic was sent. If you need to model what a new card would have cost on historical traffic, use the what-if pricing simulator.
- Deleting a card: assigned children fall back to their inline deck or flat margin immediately. The assignment row shows a stale-link badge until you clear or replace it.
Example: WhatsApp card with a 5% margin
Create the card in the parent library:sub_acme price at wholesale + 5%, and the parent’s reseller statement reports the marked-up price against the wholesale cost. The subaccount’s own admin never sees your wholesale side — the margin lands in your statement, not their dashboard.
Worked example: a lane cell captured from what-if pricing
Lane cells are how you persist a candidate rate card drafted in the what-if pricing simulator. Imagine you ran a preview for a West Africa voice plan and the simulator returned this lane:deck_cells with channel/destination/margin_pct, or assign a separate deck-only card.
Related pages
- White-label subaccounts — provision, brand, and fund children; the flat margin and inline deck the rate card builds on.
- Pricing and rate resolution — the per-operator precedence the assigned card feeds into.
- What-if pricing simulator — back-test a candidate rate card against your own traffic before you assign it.
- Wallet currency conversion — how converted top-ups interact with the wallet a subaccount draws down.
- Per-channel connection mode: BYO vs platform default — the connection your subaccounts’ sends exit on, independent of how the rate card prices them.
- Subaccounts API — full endpoint schemas for the card library and assignment routes.