$5 free credits when you sign up
← All posts
Engineering

Org, Team, Member: Scoping Keys, Budgets, Guardrails

A flat "everyone in the org" model breaks the moment a company has two teams. Here is how NemoRouter scopes resources across org, team, and key — and why the active team comes only from the authenticated context.

Nemo Team9 min read

A single-level tenancy model — "you're in the org or you're not" — works right up until a real company shows up. Now there are two teams who shouldn't see each other's keys, a platform group that needs org-wide budgets, and a contractor who should touch exactly one project. NemoRouter models this with three nested scopes: org → team → member, with resources attaching at the level that matches their blast radius. This post is the model and the one security rule that holds it together.

The hierarchy

ORG  (one billing entity, one UUID)
 └─ TEAM(S)  (a "Default" team always exists)
      └─ MEMBER(S)  (each user in exactly ONE team within the org)
           └─ resources scope at three levels:
                · org   — credits, billing, subscription
                · team  — virtual keys, team guardrails, team budgets/limits
                · key   — per-key guardrails, per-key RPM/TPM, per-key spend

Two invariants keep this clean: every user belongs to exactly one org and exactly one team within it, and every org has at least one team (the auto-created Default). There's no multi-team membership to reason about — "change teams" means leave and rejoin — which keeps authorization decisions unambiguous.

What scopes where, and why

The level a resource attaches to is chosen by who should be affected when it changes:

ResourceScopeWhy that level
Credits, billing, subscriptionOrgMoney is one pool per billing entity
Virtual keysTeamA key belongs to the team that uses it
GuardrailsKey > team > orgStrict cases override loose defaults
Budgets / rate limitsOrg / team / keyCap at whichever blast radius you mean
Per-key spendKeyAttribution is per key

Credits live at the org because the bill is one bill. Keys live at the team because that's the unit that owns and uses them. Guardrails resolve most-specific-first so a sensitive key can be locked down without weakening the org default. The pattern is consistent: attach the control at the level whose blast radius it's meant to bound.

The security rule: team identity comes from auth, never the request

Here's the load-bearing rule. The active team for a request is read from the authenticated context (AuthContext.team_id), resolved from the key/JWT — never from a field in the request body or query string.

Trusting request-supplied team_id is a spend-attribution exploit

If the gateway took team_id from the request body, a caller could attribute their spend to another team — draining a rival team's budget or hiding their own usage. So team identity for any authorization or attribution decision comes only from the authenticated key. The request can say anything; the gateway believes only the context.

This mirrors why rate limits and A/B assignment aren't request-overridable: anything that decides authorization or attribution must come from a source the caller can't forge. The team_id does flow outward into provider metadata for tracking — but it's sourced from AuthContext, not echoed from the caller's input.

Enforced at the database, not just the app

Team scoping isn't only application logic — it's enforced at the database with row-level security. Team-scoped queries are filtered by the authenticated team_id, and the same-UUID architecture means the membership check joins directly against the shared membership table. So even an application bug that forgets a team_id filter can't leak another team's keys: the database refuses the rows. Defense in depth — the app scopes, and the database scopes again underneath it.

The Default team and why it matters

Every org gets a Default team at creation, and it can't be deleted while it has members. This avoids a nasty edge case: an org with zero teams is an org where keys have nowhere to live and new members have nowhere to land. Guaranteeing at least one team means the "where does this key go" question always has an answer, and onboarding never hits a teamless dead-end.

The takeaway

Real organizations are nested, so the tenancy model is too: org for money, team for keys and shared policy, key for per-use limits and attribution — each control attached at the blast radius it's meant to bound. The rule that makes it safe is simple and absolute: team identity for any authorization or attribution decision comes from the authenticated context, never the request. Set up teams in the dashboard; it's the same org-team-member model end to end.

Written by Nemo TeamEngineering, product, and company posts from the Nemo Router team — code-first, cost-honest, no vendor-marketing fluff.

More from Engineering

All posts →
Engineering

Hydration-Safe Rendering for Money and Time

new Date() and Math.random() in a React render body cause hydration mismatches — and on a billing dashboard, a flicker on a number erodes trust. Here is the pattern that keeps server and client agreeing.

Nemo Team
8 min
Engineering

Canary Deploys and Auto-Rollback by SLO

A deploy shouldn't need a human watching a dashboard. Here is how a 5% canary, a fixed observation window, and SLO-gated auto-rollback let changes ship and self-heal without a 3 a.m. page.

Nemo Team
9 min
Engineering

Credit Ledger Parity Checks: Catching Drift Early

If a balance and its ledger ever disagree, money is wrong somewhere. Here is how continuous parity checks compare balance to ledger sum and surface a reservation leak before it becomes a billing incident.

Nemo Team
8 min