Skip to content

Serve Your Own Customers

Model Your Application in Caracal 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.

Use one shared zone only when customers share signing keys, policy ownership, audit storage, and operational rate limits. If any customer requires cryptographic or audit isolation, use a zone per customer and automate provisioning yourself.

  • An application-owned, authenticated customer ID that is stable and non-personal.
  • A reviewed label vocabulary and grant/confinement data.
  • Capacity and audit filters tested against expected customer concurrency.
  • The policy-data vocabulary from Author Policy Data - this page uses grants and confinement documents and the platform decision contract without re-introducing them.

The short answer: run one zone for your deployment and carry each customer on the work itself through a customer: label that policy confines and audit can filter. Keep customer_id metadata only when Coordinator inspection needs an additional business key. Customer separation lives below the zone - in Sessions, labels, and Delegation - which is exactly where Caracal models per-actor authority. You do not create a zone per customer.

LayerOwnsPer-customer?
ZoneSigning keys, policy set, resources, providers, audit trailNo - one per deployment
ApplicationYour product’s service identityNo - shared by all customers
Customer label and metadataThe customer the work is for: a customer:<id> label policy and audit can filter; optional customer_id metadata remains Coordinator inspection dataYes - stamped on every Session
SessionOne agent run, labeled and attributed to one customerYes - one per customer task
DelegationThe scoped authority that agent holds for one resourceYes - 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 work - carried as a label the platform decision contract confines and a metadata key the audit trail filters. Keep customer identity in labels and metadata - never in scope names.

Provision a single zone for the deployment and one managed application for your product, as in Model Your Application in Caracal. Set the application identity once in the environment; it is shared across all customers.

Terminal window
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: Choose a Stable Customer Identifier

Section titled “Step 2: Choose a Stable Customer Identifier”

Caracal does not authenticate your customers, does not own your user directory, and never generates a customer identifier. Your application’s existing authentication system - Auth0, Keycloak, Better Auth, a custom issuer - authenticates the customer; Caracal receives the identifier your application asserts and treats it as an opaque, stable string. It does not need to know what the identifier represents, only that the same customer always presents the same value.

Choose an id that never changes for the life of the account - an account UUID, not an email or display name - so audit history stays attributable even after profile changes. Do not reuse one identifier across two customers: separation is only as strong as your identifier discipline.

The identifier enters Caracal on every Session your application starts for that customer as a customer:<id> label. Policy and audit consume that label. Add a customer_id metadata key only for direct Coordinator inspection; Session metadata is not STS policy or audit input. The sub recorded on the underlying STS Authority record is your application’s identity from the client-credentials chain; the customer rides on the work itself.

Step 3: Start a per-Customer Session with a Correlation Key

Section titled “Step 3: Start a per-Customer Session with a Correlation Key”

Start the Session for a customer’s request inside the one zone and stamp the customer ID into a label. The label travels into policy and audit, making per-customer attribution a direct lookup instead of a guess. Optional metadata can support direct Coordinator inspection but is not copied into decision audit events.

async with caracal.session(
labels=[f"customer:{customer_id}"],
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 work.
await do_work(ctx)

The Session ID and Authority record are the exact authority anchors; the customer: label is the business correlation key exposed to policy and audit. Keep the optional customer_id metadata key aligned with that label when Coordinator inspection needs the raw value.

For fan-out work, give each child agent the least authority it needs with delegation, keeping one customer’s blast radius contained even though all customers share the zone.

When policy must confine the work itself - a PiperNet report run, a retention cycle, or any job acting on one customer’s records - carry the customer on the Session as a label:

async with caracal.session(
authority=Authority.narrow(["pipernet:process"], ttl_seconds=600),
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:

# caracal:data-document
package caracal.authz
import rego.v1
confinement := [{
"label_prefix": "customer:",
"scopes": ["pipernet:process", "pipernet:read"],
}]

Publish this confinement document and a worker Session started for one customer can never mint authority outside that surface, even by accident. The Lynx Capital example ships this confinement data with tests.

Step 4: Differentiate Authority per Customer

Section titled “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 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.

# caracal:data-document
package caracal.authz
import rego.v1
grants := {
"resource://payouts": {
"application": "pipernet",
"roles": {"scale-plan": ["payouts:run"]},
},
}

A Session started 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.

Every decision is written to the zone audit ledger with the application, Authority record, Session, labels, and Delegation chain. The customer ID rides in the customer: label set in Steps 2 and 3, so you can answer “what did this customer do, with what authority” from the audit trail. See Audit and Request Traces.

To build a per-customer read-only view - for an internal support console or a customer-facing activity page - filter the shared Session and audit surfaces by the customer: label, then collect each Session’s decisions, Delegations, and Gateway events by Session ID. This remains a filter over one audit trail, not a separate per-customer store.

To cut a customer off, find Sessions by the customer: label, terminate them, and revoke their Delegations; cascade revocation tears down the chain beneath them. There is no single “purge everything for a customer” call, so iterate the matching Sessions. See Sessions and Revocation.

One shared zone serves many customers well, with two ceilings to design around. Session concurrency caps and the rate-limit scope are in Defaults and 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.

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.

In this open-source product you provision and automate those zones yourself through the Admin API, and your application authenticates separately into each. A scoped Control key cannot create zones because it is bound to the zone that issued it. Automated managed-tenant lifecycle is not implemented in this repository.

  • Sign in as two different customers, run the same workflow, and confirm each produces a distinct Session carrying the correct customer: label in the web 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.

Expected result: customer A cannot mint customer B’s resource authority, customer activity is discoverable by Session labels and IDs, and revoking A does not terminate B.

Implement label and confinement tests in Test Caracal Integrations before onboarding production customers.