What a purpose is
A scope says what an agent may do. A purpose says why. A grant issued for customer due diligence at onboarding should not be usable to enrich a marketing list, even though both call the same verification tool. A grant carries one purpose from a controlled vocabulary:
Organisations add private terms under their own namespace:
x-<org>.<term>, for example x-acme-bank.kyb_refresh. Any other value is
rejected when the grant is requested, and denied by enforce() if it ever
appears in a token.
Requesting a purpose-bound grant
Passpurpose when creating the authorization request. It needs at least one
tool:<connector>:<permission> scope, because the purpose binds tool calls on
those connectors.
purpose on audit entries and on
GET /v1/grants/:id), inherited by delegated grants, and kept when the grant
token is refreshed.
How the purpose travels in the token
Grant tokens carry the purpose inauthorization_details (RFC 9396), one
entry per connector named by the grant’s scopes:
tools (tool names, or prefixes ending
in *), caps and data_region. enforce() applies an entry’s tools list
in addition to the scopes. data_region is carried and reported but not yet
evaluated. Entries of other types are ignored. A claim that cannot be read
unambiguously (not an array, an entry without type, an unknown key in a
urn:grantex:tools:v1 entry, or two entries for the same connector) denies
every call with token_invalid / malformed_authorization_details.
Restricting a tool to purposes
A manifest declares which purposes a tool may be called for withallowed_purposes (see the manifest 0.6 schema in spec/manifest-0.6.md):
Matching rules
Patterns match whole dot-separated segments.- A pattern without a wildcard matches only the identical purpose.
aml.cdd.ongoingmatchesaml.cdd.ongoingand nothing else. prefix.*matches a purpose that starts with all ofprefix’s segments and has at least one more.aml.cdd.*matchesaml.cdd.onboardingandaml.cdd.ongoing; it does not matchaml.cddx(a different segment) oraml.cdditself.aml.*does not matchaml. A wildcard stands for at least one further segment, so a pattern never matches its own prefix.amlalone is not a purpose in the vocabulary, and treatingaml.*as covering it would let a vague purpose satisfy every specific restriction below it.- A wildcard is only allowed as the whole last segment:
*,aml.*.onboardingandaml.cdd*are rejected when the manifest loads. - Matching is case-sensitive and purposes are lower case, so
AML.cdd.onboardingnever matches.
What enforce() does
For a tool that declares allowed_purposes, after the scope and permission
checks:
result.purpose (result.purpose in TypeScript as well) reports the grant’s
purpose for the connector on allowed and denied results, so it can be written
to your own audit records.
Compatibility
- Tools without
allowed_purposesbehave exactly as before, whatever purpose the grant carries or lacks. - Requests without
purposeissue the same tokens as before (noauthorization_detailsentry is added). - The purpose columns added to
auth_requests,grantsandaudit_entriesare nullable; existing rows have no purpose. - Purpose is not part of the audit entry hash. It is stamped from the grant
record, which the entry already references by
grantId. - Offline consent bundles and the OAuth agent-grants profile do not accept a purpose yet.