Custom policies
Roles, group grants, project permissions and token scopes already decide access (see Authorization). A custom policy is for what those settings cannot say: freeze a service for one group, give a team temporary access, block writes from a network, keep a group of contractors to their own agent sessions.
Requires a Console release with the IAM engine
Custom policies live in the IAM center (Settings → IAM center → Policies) and in the /api/iam/policies API. Both exist only in releases that include the IAM engine.
When to use a policy
| You want to... | Use |
|---|---|
| Give someone a different level of a service | The person's Edit access, or a group's service permissions (no policy needed) |
| Narrow what someone can do inside one project | Project service permissions (no policy needed) |
| Narrow what a token can do | The token's scope (no policy needed) |
| Block a group from a service, whatever their role says | A deny policy |
| Give a group access for a limited time or from one network | An allow policy with a time window or an IP range |
| Let more people approve agent tool calls | An allow policy on Agents · Approve tool calls (guide) |
| Stop people from seeing or acting on other people's agent sessions | A deny policy on the session actions (guide) |
| Stop a person or group from opening one portal, or one agent from running in portals | A deny policy that names the portal or agent (guide) |
The rules of the game
- A policy is enabled or disabled. There is no other state. To see the effect of a rule before it applies, use Preview impact in the editor (who would be affected) and the Access check, and save the rule disabled until you are ready.
- Allow or Deny. A deny always wins. An allow can never go beyond a token's scope or a project's limits.
- Anyone with
adminon theiamservice (owners and admins by default) can create and edit policies. Only owners and admins can make a rule that allows take effect, because allowing is permission management. A delegated IAM administrator can write denies and can save an allow disabled; trying to save an enabled allow answers403 permit_requires_admin. - A rule is built in the guided editor (who, what, where, when) or written as Cedar text. Either way it is checked against the engine's schema before it is saved.
- Limits: 500 policies, 50 statements per policy and 500 statements per workspace; 64 KiB per policy and 256 KiB in total. A policy name is at most 120 characters and a description at most 1,000.
Create a policy in the guided editor
- Open Settings → IAM center → Policies and select Create policy.
- Under Basics, name the policy (for example Contractors cannot change agents) and decide whether it is Enabled right away. A disabled policy is kept but never applied.
- Choose the Effect: Allow grants access on top of what roles and groups give; Deny removes access, whatever roles, groups or other policies allow.
- Fill in Who, What, Where and (optionally) Conditions, described below.
- Select Preview impact to see which decisions would change before you save. For a policy that reaches many people the preview checks a sample (up to 1,000 people and tokens, within 5 seconds and 20,000 evaluations) and says so.
- Select Create policy. If the policy is enabled, you are asked to confirm: it starts deciding access as soon as it is saved.
Who, what, where, when
| Part | Choices |
|---|---|
| Who | Everyone, or Specific people: groups, users, API tokens, any signed-in user, any API token. A rule about a person or group also covers the API tokens they own, so a deny cannot be walked around with a token. |
| What | Read, Write or Admin on every service (Every service), per service, or Specific actions such as models.write. |
| Where | Anywhere, or limited to services, projects, and specific portals or agents. |
| When | A start and end time (Valid from, Valid until; entered in your browser's time zone and stored in UTC), IP ranges (CIDR; a single address becomes a /32 or /128), and Channels (dashboard for a signed-in session, api for a token, portal for a person using a portal). |
The conditions of one rule must all hold for it to apply.
Levels add up in an allow rule
In an allow rule Admin includes Write and Read, and Write includes Read, the same way a person's effective level is read. Choosing Admin on a service therefore lets the people it names read, change and administer it. The editor shows the included levels as part of the choice, and the rule that is saved lists all of them.
A deny rule is different: it blocks exactly the levels it names. Denying Write does not block Read, and denying Admin does not block Write.
Administration services are checked as Write
A change on an administration service (members, providers, webhooks, and so on) is checked as Write and additionally needs Admin. To block changes, deny Write; denying Admin alone does not stop them. The editor warns about this.
Cedar text
Select Edit as Cedar to write the policy as text. The guided form cannot be recovered from Cedar text afterwards. A rule written as Cedar is taken literally, exactly what the statement names: Cedar treats webhooks.read, webhooks.write and webhooks.admin as three separate actions, so a permit that names only webhooks.admin would let somebody administer a service and still not read it. The editor warns about a permit like that and says which levels to add.
The @managed annotation is reserved for built-in policies and is refused in custom ones. The request context a condition can read includes context.ip, context.channel, context.projectId, context.method, context.path and context.now; the editor lists the attributes it accepts.
What the editor warns about
The editor warns about rules that cannot do what they say, before you save:
- a deny rule on an action nothing in the product asks (
models.adminwhen no route needs it); - a rule about a single resource other than a portal, an agent or a Files bucket (the gates decide on services; a deny of that kind is refused);
- an allow rule about portals (a portal is opened by its access list alone);
- the
Writelevel in an allow rule, which also covers Agents · Approve tool calls and so appoints approvers; - IP rules while
TRUSTED_PROXIESis not set (see Client address).
An allow rule that nothing in the product can match is flagged This rule would not do anything; you can still save it, after confirming. A deny rule of that kind is refused, because a deny that never takes effect would be a false assurance.
A rule that cannot be evaluated
A deny rule that cannot be evaluated for a request (a condition that reads something the request does not have) counts as a deny for that request. The decision names the rule and the problem, so a rule written to block something can never stop blocking it silently.
The same holds for a broad rule: one that cannot be evaluated for every request it matches refuses all of them, except the workspace owner's access to the IAM center, so the owner can always open Policies, find the rule the decision names and fix or disable it.
A rule that cannot even be loaded (for example after an engine upgrade) is skipped instead, and the engine reports itself as degraded.
Examples
Freeze Model Hub changes for a group
Deny models.write on the models service for one group. This also covers API tokens owned by the group's members.
{
"name": "Freeze Model Hub for contractors",
"enabled": true,
"spec": {
"effect": "forbid",
"principals": { "groups": ["6b1f0c52-2f0a-4c1e-9c61-3f0d8a1b7e11"] },
"actions": ["models.write"],
"resources": { "services": ["models"] }
}
}curl -X POST https://console.example.com/api/iam/policies \
-H "Content-Type: application/json" --cookie "token=..." \
-d '{
"name": "Freeze Model Hub for contractors",
"enabled": true,
"spec": {
"effect": "forbid",
"principals": { "groups": ["6b1f0c52-2f0a-4c1e-9c61-3f0d8a1b7e11"] },
"actions": ["models.write"],
"resources": { "services": ["models"] }
}
}'The editor compiles that to:
forbid (
principal,
action == Action::"models.write",
resource
)
when {
(
principal in [Group::"6b1f0c52-2f0a-4c1e-9c61-3f0d8a1b7e11"]
|| (principal is ApiToken && principal.owner in [Group::"6b1f0c52-2f0a-4c1e-9c61-3f0d8a1b7e11"])
)
&& (["models"].contains(resource.service))
};Read-only audit access for an on-call group during one shift
An allow rule with a time window. An owner or admin has to enable it.
{
"effect": "permit",
"principals": { "groups": ["6b1f0c52-2f0a-4c1e-9c61-3f0d8a1b7e11"] },
"actions": ["audit.read"],
"resources": { "services": ["audit"] },
"conditions": { "validFrom": "2026-11-02T08:00:00Z", "validUntil": "2026-11-02T16:00:00Z" }
}No writes from tokens that call from a given network
{
"effect": "forbid",
"principals": { "anyToken": true },
"actions": ["Write"],
"resources": { "any": true },
"conditions": { "ipRanges": ["203.0.113.0/24"], "channels": ["api"] }
}Spec reference
The guided form is stored as a spec:
| Field | Values |
|---|---|
effect | permit or forbid |
principals | { "any": true }, or any of users, groups, tokens (lists of ids), anyUser, anyToken |
actions | Level groups Read, Write, Admin; service actions such as models.write; resource actions such as agent.session_view_others |
resources | { "any": true }, or any of services, projects, resources (a list of { "type", "id" }) |
conditions | validFrom, validUntil (ISO 8601), ipRanges (CIDR), channels (dashboard, api, portal) |
Per spec, at most 500 users, 200 groups, 200 tokens, 600 actions, 100 services, 200 projects, 200 resources and 50 IP ranges. GET /api/iam/catalog lists every service, action, resource type and channel with where each is enforced.
Compile and preview
Both calls are read-only, need only read on the iam service and never save anything.
# The Cedar text and the warnings for a draft
curl -X POST https://console.example.com/api/iam/policies/compile \
-H "Content-Type: application/json" --cookie "token=..." \
-d '{ "spec": { "effect": "forbid", "principals": { "anyToken": true }, "actions": ["Write"], "resources": { "any": true } } }'{ "cedar": "forbid (...) ...", "ok": true, "errors": [], "warnings": [] }POST /api/iam/policies/preview evaluates a draft against the workspace's people and tokens (up to 1,000 of them, within 5 seconds and 20,000 evaluations) and reports who would flip from allowed to denied or the other way round. To stay cheap, each person is limited to 240 compile calls, 120 access checks and 12 previews per minute; beyond that the answer is 429 rate_limited with a Retry-After header.
API reference
The policy routes authenticate with the dashboard session cookie, not an API token. GET needs read on iam; every change needs admin.
| Call | Success | What it does |
|---|---|---|
GET /api/iam/policies | 200 { managed, custom } | Built-in and custom policies. With resourceType and resourceId it lists the policies that apply to one resource. |
GET /api/iam/policies/{policyId} | 200 | One policy. |
POST /api/iam/policies | 201 | Create. Send name, description, enabled and either spec or cedar. A new policy is enabled unless you send enabled: false. |
PUT /api/iam/policies/{policyId} | 200 | Change, including enable and disable. |
DELETE /api/iam/policies/{policyId} | 204 | Delete. |
A policy is { policyId, name, description, effect, enabled, spec, cedar, statementCount, ... }. Errors are { error, code, details? }:
| Status | code | Meaning |
|---|---|---|
| 400 | validation | The body or the policy is not valid. details carries a message and, for Cedar text, a line and col. Also sent when you use the retired mode field with monitor (monitor_removed). |
| 403 | permit_requires_admin | An enabled allow rule needs an owner or admin. Save it disabled, or ask one. |
| 404 | not_found | No such policy. |
| 409 | conflict | A policy with this id already exists. |
| 409 | limit_reached | The workspace reached the limit on policies, statements or total size. |
| 429 | rate_limited | Too many compile, access-check or preview calls. Wait for Retry-After. |
| 503 | engine_unavailable | The policy engine cannot run on this server. |
Every create, change and delete is written to the audit trail (IAM center → Audit, policy changes) with the new Cedar text.
Troubleshooting
My allow rule saves but nobody gets access. An allow rule never goes beyond a token's scope or a project's limits, and it cannot open a portal that the portal's access list does not name. Run the Access check for the person and action: it names the rule that decided.
My deny rule does nothing. Check the editor's warnings. A deny on an action nothing asks (for example models.admin) or on one individual resource that no gate asks about has no effect; the editor refuses a deny of that kind. Services are checked at Read and Write, and changes to administration services need Write plus Admin, so deny Write to block changes.
I locked everybody out. Sign in as the workspace owner. The owner's session can always reach the IAM center, so you can disable or delete the rule. See Owner safety.
A person or group the policy names has been deleted. Saving a changed spec checks the people and groups it names against the directory, but a spec that is exactly the stored one is not re-checked, so you can still disable or delete the policy.

