---
title: "Serve Your Own Customers"
url: "https://docs.caracal.run/guides/serve-customers/"
markdown_url: "https://docs.caracal.run/markdown/guides/serve-customers.md"
description: "Run one Caracal deployment that serves many of your customers, with per-customer identity, policy, audit, and revocation."
page_type: "page"
concepts: []
requires: []
---

# Serve Your Own Customers

Canonical URL: https://docs.caracal.run/guides/serve-customers/
Markdown URL: https://docs.caracal.run/markdown/guides/serve-customers.md
Description: Run one Caracal deployment that serves many of your customers, with per-customer identity, policy, audit, and revocation.
Page type: page
Concepts: none
Requires: none

---

[Model Your Application in Caracal](/guides/modeling-recipes/) compares the isolation models at a high level. This page is the end-to-end pattern for the most common case: your application is a single product that serves many of *its own* customers, and you want clean per-customer authority without standing up infrastructure for each customer.

The short answer: run **one zone for your deployment** and represent **each customer as a subject**. Customer separation lives below the zone — in subjects, sessions, agents, and delegation — which is exactly where Caracal models per-actor authority. You do not create a zone per customer.

:::note[FAQ]
[What should a zone represent?](/reference/faq/#faq-003) and [does the open-source edition include managed multi-tenancy?](/reference/faq/#faq-004)
:::

## Where Each Customer Lives in the Model

| Layer | Owns | Per-customer? |
| --- | --- | --- |
| Zone | Signing keys, policy set, resources, providers, audit trail | No — one per deployment |
| Application | Your product's service identity | No — shared by all customers |
| Subject session | The authenticated customer the work is for (`sub`) | Yes — one per customer |
| Agent session | One agent run, bound to a subject session, carrying a customer correlation key in metadata | Yes — one per customer task |
| Delegation edge | The scoped authority that agent holds for one resource | Yes — least privilege per task |

A zone is the trust boundary that owns keys, policy, and audit. A customer is not a trust boundary of its own here; it is an *attribute* of the subject and the work, checked by policy and recorded in audit. Keep customer identity in the subject and metadata — never in [scope names](/concepts/resource-grant/).

## Step 1: One Zone, One Application

Provision a single zone for the deployment and one managed application for your product, as in [Model Your Application in Caracal](/guides/modeling-recipes/). Set the application identity once in the environment; it is shared across all customers.

```bash
CARACAL_ZONE_ID="<your-zone-id>"
CARACAL_APPLICATION_ID="<your-application-id>"
CARACAL_APP_CLIENT_SECRET="<your-application-secret>"
```

The zone owns one policy set and one audit trail. Every customer's authority is decided and recorded inside it.

## Step 2: Mint a Subject per Customer

When a customer signs in, mint a subject session whose `sub` is your stable customer identifier. The subject is the customer's identity inside Caracal: STS records the session as a user session, and the `sub` flows into every downstream agent, delegation, and audit event.

Use a customer id that never changes for the life of the account, so audit history stays attributable even after profile changes. Do not reuse one `sub` across two customers — separation is only as strong as your subject issuance.

## Step 3: Spawn a per-Customer Agent with a Correlation Key

Spawn the agent for a customer's request inside the one zone, bound to that customer's subject session, and stamp the customer id into the agent's metadata. The metadata travels with the agent session and makes per-customer attribution a direct lookup instead of a guess.

```python
async with caracal.spawn(
    metadata={"customer_id": customer_id},
) as ctx:
    # Every gateway and provider call in this block carries a scoped,
    # non-root mandate for this customer's subject.
    await do_work(ctx)
```

`subject_session_id` references the STS session, so it is the authority anchor; the customer id in `metadata` is the business correlation key. Use a single, consistent metadata key — `customer_id` is the recommended convention — so audit queries and dashboards can filter by customer without per-app special cases.

:::caution[Attribution is not enforcement]
Agent metadata travels with the session and lands in audit, but it is **not** part of policy input. Policy sees the subject's claims (`input.context.subject_claims`), the agent's labels (`input.principal.labels`), and the delegation edge — never the metadata map. Use metadata to *find* a customer's activity; use subject claims or labels when policy must *restrict* it.
:::

For fan-out work, give each child agent the least authority it needs with [delegation](/guides/delegation/), keeping one customer's blast radius contained even though all customers share the zone.

### Customer Labels for App-Only Workloads

When the work is app-initiated — a billing sweep, a dunning cycle, a scheduled job acting on a customer's records without that customer signing in — there is no subject session to anchor the customer. Carry the customer on the agent session itself with a label:

```python
async with caracal.spawn(
    grant=Grant.narrow(["billing:collect"]),
    labels=[role, f"customer:{customer_id}"],
    metadata={"customer_id": customer_id},
) as ctx:
    await collect_overdue(ctx)
```

Labels drive the platform decision contract's confinement: a `confinement` data document caps every customer-labeled agent to the customer-record surface — whatever its role would otherwise allow:

```text
# caracal:data-document
package caracal.authz

import rego.v1

confinement := [{
  "label_prefix": "customer:",
  "scopes": ["billing:collect", "billing:read"],
}]
```

Publish this `confinement` document and a worker spawned for one customer can never mint authority outside that surface, even by accident. The [Lynx Capital example](/examples/lynx-capital/) ships this confinement data with tests.

## Step 4: Differentiate Authority per Customer

All customers share one zone policy set, but authority is per-request and label-aware. The agent's role labels and the delegation edge are in the platform decision contract's input, and your `grants` map roles to scopes per resource. Model plan or tier differences as roles: give the scale-plan role the resource scope, withhold it from the others, and label each customer's session with the role it earns.

```text
# caracal:data-document
package caracal.authz

import rego.v1

grants := {
  "resource://payouts": {
    "application": "payments",
    "roles": {"scale-plan": ["payouts:run"]},
  },
}
```

A session spawned for a scale-plan customer carries the `scale-plan` label and mints `payouts:run`; a starter-plan session never holds that role, so the platform contract denies it. Express customer differences as roles in `grants` and prefixes in `confinement` — not as a separate policy set per customer. There is one active policy set per zone. See [Author Policy Data](/guides/author-policy/).

## Step 5: Attribute and Revoke per Customer

Every decision is written to the zone audit ledger with the application, session, agent session, and delegation chain. The customer id rides in the agent metadata you set in Step 3, and the subject is reachable through the session, so you can answer "what did this customer do, with what authority" from the audit trail. See [Audit and Request Traces](/concepts/audit-ledger/).

To build a per-customer read-only view — for an internal support console or a customer-facing activity page — query the shared trail by the convention you already established: filter agent sessions on the `customer_id` metadata key (or the `customer:` label), then collect each session's decisions, delegations, and Gateway events. Because every session carries the key from spawn, the view is a filter over one audit trail, not a separate per-customer store. The same filter scopes Console searches and SIEM exports.

To cut a customer off, revoke that customer's subject session and its agents and delegation edges; cascade revocation tears down the chain beneath them. There is no single "purge everything for a customer" call, so iterate the customer's sessions and agents. See [Sessions and Revocation](/concepts/sessions-revocation/).

## Limits to Plan For

One shared zone serves many customers well, with two ceilings to design around. The agent concurrency caps and the rate-limit scope are in [Defaults and Limits](/reference/defaults-and-limits/#agent-and-delegation-limits).

- **Concurrent agents per zone** are capped, so a single zone bounds how many customer agents can run at once. If you need more simultaneous customer agents than one zone allows, add applications within the zone, or move the busiest customers to their own zone.
- **STS rate limiting is per zone, resource, and acting application** — not per customer. Because customers share your application identity, one heavy customer draws on the shared budget. Keep this in mind for fairness, and isolate a customer into its own zone if it must have a guaranteed independent budget.

## When to Use a Zone per Customer Instead

Stay with one shared zone unless a customer genuinely requires hard isolation: independent signing keys, an audit trail that can never mix with others, or a policy change that can never affect another customer. Those needs are the **zone per customer** model in [Model Your Application in Caracal](/guides/modeling-recipes/#multiple-customers-or-workspaces).

In the open-source edition you provision and automate those zones yourself through the Admin API, and your one application authenticates separately into each. A scoped Control key cannot create zones — it is bound to the single zone it was issued in. Managed per-tenant lifecycle, where customer onboarding mints and operates a zone automatically, is an Enterprise Edition feature; see [Compare Editions](/enterprise/).

## Validate the Pattern

- Sign in as two different customers, run the same workflow, and confirm each produces a distinct subject and agent session in the Console.
- Author grant data that gives one customer's role a resource and withholds it from another, then confirm both decisions in the audit trail.
- Revoke one customer's session and confirm its in-flight agents lose authority while the other customer is unaffected.

## Related

- [Model Your Application in Caracal](/guides/modeling-recipes/)
- [Zones](/concepts/zone/)
- [Identities and Applications](/concepts/principal/)
- [Implement Multi-Agent Delegation](/guides/delegation/)
- [Author Policy Data](/guides/author-policy/)
- [Sessions and Revocation](/concepts/sessions-revocation/)
- [Audit and Request Traces](/concepts/audit-ledger/)
