---
title: Plan & Usage (your plan, your allowance, your credit)
---

# Plan & Usage (the one money screen)

## What it is

**Settings → Accounts & AI → Plan & Usage.** The ONE customer-facing money screen, organized by what a
user actually does: see the plan they're on and this month's **AI allowance**, see and top up their
prepaid **credit balance**, compare **Free vs Pro** and **upgrade or manage** ($15/mo Pro via
Stripe), and **bring their own API key**. (A per-seat **Team** card exists but is held dark until the
gateway's Team price is wired — see below.)

Renamed from "Plan & Billing" and expanded on 2026-08-18: it absorbed the credit balance + Buy-Credit
(previously buried under Settings → API Keys) and replaced the separate mock **Pricing** screen, so
there is now exactly ONE place to pay. Subscriptions and one-off **credit** top-ups are still separate
money paths with separate Stripe webhooks that never cross — see
[stripe-credit-topups.md](stripe-credit-topups.md) for the credit path.

## Where to find it

**Settings → Accounts & AI → Plan & Usage.** It is the only storefront in the app; the upgrade prompt a free user meets routes here.

## How it behaves

### What the user sees

- **Your plan** — the current tier (Free / Pro / a comped tier), read from their Global Auth claim.
- **Monthly allowance meter** — how much of the month's pooled-AI allowance is used, and when it
  resets (first instant of the next UTC month). Fetched **on open only, never polled** (cost).
  If the tier has no configured cap the meter hides and says the allowance isn't set up yet;
  at/over a real cap it clamps to 100% rather than showing a nonsense number.
  **As of 2026-08-25 the per-plan pooled-AI allowance is $0 on every tier** (owner decision — no free
  company-funded AI budget on any plan): general in-app AI declines and falls back to the user's own
  key / prepaid credit. **With a $0 allowance the meter hides entirely** and simply says AI runs on
  your own key or prepaid credit — a zero allowance is not an exhausted one, so there is no bar and
  no warning. (Until 2026-09-03 this rendered a maxed-out 100% bar and told everyone — paying Pro
  and Team subscribers included — that they had used up an allowance they never had.) When a real
  positive cap IS configured and genuinely runs out, the warning is **tier-aware**: only a Free user
  is pointed at Pro; a paid subscriber is pointed at prepaid credit or their own key.
  Two safety-net features (the inbound-email security
  screen + the Ask-Omniscio keyless help) still run on the shared company pool, exempted from the
  per-user cap in the gateway — everything else is user-paid.
  This screen is also where the **"built-in AI helpers stopped"** inbox card sends the user — it is
  the one place that answers both halves of that card (how much you get + how to keep going, whether
  by upgrading or pasting your own key). See
  [built-in-ai-helpers.md](built-in-ai-helpers.md) for the per-tier numbers and the two card variants.
  the one place that answers what that card leaves open (how to keep going, whether by upgrading or
  pasting your own key). See
  [built-in-ai-helpers.md](built-in-ai-helpers.md) for the two card variants.
- **Credit balance + Buy Credit** — the prepaid gateway credit, shown right under the allowance so
  "what's left" (this month's allowance AND your credit) sits in one place. Top up with a card via
  Stripe ($10 / $25 / $50 / $100 presets, or a custom $5–$1,000). The same balance still appears
  under Settings → API Keys (creating a pooled key needs it), now with a link back here to add credit.
  The balance + Buy-Credit UI is one shared component so both screens stay identical.
  **The balance can read negative, and it is shown that way on purpose** (e.g. `-$0.50`) with the
  note "from usage overage — your next top-up covers it". A charge is reserved as an estimate and
  settled at the real cost, so a balance can dip slightly below zero; it is small, bounded, and the
  next top-up absorbs it. (Until 2026-09-03 a negative balance displayed as `$0.00` — a real debt
  hidden behind a reassuring zero.)
- **Free vs Pro** — a plain comparison so the upgrade decision needs no docs. The **refund policy**
  is linked right here on the money screen, next to Manage/cancel.
- **Upgrade to Pro** — visible only when the user is **signed in AND on Free**. A paid or comped
  tier hides it, so nobody is ever invited to buy something they already have.
- **Team (per-seat) card — hidden by default.** It shows a seat picker and a live $25/seat total,
  but the gateway has no Team price wired yet, so an "Upgrade to Team" click would fail with "not
  available". Since 2026-09-03 the card sits behind its own in-development flag
  (`team-subscription`) and is revealed as part of Team go-live, together with the gateway price —
  never before it. A storefront that cannot take the money does not show the price.
- **Manage or cancel subscription** — visible only to an actual **Pro/Team subscriber**. Opens the
  Stripe **Billing Portal** where they can cancel, update their card, or view invoices. A Free user
  has nothing to manage, so they never see it.
  - **The portal does not accept refund requests.** Stripe's Customer Portal exposes no
    refund-request action; what it can show is a _link_ to the refund policy, and only if that is
    configured owner-side in the Stripe dashboard's Customer Portal settings. Refund requests go to
    **legal@omniscio.com** per the published [Refund & Cancellation Policy](https://omniscio.com/refunds).
    Do not tell a customer to request a refund in the portal — there is nothing there to click.
- **Bring your own API key (BYOK)** — use a personal provider key instead of the pooled allowance.

### How the upgrade actually works

1. The button calls the gateway (`POST /v1/subscription/checkout`) and opens the returned Stripe
   hosted Checkout URL in the browser. Card details never enter Omniscio.
2. The gateway pins `client_reference_id` to the **verified Firebase uid server-side** — never a
   value the client supplies. This is the load-bearing security property: the webhook trusts that
   field to decide whose account gets Pro, so a client-settable value would let a signature-valid
   checkout name any victim.
3. A **separate** Firebase webhook (`stripeSubscriptionWebhook`) receives the verified Stripe event
   and flips the user's `tier` claim `free → pro`. Cancel or lapse flips it back.
4. The tier claim is what the gateway reads to grant the larger pooled-AI monthly allowance.

Because the grant happens on a **verified webhook** and not on the browser returning, closing the
tab or a flaky network can't leave someone paid-but-not-upgraded.

### Monthly or annual (2026-09-08)

Pro is sold on two intervals, which is what the Terms advertise: **$15/month, or $144/year — about
20% off**. The Pro card carries a Monthly / Yearly toggle and shows the saving next to it.

A few things that are true and non-obvious:

- **The screen never states the discount as a fixed number.** It divides the two prices and rounds.
  If Pro is ever repriced, the percentage moves with it instead of quietly contradicting the Terms,
  and a build-failing guard reads the published Terms document and turns red if the code and the
  legal text ever disagree.
- **The choice only appears once the annual price is actually live** (the `pro-annual-billing`
  flag, revealed alongside the Stripe price id). Before that the card is monthly-only, because a
  priced offer nobody can buy is worse than no offer at all.
- **The interval you pick is what the gateway charges against.** It is validated on the way through;
  an unrecognised value is rejected rather than quietly billed as monthly. It changes *what* is
  billed, never *who* — the account charged is always the verified signed-in user.
- **Both Pro prices grant the same Pro.** The webhook maps the monthly and the annual price id to the
  same tier, so an annual buyer is entitled exactly like a monthly one.

### The annual renewal reminder (2026-09-08)

The Terms promise: *"For any annual (one-year) plan, we will send a renewal reminder before the
renewal charge."* A daily job does this.

- It emails an annual subscriber **30 days before** the charge, once per period.
- It **never** emails after the charge has happened — a notice that arrives afterwards is not a
  reminder, so a renewal date already in the past is out of range by construction.
- It skips anyone who is **already cancelling** (nothing renews), anyone on **monthly** (the promise
  is annual-only), and anyone whose payment is **retrying after a failure** (that is dunning, not a
  clean renewal — telling them "you will be charged on the 14th" would be wrong).
- It sends first and records second, deliberately. If recording fails, the worst case is a duplicate
  reminder next day; the alternative failure — marking someone as reminded when the email never
  went — would breach a published promise silently.
- Every run logs what it scanned, sent and skipped, so a quiet month and a broken job do not look
  identical.

### How manage / cancel works (2026-08-17)

The **Manage or cancel subscription** button calls the gateway (`POST /v1/billing-portal`) and opens
the returned Stripe **Billing Portal** URL in the browser. Same load-bearing security property as
checkout: the gateway resolves the Stripe **customer** SERVER-SIDE from the verified Firebase uid's
`users/{uid}.stripeCustomerId` — never a value the client supplies — so a signed-in user can only
ever open **their own** portal. Cancelling flows back through the same verified `stripeSubscriptionWebhook`
that a subscribe does (it flips the tier), so the button never changes the plan itself. The refund
terms (14-day, less consumed AI credits) live in the public
[Refund & Cancellation Policy](/refunds). The portal is where a customer
**cancels**, updates a card, and pulls invoices — it does **not** accept a refund request (Stripe's
Customer Portal has no such action); the policy routes those to legal@omniscio.com.

**Go-live (owner):** the portal needs the gateway deployed AND Stripe's **Customer Portal** activated
in the Stripe dashboard. Until then the route 503s and the button surfaces a friendly "not available"
rather than a dead action.

### Behaviour worth knowing

- **A failed card keeps Pro for Stripe's retry window** (~3 weeks, `past_due → pro`) — a deliberate
  dunning grace so one expired card doesn't instantly cut off a paying customer. Bounded by the Pro
  monthly allowance, so it can't be abused.
- **A full refund of a subscription charge revokes Pro back to Free.** When Stripe reports a
  subscription charge fully refunded (`charge.refunded`), the webhook downgrades the tier to free, so a
  refund can't leave someone holding a paid month's AI allowance for free. A _partial_ refund and a
  one-time **credit** top-up refund never touch the plan. (Needs the webhook endpoint subscribed to
  `charge.refunded`; chargebacks are covered by Stripe's dispute→cancel automation — see the
  subscription-tier contract's go-live steps.)
- **A subscription never overwrites an admin-granted tier.** It owns the free↔pro↔team axis
  (`ManagedTier = 'free' | 'pro' | 'team'`); an `enterprise` grant is left alone in both directions.
  So a Team subscriber's plan IS subscription-managed — only `enterprise` is admin territory.
  (The retired `starter` tier no longer exists; the ladder is free < pro < team < enterprise.)
- **If the gateway has no Pro price configured**, the button surfaces a friendly "not available"
  rather than a dead checkout, and credit top-ups keep working untouched.

### Company-funded AI is a Pro perk (2026-08-15)

The "extra" (non-Claude) AI services that spend Omniscio's OWN keys are **Pro-only** for signed-in
users, except where noted below — **voices moved to metering on 2026-09-02** and the API-key
gateway opened in August. Free and anonymous users always keep their own-key (BYOK) and free
on-device options:

- **Gated now:** **GLM company credits** and **cloud speech-to-text**.
- **Metered, not gated (2026-09-02):** company read-aloud/narration **voices** (Fish + Soniox) are
  no longer Pro-only. Any signed-in user can use them; each synthesis spends their plan's monthly
  voice time first, then their prepaid credit, and only a user with neither is stopped — with a
  top-up/upgrade prompt, never a message about an API key they never entered. Included voice time
  per month: **Free $0 · Pro ~$3.75 (about 5 hours) · Team & Enterprise ~$7.50 per seat (about 10
  hours)**. A free account therefore runs entirely on credit. Bringing your own voice key removes
  the metering altogether. Voice has its **own** budget line, deliberately separate from the
  company-AI allowance below, which stays `$0` on every plan.
- **Open to any signed-in user (2026-08-24):** the **API-key credits gateway** — minting + using a
  personal `jls_sk_` key. The Pro paywall on minting was removed for adoption; spend is still
  prepaid-gated (a `$0` balance blocks minting + declines calls, fail-closed). The managed proxy lanes
  CrofAI / DeepInfra / GLM keep their own Pro gate; DeepSeek was opened to all signed-in users too
  (2026-08-25), so free users with credit can call it as well. **OpenRouter company-credit sessions**
  — running any model OpenRouter serves (~400) on the company key — are likewise open to any signed-in user
  with credit (2026-08-27), priced live at the standard 10% markup.
- **Still free for everyone:** bringing your own voice/provider key, and **on-device voice**
  (local Whisper) — never gated.
- **Wired but open:** the inbound-email safety pre-screen and the in-app help fallback are
  Pro-capable but left ON for everyone today (flippable later, no redeploy).
- **Reach:** applies to signed-in users on the multi-user/cloud deployment; a plain single-user
  install runs as the operator and keeps everything (the operator is never restricted on their own
  machine). **That "keeps everything" is a DESKTOP promise only.** A service running on Omniscio's
  servers — a relay, the credits gateway — sees a plan, not an operator, so it cannot honour it.
  Where a surface has a server-side check, the server decides. This bit a real user on 2026-09-02:
  a single-user install was told locally that company voices were allowed, the server refused for
  not being Pro, and the app relayed that as *"check your Fish Audio API key"* — for a key they had
  never entered. Voice now meters instead, so it needs no operator exception at all.

A free signed-in user who hits one of these sees an **Upgrade to Pro** prompt instead of a dead
button. Enforced app-side + (for GLM) server-side; the remaining server-side backstops are a tracked
follow-up. Detail: `.claude/memory/contracts/company-funded-ai-paywall-contract.md`.

## Related

### Related

- Contract: `.claude/memory/contracts/plan-billing-storefront-contract.md`
- Contract: `.claude/memory/contracts/stripe-subscription-tier-contract.md` (the webhook that flips the tier)
- [stripe-credit-topups.md](stripe-credit-topups.md) — the separate one-off credit path

