June 2, 2026
Cross-Role PAT Issuance in Snowflake: Scaling Cortex REST API Access for Multi-Tenant Platforms
Startups and enterprises building AI-powered products on Snowflake’s Cortex REST API are increasingly adopting a multi-tenant deployment…
Navnit Shukla
6 min read
Startups and enterprises building AI-powered products on Snowflake's Cortex REST API are increasingly adopting a multi-tenant deployment model: one isolated credential per customer deployment, each scoped to exactly the permissions needed for LLM inference.
The pattern is straightforward — create a service user per deployment, issue a Programmatic Access Token (PAT) restricted to a Cortex-only role, and manage the lifecycle (create, rotate, revoke) from a centralized automation service.
But there's a subtle catch that trips up nearly every team implementing this at scale: PAT-authenticated sessions enforce role restriction matching on newly issued tokens. If your automation service authenticates via a PAT restricted to Role A, it cannot issue PATs restricted to Role B — even if Role B is in the same role hierarchy.
This guide walks through the problem, the root cause, and a complete working solution tested end-to-end via the Snowflake SQL API.
The Use Case
You're building a platform that provisions AI capabilities to hundreds or thousands of customer deployments. Each deployment needs:
- Its own credential (no shared secrets)
- Per-deployment attribution and revocation
- Access scoped only to Cortex AI inference (Claude, Llama, Mistral, etc.)
- Fully programmatic lifecycle management via API
The natural architecture:
Two roles, two trust boundaries:
- CORTEX_REST_API_ROLE : runtime role, can only call LLM inference
- AUTOMATION_ROLE : control plane, can create users and manage PATs but never calls Cortex
The Problem
Your automation service authenticates to the Snowflake SQL API using a PAT. That PAT has ROLE_RESTRICTION = 'AUTOMATION_ROLE' because your auth policy requires role restriction for service users (a security best practice).
When the automation service tries to issue a deployment PAT:
ALTER USER DEPLOYMENT_0001
ADD PROGRAMMATIC ACCESS TOKEN DEPLOYMENT_0001_PAT
ROLE_RESTRICTION = 'CORTEX_REST_API_ROLE'
DAYS_TO_EXPIRY = 90;ALTER USER DEPLOYMENT_0001
ADD PROGRAMMATIC ACCESS TOKEN DEPLOYMENT_0001_PAT
ROLE_RESTRICTION = 'CORTEX_REST_API_ROLE'
DAYS_TO_EXPIRY = 90;Snowflake returns:
Error 099421: The specified role restriction CORTEX_REST_API_ROLE
does not match the session role AUTOMATION_ROLE.
The same SQL works perfectly in a Snowsight worksheet or key-pair authenticated session. The error only appears in PAT-authenticated SQL API sessions.
Root Cause
When a session is authenticated via a PAT with **ROLE_RESTRICTION**, Snowflake locks the session role to that restriction. The PAT creation logic then enforces that the **ROLE_RESTRICTION** on any newly created PAT must match the session's own PAT role restriction not just any role the session user can assume.
This is a security guard rail: it prevents a role-restricted session from issuing tokens with broader or different privileges than its own. But it creates a chicken-and-egg problem for centralized provisioning where the automation role and the runtime role are intentionally separate.
The Solution
The fix combines two changes:
- The automation user gets a separate auth policy that does NOT require role restriction, allowing its PAT to be issued without
ROLE_RESTRICTION. The deployment users keep the strict policy. - The automation role gets OWNERSHIP of the runtime role, enabling it to GRANT the runtime role to deployment users and issue PATs restricted to it.
Step-by-Step Setup
Step 1: Create the Runtime Role
USE ROLE SECURITYADMIN;
CREATE ROLE IF NOT EXISTS CORTEX_REST_API_ROLE
COMMENT = 'Runtime role for Cortex REST API access only';
GRANT DATABASE ROLE SNOWFLAKE.CORTEX_REST_API_USER
TO ROLE CORTEX_REST_API_ROLE;USE ROLE SECURITYADMIN;
CREATE ROLE IF NOT EXISTS CORTEX_REST_API_ROLE
COMMENT = 'Runtime role for Cortex REST API access only';
GRANT DATABASE ROLE SNOWFLAKE.CORTEX_REST_API_USER
TO ROLE CORTEX_REST_API_ROLE;Step 2: Create the Automation Role
USE ROLE SECURITYADMIN;
CREATE ROLE IF NOT EXISTS AUTOMATION_ROLE
COMMENT = 'Control plane role for provisioning deployment users and PATs';
GRANT CREATE USER ON ACCOUNT TO ROLE AUTOMATION_ROLE;
-- OWNERSHIP allows AUTOMATION_ROLE to GRANT/REVOKE the runtime role
GRANT OWNERSHIP ON ROLE CORTEX_REST_API_ROLE
TO ROLE AUTOMATION_ROLE COPY CURRENT GRANTS;USE ROLE SECURITYADMIN;
CREATE ROLE IF NOT EXISTS AUTOMATION_ROLE
COMMENT = 'Control plane role for provisioning deployment users and PATs';
GRANT CREATE USER ON ACCOUNT TO ROLE AUTOMATION_ROLE;
-- OWNERSHIP allows AUTOMATION_ROLE to GRANT/REVOKE the runtime role
GRANT OWNERSHIP ON ROLE CORTEX_REST_API_ROLE
TO ROLE AUTOMATION_ROLE COPY CURRENT GRANTS;Step 3: Create Auth Policies
Two separate policies — strict for deployments, relaxed for automation:
USE ROLE ACCOUNTADMIN;
USE DATABASE <your_database>;
USE SCHEMA PUBLIC;
-- Strict policy for deployment users (role restriction required)
CREATE AUTHENTICATION POLICY IF NOT EXISTS deployment_auth_policy
PAT_POLICY = (
NETWORK_POLICY_EVALUATION = ENFORCED_NOT_REQUIRED,
MAX_EXPIRY_IN_DAYS = 365,
DEFAULT_EXPIRY_IN_DAYS = 90,
REQUIRE_ROLE_RESTRICTION_FOR_SERVICE_USERS = TRUE,
BLOCKED_ROLES_LIST = ('ACCOUNTADMIN', 'SYSADMIN', 'SECURITYADMIN')
);
-- Relaxed policy for automation user (no role restriction required)
CREATE AUTHENTICATION POLICY IF NOT EXISTS automation_auth_policy
PAT_POLICY = (
NETWORK_POLICY_EVALUATION = ENFORCED_NOT_REQUIRED,
MAX_EXPIRY_IN_DAYS = 365,
DEFAULT_EXPIRY_IN_DAYS = 90,
REQUIRE_ROLE_RESTRICTION_FOR_SERVICE_USERS = FALSE,
BLOCKED_ROLES_LIST = ('ACCOUNTADMIN', 'SYSADMIN', 'SECURITYADMIN')
);
-- Give automation role ownership of the deployment policy
GRANT OWNERSHIP ON AUTHENTICATION POLICY deployment_auth_policy
TO ROLE AUTOMATION_ROLE;USE ROLE ACCOUNTADMIN;
USE DATABASE <your_database>;
USE SCHEMA PUBLIC;
-- Strict policy for deployment users (role restriction required)
CREATE AUTHENTICATION POLICY IF NOT EXISTS deployment_auth_policy
PAT_POLICY = (
NETWORK_POLICY_EVALUATION = ENFORCED_NOT_REQUIRED,
MAX_EXPIRY_IN_DAYS = 365,
DEFAULT_EXPIRY_IN_DAYS = 90,
REQUIRE_ROLE_RESTRICTION_FOR_SERVICE_USERS = TRUE,
BLOCKED_ROLES_LIST = ('ACCOUNTADMIN', 'SYSADMIN', 'SECURITYADMIN')
);
-- Relaxed policy for automation user (no role restriction required)
CREATE AUTHENTICATION POLICY IF NOT EXISTS automation_auth_policy
PAT_POLICY = (
NETWORK_POLICY_EVALUATION = ENFORCED_NOT_REQUIRED,
MAX_EXPIRY_IN_DAYS = 365,
DEFAULT_EXPIRY_IN_DAYS = 90,
REQUIRE_ROLE_RESTRICTION_FOR_SERVICE_USERS = FALSE,
BLOCKED_ROLES_LIST = ('ACCOUNTADMIN', 'SYSADMIN', 'SECURITYADMIN')
);
-- Give automation role ownership of the deployment policy
GRANT OWNERSHIP ON AUTHENTICATION POLICY deployment_auth_policy
TO ROLE AUTOMATION_ROLE;Step 4: Create the Automation Service User
USE ROLE ACCOUNTADMIN;
CREATE USER IF NOT EXISTS AUTOMATION_USER
TYPE = SERVICE
DEFAULT_ROLE = AUTOMATION_ROLE;
GRANT ROLE AUTOMATION_ROLE TO USER AUTOMATION_USER;
ALTER USER AUTOMATION_USER
SET AUTHENTICATION POLICY <db>.<schema>.automation_auth_policy;
-- Issue unrestricted PAT (allowed by automation_auth_policy)
ALTER USER AUTOMATION_USER
ADD PROGRAMMATIC ACCESS TOKEN automation_pat
DAYS_TO_EXPIRY = 90
COMMENT = 'Control plane PAT - no role restriction';
-- >>> SAVE THIS TOKEN SECRET IMMEDIATELY <<<USE ROLE ACCOUNTADMIN;
CREATE USER IF NOT EXISTS AUTOMATION_USER
TYPE = SERVICE
DEFAULT_ROLE = AUTOMATION_ROLE;
GRANT ROLE AUTOMATION_ROLE TO USER AUTOMATION_USER;
ALTER USER AUTOMATION_USER
SET AUTHENTICATION POLICY <db>.<schema>.automation_auth_policy;
-- Issue unrestricted PAT (allowed by automation_auth_policy)
ALTER USER AUTOMATION_USER
ADD PROGRAMMATIC ACCESS TOKEN automation_pat
DAYS_TO_EXPIRY = 90
COMMENT = 'Control plane PAT - no role restriction';
-- >>> SAVE THIS TOKEN SECRET IMMEDIATELY <<<Per-Deployment Provisioning
-- 1. Create deployment user
CREATE USER IF NOT EXISTS DEPLOYMENT_<ID>
TYPE = SERVICE
DEFAULT_ROLE = CORTEX_REST_API_ROLE;
-- 2. Grant runtime role
GRANT ROLE CORTEX_REST_API_ROLE TO USER DEPLOYMENT_<ID>;
-- 3. Apply strict auth policy
ALTER USER DEPLOYMENT_<ID>
SET AUTHENTICATION POLICY <db>.<schema>.deployment_auth_policy;
-- 4. Grant PAT management to automation role
GRANT MODIFY PROGRAMMATIC AUTHENTICATION METHODS
ON USER DEPLOYMENT_<ID> TO ROLE AUTOMATION_ROLE;
-- 5. Issue deployment PAT (restricted to runtime role)
ALTER USER DEPLOYMENT_<ID>
ADD PROGRAMMATIC ACCESS TOKEN DEPLOYMENT_<ID>_PAT
ROLE_RESTRICTION = 'CORTEX_REST_API_ROLE'
DAYS_TO_EXPIRY = 90;
-- >>> CAPTURE token_secret — ONLY SHOWN ONCE <<<-- 1. Create deployment user
CREATE USER IF NOT EXISTS DEPLOYMENT_<ID>
TYPE = SERVICE
DEFAULT_ROLE = CORTEX_REST_API_ROLE;
-- 2. Grant runtime role
GRANT ROLE CORTEX_REST_API_ROLE TO USER DEPLOYMENT_<ID>;
-- 3. Apply strict auth policy
ALTER USER DEPLOYMENT_<ID>
SET AUTHENTICATION POLICY <db>.<schema>.deployment_auth_policy;
-- 4. Grant PAT management to automation role
GRANT MODIFY PROGRAMMATIC AUTHENTICATION METHODS
ON USER DEPLOYMENT_<ID> TO ROLE AUTOMATION_ROLE;
-- 5. Issue deployment PAT (restricted to runtime role)
ALTER USER DEPLOYMENT_<ID>
ADD PROGRAMMATIC ACCESS TOKEN DEPLOYMENT_<ID>_PAT
ROLE_RESTRICTION = 'CORTEX_REST_API_ROLE'
DAYS_TO_EXPIRY = 90;
-- >>> CAPTURE token_secret — ONLY SHOWN ONCE <<<Python Provisioning Class
import requests
import json
class CortexPATProvisioner:
def __init__(self, automation_pat: str, account_url: str,
role: str = "AUTOMATION_ROLE"):
self.url = f"https://{account_url}/api/v2/statements"
self.headers = {
"Authorization": f"Bearer {automation_pat}",
"X-Snowflake-Authorization-Token-Type": "PROGRAMMATIC_ACCESS_TOKEN",
"Content-Type": "application/json",
}
self.role = role
def _execute(self, sql: str) -> dict:
resp = requests.post(self.url, headers=self.headers,
json={"statement": sql, "role": self.role})
result = resp.json()
if resp.status_code != 200:
raise Exception(f"SQL API error: {result.get('message')}")
return result
def provision_deployment(self, deployment_id: str,
auth_policy_fqn: str,
days_to_expiry: int = 90) -> str:
user = f"DEPLOYMENT_{deployment_id}"
pat_name = f"DEPLOYMENT_{deployment_id}_PAT"
self._execute(
f"CREATE USER IF NOT EXISTS {user} "
f"TYPE = SERVICE DEFAULT_ROLE = CORTEX_REST_API_ROLE"
)
self._execute(
f"GRANT ROLE CORTEX_REST_API_ROLE TO USER {user}"
)
self._execute(
f"ALTER USER {user} SET AUTHENTICATION POLICY {auth_policy_fqn}"
)
self._execute(
f"GRANT MODIFY PROGRAMMATIC AUTHENTICATION METHODS "
f"ON USER {user} TO ROLE {self.role}"
)
result = self._execute(
f"ALTER USER {user} ADD PROGRAMMATIC ACCESS TOKEN {pat_name} "
f"ROLE_RESTRICTION = 'CORTEX_REST_API_ROLE' "
f"DAYS_TO_EXPIRY = {days_to_expiry} "
f"COMMENT = 'Deployment {deployment_id}'"
)
token_secret = result["data"][0][1]
return token_secret
def rotate_pat(self, deployment_id: str,
grace_hours: int = 24) -> str:
user = f"DEPLOYMENT_{deployment_id}"
pat_name = f"DEPLOYMENT_{deployment_id}_PAT"
result = self._execute(
f"ALTER USER {user} ROTATE PROGRAMMATIC ACCESS TOKEN {pat_name} "
f"EXPIRE_ROTATED_TOKEN_AFTER_HOURS = {grace_hours}"
)
return result["data"][0][1]
def revoke_deployment(self, deployment_id: str):
user = f"DEPLOYMENT_{deployment_id}"
pat_name = f"DEPLOYMENT_{deployment_id}_PAT"
self._execute(
f"ALTER USER {user} REMOVE PROGRAMMATIC ACCESS TOKEN {pat_name}"
)
def disable_deployment(self, deployment_id: str):
user = f"DEPLOYMENT_{deployment_id}"
self._execute(f"ALTER USER {user} SET DISABLED = TRUE")import requests
import json
class CortexPATProvisioner:
def __init__(self, automation_pat: str, account_url: str,
role: str = "AUTOMATION_ROLE"):
self.url = f"https://{account_url}/api/v2/statements"
self.headers = {
"Authorization": f"Bearer {automation_pat}",
"X-Snowflake-Authorization-Token-Type": "PROGRAMMATIC_ACCESS_TOKEN",
"Content-Type": "application/json",
}
self.role = role
def _execute(self, sql: str) -> dict:
resp = requests.post(self.url, headers=self.headers,
json={"statement": sql, "role": self.role})
result = resp.json()
if resp.status_code != 200:
raise Exception(f"SQL API error: {result.get('message')}")
return result
def provision_deployment(self, deployment_id: str,
auth_policy_fqn: str,
days_to_expiry: int = 90) -> str:
user = f"DEPLOYMENT_{deployment_id}"
pat_name = f"DEPLOYMENT_{deployment_id}_PAT"
self._execute(
f"CREATE USER IF NOT EXISTS {user} "
f"TYPE = SERVICE DEFAULT_ROLE = CORTEX_REST_API_ROLE"
)
self._execute(
f"GRANT ROLE CORTEX_REST_API_ROLE TO USER {user}"
)
self._execute(
f"ALTER USER {user} SET AUTHENTICATION POLICY {auth_policy_fqn}"
)
self._execute(
f"GRANT MODIFY PROGRAMMATIC AUTHENTICATION METHODS "
f"ON USER {user} TO ROLE {self.role}"
)
result = self._execute(
f"ALTER USER {user} ADD PROGRAMMATIC ACCESS TOKEN {pat_name} "
f"ROLE_RESTRICTION = 'CORTEX_REST_API_ROLE' "
f"DAYS_TO_EXPIRY = {days_to_expiry} "
f"COMMENT = 'Deployment {deployment_id}'"
)
token_secret = result["data"][0][1]
return token_secret
def rotate_pat(self, deployment_id: str,
grace_hours: int = 24) -> str:
user = f"DEPLOYMENT_{deployment_id}"
pat_name = f"DEPLOYMENT_{deployment_id}_PAT"
result = self._execute(
f"ALTER USER {user} ROTATE PROGRAMMATIC ACCESS TOKEN {pat_name} "
f"EXPIRE_ROTATED_TOKEN_AFTER_HOURS = {grace_hours}"
)
return result["data"][0][1]
def revoke_deployment(self, deployment_id: str):
user = f"DEPLOYMENT_{deployment_id}"
pat_name = f"DEPLOYMENT_{deployment_id}_PAT"
self._execute(
f"ALTER USER {user} REMOVE PROGRAMMATIC ACCESS TOKEN {pat_name}"
)
def disable_deployment(self, deployment_id: str):
user = f"DEPLOYMENT_{deployment_id}"
self._execute(f"ALTER USER {user} SET DISABLED = TRUE")Usage
provisioner = CortexPATProvisioner(
automation_pat="<your_automation_pat>",
account_url="<org>-<account>.snowflakecomputing.com"
)
# Provision
token = provisioner.provision_deployment(
"0001",
auth_policy_fqn="MY_DB.PUBLIC.DEPLOYMENT_AUTH_POLICY"
)
# >>> Store token in your deployment's secret manager <<<
# Rotate (old token valid for 24h grace period)
new_token = provisioner.rotate_pat("0001", grace_hours=24)
# Revoke (immediate)
provisioner.revoke_deployment("0001")provisioner = CortexPATProvisioner(
automation_pat="<your_automation_pat>",
account_url="<org>-<account>.snowflakecomputing.com"
)
# Provision
token = provisioner.provision_deployment(
"0001",
auth_policy_fqn="MY_DB.PUBLIC.DEPLOYMENT_AUTH_POLICY"
)
# >>> Store token in your deployment's secret manager <<<
# Rotate (old token valid for 24h grace period)
new_token = provisioner.rotate_pat("0001", grace_hours=24)
# Revoke (immediate)
provisioner.revoke_deployment("0001")Testing: Using a Deployment PAT
Once provisioned, each deployment authenticates to Cortex REST API:
curl -X POST \
"https://<org>-<account>.snowflakecomputing.com/api/v2/cortex/inference:complete" \
-H "Authorization: Bearer <deployment_token_secret>" \
-H "X-Snowflake-Authorization-Token-Type: PROGRAMMATIC_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"messages": [{"role": "user", "content": "Hello from deployment 0001"}]
}'curl -X POST \
"https://<org>-<account>.snowflakecomputing.com/api/v2/cortex/inference:complete" \
-H "Authorization: Bearer <deployment_token_secret>" \
-H "X-Snowflake-Authorization-Token-Type: PROGRAMMATIC_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"messages": [{"role": "user", "content": "Hello from deployment 0001"}]
}'Or using the Anthropic-compatible endpoint:
curl -X POST \
"https://<org>-<account>.snowflakecomputing.com/api/v2/cortex/v1/messages" \
-H "Authorization: Bearer <deployment_token_secret>" \
-H "X-Snowflake-Authorization-Token-Type: PROGRAMMATIC_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"messages": [{"role": "user", "content": "Hello from deployment 0001"}],
"max_tokens": 100
}'curl -X POST \
"https://<org>-<account>.snowflakecomputing.com/api/v2/cortex/v1/messages" \
-H "Authorization: Bearer <deployment_token_secret>" \
-H "X-Snowflake-Authorization-Token-Type: PROGRAMMATIC_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"messages": [{"role": "user", "content": "Hello from deployment 0001"}],
"max_tokens": 100
}'Rotation and Revocation
Rotate (with grace period)
ALTER USER DEPLOYMENT_0001
ROTATE PROGRAMMATIC ACCESS TOKEN DEPLOYMENT_0001_PAT
EXPIRE_ROTATED_TOKEN_AFTER_HOURS = 24;
-- New token_secret returned. Old token valid for 24 more hours.ALTER USER DEPLOYMENT_0001
ROTATE PROGRAMMATIC ACCESS TOKEN DEPLOYMENT_0001_PAT
EXPIRE_ROTATED_TOKEN_AFTER_HOURS = 24;
-- New token_secret returned. Old token valid for 24 more hours.Revoke (immediate)
-- Option A: Remove PAT only
ALTER USER DEPLOYMENT_0001
REMOVE PROGRAMMATIC ACCESS TOKEN DEPLOYMENT_0001_PAT;
-- Option B: Disable user entirely
ALTER USER DEPLOYMENT_0001 SET DISABLED = TRUE;
-- Option C: Drop user
DROP USER DEPLOYMENT_0001;-- Option A: Remove PAT only
ALTER USER DEPLOYMENT_0001
REMOVE PROGRAMMATIC ACCESS TOKEN DEPLOYMENT_0001_PAT;
-- Option B: Disable user entirely
ALTER USER DEPLOYMENT_0001 SET DISABLED = TRUE;
-- Option C: Drop user
DROP USER DEPLOYMENT_0001;Troubleshooting
Error: "Role restriction does not match the session role"
Cause: Your automation PAT has ROLE_RESTRICTION set to a role different from the one you're specifying on the new PAT.
Fix: Ensure your automation user's auth policy has REQUIRE_ROLE_RESTRICTION_FOR_SERVICE_USERS = FALSE and issue the automation PAT without ROLE_RESTRICTION.
Error: "Role restriction is required when creating a programmatic access token"
Cause: The user's auth policy has REQUIRE_ROLE_RESTRICTION_FOR_SERVICE_USERS = TRUE but you're trying to create a PAT without specifying ROLE_RESTRICTION.
Fix: Use separate auth policies — strict for deployment users, relaxed for the automation user.
Error: "Grant not executed: Insufficient privileges"
Cause: The automation role cannot GRANT the runtime role to deployment users.
Fix: Grant OWNERSHIP of the runtime role to the automation role:
GRANT OWNERSHIP ON ROLE CORTEX_REST_API_ROLE
TO ROLE AUTOMATION_ROLE COPY CURRENT GRANTS;GRANT OWNERSHIP ON ROLE CORTEX_REST_API_ROLE
TO ROLE AUTOMATION_ROLE COPY CURRENT GRANTS;Error: "Role X is not granted to user Y"
Cause: The GRANT ROLE step failed or was skipped before PAT creation. The PAT's ROLE_RESTRICTION must reference a role already granted to the target user.
Fix: Ensure GRANT ROLE CORTEX_REST_API_ROLE TO USER <deployment_user> succeeds before attempting PAT creation.
Error: "Authentication policy does not exist or not authorized"
Cause: The automation role doesn't have access to the auth policy object.
Fix: Grant OWNERSHIP of the auth policy to the automation role:
GRANT OWNERSHIP ON AUTHENTICATION POLICY <db>.<schema>.deployment_auth_policy
TO ROLE AUTOMATION_ROLE;GRANT OWNERSHIP ON AUTHENTICATION POLICY <db>.<schema>.deployment_auth_policy
TO ROLE AUTOMATION_ROLE;Security Considerations
Key Takeaways
-
PAT-authenticated sessions enforce role restriction matching. If your automation PAT has
ROLE_RESTRICTION = X, you can only create new PATs withROLE_RESTRICTION = X. This is by design. -
Separate auth policies for separate trust boundaries. Automation users need flexibility to issue cross-role PATs. Deployment users need strict role scoping. Two policies solve this cleanly.
-
OWNERSHIP on the runtime role is the enabler. It lets the automation role grant/revoke the runtime role without needing ACCOUNTADMIN in the provisioning loop.
-
The combination is least-privilege. The automation user can only provision users and manage PATs. Deployment users can only call Cortex. Neither can escalate to admin roles.
Co-author Chris Cardillo
References