Scopes
A scope is a string that names a specific action an agent is allowed to take. Every
DelegationCert.scope field is a list of scope strings. The verifier checks that the action
being attempted is covered by the effective scope of the delegation chain.
┌──────────────────────────────────────────────────────────────┐│ Ratify v1 scope vocabulary ││ ││ 52 canonical scopes + 14 wildcards + custom: extension │└──────────────────────────────────────────────────────────────┘Why a canonical vocabulary
Section titled “Why a canonical vocabulary”Without one, every integrator invents their own scope strings and you get the OAuth scope
nightmare: read, read:files, files.read, Files.Read, FILE_READ, etc., all meaning the
same thing and none cross-compatible. Ratify fixes this for v1: the protocol defines the
canonical set, every SDK exports the same constants, every verifier knows the same
vocabulary.
If you need an application-specific scope outside the canonical set, use the custom: prefix
(see the bottom of this page).
The 14 domains
Section titled “The 14 domains”Scopes are grouped by domain. Within a domain, wildcards expand to every concrete scope.
| Domain | Concrete scopes | Wildcard | Typical use |
|---|---|---|---|
meeting: | attend, speak, video, chat, share_screen, record | meeting:* | Zoom/Teams/Meet agents |
voice: | inbound, outbound, transfer, record, dtmf | voice:* | SIP / WebRTC voice agents |
api: | read, write, admin, delete | api:* | API gateways, MCP servers |
files: | read, write, delete, share | files:* ⚠️ sensitive | File-system / cloud-drive agents |
calendar: | read, write, delete, share | calendar:* | Calendar assistants |
email: | read, send, delete | email:* ⚠️ sensitive | Email agents |
payment: | query, initiate, approve | payment:* ⚠️ sensitive — no wildcard | Financial agents |
commerce: | browse, purchase, return | commerce:* | Shopping agents |
identity: | present, prove, vouch | identity:* | Identity-vouching agents |
system: | execute, install, configure | system:* ⚠️ sensitive | Sysadmin agents |
physical: | enter, move, pickup, dropoff, actuate | physical:* ⚠️ sensitive — geo-gated | Drones, robots |
vehicle: | drive, unlock, start | vehicle:* ⚠️ sensitive | Vehicle agents |
mcp: | tool, resource, prompt | mcp:* | Model Context Protocol |
a2a: | negotiate, commit, report | a2a:* | Agent-to-agent transactions |
52 concrete scopes + 14 domain wildcards + the custom: extension pattern = the
complete vocabulary.
Sensitive scopes (⚠️)
Section titled “Sensitive scopes (⚠️)”Some scopes are flagged sensitive in the canonical vocabulary:
files:write,files:delete,files:shareemail:send,email:deletepayment:initiate,payment:approvesystem:execute,system:install,system:configurephysical:*(all)vehicle:drive,vehicle:unlock,vehicle:start
Sensitive scopes cannot be granted via wildcard expansion. If a delegation has scope: ["files:*"],
it expands to files:read and files:share but not files:write or files:delete. To
grant a sensitive scope, the issuer must enumerate it explicitly: scope: ["files:read", "files:write"].
This is the structural defense against “AI got * and somehow wiped the drive.” Sensitive
scopes have to be intentionally typed.
payment:* has no wildcard at all
Section titled “payment:* has no wildcard at all”Some domains are too sensitive even for non-sensitive wildcard expansion. payment:* is
rejected by the verifier — payment scopes must always be enumerated. Same for the
“no-wildcards-period” group: physical:* wildcards exist but every concrete physical: scope
is sensitive and must be enumerated.
Scope intersection across a chain
Section titled “Scope intersection across a chain”When Alice delegates to Agent-A and Agent-A sub-delegates to Agent-B, the effective scope of Agent-B’s chain is the intersection across every link.
Alice → Agent-A scope: [meeting:*] meeting:* expands to → ┌──────────────────┐ │ meeting:attend │ │ meeting:speak │ │ meeting:video │ │ meeting:chat │ │ meeting:share_ │ │ screen │ └──────────────────┘ ∩ Agent-A → Agent-B ┌──────────────────┐ scope: [meeting:attend, meeting:speak] │ meeting:attend │ │ meeting:speak │ └──────────────────┘ ‖ ▼ Effective scope at Agent-B: ┌──────────────────┐ │ meeting:attend │ │ meeting:speak │ └──────────────────┘
Note: meeting:record was NOT in either delegation's scope after expansion, so it's not in the effective scope. Even if Alice intended to grant it, she wrote meeting:* which expands to the 5 non-sensitive scopes only.This is why the canonical vocabulary matters. The verifier knows exactly what each scope means,
how each wildcard expands, and which scopes are sensitive. Implementations of expandScopes()
and intersectScopes() are byte-identical across all four SDKs and validated by the conformance
fixtures.
Custom scopes
Section titled “Custom scopes”For application-specific rights not covered by the canonical set, use the custom: extension
pattern:
custom:<your-namespace>:<verb>[:<resource>]Examples:
custom:acme:inventory:readcustom:acme:warehouse:pickupcustom:bigco:hr:approve_ptocustom:globex:trading:executeThe verifier treats custom scopes as opaque strings. It checks them with literal string equality (no wildcard expansion, no domain semantics). The application is responsible for interpreting what its own custom scopes mean.
Custom scopes are not sensitive by default. If your application has a custom scope that should never ride a wildcard, just don’t ever issue a wildcard delegation in the first place; the canonical wildcards are scoped to the canonical domain prefixes only.
import { SCOPE_MEETING_ATTEND, // "meeting:attend" SCOPE_FILES_WRITE, // "files:write" — sensitive expandScopes, intersectScopes, isSensitive, validateScopes, CUSTOM_SCOPE_PREFIX, // "custom:"} from "@identities-ai/ratify-protocol";
expandScopes(["meeting:*"]);// → ["meeting:attend", "meeting:speak", "meeting:video", "meeting:chat", "meeting:share_screen"]// (NOT meeting:record — that's sensitive)
intersectScopes(["meeting:*"], ["meeting:attend", "meeting:record"]);// → ["meeting:attend"] (record dropped because not in expanded LHS)
isSensitive("files:write");// → true
validateScopes(["custom:acme:inventory:read"]);// → null (valid)
validateScopes(["MEETING:ATTEND"]);// → "scope must be lowercase: MEETING:ATTEND"Equivalent constants exist in the Go, Python, and Rust SDKs.
Source
Section titled “Source”The full scope vocabulary with sensitivity flags and wildcard expansion rules is normative — SPEC.md §9. The vocabulary is frozen for v1.x; any new scope in v1 requires a minor version bump. New sensitive scopes can ship in minor versions only if they default to non-wildcard-eligible.
Where to next
Section titled “Where to next”- Constraints — additional gates on top of scopes (geo, time, version)
- Delegate, Present, Verify — how scopes fit into the verifier