Appearance
Billing and usage
OpenBrain bills organizations (not individual users) via Laravel Cashier and Stripe.
Plans
- Configure Stripe Price IDs in
.env(STRIPE_PRICE_HOBBY,STRIPE_PRICE_PRO). - Plan copy and marketing labels live in
config/openbrain.phpunderplans. - Checkoutable plan keys are
hobbyandpro.
Subscribing
- Sign in to the dashboard at
/app. - Open Billing for your organization.
- Owners can start Subscribe on Hobby or Pro (POST Checkout with required
plan=hobby|pro) when the organization has no subscription, or the previous one has ended. An existing Stripe customer can check out again. Pro Checkout includes a 14-day trial; Hobby is billed immediately. - While a subscription is active, trialing, in its grace period,
past_due,incomplete, orunpaid, use Manage subscription (Customer Portal) for the payment method, invoices, cancellation, and plan changes. Confirm payment appears when Cashier reports an incomplete payment. Checkout is not offered again until the subscription has ended. - In the Customer Portal, enable both Hobby and Pro products so customers can switch plans.
- After Checkout, the success URL returns to Billing with
checkout=success. The subscription row appears when Cashier processescustomer.subscription.created. Until then the page shows a pending notice and writes stay unavailable. Cancel returns withcheckout=cancelledand Subscribe stays available.
What the Billing page shows
| Field | Source |
|---|---|
| Subscription status / plan / trial / grace period | Local Cashier tables (subscriptions), kept in sync by Stripe webhooks — not a live Stripe API read on page load |
| Stripe customer ID | Local organizations.stripe_id (set when Checkout creates the customer) |
| Ingest / search usage | Local organization_usage meters (app quotas), not Stripe metered billing |
| Plan and monthly caps | Resolved from the synced subscription price (see Plan limits) |
After Checkout returns, status may briefly show none until customer.subscription.* webhooks are processed.
Reads stay free, writes need a plan
When OPENBRAIN_BILLING_ENFORCED=true:
| Operation group | Operations | Unpaid organization |
|---|---|---|
| Writes | ingest, upsert, batch, update, delete | 402 Payment Required (code: "entitlement_required"; MCP returns a tool error) |
| Reads | search, get, recent, export, stats, changes, MCP read tools and resources | Allowed |
The same rule applies on every surface: REST, MCP, the dashboard (including Filament delete actions), and inbound GitHub/Linear webhooks. Inbound webhook deliveries that are denied are dropped without retry — no memory and no usage is recorded.
If OPENBRAIN_BILLING_ENFORCED=true while STRIPE_SECRET is empty, entitlement cannot be verified. Writes then fail closed with 503 Service Unavailable (code: "service_unavailable") instead of being silently allowed; reads continue to work.
Local development typically leaves enforcement off and omits STRIPE_SECRET.
Usage metering
Per-organization counters are stored monthly (organization_usage):
ingest_count— ingest, upsert, and accepted batch itemssearch_count— search, export, stats, changes, and recent
get, list, and dashboard record views are not metered. Dashboard semantic search and JSONL export follow the same metering and policy rules as their API equivalents.
Exceeding a cap returns 429 with code: "quota_exceeded" (MCP: tool error) until the next monthly period.
Plan limits
While OPENBRAIN_BILLING_ENFORCED=true, caps come from the organization's plan:
| Plan | Monthly ingest | Monthly search |
|---|---|---|
| Free (no subscription) | 0 (writes require a plan) | 1,000 |
| Hobby | 10,000 | 50,000 |
| Pro | 100,000 | 500,000 |
| Trial | mirrors Pro | mirrors Pro |
Override the defaults with:
env
OPENBRAIN_FREE_SEARCH_LIMIT=1000
OPENBRAIN_HOBBY_INGEST_LIMIT=10000
OPENBRAIN_HOBBY_SEARCH_LIMIT=50000
OPENBRAIN_PRO_INGEST_LIMIT=100000
OPENBRAIN_PRO_SEARCH_LIMIT=500000In each plan's limits under openbrain.plans, 0 allows nothing and -1 means unmetered.
An active subscription on a price that matches neither STRIPE_PRICE_HOBBY nor STRIPE_PRICE_PRO gets no plan allowance at all: it never inherits a paid cap and never becomes unmetered. OpenBrain logs a warning and every metered operation (ingest, upsert, batch, search, export, stats, changes, recent) returns 503 with code: "service_unavailable" until the price is mapped, and the Billing page shows the mismatch. Fix it by pointing STRIPE_PRICE_HOBBY / STRIPE_PRICE_PRO at the price IDs your customers are actually subscribed to.
Legacy global limits (enforcement off)
While OPENBRAIN_BILLING_ENFORCED=false, plan limits are ignored and the legacy global caps stay authoritative, where 0 means "no cap":
env
OPENBRAIN_USAGE_LIMIT_INGEST=0
OPENBRAIN_USAGE_LIMIT_SEARCH=0Webhooks
Cashier registers POST /stripe/webhook. Set STRIPE_WEBHOOK_SECRET and forward events from Stripe (or stripe listen locally).
Environment
| Variable | Purpose |
|---|---|
STRIPE_KEY | Publishable key |
STRIPE_SECRET | Secret API key |
STRIPE_WEBHOOK_SECRET | Webhook signing secret |
STRIPE_PRICE_HOBBY | Hobby plan price ID |
STRIPE_PRICE_PRO | Pro plan price ID |
OPENBRAIN_BILLING_ENFORCED | Require an active subscription for memory writes |
OPENBRAIN_FREE_SEARCH_LIMIT | Monthly search cap for unpaid organizations |
OPENBRAIN_HOBBY_INGEST_LIMIT | Monthly ingest cap on Hobby |
OPENBRAIN_HOBBY_SEARCH_LIMIT | Monthly search cap on Hobby |
OPENBRAIN_PRO_INGEST_LIMIT | Monthly ingest cap on Pro (and trials) |
OPENBRAIN_PRO_SEARCH_LIMIT | Monthly search cap on Pro (and trials) |
OPENBRAIN_USAGE_LIMIT_INGEST | Legacy ingest cap, used only while enforcement is off (0 = off) |
OPENBRAIN_USAGE_LIMIT_SEARCH | Legacy search cap, used only while enforcement is off (0 = off) |