# Usage Policy API

> Read and write a workspace's or organization's usage policy — the governance layer that limits which outside apps, knowledge, MCP servers, agents, and flows members may use.

**URL:** https://inference.sh/docs/api/rest/usage-policy
**Last updated:** 2026-10-11

---

Read and write a workspace's or organization's **usage policy** — the governance layer that limits which outside apps, knowledge, MCP servers, agents, and flows members may use.

→ [Usage policy (product guide)](/docs/org/usage-policy) — reach, allowed/blocked lists, and blocked attempts in the web app  
→ [Workspaces API](/docs/api/rest/teams) — members, roles, and workspace context

Requires API key scopes **`teams:read`** (read policy, denials, effective access) or **`teams:write`** (replace or clear a layer). Within a workspace, the server also checks member **capabilities**:

| Capability | Who typically has it | Used for |
|------------|----------------------|----------|
| **`view_policy`** | Every member of the workspace, and members of any workspace in its organization | `GET` workspace/org rules and `GET` effective access |
| **`manage_policy`** | Workspace owner (standalone team); org admins on the org workspace and on every member workspace | `PUT` / `DELETE` rules, denials feeds, `POST` effective access (draft preview) |

Org routes use `{id}` = the **organization workspace** team id (`type: "org"`). Member workspace routes use that workspace's team id.

---

## Model

A usage policy is a **`PolicyLayerDTO`**: the same shape as other governance layers in the rules API (for example an agent's rules at `/agents/{id}/rules`), but scoped to **usage kinds** only.

| Field | Description |
|-------|-------------|
| `subject_type` | `team`, `org`, or `user` (a **member policy** written by a workspace admin) |
| `subject_id` | Team or org id |
| `label` | Display name for the layer |
| `governance` | Always `true` for usage policy |
| `editable` | `true` when the caller may change this layer (see below) |
| `reach` | Map of usage kind → reach (`public`, `org`, `team`, or `private`). A missing kind means **public** (no restriction for that category). |
| `reach_enforced` | Usage kinds whose **reach** is **enforced** on this governance layer (see below). Each kind must also appear in `reach` with a non-empty value. |
| `rules` | Governance rules for this layer. **Usage** rules use `effect` **`allow`** or **`deny`** on `App`, `Agent`, `Knowledge`, `Mcp`, or `Flow`. **Agent-action** rules use **`allow`**, **`ask`**, or **`deny`** on `RemoteExec`, `Workspace`, `WebFetch`, `Harness`, or `Tool` (see below). Each rule has `kind`, `specifier`, optional `selector` (machine or `tag:…`), and `label`. |
| `rule_kinds` | When `editable`, lists which rule kinds may be added on this layer (usage kinds and/or agent-action kinds, with `selector: true` when a machine may be named) |
| `lint` | When `editable`, problems with the layer's rules: rules that cancel each other, can never match, allow too much, or never take effect |

**Usage kinds** (also used in `reach` keys and rule `kind`):

| Kind | Category |
|------|----------|
| `App` | Apps |
| `Knowledge` | Knowledge and skills |
| `Mcp` | MCP connectors |
| `Agent` | Agents |
| `Flow` | Flows |

**Reach** values match the product guide: `public` (everything visible), `org` (same organization), `team` / `private` (this workspace only).

**Enforced reach** (`reach_enforced`) is a governance-only floor for a category. When an org or workspace admin enforces **apps** at **organization**, for example, every lower admin layer (member workspace policy, member policy) may **narrow** that reach (stricter reach, **deny** rules) but cannot **loosen** it — not with a wider reach and not with an **allow** rule, including rules marked **`enforced`**. The layer that set the enforced reach keeps its own **allow** rules as exceptions to that floor. Only kinds with a reach set in `reach` may appear in `reach_enforced`; the server returns **400** otherwise. Narrow-only layers (chat, agent, user personal rules) cannot enforce reach.

Each rule has **`kind`**, **`effect`**, **`enforcement`**, **`specifier`**, and **`label`**. Usage rules accept **`allow`** or **`deny`** only. Agent-action rules also accept **`ask`** (prompt before proceeding). At write time, usage rules may send a human **`ref`** (`publisher/app-name`, `publisher/*`, an MCP slug, and so on) or reuse a **`specifier`** from a prior response or denial row. Agent-action rules send **`specifier`** directly (for example `git push:*`, `~/secrets/**`, `domain:github.com`, `Bash(git status:*)`).

### Agent-action rule kinds

These rules govern what agents may run on machines and call over the network. In the web app they appear under **usage policy → agents → what agents can do**. They share the governance layer with usage reach and exceptions but are added or removed one at a time with **`POST`** / **`DELETE`** (or on member policies, the member routes below).

| `kind` | `specifier` examples | `selector` (optional) |
|--------|----------------------|------------------------|
| `RemoteExec` | `git push`, `git push:*` | Machine id or `tag:prod` |
| `Workspace` | `~/secrets`, `~/proj/**` | Machine id or `tag:…` |
| `WebFetch` | `domain:github.com` | — |
| `Harness` | `Edit`, `Bash(git status:*)` | Machine id or `tag:…` |
| `Tool` | `web_search` | — |

**`enforcement`** controls how the rule participates in decisions. Omit it on create or replace; the server stores **`default`**.

| Value | Meaning |
|-------|---------|
| `default` | The rule decides in its layer. A more specific admin layer with a matching rule can override it. |
| `enforced` | **Governance only** (org or workspace policy). Final for admin layers: checked before other governance rules and cannot be overridden by a lower admin layer. |
| `evaluate` | The rule never decides. When it would have changed an outcome, the **blocked attempts** feed records what it would have done — useful for trying a rule before turning it on. |
| `disabled` | Kept in the layer but ignored. |

Only **`manage_policy`** callers may set `enforced` on org or workspace rules. Chat, agent, and remote rules always use `default`.

For usage kinds, after resolution **`specifier`** is always a stable id:

| Target | Stored `specifier` |
|--------|-------------------|
| One resource | That resource's id (for example `app_123`) |
| Every resource from a publisher | `publisher:<team id>` |

**`label`** is the display string for the rule — the same ref you wrote (for example `bytedance/seedance` or `bytedance/*`). Responses and denials echo **`specifier`** and **`label`** so clients never construct a specifier. **`deny`** rules correspond to **blocked** entries in settings; **`allow`** rules correspond to **allowed** exceptions.

### How layers combine

Governance layers (organization, workspace, member policy) are checked most specific first. The most specific layer that speaks to a call decides it: one with a rule that matches the call, or, for a usage kind, one that sets a reach for that kind. A layer with nothing to say passes the call up. A more specific governance layer may therefore be **looser** than the one above it, except where the higher layer **enforces** a reach (`reach_enforced`) or a rule (`enforcement: "enforced"`); those are final for every lower admin layer.

When a workspace inside an organization has no layer of its own, `GET /teams/{id}/rules` returns the **org's** layer with `editable: false`; change it with `/orgs/{id}/rules`. In an organization, **`manage_policy`** on a member workspace belongs to the org admins, so only they can write that workspace's own layer. The web app edits org-governed workspaces only through the organization's policy page.

---

## Workspace policy

### Get layer

`GET /teams/{teamId}/rules`

Returns the governance layer in force for the workspace: its own layer, or the org's layer when it has none.

```bash
curl "https://api.inference.sh/teams/team_xyz/rules" \
  -H "Authorization: Bearer inf_your_key"
```

**Example response:**

```json
{
  "subject_type": "team",
  "subject_id": "team_xyz",
  "label": "Acme",
  "governance": true,
  "editable": true,
  "reach": {
    "App": "org",
    "Mcp": "public"
  },
  "reach_enforced": ["App"],
  "rules": [
    {
      "id": "rule_abc",
      "effect": "deny",
      "enforcement": "default",
      "kind": "App",
      "specifier": "app_123",
      "label": "competitor/scraper",
      "created_at": "2026-09-01T12:00:00Z",
      "created_by": "usr_owner"
    }
  ],
  "rule_kinds": [
    { "kind": "App", "selector": false },
    { "kind": "Agent", "selector": false }
  ]
}
```

### Replace layer

`PUT /teams/{teamId}/rules`

Replaces the workspace's **reach** and **usage-kind rules** in one request. Rules of other kinds on the same subject are unchanged.

**Request body** (`PolicyLayerSetRequest`):

```json
{
  "reach": {
    "App": "org",
    "Agent": "team",
    "Knowledge": "public",
    "Mcp": "public",
    "Flow": "public"
  },
  "reach_enforced": ["App"],
  "rules": [
    {
      "effect": "allow",
      "kind": "App",
      "ref": "partner/vendor-tool",
      "label": "partner/vendor-tool"
    },
    {
      "effect": "deny",
      "enforcement": "default",
      "kind": "Mcp",
      "ref": "untrusted-mcp"
    }
  ]
}
```

Requires `teams:write` and **`manage_policy`**.

### Clear layer

`DELETE /teams/{teamId}/rules`

Removes the workspace's own governance layer so defaults apply (or the org layer when inside an org). Requires `teams:write` and **`manage_policy`**.

**Response:** `{ "status": "cleared" }`

### Update one rule

`PUT /teams/{teamId}/rules/{ruleId}`

Changes only **`enforcement`** on an existing usage rule (`PolicyRuleUpdateRequest`). The rule's `kind`, `effect`, and target stay the same; to change those, delete the rule and add another with `PUT /teams/{id}/rules` or `POST` on a narrow-only layer.

```json
{ "enforcement": "evaluate" }
```

Requires `teams:write` and **`manage_policy`**.

`DELETE /teams/{teamId}/rules/{ruleId}` removes a single rule without replacing the whole layer.

### Add or remove agent-action rules

`POST /teams/{teamId}/rules` — body is a **`PolicyRuleCreateRequest`** with an agent-action **`kind`** (`RemoteExec`, `Workspace`, `WebFetch`, `Harness`, or `Tool`), **`effect`** (`allow`, `ask`, or `deny`), **`specifier`**, and optional **`selector`** / **`enforcement`**. Other rules on the layer are unchanged.

`DELETE /teams/{teamId}/rules/{ruleId}` removes one agent-action or usage rule.

Requires `teams:write` and **`manage_policy`**.

---

## Organization policy

### Get layer

`GET /orgs/{orgTeamId}/rules`

Same response shape as the workspace route. `{orgTeamId}` must be the organization workspace's team id.

### Replace layer

`PUT /orgs/{orgTeamId}/rules`

Same body as `PUT /teams/{id}/rules`. Governs every workspace in the organization. A member workspace's own layer, written by org admins, may loosen it except where it enforces a reach or a rule.

### Clear layer

`DELETE /orgs/{orgTeamId}/rules`

Clears the org-wide layer.

### Update one rule

`PUT /orgs/{orgTeamId}/rules/{ruleId}` — same body and semantics as the workspace route. `DELETE /orgs/{orgTeamId}/rules/{ruleId}` removes one rule.

### Add or remove agent-action rules

`POST /orgs/{orgTeamId}/rules` and `DELETE /orgs/{orgTeamId}/rules/{ruleId}` — same bodies and semantics as the workspace **`POST`** / **`DELETE`** routes above.

---

## Member policy

A workspace admin with **`manage_policy`** can attach a **member policy** to one user in that workspace. It is governance scoped to the workspace that wrote it (or to every workspace in the organization when written from the **organization workspace**). For that member, it decides the usage calls its **reach** and usage-kind **rules** match; other members follow the workspace or org layer only.

Until a member policy exists, `GET /teams/{teamId}/members/{userId}/rules` returns the workspace's layer with `subject_type: "team"` and `editable: false` — a template to start from. After `PUT`, responses use `subject_type: "user"`, `governance: true`, and `editable: true`.

Member policies are stored separately from the member's own **narrow-only** rules (`GET /me/rules`). Both can be attached at once: the admin layer governs what the member may use in this workspace; personal rules only narrow tool and machine access and apply everywhere that person works.

When someone leaves a workspace (or the organization), member policies that workspace or org wrote for them are removed so they do not apply again on a later re-invite.

### Get layer

`GET /teams/{teamId}/members/{userId}/rules`

Requires `teams:read` and **`manage_policy`**. `{userId}` must be a member of `{teamId}`.

### Replace usage layer

`PUT /teams/{teamId}/members/{userId}/rules`

Same body as `PUT /teams/{id}/rules` (reach plus usage-kind rules). Requires `teams:write` and **`manage_policy`**.

### Clear layer

`DELETE /teams/{teamId}/members/{userId}/rules`

Removes the member policy; the user follows the workspace (or org) layer again.

### Machine rules on a member

Usage kinds must be written with `PUT` as a whole. To govern what that member's agents may run on machines (`RemoteExec`, `Workspace`, `WebFetch`, `Harness`, `Tool`), add or remove rules one at a time:

- `POST /teams/{teamId}/members/{userId}/rules`
- `DELETE /teams/{teamId}/members/{userId}/rules/{ruleId}`
- `PUT /teams/{teamId}/members/{userId}/rules/{ruleId}` — change **`enforcement`** only

Same rule shapes as chat and agent permission rules (`RemoteExec`, `Workspace`, `WebFetch`, `Harness`, `Tool`).

---

## Test a call

`POST /teams/{teamId}/rules/explain`  
`POST /orgs/{orgTeamId}/rules/explain`

Tests one call against the governance layers in force for the workspace, without making it. Requires `teams:read` and **`view_policy`**.

**Request body** (`ChatRuleExplainRequest`):

| Field | Description |
|-------|-------------|
| `kind` | `RemoteExec`, `Workspace`, `Harness`, `Tool`, or a usage kind (`App`, `Agent`, `Knowledge`, `Mcp`, `Flow`) |
| `target` | The call: a command, a path, a harness tool call (`Bash(git status)`), a tool name, or a resource id for a usage kind |
| `owner_team_id` | Optional. The team that owns a usage kind's resource, so publisher rules, org reach and the workspace's own resources apply |
| `remote` | Optional. The remote the call runs on, so rules with a selector apply |
| `cwd` | Optional. Working directory for relative paths |

**Response** (`ChatRuleExplainDTO`): `effect`, the deciding `rung` and `rule` (null when no single rule decided), a one-sentence `reason`, and `would_have` when a rule in **evaluate** mode would have decided otherwise.

---

## Blocked attempts

Aggregated refusals for the policy editor's **blocked attempts** feed.

`GET /teams/{teamId}/rules/denials`  
`GET /orgs/{orgTeamId}/rules/denials`

Requires `teams:read` and **`manage_policy`**.

Each row (`PolicyDenialDTO`) includes `kind`, `specifier`, `label`, `count`, `last_at`, and optionally `owner_team_id`. Use the same `kind` and `specifier` (or `ref` at write time) when adding an **allow** rule from the feed.

---

## Effective access

Paginated list of resources for one usage kind, each with **allowed** or **blocked** and a **reason** — the same evaluation the server uses on real usage. Powers search in the allowed/blocked pickers and reach previews.

`GET /teams/{teamId}/rules/access`  
`POST /teams/{teamId}/rules/access`

Requires `teams:read` and **`view_policy`** for `GET`. **`POST`** (draft preview) additionally requires **`manage_policy`** on the workspace. Only defined for **workspaces** (not org routes).

**Query parameters (GET):**

| Parameter | Description |
|-----------|-------------|
| `kind` | Required. Usage kind: `App`, `Agent`, `Knowledge`, `Mcp`, or `Flow` |
| `query` | Search names and publishers |
| `outcome` | `allowed` or `blocked` to filter; omit for both |
| `cursor`, `limit` | Pagination |

**POST body** (`UsageAccessRequest`): same fields as query parameters, plus optional **`draft`**:

```json
{
  "kind": "App",
  "query": "invoice",
  "outcome": "blocked",
  "limit": 20,
  "draft": {
    "reach": { "App": "org" },
    "rules": []
  }
}
```

When `draft` is set, the server substitutes that unsaved reach and rule set for the layer you are editing **inside the full policy ladder** (organization, workspace, member, and personal rungs that already apply). Higher layers — including **enforced** org reach — still decide outcomes the draft cannot loosen. This matches enforcement on real usage, not an isolated preview of the draft alone.

**Response** (`UsageAccessPageDTO`):

```json
{
  "items": [
    {
      "resource_id": "app_456",
      "specifier": "app_456",
      "publisher_specifier": "team_partner",
      "ref": "partner/invoice-ocr",
      "owner_team_id": "team_partner",
      "publisher": "partner",
      "source": "organization",
      "source_name": "Acme Org",
      "outcome": "blocked",
      "reason": "outside_reach",
      "decided_by": {
        "subject_type": "org",
        "subject_id": "team_org_acme",
        "label": "Acme",
        "governance": true
      },
      "enforced": true
    }
  ],
  "next_cursor": "..."
}
```

| Field | Meaning |
|-------|---------|
| `decided_by` | Which policy layer's reach or rule decided (`PolicyRungDTO`). Omitted for this workspace's own resources and for ungoverned defaults inside reach. |
| `enforced` | `true` when an **enforced** reach or rule on that layer decided; the draft being edited cannot change this outcome. |

Clients that add **allow** or **block** shortcuts from a row should treat `enforced: true` with `decided_by` pointing at a layer other than the one being edited as **locked**: the outcome cannot be widened from that editor (for example the web app appends `by Acme (enforced)` to the reason line and hides **allow**). Saving an **allow** rule for that resource on the lower layer is rejected the same way.

| `reason` | Meaning |
|----------|---------|
| `own_workspace` | Owned by this workspace |
| `default` | Inside reach, not blocked |
| `allow_rule` | Named by an allow rule |
| `block_rule` | Named by a deny rule |
| `outside_reach` | Outside reach with no allow rule |
| `unresolved` | Policy could not be loaded; only own workspace resources are allowed |

Each item includes **`specifier`** and **`publisher_specifier`** for building rules without guessing ids.

---

## Errors

When usage is refused at runtime, API responses may include error code **`blocked_by_usage_policy`**. Denials are also written to the audit log.

---

## Migration from `/usage-policy`

The former **`/teams/{id}/usage-policy`** and **`/orgs/{id}/usage-policy`** routes and **`UsagePolicy*`** request/response types are removed. Use the **`/rules`** paths and **`PolicyLayerDTO`** / **`PolicyLayerSetRequest`** above instead. Storage and enforcement behavior are unchanged; only the HTTP surface and DTO names moved into the shared rules API.
