Skip to content

Scopes

A scope is a string that names a specific action an agent is authorized to take. Every DelegationCert.scope field is a list of scope strings, and the verifier checks that the action being attempted is covered by the chain’s effective scope (the lex-sorted intersection of every cert in the chain).

The full canonical vocabulary at v1.0.0-alpha.7:

┌──────────────────────────────────────────────────────────────┐
│ Ratify v1 scope vocabulary │
│ │
│ 53 canonical scopes • 14 wildcards • custom: extension │
└──────────────────────────────────────────────────────────────┘

Stability: every scope listed below is stable in v1. New scopes can be added in minor versions without breaking existing certs; nothing on this page is going to be renamed or removed.

Without one, every integrator invents their own scope strings — read, read:files, files.read, Files.Read, FILE_READ — all meaning the same thing and none of them cross-compatible. That’s the OAuth scope graveyard. Ratify fixes it by having the protocol define the set: every SDK exports the same constants, every verifier recognizes the same vocabulary, every issuer signs against the same strings.

The full list lives in scope.go (Go), scope.ts (TypeScript), scope.py (Python), scope.rs (Rust), and ratify.h (C/C++). All five are byte-for-byte aligned via the conformance suite.

The 53 canonical scopes group into 16 first-level domains. Sensitive scopes are marked with a 🔒 — they are NEVER introduced by wildcard expansion (§9.1) and must always be granted explicitly.

ScopePurpose
meeting:attendJoin a meeting as a listener
meeting:speakSpeak / transmit audio in a meeting
meeting:videoTransmit video in a meeting
meeting:chatPost messages in meeting chat
meeting:share_screenShare screen in a meeting
meeting:record 🔒Record a meeting
ScopePurpose
comms:message:readRead direct / group messages
comms:message:sendSend messages
comms:message:delete 🔒Delete sent messages
comms:email:readRead email
comms:email:sendSend email
comms:email:delete 🔒Delete email
comms:calendar:readRead calendar events
comms:calendar:writeCreate / modify calendar events
ScopePurpose
files:readRead files
files:write 🔒Create / modify files
ScopePurpose
identity:provePresent proof of identity (used for authn flows)
identity:delegate 🔒Sub-delegate authority to another agent. Required on the parent cert for any sub-delegation to be accepted.
ScopePurpose
transact:purchaseBuy a good or service
transact:sellSell a good or service
ScopePurpose
payments:sendInitiate an outbound payment
payments:receiveAccept an inbound payment
payments:authorize 🔒Authorize movement of funds — separated from send so a delegation can be “may receive but never debit”
ScopePurpose
contract:readRead contract terms
contract:sign 🔒Enter into a binding agreement
ScopePurpose
data:readRead data records
data:write 🔒Create or modify data records
data:delete 🔒Delete data records
data:export 🔒Bulk export — data-exfiltration concern, separated from read
data:shareShare specific records with a third party
ScopePurpose
execute:toolInvoke a tool / function call (MCP-style)
execute:code 🔒Execute arbitrary code on the principal’s behalf
ScopePurpose
generate:contentGenerate content (text, image, etc.)
generate:deepfake 🔒Generate content intended to impersonate someone — separated so platforms can refuse this scope universally
ScopePurpose
physical:enterEnter a physical zone (badge access, geofence cross)
physical:exitExit a physical zone
physical:actuate 🔒Activate a physical actuator — valve, motor, switch, robot end-effector
physical:manipulate 🔒Manipulate physical objects (grasping, moving, assembling)
ScopePurpose
robot:operateGeneral robot operation — sensor readings, state queries
robot:moveAutonomous locomotion (the robot may move in space)
robot:interactInteract with humans or other robots (gesture, speech, handoff)

Robot scopes are not individually marked sensitive, but high-stakes robot operations (picking up a knife, opening a valve) should be composed with physical:manipulate 🔒 or physical:actuate 🔒 so the human consent is explicit.

ScopePurpose
drone:fly 🔒Fly a drone — sensitive because of airspace + safety risks
drone:deliverDeliver a package via drone
drone:captureCapture imagery / sensor data from a drone
ScopePurpose
vehicle:operate 🔒Operate a vehicle (drive, ride, command)
vehicle:transportUse a vehicle as a passenger / payload carrier
vehicle:chargeInitiate vehicle charging or refueling
ScopePurpose
infrastructure:monitorRead infrastructure metrics (temperature, flow, voltage)
infrastructure:control 🔒Control infrastructure systems (HVAC, power, water)
infrastructure:access 🔒Gain administrative access to infrastructure
ScopePurpose
actuate:valve 🔒Open / close a physical valve
actuate:motor 🔒Energize / de-energize a motor
actuate:switch 🔒Flip an electrical switch

Every actuator scope is sensitive by design — physical-world actions get explicit delegation.

Sensitive scopes — what the marker means

Section titled “Sensitive scopes — what the marker means”

There are 22 sensitive scopes in v1 (every 🔒 above). The protocol enforces three rules around them:

  1. No wildcard introduction. A wildcard like comms:* expands to its non-sensitive members only. Granting comms:* does NOT include comms:message:delete 🔒 or comms:email:delete 🔒 — those must be listed explicitly in the cert’s scope.
  2. No identity:delegate shortcut. Even with identity:delegate 🔒 on the parent cert, the child cert can only sub-delegate scopes the parent itself held. The sub-delegation gate (delegation_not_authorized) keeps the chain bounded.
  3. Verifier UX recommendation. Verifiers SHOULD highlight sensitive scopes in any user-facing prompt (“Agent wants to delete your messages, are you sure?”) even when the delegation is valid.

The authoritative list is sensitiveScopes in the Go reference. All five SDKs expose is_sensitive(scope) / IsSensitive(scope) / isSensitive(scope) — in C, use ratify_scope_is_sensitive(scope).

For ergonomics, Ratify defines 14 wildcards that expand into a set of non-sensitive scopes from the same domain:

WildcardExpands to (non-sensitive members only)
meeting:*meeting:attend, meeting:speak, meeting:video, meeting:chat, meeting:share_screen (excludes meeting:record 🔒)
comms:message:*comms:message:read, comms:message:send (excludes comms:message:delete 🔒)
comms:email:*comms:email:read, comms:email:send (excludes comms:email:delete 🔒)
comms:*All of comms:message:*, comms:email:*, and comms:calendar:read + comms:calendar:write
transact:*transact:purchase, transact:sell
payments:*payments:send, payments:receive (excludes payments:authorize 🔒)
data:*data:read, data:share (excludes data:write 🔒, data:delete 🔒, data:export 🔒)
execute:*execute:tool (excludes execute:code 🔒)
generate:*generate:content (excludes generate:deepfake 🔒)
physical:*physical:enter, physical:exit (excludes physical:actuate 🔒, physical:manipulate 🔒)
robot:*robot:operate, robot:move, robot:interact
drone:*drone:deliver, drone:capture (excludes drone:fly 🔒)
vehicle:*vehicle:transport, vehicle:charge (excludes vehicle:operate 🔒)
infrastructure:*infrastructure:monitor (excludes infrastructure:control 🔒, infrastructure:access 🔒)

files:*, identity:*, contract:*, and actuate:* are not defined as wildcards — every scope in those domains is either sensitive or singleton, so wildcards would either expand to nothing useful or introduce a sensitive scope (which the protocol forbids).

When a wildcard appears in a DelegationCert.scope list, the SDK expands it during verification — the cert’s signable bytes still contain the wildcard form, but the effective scope used for intersection / required-scope checks contains the expanded set.

If you need an application-specific scope outside the canonical set, prefix it with custom::

custom:acme:invoice:approve
custom:my-platform:moderate-content
custom:internal:archive:purge

Custom scopes:

  • Are accepted by validate_scopes() / ValidateScopes() without lookup against the canonical list.
  • Pass through expand_scopes() / ExpandScopes() unchanged (they don’t participate in wildcard expansion).
  • Are treated as non-sensitive by default unless the application opts in via out-of-band policy.
  • Do not have cross-application meaning — custom:acme:invoice:approve issued by Acme Corp is meaningless to a verifier outside Acme’s ecosystem.

The intent is to let teams ship Ratify-secured features that aren’t yet part of the canonical vocabulary, without forking the protocol. If a custom scope sees broad adoption, file a proposal to add it to the canonical set in a minor version.

When a chain has more than one cert, the effective scope is the lex-sorted intersection of every cert’s expanded scope list:

Alice → Agent A: ["meeting:attend", "meeting:speak", "identity:delegate"]
Agent A → Agent B: ["meeting:attend"]
Effective scope of [A→B, Alice→A]: ["meeting:attend"]

Two implications:

  1. A child can never have more than the parent. The intersection is monotonically shrinking.
  2. identity:delegate doesn’t propagate downward. Even though Alice granted A identity:delegate, B’s effective scope doesn’t include it — A didn’t grant it onward. B cannot mint further sub-delegations.

This is the foundation of Ratify’s least-privilege story: every link in the chain can only narrow, never broaden, what the previous link granted.

All five SDKs export the canonical scope strings as constants. Use them so the compiler / linter catches typos before they reach the verifier:

// Go
import "github.com/identities-ai/ratify-protocol"
ratify.ScopeMeetingAttend // "meeting:attend"
ratify.ScopeIdentityDelegate // "identity:delegate"
ratify.IsSensitive(s) // bool
ratify.ExpandScopes(scopes) // []string with wildcards expanded
ratify.IntersectScopes(a, b, c) // []string lex-sorted intersection
// TypeScript
import {
SCOPE_MEETING_ATTEND,
SCOPE_IDENTITY_DELEGATE,
isSensitive,
expandScopes,
intersectScopes,
} from "@identities-ai/ratify-protocol";
# Python
from ratify_protocol import (
SCOPE_MEETING_ATTEND, SCOPE_IDENTITY_DELEGATE,
is_sensitive, expand_scopes, intersect_scopes,
)
// Rust
use ratify_protocol::{
SCOPE_MEETING_ATTEND, SCOPE_IDENTITY_DELEGATE,
is_sensitive, expand_scopes, intersect_scopes,
};