metered-billing.spec.md
Principles
- Prepaid credits. Agents top up a balance; metered interactions debit it.
- Agent-native payments. Insufficient balance returns HTTP
402with machine-readable top-up instructions — an agent can self-remediate. - Cloud costs are never ours. Provider spend bills to the customer's own cloud account (ADR-002 §3). Fees below are platform fees only.
- Failed deployments are free (ADR-002 §4).
- Auditable and idempotent. Every credit/debit is a ledger event with an idempotency key; retries never double-charge.
- Config-driven pricing. The price schedule is configuration, not code.
Price schedule (MVP defaults)
| Interaction | Endpoint / tool | Price (USD) |
|---|---|---|
| Generate suggestions | POST /api/v1/suggestions |
free |
| Pricing estimate | POST /api/v1/pricing/* |
free |
| Detect region / list credentials | MCP tools | free |
| Quote | POST /api/v1/quote |
$0.01 |
| Deployment status / list | GET /api/v1/deployments* |
$0.001 |
| Deployment | POST /api/v1/deployments |
$5.00 (captured on success) |
| Destroy | DELETE /api/v1/deployments/:id |
free |
| Sandbox (demo mode) | all of the above | free |
Rationale: top-of-funnel free (discoverability), micro-fees on metered reads,
meaningful but small fee on the high-value action. Schedule lives in config
(e.g. billing.prices.json / env) and can change without deploys.
Flows
Top-up — Stripe (human pays)
POST /api/v1/billing/topup/stripe { amountUsd, successUrl?, cancelUrl? }→ returns Stripe Checkout URL +topupId.- Human completes checkout. Stripe webhook (
POST /api/v1/billing/webhooks/stripe) verifies signature and credits the account. - Ledger event:
credit(stripe, amount, topupId)— idempotent on Stripe session ID.
Top-up — XRP (agent pays)
POST /api/v1/billing/topup/xrp { amountUsd }→ returns deposit address (account wallet), memo/destination tag, andtopupId.- Existing
CryptoPaymentsServicemonitors the wallet; on confirmation, credits the account at the XRP/USD rate at confirmation time. - Ledger event:
credit(xrp, amount, txHash)— idempotent on tx hash.
Metered call
- Request arrives with bearer token → resolve account.
- Look up price for the route (price schedule; sandbox mode = 0).
- Balance check: insufficient → 402 Payment Required with body:
{ error: "insufficient_credits", balanceUsd, requiredUsd, topup: { stripe: "POST /api/v1/billing/topup/stripe", xrp: "POST /api/v1/billing/topup/xrp" } }. - Sufficient → process request →
debit(route, price, requestId)— idempotent on request ID.
Deployment payment gate (replaces unverified paymentRef)
POST /api/v1/deployments { quoteId, ... }— nopaymentRefneeded; the account balance IS the payment.- Place a hold of the deployment fee on the account
(
hold(deployFee, deploymentId)). - Deployment reaches
active(verification passed) → capture the hold. - Deployment reaches
failed→ release the hold (free, ADR-002 §4). - Destroy before capture → release.
API surface (new/changed)
| Endpoint | Method | Notes |
|---|---|---|
/api/v1/billing/balance |
GET | Balance, held amount, available |
/api/v1/billing/transactions |
GET | Paginated ledger |
/api/v1/billing/topup/stripe |
POST | Returns checkout URL |
/api/v1/billing/topup/xrp |
POST | Returns deposit address + memo |
/api/v1/billing/webhooks/stripe |
POST | Signature-verified |
/api/v1/deployments |
POST | paymentRef field removed |
MCP: add get_balance and top_up tools so agents can self-fund without
leaving the MCP session.
Data model (minimum)
accounts: id, token(s), createdAtledger: id, accountId, type (credit|debit|hold|capture|release|refund), amountUsd, reference (topupId / requestId / deploymentId), idempotencyKey, createdAt- Balances are derived from the ledger (single source of truth).
Security & correctness
- Stripe webhook signature verification mandatory.
- XRP credit only after N ledger confirmations (existing service config).
- Idempotency keys on every ledger mutation; unique constraint enforced.
- Debits and holds must be atomic with the balance check (transaction).
- Never log full card/wallet secrets; reference IDs only.
Out of scope (MVP)
- Subscriptions, invoicing, fiat payouts, multi-currency balances.
- Negative balances / postpaid.
- Provider cost pass-through (customer pays their own cloud bill).