metered-billing.spec.md


Principles


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)

  1. POST /api/v1/billing/topup/stripe { amountUsd, successUrl?, cancelUrl? } → returns Stripe Checkout URL + topupId.
  2. Human completes checkout. Stripe webhook (POST /api/v1/billing/webhooks/stripe) verifies signature and credits the account.
  3. Ledger event: credit(stripe, amount, topupId) — idempotent on Stripe session ID.

Top-up — XRP (agent pays)

  1. POST /api/v1/billing/topup/xrp { amountUsd } → returns deposit address (account wallet), memo/destination tag, and topupId.
  2. Existing CryptoPaymentsService monitors the wallet; on confirmation, credits the account at the XRP/USD rate at confirmation time.
  3. Ledger event: credit(xrp, amount, txHash) — idempotent on tx hash.

Metered call

  1. Request arrives with bearer token → resolve account.
  2. Look up price for the route (price schedule; sandbox mode = 0).
  3. 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" } }.
  4. Sufficient → process request → debit(route, price, requestId) — idempotent on request ID.

Deployment payment gate (replaces unverified paymentRef)

  1. POST /api/v1/deployments { quoteId, ... } — no paymentRef needed; the account balance IS the payment.
  2. Place a hold of the deployment fee on the account (hold(deployFee, deploymentId)).
  3. Deployment reaches active (verification passed) → capture the hold.
  4. Deployment reaches failedrelease the hold (free, ADR-002 §4).
  5. 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)

Security & correctness

Out of scope (MVP)