
Cloud Access Management
- 2.1k installs
- 546 repo stars
- Updated July 22, 2026
- elastic/agent-skills
Manage Elastic Cloud organization access: invite users, assign roles to Serverless projects, and create or revoke Cloud API keys. Use when granting, modifying, or auditing user access.
About
The cloud access management skill Manage Elastic Cloud organization access: invite users, assign roles to Serverless projects, and create or revoke Cloud API keys. Use when granting, modifying, or auditing user access. Documentation covers workflows, commands, and guardrails agents should follow when users invoke this capability. Key documented areas include Invite a user to the organization and assign them a Serverless project role; List organization members and their current role assignments; Update a user's roles (org-level or project-level); Remove a user from the organization. Reference commands include EC_API_KEY=your-api-key; EC_BASE_URL=https://api.elastic-cloud.com. Use when developers or agents need structured guidance for cloud access management tasks with evidence grounded in the bundled SKILL.md rather than generic advice. Invite a user to the organization and assign them a Serverless project role List organization members and their current role assignments Update a user's roles (org-level or project-level) Remove a user from the organization Create an additional Cloud API key with scoped roles and expiration Create a Cloud API key that can also call Elasticsearch and.
- Invite a user to the organization and assign them a Serverless project role
- List organization members and their current role assignments
- Update a user's roles (org-level or project-level)
- Remove a user from the organization
- Create an additional Cloud API key with scoped roles and expiration
Cloud Access Management by the numbers
- 2,090 all-time installs (skills.sh)
- +160 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #188 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
cloud-access-management capabilities & compatibility
- Capabilities
- invite a user to the organization and assign the · list organization members and their current role · update a user's roles (org level or project leve · remove a user from the organization · create an additional cloud api key with scoped r
npx skills add https://github.com/elastic/agent-skills --skill cloud-access-managementAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.1k |
|---|---|
| repo stars | ★ 546 |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 22, 2026 |
| Repository | elastic/agent-skills ↗ |
How do I handle cloud access management tasks with agent guidance?
Manage Elastic Cloud organization access: invite users, assign roles to Serverless projects, and create or revoke Cloud API keys. Use when granting, modifying, or auditing user access.
Who is it for?
Teams needing documented cloud access management workflows.
Skip if: Developers only indexing documents in Elasticsearch or building Kibana dashboards without cloud organization administration needs.
When should I use this skill?
Manage Elastic Cloud organization access: invite users, assign roles to Serverless projects, and create or revoke Cloud API keys. Use when granting, modifying, or auditing user access.
What you get
Structured workflow from cloud access management documentation applied to the user request.
- API request examples
- Role assignment configurations
- API key management scripts
By the numbers
- Uses Elastic Cloud REST API v1 at api.elastic-cloud.com/api/v1
Files
Cloud Access Management
Manage identity and access for an Elastic Cloud organization and its Serverless projects: invite users, assign predefined or custom roles, and manage Cloud API keys.
Prerequisite: This skill assumes the cloud-setup skill has already run — EC_API_KEY is set in theenvironment and the organization context is established. If EC_API_KEY is missing, instruct the agent to invokecloud-setup first. Do NOT prompt the user for an API key directly.
For project creation, see the cloud-create-project skill. For day-2 project operations (list, update, delete), see cloud-manage-project. For Elasticsearch-level role management (native users, role mappings, DLS/FLS), see the elasticsearch-authz skill.
For detailed API endpoints and request schemas, see references/api-reference.md.
Jobs to Be Done
- Invite a user to the organization and assign them a Serverless project role
- List organization members and their current role assignments
- Update a user's roles (org-level or project-level)
- Remove a user from the organization
- Create an additional Cloud API key with scoped roles and expiration
- Create a Cloud API key that can also call Elasticsearch and Kibana APIs on Serverless projects
- List and revoke Cloud API keys
- Create a custom role inside a Serverless project with ES cluster, index, and Kibana privileges
- Assign or remove a custom role for a user on a Serverless project using the Cloud API's
application_roles - Translate a natural-language access request into invite, role, and API key tasks
Prerequisites and permissions
| Item | Description |
|---|---|
| EC_API_KEY | Cloud API key (set by cloud-setup). Required for all operations. |
| Organization ID | Auto-discovered using GET /organizations. Do not ask the user for it. |
| Project endpoint | Elasticsearch endpoint of a Serverless project. Required only for custom role operations. |
| ES credentials | API key or credentials with manage_security privilege on the project. Required only for custom roles. |
| Org owner role | Only Organization owners can create and manage Cloud API keys. Required for API key operations. |
Run python3 skills/cloud/access-management/scripts/cloud_access.py list-members to verify that EC_API_KEY is valid and auto-discover the org ID before proceeding with any operation.
Operation-level permissions
The following permissions are required for common access management operations in Elastic Cloud Serverless.
| Operation | Required permission |
|---|---|
| Invite / remove members | Organization owner (organization-admin) |
| Assign or remove roles | Organization owner (organization-admin) |
| Create / revoke Cloud API keys | Organization owner (organization-admin) |
| List members, invitations, or keys | Any organization member |
| Create / delete custom roles | manage_security cluster privilege on the project ES endpoint |
This skill does not perform a separate role pre-check. Attempt the requested operation and let the API enforce authorization. If the API returns an authorization error (for example, 403 Forbidden), stop and ask the user to verify the provided API key permissions.
Manual setup fallback (when cloud-setup is unavailable)
If this skill is installed standalone and cloud-setup is not available, instruct the user to configure Cloud environment variables manually before running commands. Never ask the user to paste API keys in chat.
| Variable | Required | Description |
|---|---|---|
EC_API_KEY | Yes | Elastic Cloud API key with Organization owner role. |
EC_BASE_URL | No | Cloud API base URL (default: https://api.elastic-cloud.com). |
ELASTICSEARCH_URL | Conditional | Elasticsearch URL. Required only for custom role operations. |
ELASTICSEARCH_API_KEY | Conditional | Elasticsearch API key with manage_security privilege. Required only for custom role operations. |
Note: If EC_API_KEY is missing, or the user does not have a Cloud API key yet, direct the user to generate oneat Elastic Cloud API keys, then configure it locally using the steps below.
Preferred method (agent-friendly): create a .env file in the project root:
EC_API_KEY=your-api-key
EC_BASE_URL=https://api.elastic-cloud.com
# Only needed for custom role operations against the project Elasticsearch endpoint:
# ELASTICSEARCH_URL=https://<project-id>.es.<region>.elastic-cloud.com
# ELASTICSEARCH_API_KEY=<your-es-manage-security-api-key>All cloud/* scripts auto-load .env from the working directory.
Alternative: export directly in the terminal:
export EC_API_KEY="<your-cloud-api-key>"
export EC_BASE_URL="https://api.elastic-cloud.com"
# Only needed for custom role operations against the project Elasticsearch endpoint:
# export ELASTICSEARCH_URL="https://<project-id>.es.<region>.elastic-cloud.com"
# export ELASTICSEARCH_API_KEY="<your-es-manage-security-api-key>"Terminal exports may not be visible to sandboxed agents running in separate shell sessions, so prefer .env when using an agent.
Decomposing Access Requests
When the user describes access in natural language (for example, "add Alice to my search project as a developer"), break the request into discrete tasks before executing.
Step 1 — Identify the components
| Component | Question to answer |
|---|---|
| Who | New org member (invite) or existing member (role update)? |
| What | Which Serverless project(s) or org-level access? |
| Access level | Predefined role (Admin/Developer/Viewer/Editor) or custom role? |
| API key? | Does the request also need a Cloud API key for programmatic access? |
Step 2 — Check if a predefined role fits
Consult the predefined roles table below. Prefer predefined roles — only create a custom role when predefined roles do not provide the required granularity.
Step 3 — Check existing state
Before creating or inviting, check what already exists:
python3 skills/cloud/access-management/scripts/cloud_access.py list-members
python3 skills/cloud/access-management/scripts/cloud_access.py list-api-keysIf the user is already a member, skip the invitation and update their roles instead.
For API key requests, only Organization owners can create and manage Cloud API keys. If the authenticated user does not have the organization-admin role, API key operations will fail with a 403 error. Review the existing keys returned by list-api-keys. If an active key already exists for the same purpose or task with the required roles and sufficient remaining lifetime, reuse it instead of creating a new one. Two keys with identical permissions are fine when they serve different purposes (for example, separate CI pipelines), but creating a second key for the same task is unnecessary and increases the management burden.
Step 4 — Run
Run the appropriate command(s) from skills/cloud/access-management/scripts/cloud_access.py. Confirm destructive actions (remove member, revoke key) with the user before executing.
Step 5 — Verify
After execution, list members or keys again to confirm the change took effect.
Predefined Roles
Organization-level roles
| Role | Cloud API role_id | Description |
|---|---|---|
| Organization owner | organization-admin | Full admin over org, deployments, projects |
| Billing admin | billing-admin | Manage billing details only |
Serverless project-level roles
| Role | Cloud API role_id | Available on | Description |
|---|---|---|---|
| Admin | admin | Search, Obs, Security | Full project management, superuser on sign-in |
| Developer | developer | Search only | Create indices, API keys, connectors, visualizations |
| Viewer | viewer | Search, Obs, Security | Read-only access to project data and features |
| Editor | editor | Obs, Security | Configure project features, read-only data indices |
| Tier 1 analyst | t1_analyst | Security only | Alert triage, general read, create dashboards |
| Tier 2 analyst | t2_analyst | Security only | Alert triage, begin investigations, create cases |
| Tier 3 analyst | t3_analyst | Security only | Deep investigation, rules, lists, response actions |
| SOC manager | soc_manager | Security only | Alerts, cases, endpoint policy, response actions |
| Rule author | rule_author | Security only | Detection engineering, rule creation |
Project-level roles are assigned during invitation (POST /organizations/{org_id}/invitations) or using the role assignment update (POST /users/{user_id}/role_assignments). See references/api-reference.md for the role_assignments JSON schema including the project scope.
Custom Roles (Serverless)
When predefined roles lack the required granularity, create a custom role inside the Serverless project using the Elasticsearch security API and assign it to users through the Cloud API's application_roles field.
Security: do not assign a predefined Cloud role separately when using a custom role. Custom roles implicitly grant
Viewer-level Cloud access for the project scope. If you also assign viewer (or any other predefined role) as aseparate Cloud role assignment for the same project, the user receives the union of both roles when they SSO into
the project — the Viewer stack role is broader than most custom roles and will override the restrictions you intended.
How custom role assignment works
- Predefined roles (
viewer,developer,admin, etc.) are assigned via Cloud APIs (invite-user,
assign-role). When the user SSOs into the project, they receive the stack role mapped to their Cloud role (for example, Cloud viewer maps to the viewer stack role).
- Custom roles are created in the project via the Elasticsearch security API (
create-custom-role) and assigned via
the Cloud API's application_roles field (assign-custom-role). When application_roles is set, the user gets only the specified custom role on SSO — not the default stack role for their Cloud role.
- The
assign-custom-rolecommand setsrole_idto the project-type Viewer role (elasticsearch-viewer,
observability-viewer, or security-viewer) and sets application_roles to the custom role name. This ensures the user can see and access the project in the Cloud console but receives only the custom role's restricted permissions inside the project.
- Cloud API keys can also use
application_rolesto gain ES/Kibana API access on Serverless projects. See
Cloud API Keys — ES and Kibana API Access below for details.
Canonical custom-role onboarding flow
1. Create the custom role in the project (create-custom-role). 2. Invite the user to the organization if they are not already a member (invite-user). Do not include project role assignments in the invitation — the custom role assignment in the next step handles project access. 3. Assign the custom role to the user (assign-custom-role --user-id ... --project-id ... --custom-role-name ...). 4. Verify with list-members and list-roles.
Create a custom role
python3 skills/cloud/access-management/scripts/cloud_access.py create-custom-role \
--role-name marketing-analyst \
--body '{"cluster":[],"indices":[{"names":["marketing-*"],"privileges":["read","view_index_metadata"]}]}'This calls PUT /_security/role/{name} on the project Elasticsearch endpoint.
Naming constraints
Role names must begin with a letter or digit and contain only letters, digits, _, -, and .. Run-as privileges are not available in Serverless.
When to use custom roles versus predefined
| Scenario | Use |
|---|---|
| Standard admin/developer/viewer access | Predefined role |
| Read-only access to specific index pattern | Custom role |
| DLS or FLS restrictions | Custom role |
| Kibana feature-level access control | Custom role |
For advanced DLS/FLS patterns (templated queries, ABAC), see the elasticsearch-authz skill.
Cloud API Keys — ES and Kibana API Access
Cloud API keys can now optionally access Elasticsearch and Kibana APIs on Serverless projects, in addition to the Cloud API. This enables a single credential for both control plane (Cloud API) and data plane (ES/Kibana API) operations — for example, a CI pipeline that creates a project via Cloud API and then indexes data via ES API.
How it works
Add application_roles to the key's role_assignments at creation time. This field accepts an array of predefined role names (admin, developer, viewer, and solution-specific roles like t1_analyst, editor) or custom role names created in the project via PUT /_security/role/{name}. Predefined roles are available in every project by default. Custom roles must be created individually in each project where the key should have access — if a referenced custom role does not exist in a project, the key silently gets no access there.
Critical rule: no implicit inheritance
Unlike users, API keys never inherit stack roles from their role_id. If application_roles is omitted or empty, the key has Cloud API access only. Calling an ES or Kibana endpoint with such a key returns 403 Forbidden. This is by design for backward compatibility — existing keys without application_roles continue to work as Cloud-only keys.
Scoping modes
- Project-scoped (preferred) — grants access to specific projects or all projects of a given type. Uses the
project key in role_assignments with application_roles on each entry. Use this by default unless the user explicitly needs cross-project access.
- Organization-scoped — grants access to all current and future projects in the organization. Uses the
organization key in role_assignments with application_roles. This is the broadest possible data-plane scope. Only use when the key genuinely needs access to every project (for example, platform automation or cross-project search across the whole org). Always confirm with the user before creating an org-scoped key with application_roles, as it grants ES/Kibana access to projects that may not exist yet.
Custom roles and org-scoped access: When using a custom role name in application_roles with organization-scopedassignments, the custom role must exist in each project where you want the key to have access. If a project does not
have that custom role defined (via PUT /_security/role/{name}), the key silently gets no access to that project — noerror is raised. For org-wide access, prefer predefined roles (admin,developer,viewer) which are available in
every project by default. If you must use custom roles across multiple projects, ensure the role is created in each
target project first.
Agent guidance: When a user asks for an API key with ES/Kibana access, default to project-scoped assignments. Only
suggest organization-scoped application_roles if the user explicitly needs access across all projects. Confirm theintent before proceeding — org-scoped access applies to future projects too. If the user specifies a custom role name
with org-scoped access, warn them that the role must be defined in each project individually.
Examples
Project-scoped key with developer ES access (using --stack-access convenience flag):
python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
--description "CI pipeline - ES ingest" \
--expiration 30d \
--roles '{"project":{"elasticsearch":[{"role_id":"developer","organization_id":"$ORG_ID","all":true}]}}' \
--stack-access developerOrganization-scoped key with admin ES access (access to ALL projects — use with caution):
python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
--description "Platform automation" \
--expiration 7d \
--roles '{"organization":[{"role_id":"organization-admin","organization_id":"$ORG_ID"}]}' \
--stack-access adminProject-scoped key with a custom role (raw JSON):
python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
--description "Marketing ETL" \
--expiration 14d \
--roles '{"project":{"elasticsearch":[{"role_id":"elasticsearch-viewer","organization_id":"$ORG_ID","all":false,"project_ids":["$PROJECT_ID"],"application_roles":["marketing-writer"]}]}}'Replace $ORG_ID and $PROJECT_ID with the actual organization and project IDs. Use list-members to discover the org ID.
Common mistake: If your API key gets a 403 when calling an ES or Kibana endpoint, the most likely cause is missing
application_roles. Unlike users, API keys must have explicitapplication_rolesto access the stack — therole_id
alone is not sufficient.
Examples
Invite a user as a Viewer on a search project
Prompt: "Add alice@example.com to my search project with read-only access."
python3 skills/cloud/access-management/scripts/cloud_access.py invite-user \
--emails alice@example.com \
--roles '{"project":{"elasticsearch":[{"role_id":"viewer","organization_id":"$ORG_ID","all":false,"project_ids":["$PROJECT_ID"]}]}}'Replace $ORG_ID and $PROJECT_ID with the actual IDs. The Viewer role is assigned when the invitation is accepted. For custom role access, use assign-custom-role after the user has accepted the invitation — do not combine a predefined role assignment with a custom role for the same project.
Create a CI/CD API key
Prompt: "Create an API key for our CI pipeline that expires in 30 days with editor access to all deployments."
python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
--description "CI/CD pipeline" \
--expiration "30d" \
--roles '{"deployment":[{"role_id":"deployment-editor","all":true}]}'The actual key value is written to a secure temp file (0600 permissions). The stdout JSON contains a _secret_file path instead of the raw secret. Tell the user to retrieve the key from that file — it is shown only once. When the CI pipeline no longer needs this key, revoke it using delete-api-key to avoid unused keys accumulating.
Create a CI/CD API key with ES access
Prompt: "Create an API key for our CI pipeline that can index data into our search projects."
python3 skills/cloud/access-management/scripts/cloud_access.py create-api-key \
--description "CI pipeline - ES ingest" \
--expiration 30d \
--roles '{"project":{"elasticsearch":[{"role_id":"developer","organization_id":"$ORG_ID","all":true}]}}' \
--stack-access developerReplace $ORG_ID with the actual organization ID. The --stack-access flag injects application_roles: ["developer"] into the role assignments, granting the key developer-level ES/Kibana API access on all Elasticsearch projects. Without --stack-access (or explicit application_roles in the JSON), the key would only have Cloud API access and receive 403 on ES/Kibana calls.
Create a custom role for marketing data
Prompt: "Create a role that gives read-only access to marketing-\* indices on my search project."
python3 skills/cloud/access-management/scripts/cloud_access.py create-custom-role \
--role-name marketing-reader \
--body '{"cluster":[],"indices":[{"names":["marketing-*"],"privileges":["read","view_index_metadata"]}]}'Then assign the custom role to a user using the assign-custom-role command, which sets application_roles in the Cloud API role assignment.
Full custom-role flow for read-only dashboards
Prompt: "Add bob@example.com to my search project with read-only dashboard access."
# 1) Create custom role in the project
python3 skills/cloud/access-management/scripts/cloud_access.py create-custom-role \
--role-name dashboard-reader \
--body '{"cluster":[],"indices":[],"applications":[{"application":"kibana-.kibana","privileges":["feature_dashboard.read"],"resources":["*"]}]}'
# 2) Invite user to the organization (no project roles — custom role handles access)
python3 skills/cloud/access-management/scripts/cloud_access.py invite-user \
--emails bob@example.com
# 3) After invitation is accepted, assign the custom role via application_roles
python3 skills/cloud/access-management/scripts/cloud_access.py assign-custom-role \
--user-id "$USER_ID" \
--project-id "$PROJECT_ID" \
--project-type elasticsearch \
--custom-role-name dashboard-readerThe user receives Viewer-level Cloud access (can see the project in the console) and only dashboard-reader permissions when they SSO into the project. Do not also assign viewer as a separate Cloud role for this project — doing so would grant the broader Viewer stack role and override the custom role's restrictions.
Update a user's project role
Prompt: "Promote Bob to admin on our observability project."
python3 skills/cloud/access-management/scripts/cloud_access.py assign-role \
--user-id "$USER_ID" \
--roles '{"project":{"observability":[{"role_id":"admin","organization_id":"$ORG_ID","all":false,"project_ids":["$PROJECT_ID"]}]}}'Replace $USER_ID, $ORG_ID, and $PROJECT_ID with actual values. Use list-members to look up the user ID. To remove a role assignment, use remove-role-assignment with the same --roles schema.
List all members and their roles
Prompt: "Show me who has access to my organization."
python3 skills/cloud/access-management/scripts/cloud_access.py list-membersThe output includes each member's user ID, email, and assigned roles.
Guidelines
- If
EC_API_KEYis not set, do not prompt the user — instruct the agent to invoke cloud-setup first. - Always confirm destructive actions (remove member, revoke key) with the user before executing.
- Prefer predefined roles over custom roles when they satisfy the access requirement.
- API keys created here are additional keys for CI/CD, scoped access, or team members. The initial key is managed by
cloud-setup.
- Secrets are never printed to stdout or stderr. The script replaces sensitive fields (
key,token,
invitation_token) with a REDACTED placeholder in stdout and writes the full unredacted response to a temporary file with 0600 (owner-read-only) permissions. The stdout JSON includes a _secret_file path pointing to that file. Never attempt to read, extract, or summarize the contents of the secret file. If the user asks for the key, tell them to open the file at the _secret_file path. After the user retrieves the secret, advise them to delete the file.
- Cloud API keys inherit roles at creation and cannot be updated — revoke and recreate to change roles.
- API key hygiene — minimize, scope, and expire:
- Before creating a key, always run
list-api-keysand check whether an existing key for the same purpose or task
already has the required roles and sufficient remaining lifetime. Keys with identical permissions serving different purposes (for example, two separate CI pipelines) are legitimate — the goal is to avoid redundant keys for the same task.
- Always set an
--expirationthat matches the intended task lifetime. Short-lived tasks (CI runs, one-time
migrations) should use short-lived keys (for example, 1d, 7d).
- After a task is complete, prompt the user to revoke any keys that are no longer needed using
delete-api-key. This
applies to both short-lived and long-running keys.
- Long-running keys (for example, monitoring pipelines) should still have a defined expiration and be rotated
periodically rather than set to never expire.
- Each organization supports up to 500 active API keys. Default expiration is 3 months.
- Invitations expire after 72 hours by default. Resend if the user has not accepted.
- For SAML SSO configuration, refer to
- Custom role security — do not over-assign: Never assign a predefined Cloud role (for example,
viewer) for a
project when using assign-custom-role for the same project. The custom role assignment implicitly grants Viewer-level Cloud access. Adding a predefined role on top widens the user's in-project permissions beyond what the custom role intended.
- If a custom role exists but the user cannot access the project, verify the role was assigned with
assign-custom-role
(which uses application_roles in the Cloud API). Creating a custom role alone does not grant project access — the Cloud API assignment is required.
- For network-level security (traffic filters, private links), see the cloud-network-security skill.
- For ES-level role management beyond Cloud roles (native users, DLS/FLS), see elasticsearch-authz.
Cloud Access Management — API Reference
All Cloud API calls use base URL https://api.elastic-cloud.com/api/v1 and require the header Authorization: ApiKey $EC_API_KEY.
Serverless ES API calls use the project Elasticsearch endpoint and require either basic auth or an Elasticsearch API key with manage_security privileges.
Table of Contents
- Organization Discovery
- Organization Members
- List members
- Invite users
- List pending invitations
- Cancel invitations
- Remove members
- Role Assignments
- Add role assignments to a user
- Role assignments schema
- Assign a custom role using application_roles
- Remove role assignments
- Cloud API Keys
- Create an API key
- List all API keys
- Delete API keys
- Serverless Custom Roles (Elasticsearch Security API)
- Create or update a custom role
- Get a custom role
- List all roles
- Delete a custom role
---
Organization Discovery
Official API docs:
List organizations
Get organizations
GET /organizationsReturns the list of organizations the authenticated user belongs to. Use to auto-discover organization_id.
curl -s -H "Authorization: ApiKey $EC_API_KEY" \
"https://api.elastic-cloud.com/api/v1/organizations"Response (200):
{
"organizations": [
{
"id": "org-uuid-here",
"name": "My Organization"
}
]
}---
Organization Members
Official API docs:
List members ·
Invite users ·
List invitations ·
Delete invitations ·
Remove members
List members
GET /organizations/{organization_id}/memberscurl -s -H "Authorization: ApiKey $EC_API_KEY" \
"https://api.elastic-cloud.com/api/v1/organizations/$ORG_ID/members"Response (200):
{
"members": [
{
"user_id": "user-uuid",
"email": "alice@example.com",
"name": "Alice",
"role_assignments": { ... }
}
]
}| Status | Meaning |
|---|---|
| 200 | Members listed successfully |
| 404 | Organization not found |
Invite users
POST /organizations/{organization_id}/invitationscurl -s -X POST \
-H "Authorization: ApiKey $EC_API_KEY" \
-H "Content-Type: application/json" \
"https://api.elastic-cloud.com/api/v1/organizations/$ORG_ID/invitations" \
-d '{
"emails": ["alice@example.com", "bob@example.com"],
"expires_in": "3d",
"role_assignments": {
"organization": [
{ "role_id": "billing-admin" }
],
"deployment": [
{
"role_id": "deployment-viewer",
"organization_id": "'"$ORG_ID"'",
"all": true
}
]
}
}'Request body fields:
| Field | Type | Required | Description |
|---|---|---|---|
emails | array\[string\] | Yes | Email addresses to invite |
expires_in | string | No | Expiration duration (default: 3d) |
role_assignments | object | No | Cloud roles to assign on acceptance (see Role Assignments below) |
| Status | Meaning | Error code |
|---|---|---|
| 201 | Invitations created | |
| 400 | User already in org | organization.user_organization_already_belongs |
| 400 | Invitation exists | organization.invitation_already_exists |
| 400 | Invalid email | organization.invitation_invalid_email |
| 403 | Invalid auth | root.invalid_authentication |
| 404 | Org or user not found | organization.not_found |
| 429 | Rate limit exceeded | organization.invitations_rate_limit_exceeded |
Cloud invitation payloads support Cloud role assignments including application_roles for custom roles. However, the recommended flow is to invite the user first (without project roles) and then assign the custom role separately using assign-custom-role after the invitation is accepted. This avoids accidentally combining a predefined role with a custom role for the same project.
List pending invitations
GET /organizations/{organization_id}/invitationscurl -s -H "Authorization: ApiKey $EC_API_KEY" \
"https://api.elastic-cloud.com/api/v1/organizations/$ORG_ID/invitations"Cancel invitations
DELETE /organizations/{organization_id}/invitations/{invitation_tokens}invitation_tokens is a comma-separated list of invitation token strings.
curl -s -X DELETE -H "Authorization: ApiKey $EC_API_KEY" \
"https://api.elastic-cloud.com/api/v1/organizations/$ORG_ID/invitations/$TOKEN"Remove members
DELETE /organizations/{organization_id}/members/{user_ids}user_ids is a comma-separated list of user IDs.
curl -s -X DELETE -H "Authorization: ApiKey $EC_API_KEY" \
"https://api.elastic-cloud.com/api/v1/organizations/$ORG_ID/members/$USER_ID"| Status | Meaning |
|---|---|
| 200 | Members removed |
| 404 | Organization or membership not found |
---
Role Assignments
Official API docs:
Add role assignments ·
Remove role assignments
Add role assignments to a user
POST /users/{user_id}/role_assignmentscurl -s -X POST \
-H "Authorization: ApiKey $EC_API_KEY" \
-H "Content-Type: application/json" \
"https://api.elastic-cloud.com/api/v1/users/$USER_ID/role_assignments" \
-d '{
"organization": [
{ "role_id": "organization-admin" }
],
"deployment": [
{
"role_id": "deployment-editor",
"organization_id": "'"$ORG_ID"'",
"all": true
}
],
"project": {
"elasticsearch": [
{
"role_id": "admin",
"organization_id": "'"$ORG_ID"'",
"all": false,
"project_ids": ["project-uuid-here"]
}
],
"observability": [],
"security": []
}
}'Role assignments schema
{
"organization": [
{
"role_id": "<org-role-id>",
"organization_id": "<org-id>",
"application_roles": ["<predefined-or-custom-role>"]
}
],
"deployment": [
{
"role_id": "<deployment-role-id>",
"organization_id": "<org-id>",
"all": true,
"deployment_ids": []
}
],
"project": {
"elasticsearch": [
{
"role_id": "<project-role-id>",
"organization_id": "<org-id>",
"all": false,
"project_ids": ["<project-id>"],
"application_roles": ["<custom-role-name>"]
}
],
"observability": [],
"security": []
}
}Organization role IDs: organization-admin, billing-admin
Deployment role IDs: deployment-admin, deployment-editor, deployment-viewer
Serverless project role IDs (Elasticsearch): admin, developer, viewer
Serverless project role IDs (Observability): admin, editor, viewer
Serverless project role IDs (Security): admin, editor, viewer, t1_analyst, t2_analyst, t3_analyst, threat_intel_analyst, rule_author, soc_manager, endpoint_operations_analyst, platform_engineer, detections_admin, endpoint_policy_manager
For project-scoped assignments that include application_roles, use project-type-specific viewer role IDs: elasticsearch-viewer, observability-viewer, security-viewer (not the generic viewer).
`application_roles` (optional, array of strings): Specifies which ES/Kibana roles to grant. Accepts predefined role names (admin, developer, viewer, and solution-specific roles) or custom role names created in the project via PUT /_security/role/{name}. Serverless only. Behavior depends on the principal type:
- For users: When set on a project-scoped assignment, the user receives these roles when signing into the project
instead of the default stack role mapped to role_id.
- For API keys: Grants ES/Kibana API access. Unlike users, API keys never inherit stack roles from
role_id—
application_roles must be explicitly provided. If omitted or empty, the API key has Cloud API access only and receives 403 Forbidden when calling ES/Kibana endpoints.
- On organization scope: Grants the specified roles across all projects (current and future) in the
organization. Supported for API keys.
Broad access warning: Organization-scoped application_roles is the broadest possible data-plane scope — itgrants ES/Kibana access to every project in the organization, including projects created after the key. Use
project-scoped assignments when the key only needs access to specific projects. Reserve org-scoped application_rolesfor platform automation keys that genuinely require cross-project access.
>
Custom roles with org scope: When using a custom role name in application_roles, the role must be defined ineach project where the key should have access (via PUT /_security/role/{name}). If a project does not have thatrole, the key silently gets no access there — no error is raised. Predefined roles (admin,developer,viewer)
are available in every project by default and do not have this limitation.
Security: When using application_roles, the user automatically receives Viewer-level Cloud access for theproject. Do not also assign a predefined Cloud role (such as viewer) for the same project as a separate roleassignment — the user would receive the union of both roles on sign-in, which is broader than the custom role intends.
Assign a custom role using application_roles
curl -s -X POST \
-H "Authorization: ApiKey $EC_API_KEY" \
-H "Content-Type: application/json" \
"https://api.elastic-cloud.com/api/v1/users/$USER_ID/role_assignments" \
-d '{
"project": {
"elasticsearch": [
{
"role_id": "elasticsearch-viewer",
"organization_id": "'"$ORG_ID"'",
"all": false,
"project_ids": ["'"$PROJECT_ID"'"],
"application_roles": ["marketing-reader"]
}
]
}
}'This grants the user Viewer-level Cloud access (project visible in the console) and only the marketing-reader custom role when they SSO into the project — not the full Viewer stack role. Use the project-type-specific Viewer role ID (elasticsearch-viewer, observability-viewer, or security-viewer) for the role_id value.
Remove role assignments
DELETE /users/{user_id}/role_assignmentsUses the same body schema as POST. Removes the specified role assignments from the user.
---
Cloud API Keys
Official API docs: Create API key
· List API keys ·
Delete API keys
Only Organization owners (organization-admin role) can create and manage Cloud API keys. Non-owner requests return 403.
Create an API key
POST /users/auth/keyscurl -s -X POST \
-H "Authorization: ApiKey $EC_API_KEY" \
-H "Content-Type: application/json" \
"https://api.elastic-cloud.com/api/v1/users/auth/keys" \
-d '{
"description": "CI/CD pipeline key",
"expiration": "30d",
"role_assignments": {
"organization": [
{ "role_id": "billing-admin" }
],
"deployment": [
{
"role_id": "deployment-editor",
"organization_id": "'"$ORG_ID"'",
"all": true
}
]
}
}'Project-scoped API key with ES access (grants developer access to all Elasticsearch projects):
curl -s -X POST \
-H "Authorization: ApiKey $EC_API_KEY" \
-H "Content-Type: application/json" \
"https://api.elastic-cloud.com/api/v1/users/auth/keys" \
-d '{
"description": "CI pipeline with ES access",
"expiration": "30d",
"role_assignments": {
"project": {
"elasticsearch": [
{
"role_id": "developer",
"organization_id": "'"$ORG_ID"'",
"all": true,
"application_roles": ["developer"]
}
]
}
}
}'Organization-scoped API key with ES access (grants admin access to ALL current and future projects):
curl -s -X POST \
-H "Authorization: ApiKey $EC_API_KEY" \
-H "Content-Type: application/json" \
"https://api.elastic-cloud.com/api/v1/users/auth/keys" \
-d '{
"description": "Platform automation key",
"expiration": "7d",
"role_assignments": {
"organization": [
{
"role_id": "organization-admin",
"organization_id": "'"$ORG_ID"'",
"application_roles": ["admin"]
}
]
}
}'Caution: This grants admin-level ES/Kibana access to every project in the organization, including projects created
after the key. Use project-scoped assignments for narrower access.
Request body fields:
| Field | Type | Required | Description |
|---|---|---|---|
description | string | No | Human-readable label for the key |
expiration | string | No | Duration string (for example, 1d, 30d, 3h). Default: 3mo |
role_assignments | object | No | Roles scoped to the key (same schema as above) |
role_assignments.organization[].application_roles | array[string] | No | ES/Kibana role names for stack access across all projects (org-scoped). If omitted or empty, the key has Cloud API access only. Predefined: admin, developer, viewer, and solution-specific roles. Custom role names also accepted. |
role_assignments.project.<type>[].application_roles | array[string] | No | ES/Kibana role names for stack access on specific projects (project-scoped). Same values as above. Required for ES/Kibana API calls — without it, the key receives 403. |
403 on ES/Kibana calls: If an API key without application_roles calls an Elasticsearch or Kibana endpoint, therequest returns 403 Forbidden. Unlike users, API keys never inherit stack roles from role_id. Add explicitapplication_roles when the key needs data-plane access.Response (201):
{
"id": "key-uuid",
"key": "the-actual-api-key-value",
"description": "CI/CD pipeline key",
"creation_date": "2026-02-27T10:00:00Z",
"expiration_date": "2026-03-29T10:00:00Z",
"organization_id": "org-uuid",
"user_id": "user-uuid",
"role_assignments": { ... }
}The key field is returned only once at creation. Store it securely.
List all API keys
GET /users/auth/keyscurl -s -H "Authorization: ApiKey $EC_API_KEY" \
"https://api.elastic-cloud.com/api/v1/users/auth/keys"Delete API keys
DELETE /users/auth/keyscurl -s -X DELETE \
-H "Authorization: ApiKey $EC_API_KEY" \
-H "Content-Type: application/json" \
"https://api.elastic-cloud.com/api/v1/users/auth/keys" \
-d '{"keys": ["key-id-1", "key-id-2"]}'| Status | Meaning |
|---|---|
| 200 | Keys deleted |
---
Serverless Custom Roles (Elasticsearch Security API)
Official API docs:
Create/update role ·
Get role ·
Delete role
These endpoints run against the project Elasticsearch endpoint, not the Cloud API. They require an Elasticsearch API key or credentials with manage_security cluster privilege.
Create or update a custom role
PUT /_security/role/{name}curl -s -X PUT \
-H "Authorization: ApiKey $ELASTICSEARCH_API_KEY" \
-H "Content-Type: application/json" \
"$ELASTICSEARCH_URL/_security/role/marketing-analyst" \
-d '{
"cluster": [],
"indices": [
{
"names": ["marketing-*"],
"privileges": ["read", "view_index_metadata"]
}
],
"applications": [
{
"application": "kibana-.kibana",
"privileges": ["feature_discover.read", "feature_dashboard.read"],
"resources": ["*"]
}
]
}'Request body fields:
| Field | Type | Description |
|---|---|---|
cluster | array\[string\] | Cluster-level privileges |
indices | array\[object\] | Index privilege entries (names, privileges, DLS/FLS) |
applications | array\[object\] | Kibana feature privileges |
Naming rules: Must start with a letter or digit. Only letters, digits, _, -, . are allowed.
Limitation: Run-as privileges are not available in Serverless.
Get a custom role
GET /_security/role/{name}curl -s -H "Authorization: ApiKey $ELASTICSEARCH_API_KEY" \
"$ELASTICSEARCH_URL/_security/role/marketing-analyst"List all roles
GET /_security/rolecurl -s -H "Authorization: ApiKey $ELASTICSEARCH_API_KEY" \
"$ELASTICSEARCH_URL/_security/role"Delete a custom role
DELETE /_security/role/{name}curl -s -X DELETE -H "Authorization: ApiKey $ELASTICSEARCH_API_KEY" \
"$ELASTICSEARCH_URL/_security/role/marketing-analyst"#!/usr/bin/env python3
"""Elastic Cloud access management helper.
Uses only Python standard library (urllib, json, os, sys, argparse).
All commands read EC_API_KEY from the environment. Custom-role commands
also require ELASTICSEARCH_URL and ELASTICSEARCH_API_KEY.
"""
import argparse
import json
import os
import stat
import sys
import tempfile
import urllib.error
import urllib.request
CLOUD_BASE_DEFAULT = "https://api.elastic-cloud.com"
PROJECT_VIEWER_ROLE_IDS = {
"elasticsearch": "elasticsearch-viewer",
"observability": "observability-viewer",
"security": "security-viewer",
}
def _get_env(name, required_for="this operation"):
val = os.environ.get(name, "").strip()
if not val:
_fail(
f"Environment variable {name} is not set. "
f"It is required for {required_for}. "
"Run the cloud-setup skill first to configure Cloud access."
)
return val
def _fail(message, code=1):
json.dump({"error": message}, sys.stderr)
print(file=sys.stderr)
sys.exit(code)
# Keys that must never appear in stdout (agent-visible output).
_SECRET_FIELDS = {"key", "token", "invitation_token", "secret"}
def _has_secrets(obj):
"""Return True if obj (dict or list) contains any _SECRET_FIELDS with values."""
if isinstance(obj, dict):
for k, v in obj.items():
if k in _SECRET_FIELDS and isinstance(v, str) and v:
return True
if _has_secrets(v):
return True
elif isinstance(obj, list):
for item in obj:
if _has_secrets(item):
return True
return False
def _redact_for_stdout(obj):
"""Return a deep copy with secret fields replaced by a redaction notice."""
if isinstance(obj, dict):
out = {}
for k, v in obj.items():
if k in _SECRET_FIELDS and isinstance(v, str) and v:
out[k] = "REDACTED — written to secure file (see _secret_file path)"
else:
out[k] = _redact_for_stdout(v)
return out
if isinstance(obj, list):
return [_redact_for_stdout(item) for item in obj]
return obj
def _write_secrets_to_file(result):
"""Write the full unredacted response to a temp file with 0600 permissions.
Returns the file path if secrets were found, otherwise None.
"""
if not _has_secrets(result):
return None
fd, path = tempfile.mkstemp(prefix="ec_secret_", suffix=".json")
try:
os.fchmod(fd, stat.S_IRUSR | stat.S_IWUSR)
with os.fdopen(fd, "w") as f:
json.dump(result, f, indent=2)
except Exception:
os.close(fd)
raise
return path
def _safe_output(result):
"""Write redacted JSON to stdout; secrets to a secure temp file."""
secret_file = _write_secrets_to_file(result)
redacted = _redact_for_stdout(result)
if secret_file:
redacted["_secret_file"] = secret_file
print(
f"[NOTICE] Secret values written to: {secret_file} "
f"(permissions: 0600, owner-read only)",
file=sys.stderr,
)
json.dump(redacted, sys.stdout, indent=2)
print()
def _cloud_base():
base = os.environ.get("EC_BASE_URL", "").rstrip("/") or CLOUD_BASE_DEFAULT
return f"{base}/api/v1"
def _cloud_request(method, path, body=None):
api_key = _get_env("EC_API_KEY", "Cloud API calls")
url = f"{_cloud_base()}{path}"
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(url, data=data, method=method)
req.add_header("Authorization", f"ApiKey {api_key}")
req.add_header("Content-Type", "application/json")
req.add_header("User-Agent", "elastic-agentic")
try:
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as exc:
try:
detail = json.loads(exc.read().decode())
except Exception:
detail = {"status": exc.code, "reason": exc.reason}
_fail(f"Cloud API error ({exc.code}): {json.dumps(detail)}")
def _es_request(method, path, body=None):
endpoint = _get_env("ELASTICSEARCH_URL", "custom role operations").rstrip("/")
api_key = _get_env("ELASTICSEARCH_API_KEY", "custom role operations")
url = f"{endpoint}{path}"
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(url, data=data, method=method)
req.add_header("Authorization", f"ApiKey {api_key}")
req.add_header("Content-Type", "application/json")
req.add_header("User-Agent", "elastic-agentic")
try:
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as exc:
try:
detail = json.loads(exc.read().decode())
except Exception:
detail = {"status": exc.code, "reason": exc.reason}
_fail(f"Elasticsearch API error ({exc.code}): {json.dumps(detail)}")
def _discover_org_id():
result = _cloud_request("GET", "/organizations")
orgs = result.get("organizations", [])
if not orgs:
_fail("No organizations found for the authenticated user.")
return orgs[0]["id"]
def _resolve_org_id(args):
return args.org_id if args.org_id else _discover_org_id()
def _project_viewer_role_id(project_type):
role_id = PROJECT_VIEWER_ROLE_IDS.get(project_type)
if not role_id:
_fail(f"Unsupported --project-type: {project_type}")
return role_id
def _inject_application_roles(role_assignments, app_roles):
"""Inject application_roles into role assignment entries that lack them."""
for entry in role_assignments.get("organization", []):
if "application_roles" not in entry:
entry["application_roles"] = app_roles
project = role_assignments.get("project", {})
for project_type in ("elasticsearch", "observability", "security"):
for entry in project.get(project_type, []):
if "application_roles" not in entry:
entry["application_roles"] = app_roles
# ---------------------------------------------------------------------------
# Commands
# ---------------------------------------------------------------------------
def cmd_list_members(args):
org_id = _resolve_org_id(args)
result = _cloud_request("GET", f"/organizations/{org_id}/members")
json.dump(result, sys.stdout, indent=2)
print()
def cmd_invite_user(args):
org_id = _resolve_org_id(args)
emails = [e.strip() for e in args.emails.split(",")]
body = {"emails": emails}
if args.expires_in:
body["expires_in"] = args.expires_in
if args.roles:
body["role_assignments"] = json.loads(args.roles)
result = _cloud_request("POST", f"/organizations/{org_id}/invitations", body)
_safe_output(result)
def cmd_remove_member(args):
org_id = _resolve_org_id(args)
if not args.user_id:
_fail("--user-id is required for remove-member.")
if args.dry_run:
json.dump({"dry_run": True, "action": "remove-member", "org_id": org_id, "user_id": args.user_id}, sys.stdout, indent=2)
print()
return
result = _cloud_request("DELETE", f"/organizations/{org_id}/members/{args.user_id}")
json.dump(result, sys.stdout, indent=2)
print()
def cmd_create_api_key(args):
body = {}
if args.description:
body["description"] = args.description
if args.expiration:
body["expiration"] = args.expiration
if args.roles:
body["role_assignments"] = json.loads(args.roles)
if args.stack_access:
if "role_assignments" not in body:
_fail(
"--stack-access requires --roles to specify the scope "
"(project or organization) for the API key."
)
app_roles = [r.strip() for r in args.stack_access.split(",") if r.strip()]
if not app_roles:
_fail("--stack-access must specify at least one non-empty role name.")
_inject_application_roles(body["role_assignments"], app_roles)
result = _cloud_request("POST", "/users/auth/keys", body)
_safe_output(result)
def cmd_list_api_keys(args):
result = _cloud_request("GET", "/users/auth/keys")
json.dump(result, sys.stdout, indent=2)
print()
def cmd_delete_api_key(args):
if not args.key_ids:
_fail("--key-ids is required for delete-api-key.")
ids = [k.strip() for k in args.key_ids.split(",")]
if args.dry_run:
json.dump({"dry_run": True, "action": "delete-api-key", "key_ids": ids}, sys.stdout, indent=2)
print()
return
result = _cloud_request("DELETE", "/users/auth/keys", {"keys": ids})
json.dump(result, sys.stdout, indent=2)
print()
def cmd_assign_role(args):
if not args.user_id:
_fail("--user-id is required for assign-role.")
if not args.roles:
_fail("--roles JSON is required for assign-role.")
body = json.loads(args.roles)
result = _cloud_request("POST", f"/users/{args.user_id}/role_assignments", body)
json.dump(result, sys.stdout, indent=2)
print()
def cmd_remove_role_assignment(args):
if not args.user_id:
_fail("--user-id is required for remove-role-assignment.")
if not args.roles:
_fail("--roles JSON is required for remove-role-assignment.")
body = json.loads(args.roles)
result = _cloud_request("DELETE", f"/users/{args.user_id}/role_assignments", body)
json.dump(result, sys.stdout, indent=2)
print()
def cmd_create_custom_role(args):
if not args.role_name:
_fail("--role-name is required for create-custom-role.")
body = json.loads(args.body) if args.body else {}
if args.body_file:
with open(args.body_file) as f:
body = json.load(f)
result = _es_request("PUT", f"/_security/role/{args.role_name}", body)
json.dump(result, sys.stdout, indent=2)
print()
def cmd_list_roles(args):
path = f"/_security/role/{args.role_name}" if args.role_name else "/_security/role"
result = _es_request("GET", path)
json.dump(result, sys.stdout, indent=2)
print()
def cmd_delete_role(args):
if not args.role_name:
_fail("--role-name is required for delete-role.")
if args.dry_run:
json.dump({"dry_run": True, "action": "delete-role", "role_name": args.role_name}, sys.stdout, indent=2)
print()
return
result = _es_request("DELETE", f"/_security/role/{args.role_name}")
json.dump(result, sys.stdout, indent=2)
print()
def cmd_assign_custom_role(args):
if not args.user_id:
_fail("--user-id is required for assign-custom-role.")
if not args.project_id:
_fail("--project-id is required for assign-custom-role.")
if not args.custom_role_name:
_fail("--custom-role-name is required for assign-custom-role.")
org_id = _resolve_org_id(args)
role_id = _project_viewer_role_id(args.project_type)
body = {
"project": {
args.project_type: [
{
"role_id": role_id,
"organization_id": org_id,
"all": False,
"project_ids": [args.project_id],
"application_roles": [args.custom_role_name],
}
]
}
}
if args.dry_run:
json.dump(
{"dry_run": True, "action": "assign-custom-role", "request_body": body},
sys.stdout,
indent=2,
)
print()
return
result = _cloud_request("POST", f"/users/{args.user_id}/role_assignments", body)
json.dump(result, sys.stdout, indent=2)
print()
def cmd_remove_custom_role_assignment(args):
if not args.user_id:
_fail("--user-id is required for remove-custom-role-assignment.")
if not args.project_id:
_fail("--project-id is required for remove-custom-role-assignment.")
if not args.custom_role_name:
_fail("--custom-role-name is required for remove-custom-role-assignment.")
org_id = _resolve_org_id(args)
role_id = _project_viewer_role_id(args.project_type)
body = {
"project": {
args.project_type: [
{
"role_id": role_id,
"organization_id": org_id,
"all": False,
"project_ids": [args.project_id],
"application_roles": [args.custom_role_name],
}
]
}
}
if args.dry_run:
json.dump(
{
"dry_run": True,
"action": "remove-custom-role-assignment",
"request_body": body,
},
sys.stdout,
indent=2,
)
print()
return
result = _cloud_request("DELETE", f"/users/{args.user_id}/role_assignments", body)
json.dump(result, sys.stdout, indent=2)
print()
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="Elastic Cloud access management helper"
)
sub = parser.add_subparsers(dest="command")
sub.required = True
# list-members
p = sub.add_parser(
"list-members",
help="List organization members",
epilog="Example: %(prog)s",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument("--org-id", default=None, help="Organization ID (auto-discovered if omitted)")
p.set_defaults(func=cmd_list_members)
# invite-user
p = sub.add_parser(
"invite-user",
help="Invite user(s) to the organization",
epilog=(
"Examples:\n"
" %(prog)s --emails alice@example.com\n"
' %(prog)s --emails alice@example.com,bob@example.com --expires-in 7d \\\n'
""" --roles '{"project":{"elasticsearch":[{"role_id":"viewer","organization_id":"ORG","all":true}]}}'"""
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument("--org-id", default=None)
p.add_argument("--emails", required=True, help="Comma-separated email addresses")
p.add_argument("--roles", default=None, help="Role assignments JSON string")
p.add_argument("--expires-in", default=None, help="Invitation expiration (for example, 3d)")
p.set_defaults(func=cmd_invite_user)
# remove-member
p = sub.add_parser(
"remove-member",
help="Remove a member from the organization",
epilog=(
"Examples:\n"
" %(prog)s --user-id abc-123\n"
" %(prog)s --user-id abc-123 --dry-run"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument("--org-id", default=None)
p.add_argument("--user-id", required=True, help="User ID to remove")
p.add_argument("--dry-run", action="store_true", help="Preview the action without executing it")
p.set_defaults(func=cmd_remove_member)
# create-api-key
p = sub.add_parser(
"create-api-key",
help="Create a Cloud API key",
epilog=(
"Examples:\n"
' %(prog)s --description "CI key" --expiration 30d\n'
' %(prog)s --description "Scoped key" --expiration 7d \\\n'
""" --roles '{"deployment":[{"role_id":"deployment-viewer","all":true}]}'\n"""
"\n"
" # Cloud + ES API access on specific projects (convenience flag):\n"
' %(prog)s --description "CI with ES" --expiration 30d \\\n'
""" --roles '{"project":{"elasticsearch":[{"role_id":"developer","organization_id":"ORG","all":true}]}}' \\\n"""
" --stack-access developer\n"
"\n"
" # Cloud + ES API access (raw JSON with application_roles):\n"
' %(prog)s --description "CI with ES" --expiration 30d \\\n'
""" --roles '{"project":{"elasticsearch":[{"role_id":"developer","organization_id":"ORG","all":true,"application_roles":["developer"]}]}}'\n"""
"\n"
" # Org-scoped with ES admin access to ALL current and future projects:\n"
" # WARNING: grants ES/KB access to every project in the organization\n"
' %(prog)s --description "Platform key" --expiration 7d \\\n'
""" --roles '{"organization":[{"role_id":"organization-admin","organization_id":"ORG"}]}' \\\n"""
" --stack-access admin"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument("--description", default=None, help="Key description")
p.add_argument("--expiration", default=None, help="Expiration duration (for example, 30d, 3h)")
p.add_argument("--roles", default=None, help="Role assignments JSON string")
p.add_argument(
"--stack-access",
default=None,
help=(
"Comma-separated ES/Kibana role names to grant via application_roles. "
"Injects into organization and project role assignment entries from "
"--roles that lack application_roles (deployment entries are not "
"modified). Predefined: admin, developer, viewer. "
"Custom roles also accepted."
),
)
p.set_defaults(func=cmd_create_api_key)
# list-api-keys
p = sub.add_parser("list-api-keys", help="List all Cloud API keys")
p.set_defaults(func=cmd_list_api_keys)
# delete-api-key
p = sub.add_parser(
"delete-api-key",
help="Delete Cloud API key(s)",
epilog=(
"Examples:\n"
" %(prog)s --key-ids key-abc-123\n"
" %(prog)s --key-ids key-1,key-2 --dry-run"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument("--key-ids", required=True, help="Comma-separated key IDs to delete")
p.add_argument("--dry-run", action="store_true", help="Preview the action without executing it")
p.set_defaults(func=cmd_delete_api_key)
# assign-role
p = sub.add_parser(
"assign-role",
help="Add role assignments to a user",
epilog=(
"Example:\n"
" %(prog)s --user-id abc-123 \\\n"
""" --roles '{"project":{"elasticsearch":[{"role_id":"admin","organization_id":"ORG","all":false,"project_ids":["PROJ"]}]}}'"""
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument("--user-id", required=True, help="User ID to assign roles to")
p.add_argument("--roles", required=True, help="Role assignments JSON string")
p.set_defaults(func=cmd_assign_role)
# remove-role-assignment
p = sub.add_parser("remove-role-assignment", help="Remove role assignments from a user")
p.add_argument("--user-id", required=True, help="User ID to remove roles from")
p.add_argument("--roles", required=True, help="Role assignments JSON string to remove")
p.set_defaults(func=cmd_remove_role_assignment)
# create-custom-role
p = sub.add_parser(
"create-custom-role",
help="Create a Serverless custom role",
epilog=(
"Examples:\n"
" %(prog)s --role-name marketing-reader \\\n"
""" --body '{"cluster":[],"indices":[{"names":["marketing-*"],"privileges":["read"]}]}'\n"""
" %(prog)s --role-name analytics --body-file role-def.json"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument("--role-name", required=True, help="Role name (kebab-case)")
p.add_argument("--body", default=None, help="Role body as JSON string")
p.add_argument("--body-file", default=None, help="Path to JSON file with role body")
p.set_defaults(func=cmd_create_custom_role)
# list-roles
p = sub.add_parser("list-roles", help="List custom roles on a Serverless project")
p.add_argument("--role-name", default=None, help="Specific role name (lists all if omitted)")
p.set_defaults(func=cmd_list_roles)
# delete-role
p = sub.add_parser(
"delete-role",
help="Delete a custom role from a Serverless project",
epilog=(
"Examples:\n"
" %(prog)s --role-name marketing-reader\n"
" %(prog)s --role-name marketing-reader --dry-run"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument("--role-name", required=True, help="Role name to delete")
p.add_argument("--dry-run", action="store_true", help="Preview the action without executing it")
p.set_defaults(func=cmd_delete_role)
# assign-custom-role
p = sub.add_parser(
"assign-custom-role",
help="Assign a custom role to a user on a Serverless project via application_roles",
epilog=(
"Examples:\n"
" %(prog)s --user-id abc-123 --project-id proj-456 --project-type elasticsearch \\\n"
" --custom-role-name dashboard-reader\n"
" %(prog)s --user-id abc-123 --project-id proj-456 --project-type elasticsearch \\\n"
" --custom-role-name dashboard-reader --dry-run"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument("--org-id", default=None, help="Organization ID (auto-discovered if omitted)")
p.add_argument("--user-id", required=True, help="User ID to assign the custom role to")
p.add_argument("--project-id", required=True, help="Serverless project ID")
p.add_argument(
"--project-type",
required=True,
choices=tuple(PROJECT_VIEWER_ROLE_IDS.keys()),
help="Project solution type",
)
p.add_argument("--custom-role-name", required=True, help="Name of the custom role created in the project")
p.add_argument("--dry-run", action="store_true", help="Preview the action without executing it")
p.set_defaults(func=cmd_assign_custom_role)
# remove-custom-role-assignment
p = sub.add_parser(
"remove-custom-role-assignment",
help="Remove a custom role assignment from a user on a Serverless project",
epilog=(
"Examples:\n"
" %(prog)s --user-id abc-123 --project-id proj-456 --project-type elasticsearch \\\n"
" --custom-role-name dashboard-reader\n"
" %(prog)s --user-id abc-123 --project-id proj-456 --project-type elasticsearch \\\n"
" --custom-role-name dashboard-reader --dry-run"
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument("--org-id", default=None, help="Organization ID (auto-discovered if omitted)")
p.add_argument("--user-id", required=True, help="User ID to remove the custom role from")
p.add_argument("--project-id", required=True, help="Serverless project ID")
p.add_argument(
"--project-type",
required=True,
choices=tuple(PROJECT_VIEWER_ROLE_IDS.keys()),
help="Project solution type",
)
p.add_argument("--custom-role-name", required=True, help="Name of the custom role to remove")
p.add_argument("--dry-run", action="store_true", help="Preview the action without executing it")
p.set_defaults(func=cmd_remove_custom_role_assignment)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()
Related skills
Forks & variants (1)
Cloud Access Management has 1 known copy in the catalog totaling 2 installs. They canonicalize to this original listing.
- elastic - 2 installs
How it compares
Use cloud-access-management over generic cloud skills when automating Elastic Cloud org access and Elasticsearch security roles via the official API.
FAQ
What does cloud access management do?
Manage Elastic Cloud organization access: invite users, assign roles to Serverless projects, and create or revoke Cloud API keys. Use when granting, modifying, or auditing user access.
When should I invoke cloud access management?
Manage Elastic Cloud organization access: invite users, assign roles to Serverless projects, and create or revoke Cloud API keys. Use when granting, modifying, or auditing user access.
What are key capabilities?
Invite a user to the organization and assign them a Serverless project role
Is Cloud Access Management safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.