OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Services

keystone — REST API

REST API — keystone — branch stable/2026.2

Branch stable/2026.2 @ e6dc5ae079e6 — repository https://opendev.org/openstack/keystone — analysed on 2026-10-28

The route table below was not read from the api-ref: it was extracted from the running Flask application (app.url_map of keystone.server.flask.application.application_factory). That gives 259 method + path pairs on 125 paths. The handler of each one is the real method (decorators unwrapped), and its policy rule comes from the operations declared by each default policy rule (keystone/common/policies/). HEAD is accepted wherever GET is, and is not listed.

1. Conventions shared by every endpoint

Request processing

  • Pipeline (keystone/server/flask/core.py:39-70, applied by keystone/server/flask/core.py:93-142), in request order:
    1. ProxyFix (werkzeug);
    2. oslo CORS;
    3. RequestBodySizeLimiter;
    4. HTTPProxyToWSGI;
    5. osprofiler;
    6. RequestId;
    7. URLNormalizingMiddleware, which removes a trailing / (keystone/server/flask/request_processing/middleware/url_normalize.py:16);
    8. AuthContextMiddleware, which validates X-Auth-Token and builds the request context (keystone/server/flask/request_processing/middleware/auth_context.py). Then the Flask application runs before_request request logging and JSON body checks (keystone/server/flask/application.py:211-212).
  • **/healthcheck** is served by oslo.middleware's healthcheck application, mounted next to Flask (keystone/server/flask/application.py:228-237).
  • Body: on POST, PUT and PATCH, a body that is not JSON is rejected with 400 (keystone/server/flask/request_processing/json_body.py:23). Exceptions are application/x-www-form-urlencoded on /v3/OS-OAUTH2/token, and the federation protocol endpoints, which receive SAML/OIDC posts.
  • Every response carries Vary: X-Auth-Token (keystone/server/flask/application.py:106-110). A 204 has no Content-Type (keystone/server/flask/common.py:182).
  • No microversions: Keystone exposes a single v3 version. Discovery is GET / (300 Multiple Choices, the version list) and GET /v3 (version document). Both answer in JSON Home when the client sends Accept: application/json-home (keystone/api/discovery.py:44-65).

Authorisation

  • Every handler must call ENFORCER.enforce_call(action=...) or be marked @unenforced_api. Otherwise the request fails: _assert_rbac_enforcement_called (keystone/server/flask/common.py:160) turns a missing check into a 500.
  • enforce_call first requires an authenticated context (401 otherwise, keystone/common/rbac_enforcer/enforcer.py:300-320). It then evaluates the oslo.policy rule with the token's roles and scope; a refusal is 403 ForbiddenAction (keystone/common/rbac_enforcer/enforcer.py:102-136).
  • The default rules combine three ingredients (keystone/common/policies/base.py:47-76):
    • the token's scope: system (system_scope:all), domain or project;
    • the default roles reader ⊂ member ⊂ admin, as implied roles created by keystone-manage bootstrap;
    • ownership, for example user_id:%(target.credential.user_id)s.

Lists

  • Filters are query parameters named after an attribute, with optional comparators __contains, __icontains, __startswith, __istartswith, __endswith, __iendswith, and __eq/__neq… where supported (keystone/server/flask/common.py:903-972).
  • Pagination: limit and marker are bounded by [DEFAULT]/max_db_limit and by the per-driver list_limit. When the list is cut, the response says so: "truncated": true and a links.next URL with the next marker (keystone/server/flask/common.py:701-764).
  • Links: every entity has links.self. Every collection has links.self, links.next and links.previous.

Quirk: methods declared on a shared resource

Some resources serve the collection and the member with the same class, for example /v3/policies and /v3/OS-FEDERATION/mappings. Flask therefore lists the member methods on the collection path too, for example DELETE /v3/policies. Calling them there without an ID raises a TypeError, which the global handler turns into a **400 ValidationError** (keystone/server/flask/application.py:176-183), not a 405. A rewrite should answer 405, but must know that clients currently see 400.

2. Functional description by area

2.1 Authentication and tokens (/v3/auth/*)

**POST /v3/auth/tokens: issue a token.**

  • Purpose: authenticate a user with one or more methods and obtain a token, unscoped or scoped to a project, a domain, the system, or a trust.
  • Actor: anyone; the endpoint is not authenticated (@unenforced_api).
  • Flow (keystone/api/auth.py:320-336, keystone/api/_shared/authentication.py:208-282):
    1. The auth body is validated by validate_issue_token_auth.
    2. If the WSGI server set REMOTE_USER, the external method runs first.
    3. Each method listed in identity.methods runs its plugin, chosen from [auth]/methods (default external, password, token, oauth1, mapped, application_credential, ec2credential, keystone/conf/constants.py:19-27). A plugin may answer "more steps needed": the response is then 401 with an auth receipt (MFA, see below).
    4. Without an explicit scope, the user's default_project_id is used, if the project and its domain are enabled and the user has a role on it. Otherwise the token is unscoped (keystone/api/_shared/authentication.py:34-117).
    5. The user's MFA rules are checked. Missing methods give 401 InsufficientAuthMethods, with an Openstack-Auth-Receipt header.
    6. The token is issued (keystone/token/provider.py:276-390). Its expiry is [token]/expiration (default 3600 s), capped by the expiry of the trust or application credential in use.
    7. A trust with remaining_uses has one use consumed after issuance.
  • Response: 201 with the token ID in the X-Subject-Token header. The body holds token (user, methods, roles, scope, expires_at, issued_at, audit_ids, and catalog unless ?nocatalog).
  • Password method (keystone/auth/plugins/password.py:24-44, keystone/identity/backends/sql.py:63-123). Checks run in this order:
    1. locked account → 401 AccountLocked;
    2. wrong password → failure counter +1 and 401 "Invalid username or password";
    3. disabled user → 401 UserDisabled;
    4. expired password → 401 PasswordExpired. The lockout is driven by [security_compliance]/lockout_failure_attempts and lockout_duration. A successful authentication resets the counter and updates last_active_at.
  • Auth receipts (MFA): when a user has MFA rules and only part of them is satisfied, the 401 carries a receipt. The client sends it back in Openstack-Auth-Receipt with the missing methods (keystone/receipt/handlers.py, keystone/server/flask/application.py:128-129).

**GET/HEAD /v3/auth/tokens: validate a token** (keystone/api/auth.py:279-317).

  • The token to check is in X-Subject-Token; the caller authenticates with its own X-Auth-Token. Policy: identity:validate_token (GET) or identity:check_token (HEAD).
  • ?allow_expired=1 accepts a token expired for less than [token]/allow_expired_window (default 2 days). ?nocatalog drops the catalog.
  • A token is valid when all these hold (keystone/token/provider.py:139-275):
    • it decrypts with one of the Fernet keys (or verifies with a JWS key);
    • it has not expired;
    • it matches no revocation event;
    • its user is still enabled;
    • if it comes from an application credential with access rules, the caller sends OpenStack-Identity-Access-Rules ≥ 1.0.
  • Every failure becomes **404 TokenNotFound**, never a 401: a stale token looks like a token that does not exist.
  • Side effect: validating a token whose user was disabled writes a revocation event for that user and empties the token cache (keystone/token/provider.py:218-229).

**DELETE /v3/auth/tokens: revoke a token** (keystone/api/auth.py:338-357, keystone/token/provider.py:406-434).

  • Writes a revocation event on the token's audit_id.
  • By default (?cascade=true), it also writes one on its chain (audit_chain_id), so the scoped tokens obtained from it are revoked too, and the whole token cache is emptied. ?cascade=false limits the revocation to that token.

**Other /v3/auth endpoints**: GET /v3/auth/catalog, /v3/auth/projects, /v3/auth/domains and /v3/auth/system list, for the caller's token, its catalog and the projects, domains or system scope it could rescope to (keystone/api/auth.py:119-260). GET /v3/auth/tokens/OS-PKI/revoked is a fossil: it answers 410 if [token]/revoke_by_id = false, 403 otherwise (keystone/api/auth.py:262-276).

2.2 Users, groups, passwords

  • Users (keystone/api/users.py, keystone/identity/core.py): CRUD within a domain.
    • The identity driver is chosen per domain: SQL, or LDAP when [identity]/domain_specific_drivers_enabled points a domain to an LDAP configuration.
    • Users from LDAP or federation get a public ID computed in the id_mapping table (sha256 generator).
    • default_project_id cannot be a domain (keystone/identity/core.py:1076-1093).
    • Disabling or deleting a user triggers, through internal callbacks: revocation of the user's tokens, deletion of their application credentials and of the trusts where they are trustee, and invalidation of the caches (see async processing).
  • Passwords (POST /v3/users/{user_id}/password) is the self-service change. It is unauthenticated: the body carries the original password, which is checked. The rules come from [security_compliance]:
    • password_regex;
    • unique_last_password_count (password history);
    • minimum_password_age;
    • password_expires_days;
    • change_password_upon_first_use. Breaking one of them gives 400 PasswordValidationError and its variants (see errors).
  • Groups: CRUD and membership (/v3/groups/{group_id}/users/{user_id}). A user and a group from different backends cannot be linked (403 CrossBackendNotAllowed).
  • **DELETE /v3/users/{user_id}/tokens** revokes all of a user's tokens (policy identity:revoke_tokens_for_user, keystone/api/users.py:949).

2.3 Projects, domains, tags, domain configuration

  • Projects form a tree (parent_id). A domain is a root project with is_domain = true. The rules (keystone/resource/core.py:62-620):
    • Hierarchy:
      • maximum depth [DEFAULT]/max_project_tree_depth, or less if the limit model requires it (403);
      • a project's domain must be the domain of its parent (400);
      • no creation under a disabled project (400);
      • parent_id and is_domain cannot be changed (403 and 400).
    • Names: a name is unique within a domain (409). With [resource]/project_name_url_safe set, a name with reserved URL characters is refused (400).
    • Disabling: a project with enabled children cannot be disabled (403), unless ?cascade. In that case only enabled can change (400 otherwise).
    • Deleting: only a leaf can be deleted, unless ?cascade, which requires the whole subtree to be disabled (403). Deleting a project removes its assignments, credentials, trusts and limits (keystone/resource/core.py:521-539).
    • Immutable resources: a project, domain or role with the immutable option refuses updates and deletion (403 ResourceUpdateForbidden / ResourceDeleteForbidden).
  • Domains: an enabled domain cannot be deleted. Deleting a domain removes its projects, users, groups, assignments and federated identity providers (internal callbacks).
  • Tags: /v3/projects/{id}/tags[/{tag}], and filters tags, tags-any, not-tags, not-tags-any on GET /v3/projects.
  • Domain configuration (/v3/domains/{id}/config[...]): per-domain identity settings (an LDAP driver per domain). Only whitelisted options are allowed; sensitive ones (passwords) are stored apart and never returned (keystone/resource/core.py:1226-1600).

2.4 Roles, implied roles, assignments

  • Roles are global, or domain-specific (domain_id). A domain-specific role can only be assigned in its domain (403 DomainSpecificRoleMismatch).
  • Implied roles (/v3/roles/{prior}/implies/{implied}): a role brings others when the token is computed. The default chain is admin → member → reader. Some roles cannot be implied: [assignment]/prohibited_implied_role, admin by default (403 InvalidImpliedRole).
  • Assignments (grants): user or group × role × project, domain or system. OS-INHERIT assignments are inherited by every project under a domain or project.
  • **GET /v3/role_assignments** lists them, with ?effective (groups expanded, inheritance and implied roles applied), ?include_names and ?include_subtree. Effective assignments are cached in the computed assignments region, invalidated on every change.

2.5 Catalog, endpoint filtering, endpoint policies

  • Regions (tree, cycles refused with 400 CircularRegionHierarchyError), services and endpoints (interface = public/internal/admin, URL validated). These build the catalog returned in tokens.
  • OS-EP-FILTER: associates endpoints or endpoint groups with projects, to restrict the catalog of a project-scoped token. It needs [catalog]/driver = sql and [endpoint_filter].
  • OS-ENDPOINT-POLICY and /v3/policies: store opaque blobs (old policy files) and associate them with endpoints, services or regions. These APIs are deprecated since Queens (keystone/api/policy.py:76-96).

2.6 Credentials, EC2 and S3

  • **/v3/credentials: blobs belonging to a user, **encrypted with the credential Fernet keys ([credential]/key_repository). The ID of an EC2 credential is sha256(access). The number per user is capped by [credential]/user_limit (403).
  • Security fix on this branch (see 06): the trust_id, app_cred_id and access_token_id fields of an EC2 blob are now server-managed (keystone/api/credentials.py:144-170). Any client-supplied value is stripped, and only the context of the token used is written.
  • **POST /v3/ec2tokens** and **POST /v3/s3tokens** exchange an AWS-style signature for a Keystone token (keystone/api/_shared/EC2_S3_Resource.py). They check:
    • the signature (_check_signature);
    • the timestamp: a signed request older than [credential]/auth_ttl is refused (keystone/api/_shared/EC2_S3_Resource.py:46-68);
    • since this branch, that the credential's owner is a party to the referenced trust, or owns the application credential or the OAuth1 access token (keystone/api/_shared/EC2_S3_Resource.py:166-220). GET on these two paths answers 405.

2.7 Application credentials and access rules

  • A user creates a credential for one project with a subset of their roles (keystone/application_credential/core.py:87-150), with optional expires_at, unrestricted and access_rules (service, path, method).
    • The secret is generated if not given, returned once only, and stored hashed.
    • Requesting a role the user does not have gives 404 RoleAssignmentNotFound.
    • The number per user is capped ([application_credential]/user_limit, 403).
  • Effects elsewhere: a token obtained with an application credential:
    • expires no later than the credential;
    • carries only the credential's roles;
    • cannot create other credentials or trusts, unless unrestricted. Disabling or deleting the user, or removing their role on the project, deletes their application credentials (callbacks).

2.8 Trusts (OS-TRUST)

  • A trustor delegates roles on a project to a trustee, possibly with impersonation, an expiry, remaining_uses and re-delegation (keystone/trust/core.py:57-205).
  • Re-delegation rules (403):
    • depth within [trust]/max_redelegation_count;
    • no remaining_uses on a re-delegated trust;
    • expiry within the parent's;
    • roles a subset of the parent's;
    • no impersonation if the parent has none.
  • remaining_uses must be positive (400).
  • Deleting the trustee deletes their trusts. An exhausted trust gives 403 TrustUseLimitReached.

2.9 Federation (OS-FEDERATION)

  • Configuration:
    • identity providers (remote_ids; maximum length 255 since this branch, see 06);
    • protocols (saml2, openid, mapped…);
    • mappings: JSON rules that turn the assertion's attributes into a user, groups and projects (keystone/federation/utils.py:469-1130);
    • service providers (Keystone-to-Keystone).
  • Authentication: GET/POST /v3/OS-FEDERATION/identity_providers/{idp}/protocols/{protocol}/auth (and the WebSSO variants).
    • The web server (mod_shib, mod_auth_openidc) has already validated the assertion. Keystone reads the attributes from the WSGI environment and applies the mapping.
    • It creates or updates a shadow user (federated_user, nonlocal_user) and expiring group memberships (expiring_user_group_membership, TTL authorization_ttl).
    • It returns an unscoped federated token (keystone/auth/plugins/mapped.py:41-500).
  • Keystone as IdP: POST /v3/auth/OS-FEDERATION/saml2[/ecp] produces a SAML assertion **signed by the xmlsec1 binary** (keystone/federation/idp.py:447-541). GET /v3/OS-FEDERATION/saml2/metadata publishes the metadata.

2.10 OAuth 1.0a and OAuth 2.0

  • OAuth1 (deprecated): consumers, request token, authorisation by a user, access token, then an oauth1 token.
  • OAuth2 (POST /v3/OS-OAUTH2/token, keystone/api/os_oauth2.py:74-130):
    • grant client_credentials only (400 otherwise);
    • the client authenticates either with an application credential (client_secret_basic) or with a client certificate (tls_client_auth, mTLS);
    • in the mTLS case, the token carries the certificate thumbprint, which keystonemiddleware checks.
    • Errors follow RFC 6749 ({"error": "invalid_client", ...}, keystone/oauth2/handlers.py). The other methods answer 405.

2.11 Unified limits

  • Registered limits are a default per service, region and resource name. Limits override them per project or domain.
  • The model ([unified_limit]/enforcement_model: flat or strict_two_level, GET /v3/limits/model) checks consistency: in strict_two_level, children cannot exceed their parent, and the tree is limited to 2 levels (403 InvalidLimit).
  • A registered limit that is referenced cannot be modified (403 RegisteredLimitError).

2.12 Revocation events and certificates

  • GET /v3/OS-REVOKE/events[?since=] lists the revocation events (policy identity:list_revoke_events).
  • OS-SIMPLE-CERT is deprecated: it answers 410 (keystone/api/os_simple_cert.py).

3. Route table (extracted from the application)

Handler: the method that serves the call. Policy rule: the default rule that enforce_call evaluates (rules and check strings in _rewrite/policies_stable-2026.2.json). "none (unauthenticated)": an @unenforced_api handler. "—": no documented rule; the handler either checks nothing beyond authentication, or answers 405 or 410.

Discovery (3 operations)

MethodPathHandlerPolicy rule(s)
GET/keystone/api/discovery.py:56—
GET/static/{filename}Flask built-in static route—
GET/v3keystone/api/discovery.py:87—

Authentication and tokens (8 operations)

MethodPathHandlerPolicy rule(s)
GET/v3/auth/catalogkeystone/api/auth.py:237identity:get_auth_catalog
GET/v3/auth/domainskeystone/api/auth.py:157identity:get_auth_domains, identity:list_domains_for_user
GET/v3/auth/projectskeystone/api/auth.py:123identity:get_auth_projects
GET/v3/auth/systemkeystone/api/auth.py:189identity:get_auth_system
DELETE/v3/auth/tokenskeystone/api/auth.py:338identity:revoke_token
GET/v3/auth/tokenskeystone/api/auth.py:279identity:check_token, identity:validate_token
POST/v3/auth/tokenskeystone/api/auth.py:319none (unauthenticated)
GET/v3/auth/tokens/OS-PKI/revokedkeystone/api/auth.py:263identity:revocation_list

Federation (36 operations)

MethodPathHandlerPolicy rule(s)
GET/v3/OS-FEDERATION/domainskeystone/api/auth.py:157identity:get_auth_domains
GET/v3/OS-FEDERATION/identity_providerskeystone/api/os_federation.py:112identity:list_identity_providers
DELETE/v3/OS-FEDERATION/identity_providers/{idp_id}keystone/api/os_federation.py:194identity:delete_identity_provider
GET/v3/OS-FEDERATION/identity_providers/{idp_id}keystone/api/os_federation.py:154identity:get_identity_provider
PATCH/v3/OS-FEDERATION/identity_providers/{idp_id}keystone/api/os_federation.py:183identity:update_identity_provider
PUT/v3/OS-FEDERATION/identity_providers/{idp_id}keystone/api/os_federation.py:167identity:create_identity_provider
GET/v3/OS-FEDERATION/identity_providers/{idp_id}/protocolskeystone/api/os_federation.py:225identity:list_protocols
DELETE/v3/OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}keystone/api/os_federation.py:285identity:delete_protocol
GET/v3/OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}keystone/api/os_federation.py:243identity:get_protocol
PATCH/v3/OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}keystone/api/os_federation.py:270identity:update_protocol
PUT/v3/OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}keystone/api/os_federation.py:255identity:create_protocol
GET/v3/OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}/authkeystone/api/os_federation.py:518none (unauthenticated)
POST/v3/OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}/authkeystone/api/os_federation.py:527none (unauthenticated)
DELETE/v3/OS-FEDERATION/mappingskeystone/api/os_federation.py:379identity:delete_mapping
GET/v3/OS-FEDERATION/mappingskeystone/api/os_federation.py:303identity:list_mappings
PATCH/v3/OS-FEDERATION/mappingskeystone/api/os_federation.py:367identity:update_mapping
PUT/v3/OS-FEDERATION/mappingskeystone/api/os_federation.py:353identity:create_mapping
DELETE/v3/OS-FEDERATION/mappings/{mapping_id}keystone/api/os_federation.py:379identity:delete_mapping
GET/v3/OS-FEDERATION/mappings/{mapping_id}keystone/api/os_federation.py:303identity:get_mapping
PATCH/v3/OS-FEDERATION/mappings/{mapping_id}keystone/api/os_federation.py:367identity:update_mapping
PUT/v3/OS-FEDERATION/mappings/{mapping_id}keystone/api/os_federation.py:353identity:create_mapping
GET/v3/OS-FEDERATION/projectskeystone/api/auth.py:123identity:get_auth_projects
GET/v3/OS-FEDERATION/saml2/metadatakeystone/api/os_federation.py:499none (unauthenticated)
GET/v3/OS-FEDERATION/service_providerskeystone/api/os_federation.py:405identity:list_service_providers
DELETE/v3/OS-FEDERATION/service_providers/{service_provider_id}keystone/api/os_federation.py:488identity:delete_service_provider
GET/v3/OS-FEDERATION/service_providers/{service_provider_id}keystone/api/os_federation.py:444identity:get_service_provider
PATCH/v3/OS-FEDERATION/service_providers/{service_provider_id}keystone/api/os_federation.py:473identity:update_service_provider
PUT/v3/OS-FEDERATION/service_providers/{service_provider_id}keystone/api/os_federation.py:456identity:create_service_provider
GET/v3/auth/OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}/webssokeystone/api/auth.py:410none (unauthenticated)
POST/v3/auth/OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}/webssokeystone/api/auth.py:414none (unauthenticated)
GET/v3/auth/OS-FEDERATION/saml2keystone/api/auth.py:420—
POST/v3/auth/OS-FEDERATION/saml2keystone/api/auth.py:423none (unauthenticated)
GET/v3/auth/OS-FEDERATION/saml2/ecpkeystone/api/auth.py:440—
POST/v3/auth/OS-FEDERATION/saml2/ecpkeystone/api/auth.py:443none (unauthenticated)
GET/v3/auth/OS-FEDERATION/websso/{protocol_id}keystone/api/auth.py:391none (unauthenticated)
POST/v3/auth/OS-FEDERATION/websso/{protocol_id}keystone/api/auth.py:395none (unauthenticated)

Users, groups and passwords (19 operations)

MethodPathHandlerPolicy rule(s)
GET/v3/groupskeystone/api/groups.py:58identity:list_groups
POST/v3/groupskeystone/api/groups.py:86identity:create_group
DELETE/v3/groups/{group_id}keystone/api/groups.py:147identity:delete_group
GET/v3/groups/{group_id}keystone/api/groups.py:114identity:get_group
PATCH/v3/groups/{group_id}keystone/api/groups.py:128identity:update_group
GET/v3/groups/{group_id}/userskeystone/api/groups.py:163identity:list_users_in_group
DELETE/v3/groups/{group_id}/users/{user_id}keystone/api/groups.py:245identity:remove_user_from_group
GET/v3/groups/{group_id}/users/{user_id}keystone/api/groups.py:215identity:check_user_in_group
PUT/v3/groups/{group_id}/users/{user_id}keystone/api/groups.py:229identity:add_user_to_group
GET/v3/userskeystone/api/users.py:294identity:list_users
POST/v3/userskeystone/api/users.py:339identity:create_user
DELETE/v3/users/{user_id}keystone/api/users.py:269identity:delete_user
GET/v3/users/{user_id}keystone/api/users.py:234identity:get_user
PATCH/v3/users/{user_id}keystone/api/users.py:249identity:update_user
GET/v3/users/{user_id}/groupskeystone/api/users.py:411identity:list_groups_for_user
GET/v3/users/{user_id}/passwordkeystone/api/users.py:361none (unauthenticated)
POST/v3/users/{user_id}/passwordkeystone/api/users.py:366none (unauthenticated)
GET/v3/users/{user_id}/projectskeystone/api/users.py:392identity:list_user_projects
DELETE/v3/users/{user_id}/tokenskeystone/api/users.py:949identity:revoke_tokens_for_user

Application credentials and access rules (7 operations)

MethodPathHandlerPolicy rule(s)
GET/v3/users/{user_id}/access_ruleskeystone/api/users.py:874identity:list_access_rules
DELETE/v3/users/{user_id}/access_rules/{access_rule_id}keystone/api/users.py:926identity:delete_access_rule
GET/v3/users/{user_id}/access_rules/{access_rule_id}keystone/api/users.py:904identity:get_access_rule
GET/v3/users/{user_id}/application_credentialskeystone/api/users.py:741identity:list_application_credentials
POST/v3/users/{user_id}/application_credentialskeystone/api/users.py:763identity:create_application_credential
DELETE/v3/users/{user_id}/application_credentials/{application_credential_id}keystone/api/users.py:851identity:delete_application_credential
GET/v3/users/{user_id}/application_credentials/{application_credential_id}keystone/api/users.py:828identity:get_application_credential

Credentials, EC2 and S3 (13 operations)

MethodPathHandlerPolicy rule(s)
GET/v3/credentialskeystone/api/credentials.py:173identity:get_credential, identity:list_credentials
POST/v3/credentialskeystone/api/credentials.py:217identity:create_credential
DELETE/v3/credentials/{credential_id}keystone/api/credentials.py:315identity:delete_credential
GET/v3/credentials/{credential_id}keystone/api/credentials.py:268identity:get_credential
PATCH/v3/credentials/{credential_id}keystone/api/credentials.py:287identity:update_credential
GET/v3/ec2tokenskeystone/api/_shared/EC2_S3_Resource.py:34none (unauthenticated)
POST/v3/ec2tokenskeystone/api/ec2tokens.py:64identity:ec2tokens_validate
GET/v3/s3tokenskeystone/api/_shared/EC2_S3_Resource.py:34none (unauthenticated)
POST/v3/s3tokenskeystone/api/s3tokens.py:102identity:s3tokens_validate
GET/v3/users/{user_id}/credentials/OS-EC2keystone/api/users.py:456identity:ec2_list_credentials
POST/v3/users/{user_id}/credentials/OS-EC2keystone/api/users.py:473identity:ec2_create_credential
DELETE/v3/users/{user_id}/credentials/OS-EC2/{credential_id}keystone/api/users.py:533identity:ec2_delete_credential
GET/v3/users/{user_id}/credentials/OS-EC2/{credential_id}keystone/api/users.py:516identity:ec2_get_credential

Projects and domains (29 operations)

MethodPathHandlerPolicy rule(s)
GET/v3/domainskeystone/api/domains.py:135identity:list_domains
POST/v3/domainskeystone/api/domains.py:158identity:create_domain
DELETE/v3/domains/{domain_id}keystone/api/domains.py:116identity:delete_domain
GET/v3/domains/{domain_id}keystone/api/domains.py:87identity:get_domain
PATCH/v3/domains/{domain_id}keystone/api/domains.py:101identity:update_domain
DELETE/v3/domains/{domain_id}/configkeystone/api/domains.py:245identity:delete_domain_config
GET/v3/domains/{domain_id}/configkeystone/api/domains.py:190identity:get_domain_config
PATCH/v3/domains/{domain_id}/configkeystone/api/domains.py:230identity:update_domain_config
PUT/v3/domains/{domain_id}/configkeystone/api/domains.py:270identity:create_domain_config
DELETE/v3/domains/{domain_id}/config/{group}keystone/api/domains.py:245identity:delete_domain_config
GET/v3/domains/{domain_id}/config/{group}keystone/api/domains.py:190identity:get_domain_config
PATCH/v3/domains/{domain_id}/config/{group}keystone/api/domains.py:230identity:update_domain_config
DELETE/v3/domains/{domain_id}/config/{group}/{option}keystone/api/domains.py:245identity:delete_domain_config
GET/v3/domains/{domain_id}/config/{group}/{option}keystone/api/domains.py:190identity:get_domain_config
PATCH/v3/domains/{domain_id}/config/{group}/{option}keystone/api/domains.py:230identity:update_domain_config
GET/v3/domains/config/{group}/{option}/defaultkeystone/api/domains.py:335identity:get_domain_config_default
GET/v3/domains/config/{group}/defaultkeystone/api/domains.py:324identity:get_domain_config_default
GET/v3/domains/config/defaultkeystone/api/domains.py:313identity:get_domain_config_default
GET/v3/projectskeystone/api/projects.py:162identity:list_projects
POST/v3/projectskeystone/api/projects.py:201identity:create_project
DELETE/v3/projects/{project_id}keystone/api/projects.py:138identity:delete_project
GET/v3/projects/{project_id}keystone/api/projects.py:105identity:get_project
PATCH/v3/projects/{project_id}keystone/api/projects.py:120identity:update_project
DELETE/v3/projects/{project_id}/tagskeystone/api/projects.py:281identity:delete_project_tags
GET/v3/projects/{project_id}/tagskeystone/api/projects.py:249identity:list_project_tags
PUT/v3/projects/{project_id}/tagskeystone/api/projects.py:263identity:update_project_tags
DELETE/v3/projects/{project_id}/tags/{value}keystone/api/projects.py:335identity:delete_project_tag
GET/v3/projects/{project_id}/tags/{value}keystone/api/projects.py:297identity:get_project_tag
PUT/v3/projects/{project_id}/tags/{value}keystone/api/projects.py:311identity:create_project_tag

Roles, implied roles and assignments (53 operations)

MethodPathHandlerPolicy rule(s)
DELETE/v3/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projectskeystone/api/os_inherit.py:169identity:revoke_grant
GET/v3/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projectskeystone/api/os_inherit.py:123identity:check_grant
PUT/v3/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projectskeystone/api/os_inherit.py:146identity:create_grant
GET/v3/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/inherited_to_projectskeystone/api/os_inherit.py:194identity:list_grants
DELETE/v3/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projectskeystone/api/os_inherit.py:263identity:revoke_grant
GET/v3/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projectskeystone/api/os_inherit.py:217identity:check_grant
PUT/v3/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projectskeystone/api/os_inherit.py:240identity:create_grant
GET/v3/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/inherited_to_projectskeystone/api/os_inherit.py:288identity:list_grants
DELETE/v3/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projectskeystone/api/os_inherit.py:428identity:revoke_grant
GET/v3/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projectskeystone/api/os_inherit.py:382identity:check_grant
PUT/v3/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projectskeystone/api/os_inherit.py:405identity:create_grant
DELETE/v3/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projectskeystone/api/os_inherit.py:357identity:revoke_grant
GET/v3/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projectskeystone/api/os_inherit.py:311identity:check_grant
PUT/v3/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projectskeystone/api/os_inherit.py:334identity:create_grant
GET/v3/OS-TRUST/trusts/{trust_id}/roleskeystone/api/trusts.py:448identity:list_roles_for_trust
GET/v3/OS-TRUST/trusts/{trust_id}/roles/{role_id}keystone/api/trusts.py:503identity:get_role_for_trust
GET/v3/domains/{domain_id}/groups/{group_id}/roleskeystone/api/domains.py:434identity:list_grants
DELETE/v3/domains/{domain_id}/groups/{group_id}/roles/{role_id}keystone/api/domains.py:496identity:revoke_grant
GET/v3/domains/{domain_id}/groups/{group_id}/roles/{role_id}keystone/api/domains.py:457identity:check_grant
PUT/v3/domains/{domain_id}/groups/{group_id}/roles/{role_id}keystone/api/domains.py:476identity:create_grant
GET/v3/domains/{domain_id}/users/{user_id}/roleskeystone/api/domains.py:348identity:list_grants
DELETE/v3/domains/{domain_id}/users/{user_id}/roles/{role_id}keystone/api/domains.py:410identity:revoke_grant
GET/v3/domains/{domain_id}/users/{user_id}/roles/{role_id}keystone/api/domains.py:371identity:check_grant
PUT/v3/domains/{domain_id}/users/{user_id}/roles/{role_id}keystone/api/domains.py:390identity:create_grant
GET/v3/projects/{project_id}/groups/{group_id}/roleskeystone/api/projects.py:579identity:list_grants
DELETE/v3/projects/{project_id}/groups/{group_id}/roles/{role_id}keystone/api/projects.py:550identity:revoke_grant
GET/v3/projects/{project_id}/groups/{group_id}/roles/{role_id}keystone/api/projects.py:499identity:check_grant
PUT/v3/projects/{project_id}/groups/{group_id}/roles/{role_id}keystone/api/projects.py:524identity:create_grant
GET/v3/projects/{project_id}/users/{user_id}/roleskeystone/api/projects.py:474identity:list_grants
DELETE/v3/projects/{project_id}/users/{user_id}/roles/{role_id}keystone/api/projects.py:445identity:revoke_grant
GET/v3/projects/{project_id}/users/{user_id}/roles/{role_id}keystone/api/projects.py:394identity:check_grant
PUT/v3/projects/{project_id}/users/{user_id}/roles/{role_id}keystone/api/projects.py:419identity:create_grant
GET/v3/role_assignmentskeystone/api/role_assignments.py:39identity:list_role_assignments, identity:list_role_assignments_for_tree
GET/v3/role_inferenceskeystone/api/role_inferences.py:29identity:list_role_inference_rules
GET/v3/roleskeystone/api/roles.py:44identity:list_domain_roles, identity:list_roles
POST/v3/roleskeystone/api/roles.py:74identity:create_domain_role, identity:create_role
GET/v3/roles/{prior_role_id}/implieskeystone/api/roles.py:221identity:list_implied_roles
DELETE/v3/roles/{prior_role_id}/implies/{implied_role_id}keystone/api/roles.py:311identity:delete_implied_role
GET/v3/roles/{prior_role_id}/implies/{implied_role_id}keystone/api/roles.py:268identity:get_implied_role
PUT/v3/roles/{prior_role_id}/implies/{implied_role_id}keystone/api/roles.py:296identity:create_implied_role
DELETE/v3/roles/{role_id}keystone/api/roles.py:176identity:delete_domain_role, identity:delete_role
GET/v3/roles/{role_id}keystone/api/roles.py:107identity:get_domain_role, identity:get_role
PATCH/v3/roles/{role_id}keystone/api/roles.py:142identity:update_domain_role, identity:update_role
GET/v3/system/groups/{group_id}/roleskeystone/api/system.py:124identity:list_system_grants_for_group
DELETE/v3/system/groups/{group_id}/roles/{role_id}keystone/api/system.py:174identity:revoke_system_grant_for_group
GET/v3/system/groups/{group_id}/roles/{role_id}keystone/api/system.py:142identity:check_system_grant_for_group
PUT/v3/system/groups/{group_id}/roles/{role_id}keystone/api/system.py:158identity:create_system_grant_for_group
GET/v3/system/users/{user_id}/roleskeystone/api/system.py:60identity:list_system_grants_for_user
DELETE/v3/system/users/{user_id}/roles/{role_id}keystone/api/system.py:106identity:revoke_system_grant_for_user
GET/v3/system/users/{user_id}/roles/{role_id}keystone/api/system.py:78identity:check_system_grant_for_user
PUT/v3/system/users/{user_id}/roles/{role_id}keystone/api/system.py:92identity:create_system_grant_for_user
GET/v3/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/roleskeystone/api/users.py:625identity:list_access_token_roles
GET/v3/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/roles/{role_id}keystone/api/users.py:645identity:get_access_token_role

Catalog: regions, services, endpoints (16 operations)

MethodPathHandlerPolicy rule(s)
GET/v3/endpointskeystone/api/endpoints.py:76identity:list_endpoints
POST/v3/endpointskeystone/api/endpoints.py:93identity:create_endpoint
DELETE/v3/endpoints/{endpoint_id}keystone/api/endpoints.py:143identity:delete_endpoint
GET/v3/endpoints/{endpoint_id}keystone/api/endpoints.py:115identity:get_endpoint
PATCH/v3/endpoints/{endpoint_id}keystone/api/endpoints.py:127identity:update_endpoint
GET/v3/regionskeystone/api/regions.py:34identity:list_regions
POST/v3/regionskeystone/api/regions.py:43identity:create_region
DELETE/v3/regions/{region_id}keystone/api/regions.py:104identity:delete_region
GET/v3/regions/{region_id}keystone/api/regions.py:64identity:get_region
PATCH/v3/regions/{region_id}keystone/api/regions.py:92identity:update_region
PUT/v3/regions/{region_id}keystone/api/regions.py:70identity:create_region
GET/v3/serviceskeystone/api/services.py:32identity:list_services
POST/v3/serviceskeystone/api/services.py:45identity:create_service
DELETE/v3/services/{service_id}keystone/api/services.py:90identity:delete_service
GET/v3/services/{service_id}keystone/api/services.py:65identity:get_service
PATCH/v3/services/{service_id}keystone/api/services.py:75identity:update_service

Endpoint filtering and endpoint policies (35 operations)

MethodPathHandlerPolicy rule(s)
GET/v3/OS-EP-FILTER/endpoint_groupskeystone/api/os_ep_filter.py:65identity:list_endpoint_groups
POST/v3/OS-EP-FILTER/endpoint_groupskeystone/api/os_ep_filter.py:80identity:create_endpoint_group
DELETE/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}keystone/api/os_ep_filter.py:141identity:delete_endpoint_group
GET/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}keystone/api/os_ep_filter.py:111identity:get_endpoint_group
PATCH/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}keystone/api/os_ep_filter.py:123identity:update_endpoint_group
GET/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/endpointskeystone/api/os_ep_filter.py:239identity:list_endpoints_associated_with_endpoint_group
GET/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projectskeystone/api/os_ep_filter.py:216identity:list_projects_associated_with_endpoint_group
DELETE/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects/{project_id}keystone/api/os_ep_filter.py:288identity:remove_endpoint_group_from_project
GET/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects/{project_id}keystone/api/os_ep_filter.py:270identity:get_endpoint_group_in_project
PUT/v3/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects/{project_id}keystone/api/os_ep_filter.py:279identity:add_endpoint_group_to_project
GET/v3/OS-EP-FILTER/endpoints/{endpoint_id}/projectskeystone/api/os_ep_filter.py:150identity:list_projects_for_endpoint
GET/v3/OS-EP-FILTER/projects/{project_id}/endpoint_groupskeystone/api/os_ep_filter.py:206identity:list_endpoint_groups_for_project
GET/v3/OS-EP-FILTER/projects/{project_id}/endpointskeystone/api/os_ep_filter.py:192identity:list_endpoints_for_project
DELETE/v3/OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id}keystone/api/os_ep_filter.py:181identity:remove_endpoint_from_project
GET/v3/OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id}keystone/api/os_ep_filter.py:165identity:check_endpoint_in_project
PUT/v3/OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id}keystone/api/os_ep_filter.py:174identity:add_endpoint_to_project
GET/v3/endpoints/{endpoint_id}/OS-ENDPOINT-POLICY/policykeystone/api/endpoints.py:152identity:get_policy_for_endpoint
DELETE/v3/policieskeystone/api/policy.py:92identity:delete_policy
GET/v3/policieskeystone/api/policy.py:38identity:list_policies
PATCH/v3/policieskeystone/api/policy.py:78identity:update_policy
POST/v3/policieskeystone/api/policy.py:63identity:create_policy
DELETE/v3/policies/{policy_id}keystone/api/policy.py:92identity:delete_policy
GET/v3/policies/{policy_id}keystone/api/policy.py:38identity:get_policy
PATCH/v3/policies/{policy_id}keystone/api/policy.py:78identity:update_policy
POST/v3/policies/{policy_id}keystone/api/policy.py:63identity:create_policy
GET/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/endpointskeystone/api/policy.py:105identity:list_endpoints_for_policy
DELETE/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/endpoints/{endpoint_id}keystone/api/policy.py:142identity:delete_policy_association_for_endpoint
GET/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/endpoints/{endpoint_id}keystone/api/policy.py:122identity:check_policy_association_for_endpoint
PUT/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/endpoints/{endpoint_id}keystone/api/policy.py:132identity:create_policy_association_for_endpoint
DELETE/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/services/{service_id}keystone/api/policy.py:174identity:delete_policy_association_for_service
GET/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/services/{service_id}keystone/api/policy.py:154identity:check_policy_association_for_service
PUT/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/services/{service_id}keystone/api/policy.py:164identity:create_policy_association_for_service
DELETE/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/services/{service_id}/regions/{region_id}keystone/api/policy.py:208identity:delete_policy_association_for_region_and_service
GET/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/services/{service_id}/regions/{region_id}keystone/api/policy.py:186identity:check_policy_association_for_region_and_service
PUT/v3/policies/{policy_id}/OS-ENDPOINT-POLICY/services/{service_id}/regions/{region_id}keystone/api/policy.py:197identity:create_policy_association_for_region_and_service

Trusts (4 operations)

MethodPathHandlerPolicy rule(s)
GET/v3/OS-TRUST/trustskeystone/api/trusts.py:223admin_required, identity:list_trusts, identity:list_trusts_for_trustee, identity:list_trusts_for_trustor
POST/v3/OS-TRUST/trustskeystone/api/trusts.py:296identity:create_trust
DELETE/v3/OS-TRUST/trusts/{trust_id}keystone/api/trusts.py:398identity:delete_trust
GET/v3/OS-TRUST/trusts/{trust_id}keystone/api/trusts.py:349identity:get_trust

OAuth 1.0a and OAuth 2.0 (22 operations)

MethodPathHandlerPolicy rule(s)
GET/v3/OS-OAUTH1/access_tokenkeystone/api/os_oauth1.py:96—
POST/v3/OS-OAUTH1/access_tokenkeystone/api/os_oauth1.py:227none (unauthenticated)
GET/v3/OS-OAUTH1/authorize/{request_token_id}keystone/api/os_oauth1.py:96—
PUT/v3/OS-OAUTH1/authorize/{request_token_id}keystone/api/os_oauth1.py:327identity:authorize_request_token
DELETE/v3/OS-OAUTH1/consumerskeystone/api/os_oauth1.py:137identity:delete_consumer
GET/v3/OS-OAUTH1/consumerskeystone/api/os_oauth1.py:119identity:list_consumers
PATCH/v3/OS-OAUTH1/consumerskeystone/api/os_oauth1.py:151identity:update_consumer
POST/v3/OS-OAUTH1/consumerskeystone/api/os_oauth1.py:124identity:create_consumer
DELETE/v3/OS-OAUTH1/consumers/{consumer_id}keystone/api/os_oauth1.py:137identity:delete_consumer
GET/v3/OS-OAUTH1/consumers/{consumer_id}keystone/api/os_oauth1.py:119identity:get_consumer
PATCH/v3/OS-OAUTH1/consumers/{consumer_id}keystone/api/os_oauth1.py:151identity:update_consumer
POST/v3/OS-OAUTH1/consumers/{consumer_id}keystone/api/os_oauth1.py:124identity:create_consumer
GET/v3/OS-OAUTH1/request_tokenkeystone/api/os_oauth1.py:96—
POST/v3/OS-OAUTH1/request_tokenkeystone/api/os_oauth1.py:166none (unauthenticated)
DELETE/v3/OS-OAUTH2/tokenkeystone/api/os_oauth2.py:68none (unauthenticated)
GET/v3/OS-OAUTH2/tokenkeystone/api/os_oauth2.py:48none (unauthenticated)
PATCH/v3/OS-OAUTH2/tokenkeystone/api/os_oauth2.py:63none (unauthenticated)
POST/v3/OS-OAUTH2/tokenkeystone/api/os_oauth2.py:73none (unauthenticated)
PUT/v3/OS-OAUTH2/tokenkeystone/api/os_oauth2.py:58none (unauthenticated)
GET/v3/users/{user_id}/OS-OAUTH1/access_tokenskeystone/api/users.py:570identity:list_access_tokens
DELETE/v3/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}keystone/api/users.py:596identity:delete_access_token
GET/v3/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}keystone/api/users.py:583identity:get_access_token

Unified limits (11 operations)

MethodPathHandlerPolicy rule(s)
GET/v3/limitskeystone/api/limits.py:61identity:list_limits
POST/v3/limitskeystone/api/limits.py:111identity:create_limits
DELETE/v3/limits/{limit_id}keystone/api/limits.py:169identity:delete_limit
GET/v3/limits/{limit_id}keystone/api/limits.py:140identity:get_limit
PATCH/v3/limits/{limit_id}keystone/api/limits.py:154identity:update_limit
GET/v3/limits/modelkeystone/api/limits.py:184identity:get_limit_model
GET/v3/registered_limitskeystone/api/registered_limits.py:35identity:list_registered_limits
POST/v3/registered_limitskeystone/api/registered_limits.py:54identity:create_registered_limits
DELETE/v3/registered_limits/{registered_limit_id}keystone/api/registered_limits.py:121identity:delete_registered_limit
GET/v3/registered_limits/{registered_limit_id}keystone/api/registered_limits.py:85identity:get_registered_limit
PATCH/v3/registered_limits/{registered_limit_id}keystone/api/registered_limits.py:100identity:update_registered_limit

Revocation events and certificates (3 operations)

MethodPathHandlerPolicy rule(s)
GET/v3/OS-REVOKE/eventskeystone/api/os_revoke.py:34identity:list_revoke_events
GET/v3/OS-SIMPLE-CERT/cakeystone/api/os_simple_cert.py:30none (unauthenticated)
GET/v3/OS-SIMPLE-CERT/certificateskeystone/api/os_simple_cert.py:41none (unauthenticated)

Source: reverse-spec-keystone/01-rest-api_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.