Skip to content

Billing and Quota

Yigtwxx edited this page Jul 12, 2026 · 1 revision

Billing & Quota

Maestro is subscription-gated with a token quota per billing period. There is no free tier, no trial, and no discount — using the product (including the local Ollama model, which on a hosted instance is itself a served service) requires an active paid subscription. Values live in backend/app/core/constants.py; the frontend never hardcodes prices.

This page reflects the current code. The top-level README.md still shows older prices ($15/$50/$100) and a 14-day trial — those are stale.

Plans

Plan Price / month Token quota / period
starter $5 (500 ¢) 500,000
pro $15 (1,500 ¢) 3,000,000
scale $50 (5,000 ¢) 10,000,000

Source constants: PLAN_PRICE_USD_CENTS = {starter: 500, pro: 1500, scale: 5000}, PLAN_MONTHLY_TOKEN_QUOTA = {500k, 3M, 10M}, BILLING_PERIOD_DAYS = 30, BILLING_CURRENCY = usd.

There is no free plan in SubscriptionPlan. The SubscriptionStatus.trialing enum value remains only for grandfathered legacy rows and is never produced by the current flow — ACTIVE_SUBSCRIPTION_STATUSES = {active}.

Subscription lifecycle (services/billing_service.py)

  • Registration creates no subscription. A new user must subscribe at full price before starting any task.
  • subscribe — takes a card, charges the first period at full price, and activates the plan. create_subscription keeps a first_amount_cents / recurring_amount_cents split for future use, but today they are equal (no first-period discount).
  • cancel — stops renewal; the plan stays usable until current_period_end (cancel_at_period_end).
  • resolve_effective_status — computes the live status; there is no trial branch. Expiry (cancelled → inactive) is currently computed lazily at request time (a scheduler is on the roadmap).
  • GET /billing/subscription returns plan + status + live quota consumption.

If a user has no active subscription, POST /tasks returns HTTP 402. See API-Reference.

Quota enforcement (services/quota_service.py)

POST /tasks — the single entry point — calls enforce_can_start_task. It is a pre-flight check: it cannot know a task's eventual cost, so it only blocks a user who is already at or over the limit. resolve_task_token_budget derives the per-task token budget (see Agent-Orchestration).

Usage ledger (services/usage_service.py)

Quota trusts exactly one source: the Postgres usage_records table (see Database-Schema). MongoDB task_sessions is analytics only.

  • record_task_usage — idempotent per task_id (unique key), upsert-max so a resumed/retried task never double-charges or under-charges.
  • used_tokens_this_period — sums the ledger for the current period_start.
  • Tokens are counted by the TokenMeter adapter wrapper, which wraps every LLM call — orchestrator, planner, subagent, reviewer, synthesis (see LLM-Providers-and-BYOK). The _run_task body is wrapped in try/finally, so cancelled, errored, and timed-out tasks still pay for what they spent and reach a terminal state.

Payments (services/payment/)

The payment layer is an adapter, like everything external in Maestro.

  • PaymentProvider protocol + MockPaymentProvider — zero dependencies, zero network. It's what ships in the public repo.
  • card.py — Visa/Mastercard schema support only: luhn_valid (Luhn check), detect_brand (BIN → brand), last4, card_not_expired. Types: CardDetails, ChargeResult, SubscriptionResult, PaymentError.
  • PAN is never persisted, logged, or returned. Only brand + last4 + expiry survive (in payment_methods); the raw PAN lives inside a single CardDetails for one provider call.
  • A real processor (iyzico / PayTR / Adyen / Stripe) is a single new adapter file — existing code doesn't change. Until then, BILLING_LIVE=false and legal pages show a "no real payments are processed" notice.

Because the mock provider is not PCI-scoped, real card numbers must never be entered against it.

Dashboard aggregations

billing_service also powers the dashboard: aggregate_usage, aggregate_metrics, aggregate_trends, aggregate_cost, usage_summary, metrics_summary, cost_summary, plan_quota. These feed GET /dashboard/* (see API-Reference). Trace-derived costs (GET /dashboard/costs) come from trace_service over trace_spans.

Clone this wiki locally