Router.Africa — Pricing & Markup Model
Backlog item #11. How Router.Africa converts model costs into what customers see and get charged, in credits.
Units. The customer-facing balance is credits, equivalent to tokens. 1 credit = 1 unit of the local currency the customer topped up in, but credits are never shown with a currency symbol or code, and are not redeemable for cash (see design doc §5). Published rates are expressed in credits per token. Local currency appears only inside the rate derivation below (FX conversion), never in what the customer sees.
Overview
Section titled “Overview”Customers see a published, stable per-token rate in credits, refreshed daily. This is deliberately not “pass through the real-time cost of your specific request” — it’s a predictable, transparent rate the customer can see on the dashboard and via the API at any time, decoupled from the underlying cost volatility of any individual request.
Inputs
Section titled “Inputs”Two separate rates are computed daily, per model, per token type (input and output tracked independently — a single blended rate can’t drive settle, which charges actual input and output tokens each at their own rate, or the hold, which is driven by the output rate and max_tokens):
- Bifrost’s
usage.costfor that model, taken as the highest value across the providers Bifrost fronts for that model. This is a deliberate margin-protection choice — Router.Africa is not obligated to pass through its actual per-request COGS 1:1; taking the ceiling across providers is a normal reseller pattern. - Mid-market FX rate, from Frankfurter.app (free, open-source, ECB reference rates, no API key required).
Daily Rate Computation
Section titled “Daily Rate Computation”Run once per day, per model, per token type:
- Pull Bifrost’s cost input for the model (highest across providers, per token type).
- Convert to the local currency (whose units map 1:1 to credits) using the day’s mid-market FX rate (Frankfurter.app).
- Add markup.
- Round up to a clean denomination — e.g. a rate that computes to 45 credits rounds up to 50, not down or to the nearest.
- Publish this as the rate customers see for that day, on the dashboard and via the API.
This is the rate charged and deducted — not a per-request recomputation. It’s what the customer is shown before they spend, and it’s what settle uses (and hold, where enabled).
Charge vs cost. The customer is charged tokens as reported by Bifrost × this published rate. Bifrost’s usage.cost is logged separately as Router.Africa’s actual cost (COGS) on that request. It feeds the daily rate input and margin tracking, and never sets the customer charge. Per-request margin is charge − cost.
Rate Locking
Section titled “Rate Locking”The published daily rate can refresh while a request is in flight (hold happens, then settle happens after the daily boundary). Rule: the rate in effect at hold time is locked for that request — settle uses the same locked rate, regardless of whether the daily refresh has since occurred. A customer is never held at one rate and settled at another for the same call. The workspace’s discount is locked at the same moment.
Accounts with hold turned off (a per-account admin toggle) have no hold moment to lock at, so the lock point is when the request starts: the rate in effect then is what settle uses.
Accepted Risk: Intra-Day Exposure
Section titled “Accepted Risk: Intra-Day Exposure”A daily-locked rate means Router.Africa is exposed to provider price changes or FX movement within a day, until the next refresh — held to yesterday’s published rate in the meantime. This is an accepted tradeoff, not an oversight: the exposure is expected to be covered by margin in most cases, and the predictability this gives customers is worth more than intra-day repricing precision. Worth revisiting only if reconciliation data (below) shows this assumption isn’t holding.
Model availability
Section titled “Model availability”A model can only be sold when it has a published rate and staff have turned it on in the admin app. Rates are only computed and published for models that are on, so usage on a model with no pricing (Bifrost reports cost 0.0 when it has none) cannot occur. See the Decisions Log, v1 model list.
Rounding
Section titled “Rounding”Rounding applies to the final published rate, not to individual transaction amounts. Example: a computed rate of $0.3332 USD equivalent to 45 credits rounds up to 50 credits as the published rate. This keeps the customer-facing number clean and avoids exposing raw FX/markup arithmetic.
Markup Configuration
Section titled “Markup Configuration”Markups are defined as scoped rules, not a single hardcoded value, supporting two types:
- Flat — a fixed amount added per unit (e.g. per 1K tokens), in USD or local currency.
- Percentage — multiply the base rate by
(1 + markup%).
Scoping, mirroring the same override pattern Bifrost itself uses for its own custom pricing (global → provider/key → model, most-specific-wins): a markup rule can be set at the global default, narrowed to a specific currency/market or a specific model. Resolution takes the most specific matching rule; broader scopes act as fallback when no narrower rule exists.
What’s decided: the system must support both types (flat, percentage) and this scoping structure, so no markup design is hardcoded into the daily rate computation — it consults the rules engine instead.
Top-up transaction fee (Niobi markup). Separate from the per-token markup above: a fee added to the top-up amount, not to the token rate. Example: a 1,000-credit top-up carries a 40 fee, so the customer pays 1,040 and receives 1,000 credits. The fee is configured per vendor in the admin app and is not credited to the balance. The 40 above is an example; whether a vendor’s fee is flat or a percentage is a value set in admin, not fixed in the design.
Launch values are configuration, not a build dependency (decided 2026-09-20): the rules engine and schema ship first, and the actual markup values and percentages are entered later through the admin app. They must be set before the public price sheet is published.
Workspace Discounts
Section titled “Workspace Discounts”Rates, FX and markup are never per workspace: every workspace in a currency sees the same published rate. A negotiated or volume price is a discount on usage, set per workspace in the admin app (decided 2026-09-20; replaces account-scoped and tiered markup).
- A discount is a percentage off the published rate, workspace-wide or for one model. The model-specific one wins; discounts never stack. A discount is on or off, with a reason recorded, and every change is audit-logged.
- Applied at charge time: charge = Bifrost-reported tokens × published rate × (1 − discount). The hold is priced the same way.
- Locked with the rate: the discount in effect at hold time (at request start when hold is off) is used for settle, same as the rate.
- Discounts come out of margin. The margin report compares the discounted charge with Bifrost’s cost.
Bifrost Cost Tracking
Section titled “Bifrost Cost Tracking”No direct provider integration is built. Router.Africa relies solely on Bifrost for cost and usage data — no separate per-provider billing/usage API integrations (OpenAI, Anthropic, Bedrock, etc.) beyond what Bifrost already surfaces. This is a deliberate scope decision, not a gap: it avoids N separate provider integrations for a benefit (automated per-request reconciliation) that isn’t needed to make the pricing model work.
Bifrost’s usage.cost is logged as-is on every request (model, provider, token counts, cost) — kept consistent with how Bifrost currently computes it, no changes to that logic on Router.Africa’s side. This is the dataset the daily rate computation and any future reconciliation draw from.
Reconciliation, if and when pursued, is manual — against actual provider invoices Router.Africa receives, not an API integration. Providers bill on their own cycle regardless of what Bifrost estimates, so comparing Bifrost’s period-total estimated cost against the real invoice is a periodic finance-side check, not code. The data needed for this (per-request bifrost_cost_usd, model, provider, token counts) must be logged from day one regardless of when this check is first performed, since it can’t be reconstructed retroactively.
Purpose: determine, over time, whether Bifrost’s cost estimate runs consistently higher or lower than actual provider billing — and use that evidence, not a guess, to decide whether Router.Africa should eventually source its own pricing data rather than relying on Bifrost’s synced datasheet.
Open Items
Section titled “Open Items”- Markup values — the actual flat amounts and percentages. Set later in the admin app (see Markup Configuration); needed before the public price sheet, not before the build.
- Hold parameters — per-model default
max_tokens,max_tokensceiling and input allowance, and the minimum hold threshold. Configured in admin; launch values not yet chosen. See the Decisions Log hold/settle entry. - Top-up fee values — configured per vendor in admin (see above); launch values not yet chosen.
- FX update cadence — Frankfurter.app is ECB-anchored (updates on ECB’s schedule, weekdays). Confirmed acceptable given the accepted intra-day exposure above; revisit only if reconciliation shows FX timing is a real driver of margin variance.
Cross-reference the Decisions Log for how this connects to hold/settle and the ledger schema.