Free Tier Architecture
How GMI serves a no-credit-card free tier at near-zero marginal cost: derived entitlements, a global grade cache, server-side enforcement, and model routing
flowchart TD
REQ[API request for account] --> ENT[AccountEntitlementService]
ENT --> STAFF{Staff flag?}
STAFF -- yes --> S[Staff tier<br/>unlimited, never billed]
STAFF -- no --> CARD{Active card on file?}
CARD -- yes --> P[Paid tier<br/>unlimited, usage-billed<br/>Haiku / Sonnet / Opus]
CARD -- no --> F[Free tier<br/>8 symbols, 1 report/month<br/>Haiku only, never billed]
F --> WALLS[Enforcement points<br/>symbol add, report create,<br/>AI submit, rate limits]
P --> WALLS
S --> WALLSDerived Tiers, Not Stored Tiers
The free tier introduced no new account state. An account's tier is derived on every request from facts that already exist: a staff flag makes it Staff (unlimited, never billed), an active card on file makes it Paid (unlimited, usage-billed), and everything else is Free — capped at 8 tracked symbols, one report per calendar month, and the fastest Claude model tier.
This makes two hard problems disappear. Upgrades are instant and lossless by
construction: adding a card flips the derivation, and the user's watchlist, grades, and
history were never partitioned by tier in the first place — there is nothing to migrate.
And failed upgrades cannot leave partial state: if the card fails, the
derivation still evaluates to Free. A single AccountEntitlementService is the only
code that answers "what may this account do?", and every enforcement point consults it.
flowchart LR
subgraph Refresh[Earnings-driven refresh]
E[Earnings calendar scan] -. reported? queue +2d .-> Q
Q[Refresh job queue] --> B[Grade builder service]
B --> FMP[Market data fan-out<br/>per ticker]
FMP --> G[Shared grading model]
G --> C[(Per-ticker grade cache<br/>grade + full summary)]
end
C --> SEO[Public SEO grade page]
C --> D1[User dashboard A]
C --> D2[User dashboard B]
T[User tracks new ticker] -. registers + queues .-> QThe Global Grade Cache
Free-tier dashboards are fed by the same per-ticker grade snapshots that power the public SEO ticker pages. The grading model was already unified — the SEO builder and the report engine call the identical pure grading function — so the free tier's job was to unify the cache: each ticker row now stores the full per-symbol summary alongside the headline stats, and a user's grade dashboard is assembled from those rows.
- Viewing a dashboard costs a database read. No market-data API calls, no recomputation — refreshing the page all day costs effectively nothing.
- Grades refresh on earnings events, never on demand. A background pass watches each tracked ticker's earnings calendar and regrades it a couple of days after the company reports — exactly when the fundamentals actually move — with a staleness cap as a safety net. Only tickers that at least one user actually tracks participate, so data cost scales with real users rather than with the full multi-thousand-ticker SEO universe (which keeps its own slower cadence).
- New tickers self-register. Track a stock the cache has never seen and it is queued for the next refresh pass — and gains a public SEO grade page as a side effect.
- One computation, many consumers. A thousand users tracking NVDA share one post-earnings grade generation with each other and with the public NVDA page.
The cache rides the existing refresh-job queue and background service — the free tier added a second, lighter job type rather than a second pipeline.
Server-Side Enforcement, Payment-Grade
Entitlement checks are treated with the same discipline as payment code: the UI renders friendly walls, but every limit is enforced server-side, so a caller bypassing the web client hits the same rules via the API.
- Symbol cap — enforced at the single service-layer choke point for symbol adds, plus fail-fast pre-checks on every bulk path (list create, list replace, ETF import, template import) so a denied request writes nothing.
- Monthly report allowance — a month-scoped count over the jobs table (failed runs excluded, so they never burn the allowance), checked in the job-creation transaction and again at the API for a clean HTTP 402 with an upgrade message. The month boundary resets itself; there is no counter to maintain.
- Model routing — every Claude request's model is validated against the account's allowlist and silently coerced when out of policy; free accounts always run the fast tier. This also closed a pre-existing gap where a client-supplied model string reached the AI API unvalidated.
- Abuse controls — email verification is required before any generation endpoint runs, verification is token-only (no self-service bypass), and generation endpoints sit behind their own tighter per-account rate bucket.
- Observability from day one — every denied attempt (cap hit, allowance exhausted, model downgrade) is written to the activity log, powering the admin free-tier dashboard: signups, verification rate, wall hits, free-to-paid conversions, and per-user cost.
Email-Only Signup and the Lossless Upgrade
The old funnel required manual account approval and a card before the dashboard. The free-tier funnel is fully self-serve: sign in with Google, verify your email, land on the dashboard. Email verification doubles as the approval step — the verification handler activates the account, and admin notification became informational rather than a queue to work.
Upgrading is the same page that used to gate the product, reframed as an opt-in unlock: add a card, and because tier is derived, entitlements flip on the next status read. Downgrading is symmetric — remove the card and the account simply derives back to Free. Nothing is copied, archived, or deleted in either direction.
Why Marginal Cost Stays Near Zero
Three properties keep a free user cheap: dashboards are cache reads; grade refreshes are shared across all users of a ticker and triggered by earnings events; and the one monthly AI report runs on the fastest model tier with its cost logged per run. The design goal was that a free account's recurring cost rounds to cents — cheap enough to let the free tier be genuinely free for life, with the paid tier's usage billing untouched alongside it.