TokenPolice
Docs
Recipes

Multi-tenant SaaS budgets

Give every customer (tenant) its own budget using a custom metadata field.

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

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:

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.

Limit for each ($) and 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.

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.

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

The snippet

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

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="...")
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.

Next