API Platform / Pay-Per-Call
The pure usage-based pattern: there is no recurring fee, customers pay only for what they consume, and the unit of consumption is something countable (API calls, tokens, messages, minutes, GB). The hard parts are not the pricing curve - they are ingest at scale, deduplication, real-time visibility, and reconciling what the customer thinks they used against what you billed them.
Real-world examples. OpenAI (per-token, varies by model), Twilio (per-message, per-minute, per-recipient-country), Stripe API (per-call quotas, per-charge fees), Anthropic API (per-token), Mapbox (per-tile-load), SendGrid (per-email). Common shape: a published rate card with graduated discounts above thresholds, real-time usage dashboards in the customer portal, monthly invoice for the prior month's aggregated consumption.
The shape of the problem
The deceptive simplicity of "$X per call" hides:
- Event-rate scale. A real API platform receives millions of events per minute. The metering pipeline can't be the bottleneck - and it can't drop events under load, because dropped events become uncollected revenue.
- Idempotency at the edge. Network retries, client-side bugs, and outages all produce duplicate event submissions. Without deduplication, customers get billed for events you can prove they didn't actually originate.
- Real-time vs period totals. Customers expect to see a usage number in their dashboard within seconds of making a call - while the invoice number for the period only finalizes at month end. The same underlying data has to feed both views.
- Tier crossings during a period. Graduated discounts mean the per-unit rate changes mid-period as the customer crosses thresholds. The invoice has to walk the tiers and bill the cheaper tail correctly.
- Multi-dimensional metering. "Per call" is rarely just per call - it's per-call × per-region or per-token × per-model. The metering must split the same event stream multiple ways.
Kontier blueprint
| Concern | Kontier primitive |
|---|---|
| High-volume event ingest | POST /v1/usage-events (single event or a batch of up to 1,000) with a required idempotency_key |
| Tier-walking pricing | STAIRCASE pricing model on the product |
| Real-time customer visibility | GET /v1/usage-events/aggregate (filter by customer_id, subscription_id, product_id, metric_key) |
| Per-region or per-model breakdown | Meter filter_tree / dimensions on event metadata |
| Per-model rates | Keyed product: one price per price_key, events carry the key |
| Usage nothing bills | GET /v1/subscriptions/usage-without-line lists usage the deal has no line for |
Build it
IDs in the examples are placeholders: use the id each create call returns.
1. Define the metered product
Code
2. Define the meter and bind it to the product
Metering is a two-call flow: first create a meter that defines how raw events are aggregated, then bind it to the product as the billable quantity.
Code
Code
function is the aggregation (SUM, COUNT, MAX, MIN, AVG, UNIQUE_COUNT, LAST, P95). value_field names the event field aggregated (required for SUM/MAX/MIN/AVG/P95/LAST, forbidden for COUNT/UNIQUE_COUNT). Deduplication uses each event's idempotency_key by default; set dedup_key_path only if another event field should be the dedup key. Omit window to aggregate over the whole billing period. See Meters.
3. Publish the graduated rate card
Code
Tier up_to and unit_amount are decimal strings; the last tier omits up_to and runs to infinity. With STAIRCASE, each tier rate applies only to the units inside its band. A customer making 250,000 calls pays: 10k × $0.10 + 90k × $0.05 + 150k × $0.03 = $10,000.
The price, the meter binding and the product itself sit in the product's working draft until you publish it:
Code
4. Give the customer a subscription with a metered line
Usage is billed only through a metered line on the customer's subscription. Create the subscription, author revision 1 with one open-ended phase carrying the product, and start it:
Code
origin.kind: "product" sells the product directly, without a plan; version is the product version you published (1 for the first publish).
5. Ingest events at runtime
Code
Critical fields:
idempotency_keyis required in the body and should be your internal request ID. Submitting the same key twice records the event once. (TheIdempotency-Keyheader protects the HTTP request; the body field dedupes the event.)quantityis a non-negative decimal string.timestampshould be when the API call actually occurred, not when the event was forwarded; omit it and the event is stamped on arrival.metadatakeys can drive meterfilter_tree/dimensionsand analytics breakdowns later.
At volume, send a batch instead: the same endpoint accepts { "events": [ … ] } with 1 to 1,000 events per call.
6. Show the customer their real-time usage
Read totals from GET /v1/usage-events/aggregate. The filters (customer_id, product_id, subscription_id, metric_key) are query params. since (RFC3339) is required; bucket is hour, day (the default) or week; until defaults to now; metric defaults to count (pass sum_quantity to sum the event quantity field).
Code
Response (one row per bucket):
Code
Decimal sum_quantity totals come back as strings (in sum_totals) so they round-trip without float loss; with the default count metric the buckets carry totals instead. For the raw event list (cursor-paginated) call GET /v1/usage-events with the same filters.
Variations
- Multi-dimensional pricing (per-call × per-model). Make the product keyed: link it to a key set with one key per model (
gpt-4,gpt-4-turbo,gpt-3.5) and add one price perprice_key. Events carryprice_key: "gpt-4". The subscription needs one line per key it sells (a keyed product's line must name aprice_key); usage under a key the deal has no line for is accepted but billed by nothing, and shows up inGET /v1/subscriptions/usage-without-line. - Free tier with overage. Set the first STAIRCASE tier to
"unit_amount": "0". The customer sees usage but is only billed above the free threshold. - Per-region pricing. Add a
country_codeon the price for geo-targeted rates, or use a meterfilter_tree/dimensionsonmetadata.regionto split a single event stream. - Spending caps. Add a
thresholdsentry to the meter ({ "name": "90% of cap", "comparator": "gte", "value": "900000" }) and subscribe to themeter.threshold_breachedwebhook; throttle or notify on your side. - Prepaid usage. Fund a wallet and reserve against it before serving the call with
POST /v1/reservations; ingest the event with the hold'shold_idto capture it.
What you don't have to build
- High-throughput event ingest (single events or batches of up to 1,000 on
POST /v1/usage-events) - Idempotency / deduplication on retried submissions (each event's
idempotency_key) - Real-time aggregation between events and the dashboard query
- Tier-walking math for graduated pricing (STAIRCASE tiers handle it deterministically)
- Per-region or per-model fan-out (meter
filter_tree/dimensionsor keyed products) - Reconciliation between dashboard usage and invoice quantity (same events on both sides)
Next steps
- Usage Metering - events, batches, deduplication
- Meters - aggregation functions, windows, filters
- Pricing Models - STAIRCASE walks vs VOLUME vs PACKAGE
- Key Sets - one product, many priced dimensions
- Analytics - per-customer, per-product usage trends