Studio Backend

Usage and Billing

Conversations are billed by the minute through Stripe. The engine records how long every realtime conversation took; the platform turns those records into per-minute usage, reports it to Stripe as metered usage, and manages each account’s Stripe customer and subscription.

Accounts and Subscriptions

Every account has a BillingDetails document (billingDetails, model in promethist-common): the Stripe customer id, the subscription id and an active flag. The customer is created together with the account (BillingService.subscribe), named after it and tagged with the account id and this deployment’s region (stripe.region).

The active flag matters beyond the Studio: the engine refuses conversations of an account whose billing is not active ("Billing is not active"). The platform keeps it in step with the subscription through the Stripe webhook. Seat-based limits that the engine applies on top come from its own pricing records, which the platform does not manage.

What can be bought is described by Package documents (package collection): a name and description, a default Stripe price, optional additional prices, a spend threshold and an active flag.

Endpoint Behavior

POST /api/v1/tenant/{id}/billing/checkout-session (Owner)

Starts a Stripe Checkout session in subscription mode for a package, with success and cancel URLs. Refused when the customer has an open or uncollectible invoice, or already has a subscription. The subscription carries tenant_id, region and package_id in its metadata.

POST /api/v1/tenant/{id}/billing/portal-session (Owner)

A Stripe customer-portal session for the account’s customer, which returns to the given URL.

GET /api/v1/{tenant|project|agent}/{id}/subscription (Viewer)

The account’s subscription: id, raw Stripe status and description. An account is expected to have at most one; more than one is reported as an error.

GET /api/v1/tenant/{id}/invoices (Viewer)

The customer’s Stripe invoices with status, total in the currency’s minor unit, currency and the PDF and hosted-invoice links.

GET /api/v1/tenant/{id}/usage?from=...&to=... (Viewer)

Usage per day of the range: billed minutes, testMinutes from test-mode sessions and the number of conversations, with days without usage filled with zeros.

Stripe Webhook

POST /api/stripe/callback (StripeController, registered when stripe.webhookSecret is present) verifies the Stripe-Signature header and handles three events for subscriptions whose region metadata matches this deployment:

customer.subscription.created

Applies the package of the subscription — a billing threshold when the package has a spend threshold, and its additional prices — and stores whether the subscription is active.

customer.subscription.updated, customer.subscription.deleted

Store whether the subscription is active (status == "active").

Other events are ignored.

The Usage Pipeline

Three scheduled jobs (util/Schedulers.kt, each triggered every minute) move usage from the engine to Stripe. They run only when promethist.scheduling.enabled is on; the committed default is off, so a deployment enables them in its own configuration.

  1. Tasks — createEdrProcessingTasks creates one usageProcessingTask per minute, from the end of the newest existing task up to two hours ahead. A unique index on the window keeps tasks from being duplicated.

  2. Aggregation — processEdrData claims each new task whose window ended more than 15 seconds ago and aggregates the engine’s edr records of type Realtime created in that minute, grouped by account, project, agent and test mode. A record contributes its duration rounded up to whole minutes. The result is saved as usage documents in state New.

  3. Reporting — reportUsagesToStripe claims new usage documents one at a time and sends each as a Stripe meter event named minutes (value = the minutes, identifier = the usage id, timestamp = the end of its window, the account’s Stripe customer id). The document ends Reported, or Failed with the reason.

Claiming is a findAndModify from New to Processing, so several backend replicas can run the jobs without processing the same window twice. Every usage document is reported, test-mode minutes included; the split by testMode exists in the usage report. A fourth job removes pending invitations older than seven days every night.

In the Studio and in Claude

The billing page is shown to account Owners only. In local development the Studio skips billing and subscription requests unless NEXT_PUBLIC_ENABLE_BILLING and NEXT_PUBLIC_FORCE_SUBSCRIPTION_CHECKS are set, because a local backend usually has no Stripe configuration.

The MCP server can read the subscription, invoices and usage, but it has no tool that changes payment state: checkout, portal and webhook operations are excluded by policy, and McpToolRegistryTest fails when a tool name suggests one.