The tenant API can tell a client what the caller is allowed to do, so an admin UI can stop offering operations that will be refused. me { permissions } covers the tenant-wide ones and project { permissions } the per-project ones.

Available since 2.2

IdentityGlobalPermissions previously carried only canCreateProject and canDeployEntrypoint. The remaining fields and the whole ProjectPermissions type ship in engine 2.2.

Advisory, not authorization

These flags mirror the ACL; they do not apply it. Every operation is checked again when it runs, so a true here grants nothing and a client that ignores them is not less secure — only less pleasant. Never use them as a substitute for a server-side check.

Why it exists

Permission failure is not uniform across this API, so a client cannot discover its own rights by trying:

BehaviourWhere
Answers an empty listglobalApiKeys, persons, project.members, project.apiKeys, project.secrets
Throws ForbiddenErrorconfiguration, authPolicies, identityProviders, mailTemplates, authLog
Throws ForbiddenErrorevery mutation — a denial is never reported as ok: false

An empty list is indistinguishable from "there is nothing here", so a UI reading globalApiKeys cannot tell a tenant with no keys from a caller who may not see them. Probing with a mutation is worse: it would have to perform the write to find out.

Tenant-wide

query {
  me {
    permissions {
      canCreateProject
      canDeployEntrypoint
      canViewConfiguration
      canListIdentityProviders
      canListMailTemplates
      canManageConfiguration
      canViewAuthLog
      canListPersons
      canListGlobalApiKeys
      canCreateGlobalApiKey
    }
  }
}
FieldPermissionCovers
canCreateProjectproject:createcreateProject
canDeployEntrypointentrypoint:deployEntrypointdeploy
canViewConfigurationsystem:viewConfigconfiguration
canListIdentityProvidersidp:listidentityProviders
canListMailTemplatesmailTemplate:listmailTemplates
canManageConfigurationsystem:configureconfigure, auth-policy mutations, and authPolicies
canViewAuthLogsystem:viewAuthLogauthLog
canListPersonsperson:listthe full persons listing
canListGlobalApiKeysapiKey:listglobalApiKeys
canCreateGlobalApiKeyapiKey:createGlobalcreateGlobalApiKey

canListPersons marks a full listing, not access to one. Without it persons still answers, narrowed to the members of the projects the caller administers — so hiding the listing on a false would remove something that works.

The four configuration queries do not share one gate: configuration needs system:viewConfig, authPolicies needs system:configure, and identityProviders and mailTemplates gate on idp:list and mailTemplate:list.

permissions is nullable, and is null when read on another identity whose roles the caller may not see (identity:viewPermissions). On me it is always present.

Per project

Project.permissions is evaluated for the calling identity, so it is reachable wherever a project is — including me { projects }, which is the cheap way to get them all at once:

query {
  me {
    projects {
      project {
        slug
        permissions {
          canViewMembers
          canAddMember
          canUpdateMember
          canRemoveMember
          canViewSecrets
          canSetSecret
          canCreateApiKey
          canUpdate
        }
      }
    }
  }
}
FieldPermissionCovers
canViewMembersproject:viewMembersproject.members and project.apiKeys
canAddMemberproject:addMemberinvite, unmanagedInvite, addProjectMember
canUpdateMemberproject:updateMemberupdateProjectMember
canRemoveMemberproject:removeMemberremoveProjectMember
canViewSecretsproject:viewSecretsproject.secrets
canSetSecretproject:setSecretsetProjectSecret
canCreateApiKeyapiKey:createcreateApiKey
canUpdateproject:updateupdateProject

The membership-parameterized permissions (canAddMember, canUpdateMember, canRemoveMember) are evaluated with an empty membership list — the same coarse "do you hold this at all" question that project.members asks before returning anything. Which roles the caller may actually grant is still decided per membership when the mutation runs, so a true here does not promise that every role is available.

canViewSecrets and canSetSecret are genuinely independent, and the split is not academic: the default project ACL gives a project admin the first and not the second. A UI that infers one from the other will offer a Set secret button that always fails.

Worked example

A person whose only right is the admin role in one project:

{
  "me": {
    "permissions": {
      "canCreateProject": false,
      "canDeployEntrypoint": false,
      "canViewConfiguration": false,
      "canListIdentityProviders": false,
      "canListMailTemplates": false,
      "canManageConfiguration": false,
      "canViewAuthLog": false,
      "canListPersons": false,
      "canListGlobalApiKeys": false,
      "canCreateGlobalApiKey": false
    },
    "projects": [
      {
        "project": {
          "slug": "blog",
          "permissions": {
            "canViewMembers": true,
            "canAddMember": true,
            "canUpdateMember": true,
            "canRemoveMember": true,
            "canViewSecrets": true,
            "canSetSecret": false,
            "canCreateApiKey": true,
            "canUpdate": false
          }
        }
      }
    ]
  }
}

Read straight off: no tenant administration at all, full run of one project's membership, and secrets that may be read but not written.