On this page
Build & explore / Permissions
Describe access.
Share it precisely.
Permissions are declared by the app that owns them. Each declaration defines a template, typed values, validation rules and the words people see when they share access.
A context identifies the app that owns a permission.
Every AXUS ID user/AUID can be an app. A permission is identified by both its context and its key. The same key in two app contexts represents two different permissions. An app’s context is its owner AUID; it is separate from the account receiving or sharing access.
AXUS ID system permissions live in the system context, currently 3 on this website. Omitting permissionContext in checks or delegations selects the engine’s system context. Discovery and descriptions always require an explicit contextAuid.
System keys keep their existing meanings: identity.<auid>.username.write, .variation.write, .password.change, .token.issue, .grants.read, .grants.delegate, .mfa.read, .mfa.write, .parents.write, .parents.agree, .ratelimit.drain, and identity.<context>.create.
AXUS ID resolves account AUIDs to default usernames in system permission descriptions and parameter values when one is available. For example, identity.123.ratelimit.drain can display “Use rate-limit budget of @likespro”. If the account has no username, the description shows its AUID.
A template defines which keys exist.
A declaration such as section.{section}.posts.create allows concrete keys such as section.news.posts.create. Each placeholder has a matching parameter definition. Types are STRING, INTEGER, LONG, DOUBLE, BOOLEAN and AUID.
Parameters can restrict allowed values, regex, text length and numeric range. An optional JavaScript invariant checks combinations; a dynamic validator can check large or changing sets at grant time. Permission titles and descriptions can include {param} interpolation. Parameter labels, descriptions, icons and hints explain individual choices. An app with a validator URL can also personalize permission and parameter text for concrete bindings.
Bindings occupy individual dot-separated segments. Keep them as strings to preserve LONG values and nested AUIDs such as 1,2. A value containing a dot adds a segment and will not match the template. DOUBLE values can use exponent notation without a dot, such as 125e-2 for 1.25. Boolean values are true or false.
The current declaration summary and parameter-options APIs do not return parameter types, constraints or allowWildcard. Keep published source definitions in your app. A picker can use enums/search and text inputs, then let the engine validate its preview. Read the publishing guide to define permissions.
Wildcards belong to parameters.
section.*.posts.create is valid only if the declaration’s section parameter allows wildcards. It covers the same declared action across section values. It does not cover posts.delete or arbitrary descendants.
Legacy stored grants such as identity.1.* and bare * cannot be created through delegation. Bare * remains valid in login/token permission scopes, where it means everything the caller holds in the token’s context. AXUS ID now grants concrete account permissions directly; a legacy identity.<auid>.* grant may still appear while accounts are migrated. Share a concrete declared capability instead.
Build a sharing picker from declarations.
- Use
receivedPermissionContextsto suggest app contexts without loading every grant. It lists contexts with stored ALLOW identity grants, including grants that may be inactive. UsesearchUsernamesfor account suggestions; escape typed text as a regex literal and cap results. - Load
permissionTreeand declaration summaries for the selected app. Render groups, declarations and parameter options in the order returned; apps can setorderon each declaration. Discovery alone does not prove the signed-in account holds access. - Use
searchPermissionValuesfor dynamic parameters. Debounce searches and discard stale responses. Honordegraded: truewith raw input and a “Validated by app” hint. - Build a key from the selected bindings. Call
describePermissionfor a live preview, including personalized text from the app when available, andcheckPermissionfor effective access, with the same context. - Review the recipient and values. In your own app context, call
issuePermission; in another app’s context, calldelegatePermission. Validation happens again at grant time; a preview is not a promise that a later grant will succeed.
query DiscoverPermissions($app: ID!) {
permissionDeclarations(contextAuid: $app) {
id context name version template title description icon validatorUrl order
}
permissionTree(contextAuid: $app) {
keyPrefix title
children {
keyPrefix title declarations
params {
name label dynamic degraded
values { value label icon description }
}
}
}
}query SearchValues($app: ID!, $declaration: ID!, $query: String!) {
searchPermissionValues(contextAuid: $app, declarationId: $declaration,
param: "subject", query: $query, limit: 20) {
name label dynamic degraded
values { value label icon description }
}
}query PreviewPermission($app: ID!, $account: ID!, $key: String!) {
describePermission(contextAuid: $app, permission: $key) {
key context declarationId title description icon
params { name value label description icon hint valueLabel valueIcon }
}
checkPermission(auid: $account, permission: $key, permissionContext: $app) {
allowed reason
}
}mutation SharePermission($from: ID!, $to: ID!, $app: ID!, $key: String!) {
delegatePermission(granterAuid: $from, granteeAuid: $to,
permission: $key, permissionContext: $app) {
id permission permissionContext granteeAuid activationState
}
}Send the native AXUS token as Authorization: Bearer …. Issuing permissions requires identity.<issuer>.grants.delegate and a declaration in the issuer’s own context; no self-grant is needed. Read issued account grants with issuedGrants and remove them with revokeGrant without granterAuid. Sharing another app’s permissions requires the granter’s system capability identity.<granter>.grants.delegate and the granter must hold the requested permission. Reading incoming and outgoing grants requires identity.<auid>.grants.read. Grant results include nullable permissionContext; null means system context. Use that context in later checks and displays.
OAuth scopes and app contexts.
The four OIDC scopes control identity claims and refresh tokens. Unprefixed custom OAuth scopes use system-context declared keys. To request another app’s permission, use app:<app AUID>:<permission key>, for example app:5:posts.read. One authorization can include keys from several contexts. The app AUID identifies the declaration owner; the OAuth client ID does not select a permission context. The consent screen names each context, and the native token receives only the approved permissions in each context. A bare * can be requested in only one context per authorization.
Use scope for mandatory permissions, optional_scope for permissions the user can toggle, and conditional_scope for permissions required only if the account holds them. Each list is space-separated and a scope must occur in only one list. Optional scopes can include OIDC scopes; conditional scopes are AXUS permissions only. Missing mandatory permissions return access_denied without an authorization code. Missing optional and conditional permissions are omitted.
scope=openid app:5:posts.read
optional_scope=app:5:posts.write
conditional_scope=app:5:posts.moderateAvailability is checked again when consent is submitted. Only the approved scopes reach the tokens and the token response’s scope field, including OIDC scopes. Apps must inspect that field before enabling optional features. Declined optional scopes prompt again if requested later; prompt=consent lets the user review previous approval. A bare wildcard means whatever access the user holds in that context, even if empty.
For the example above, a user with read and moderate access can turn write access off and still complete sign-in. The token response includes openid app:5:posts.read app:5:posts.moderate. If moderate access is absent too, the result is openid app:5:posts.read. If read access is absent, the callback receives access_denied, state and an explanation; no code or tokens are issued.
Keep the approved scope set with your app’s server session when you use it to show features, and read the returned set again after refresh. Your protected APIs must still enforce access on every operation. Follow the OAuth flow walkthrough for request construction, callback handling and feature checks.
The website reuses consent when the existing approval covers every currently available requested scope. A token wildcard does not include OIDC scopes. Undeclared keys or invalid bindings produce invalid_scope; validator outages produce server_error. Existing sessions are retained so the person can correct the request.
Check the app token when authorizing an API request.
checkPermission(auid, permission, permissionContext) checks the account’s holdings and requires the caller’s system permission identity.<auid>.grants.read. Consent uses that account check. For a protected API, require the approved OAuth scope and call checkTokenPermission(permission, permissionContext) with the app’s native axus_access_token. It evaluates the bearer’s effective access, including its scopes and delegation chain, without requiring account introspection rights.
query CheckAppToken($app: ID!, $key: String!) {
checkTokenPermission(permission: $key, permissionContext: $app) {
allowed reason
}
}An account-level allow can coexist with a token-level denial: the token may have narrower scopes or a revoked parent. Declaration descriptions establish which keys exist; publishing a declaration creates no grant. Never substitute an operator token when the user token is absent, and distinguish a denied permission from a failed check while rejecting access in both cases.
A browser session must hold the app-context permission before it can delegate it to an OAuth token. AXUS ID primary login requests ["*", "app:*:*"] for the system context and currently received app contexts, with access still bounded by the account’s live grants. The second option is rejected in OAuth requests and token-to-token login. Deploy engine support first, then sign in to AXUS ID again; old browser sessions do not gain new contexts through OAuth refresh.
Store replacement scopes after refresh, including empty or OIDC-only sets. Refresh does not approve newly available conditional access; request authorization through consent again. Compare the exact account or token subject, context, key, endpoint, caller credential and timestamp when investigating different verdicts. A generic access_denied can also represent an engine authorization failure during issuance, so it does not by itself prove that an account lacks a grant.
Permission API signatures.
# Queries
receivedPermissionContexts(auid: ID!): [ID!]!
searchUsernames(regex: String!, limit: Int = 20): [UsernameMatch!]!
checkPermission(auid: ID!, permission: String!, permissionContext: ID): PermissionCheck!
checkTokenPermission(permission: String!, permissionContext: ID): PermissionCheck!
permissionDeclarations(contextAuid: ID!): [PermissionDeclaration!]!
describePermission(contextAuid: ID!, permission: String!): DescribedPermission!
permissionTree(contextAuid: ID!): [PermissionTreeNode!]!
searchPermissionValues(contextAuid: ID!, declarationId: ID!, param: String!, query: String!, limit: Int): ParamOption!
# Mutations
delegatePermission(granterAuid: ID!, granteeAuid: ID!, permission: String!, permissionContext: ID): PermissionGrant!
publishPermissionDeclaration(ownerAuid: ID!, declaration: PermissionDeclarationInput!): PermissionDeclaration!
notifyValidationChanged(declarationId: ID!): Boolean!Inspect every input and return type in the schema explorer. DescribedPermission.params returns labels, descriptions, hints and value labels/icons; ParamOption returns values plus dynamic/degraded state.
Validation errors reject grants.
| extensions.code | What to do |
|---|---|
UNDECLARED_PERMISSION | The key matches no declaration in the selected context. Check the context and discover current declarations. |
INVALID_PERMISSION_BINDINGS | A value has the wrong type or violates a constraint, including wildcard eligibility. |
PERMISSION_INVARIANT_VIOLATED | The combination of values fails the app’s invariant. Choose a valid combination. |
PERMISSION_DYNAMIC_REJECTED | The app’s validator rejected the values. No grant was created. |
PERMISSION_VALIDATOR_UNAVAILABLE | The validator timed out or could not be reached. The grant is rejected; retry later. |
INVALID_DECLARATION_TEMPLATE | Check placeholder definitions and reserved or empty template literals. |
DECLARATION_DUPLICATE | Refresh declarations and check the publishing name. |
INVALID_PARAM_DEF | Check parameter names, types and constraints. |
Existing NOT_AUTHORIZED and TOKEN_* meanings are unchanged. Metadata/search availability never grants access, and degraded search never bypasses a validator.
Migrate free-form permissions.
Publish declarations for each app’s permissions, replace legacy wildcard delegation with declared actions and allowed parameter wildcards, and preserve context alongside every grant key. Regenerate client SDKs and replace label guessing with permission descriptions.
The engine’s V32 migration removes old HierarchicalPermission grants. For AXUS ID system account permissions, the engine now grants each declared capability when an account is created and backfills existing accounts at startup before removing their direct account-root grants. The website does not create these grants. Publishing an app’s own declaration still does not grant it to the app or anyone else.