# Multi-tenant SaaS budgets (/docs/recipes/multi-tenant-budgets)



If you sell to organizations, you usually budget per **tenant**, not per end user. Any metadata
field you attach — like `tenant_id` — can drive its own separate budget.

## The rule [#the-rule]

Attach a `tenant_id` to your calls (below), then, on the Firewall page, click
**New rule from template** and pick **Give each team or feature its own budget**:

<Steps>
  <Step>
    **Which tag?** — `tenant_id`. Every *value* of that tag gets its own separate budget: when one
    tenant spends past the limit, only that tenant is paused; the others are untouched.
  </Step>

  <Step>
    **Limit for each ($)** and &#x2A;*Resets after (minutes of no spend)** — the limit each tenant gets,
    and how long a tenant has to go quiet before its counter starts again. That's an inactivity
    window: a tenant that keeps spending never resets. For a calendar period (hourly, daily, weekly,
    monthly) build a **Custom rule** instead.
  </Step>
</Steps>

<Callout type="info">
  Want one shared budget for a *single* named tenant instead of a budget per tenant? That's
  **Shared limit for a group** — same tag, one specific value, one pool. The difference is the
  APPLY TO: see [Filter vs apply-to](/docs/rules/match-vs-groupby).
</Callout>

Prefer **Block** to cut a tenant off, or **Reroute** to keep them running on a cheaper model.

## The snippet [#the-snippet]

Attach `tenant_id` as metadata. With `@tp.workflow`, a `metadata` argument on your entrypoint is
picked up automatically:

```python
import token_police as tp

@tp.workflow(name="tenant_app")
def handle(user_id: str, metadata: dict, query: str):
    return client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": query}],
    )

handle(user_id="u_9", metadata={"tenant_id": "acme_co"}, query="...")
```

```typescript
import { workflow } from "token-police";

const handle = workflow({ name: "tenant_app" }, (input: {
  userId: string; metadata: Record<string, string>; query: string;
}) => client.chat.completions.create({ /* ... */ }));

handle({ userId: "u_9", metadata: { tenant_id: "acme_co" }, query: "..." });
```

You can also pass a fixed `metadata={"tenant_id": ...}` to `@tp.workflow(...)` / `workflow({...})`,
or set it per call with `session(...)`. Tag values are matched exactly, so keep the spelling
stable — see [Identity](/docs/concepts/identity).

## Next [#next]

<Cards>
  <Card title="Per-feature budgets" href="/docs/recipes/per-feature-budgets" description="The same idea, keyed by feature instead of tenant." />

  <Card title="Identity" href="/docs/concepts/identity" description="user_id, paid_plan, and custom metadata." />
</Cards>
