Elaichi Roles API
The Roles API from Elaichi — 3 operation(s) for roles.
The Roles API from Elaichi — 3 operation(s) for roles.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/elaichi-ai:elaichi-ai-roles-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Elaichi Roles API
version: 0.1.0
description: 'Control-plane HTTP API for Elaichi, the MCP control plane for SaaS teams. Authenticate with an organization API token (create one in the Elaichi app under Settings → API tokens) sent as a bearer token, plus the `X-Organization-Id` header on org-scoped routes; the MCP and OAuth endpoints use their own protocols. A few actions are deliberately not available to an API token and must be done by a person in the Elaichi app: managing API and SCIM tokens, creating an organization, approving an access request, deleting an organization, and changing the two-factor requirement. The operations concerned say so. Endpoints used only by the Elaichi app itself (sign-in, consent screens, browser redirects, inbound webhooks) are not documented.'
servers:
- url: https://api.elaichi.ai
description: Production
tags:
- name: Roles
paths:
/role:
get:
operationId: listRoles
summary: List roles
tags:
- Roles
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required:
- result
- next_cursor
- prev_cursor
properties:
result:
type: array
items:
$ref: '#/components/schemas/Role'
next_cursor:
type:
- string
- 'null'
prev_cursor:
type:
- string
- 'null'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limited
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Roles in this organization, system and custom alike. Any member may read them — this is what populates a role picker before `PATCH /member/{userId}` or `POST /invite`.
security:
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/OrganizationIdHeader'
- $ref: '#/components/parameters/LimitQuery'
- $ref: '#/components/parameters/CursorQuery'
post:
operationId: createRole
summary: Create role
tags:
- Roles
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/Role'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limited
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Authoring custom roles requires the `role:manage` permission and the `custom_roles` plan feature; the predefined system roles stay readable and assignable on every plan. You may only grant permissions you hold yourself — otherwise an admin could mint a role carrying `billing:manage` and assign it to themselves. Validate names against `GET /permission`.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
- permissions
properties:
name:
type: string
minLength: 1
maxLength: 120
description:
type:
- string
- 'null'
maxLength: 2000
permissions:
type: array
maxItems: 100
items:
type: string
description: Permission names from `GET /permission`. The complete set for this role.
parameters:
- $ref: '#/components/parameters/OrganizationIdHeader'
/role/{id}:
get:
operationId: getRole
summary: Get role
tags:
- Roles
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Role'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limited
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Returns one role with its permissions and member counts. Any member may read it.
security:
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/OrganizationIdHeader'
- name: id
in: path
required: true
schema:
type: string
description: Role id (`role_…`).
patch:
operationId: patchRole
summary: Update role
tags:
- Roles
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Role'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limited
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: 'Updates a custom role. **`permissions` is REPLACED wholesale, not merged** — the array you send becomes the role''s entire permission set, so anything omitted is revoked from every member holding the role. Read the role first and send the full intended list. Omitting `permissions` entirely leaves it untouched. Edits apply immediately: effective permissions are recomputed on every request. Requires `role:manage`, the `custom_roles` feature, and that you hold every permission you are granting.'
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 120
description:
type:
- string
- 'null'
maxLength: 2000
permissions:
type: array
maxItems: 100
items:
type: string
description: Replaces the role's permission set entirely.
parameters:
- $ref: '#/components/parameters/OrganizationIdHeader'
- name: id
in: path
required: true
schema:
type: string
description: Role id (`role_…`).
delete:
operationId: deleteRole
summary: Delete role
tags:
- Roles
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required:
- success
- reassigned_member_count
- reassigned_invite_count
properties:
success:
type: boolean
reassigned_member_count:
type: integer
description: How many members were moved onto `reassign_to`. 0 when none held the role.
reassigned_invite_count:
type: integer
description: How many pending invites were re-pointed at `reassign_to`.
reassigned_by_role:
type: array
items:
type: object
properties:
role_id:
type: string
role_name:
type: string
count:
type: integer
description: One row per distinct role members were moved onto (just `reassign_to` here). Empty when nobody held the role.
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limited
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: 'Deletes a custom role. Irreversible. A member holds exactly one role, so a role anybody still holds cannot simply disappear: without `reassign_to` the call answers `400 role_in_use` while `member_count` or `pending_invite_count` is above zero (`GET /member?role_id={id}` lists the holders). With `reassign_to`, every holder and every pending invite for the role is moved onto that role and the role is deleted in one atomic operation — everything moves and the role is gone, or nothing changes. The move takes effect on each member''s next request. A role that a SCIM group mapping assigns (`group_mapping_count` above zero) is refused with `409 role_mapped`, `reassign_to` or not: change the mapping first. Requires `role:manage` and the `custom_roles` feature. Sending `reassign_to` at all additionally requires `member:manage` and that you hold every permission the replacement role carries (the `PATCH /member/{userId}` rules), and the target must exist and differ from `id` — checked whether or not anything needs moving. It is further refused if you hold the deleted role yourself, if the replacement is Org Owner and you are not one, or if it would leave no billable member. System roles cannot be deleted. This is the query-parameter-only form; `POST /role/{id}/delete` is the same operation with a JSON body, for a client that also wants to move specific holders onto a role of their own choosing. A browser session must also pass a fresh step-up reauthentication in the app (without it the call answers `428 step_up_required`). An organization API token is not challenged.'
security:
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/OrganizationIdHeader'
- name: id
in: path
required: true
schema:
type: string
description: Role id (`role_…`).
- name: reassign_to
in: query
required: false
description: Role id (`role_…`) to move every current holder and pending invite onto before deleting. Must exist and differ from `id`; its authorization (`member:manage` and the grant ceiling) applies even when nothing holds the role, in which case nothing is moved.
schema:
type: string
/role/{id}/delete:
post:
operationId: deleteRoleStructured
summary: Delete a role, with a JSON body
tags:
- Roles
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required:
- success
- reassigned_member_count
- reassigned_invite_count
- reassigned_by_role
properties:
success:
type: boolean
reassigned_member_count:
type: integer
reassigned_invite_count:
type: integer
reassigned_by_role:
type: array
items:
type: object
properties:
role_id:
type: string
role_name:
type: string
count:
type: integer
description: One row per distinct target role that received at least one member.
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limited
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: 'Deletes a role, taking a JSON body. The same operation as `DELETE /role/{id}` — same authorization, same guards, same atomic transaction — with a JSON body instead of a query parameter, so a caller can also move SPECIFIC holders onto a role of their own choosing before the rest fall back to `reassign_to`. `reassignments` is capped at 200 entries; past it the call answers `400` naming the ceiling. Each entry''s `role_id` is checked against the same grant ceiling as `reassign_to` (never the role being deleted), and each `user_id` must currently hold the role — one that no longer does answers `409` (`role_holder_changed`), so the caller can refresh and retry. `reassign_to` becomes optional once every holder is covered by `reassignments` AND no pending invite is for the role (invites are never individually overridden, and always fall back to `reassign_to`); short of that it is still required, exactly as on the plain DELETE form. The response adds `reassigned_by_role`: one row per DISTINCT target role actually used, bounded by how many roles the caller named.'
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
reassign_to:
type: string
description: Same as `DELETE /role/{id}`'s `reassign_to` query parameter — the default target.
reassignments:
type: array
maxItems: 200
items:
type: object
required:
- user_id
- role_id
properties:
user_id:
type: string
description: User id (`usr_…`) of a current holder of `id`.
role_id:
type: string
description: Role id (`role_…`) to move that one member onto.
description: Per-member overrides. Bounded, and never a substitute for `reassign_to` on invites.
parameters:
- $ref: '#/components/parameters/OrganizationIdHeader'
- name: id
in: path
required: true
schema:
type: string
description: Role id (`role_…`) being deleted.
components:
parameters:
OrganizationIdHeader:
name: X-Organization-Id
in: header
required: false
description: Target organization (`org_…`). Required for cookie/session auth on org-scoped routes; optional for API tokens, which carry their organization, and rejected when it names a different one.
schema:
type: string
CursorQuery:
name: cursor
in: query
required: false
description: Opaque cursor — pass the `next_cursor` from a previous response. Ignored by unpaginated endpoints.
schema:
type: string
LimitQuery:
name: limit
in: query
required: false
description: Page size. Defaults to 50. Values above 200 are silently clamped to 200 rather than rejected; a value below 1, or a non-integer, is a 400. Ignored by the endpoints documented as unpaginated.
schema:
type: integer
minimum: 1
maximum: 200
default: 50
schemas:
Error:
type: object
required:
- error
description: The Elaichi error envelope. The MCP OAuth endpoints under `/oauth` are the exception — they return RFC 6749 errors instead (see `OAuthError`).
properties:
error:
type: object
required:
- message
- code
properties:
message:
type: string
description: Human-readable. 5xx messages are deliberately generic.
code:
type: string
description: Stable machine-readable code, e.g. `not_found`, `subscription_required`.
details:
description: Structured detail for codes that carry one, e.g. `toolboxes_need_resolution`.
feature:
type: string
description: The gated feature, on entitlement failures.
plan:
type:
- string
- 'null'
enum:
- personal
- gold
- black
- null
additionalProperties: true
Role:
type: object
properties:
id:
type: string
description: Role id (`role_…`).
name:
type: string
description:
type:
- string
- 'null'
is_system:
type: boolean
description: Predefined roles ship with every organization and cannot be edited away.
seat_class:
type: string
enum:
- billable
- free_admin
description: Whether members holding only this role count toward billable seats.
permissions:
type: array
items:
type: string
description: Every permission the role holds, including any `GET /permission` does not list for this organization (for example the Apps permissions while Apps are unavailable). `PATCH /role/{id}` replaces this list wholesale, so send back the entries you did not mean to remove.
permission_count:
type: integer
description: 'How many of `permissions` `GET /permission` lists for this organization: the number to show beside the role. Can be lower than `permissions.length`.'
member_count:
type: integer
description: How many members hold this role right now. `GET /member?role_id={id}` lists them; while it is above zero, `DELETE /role/{id}` needs `reassign_to` (or, on `POST /role/{id}/delete`, `reassign_to` and/or a `reassignments` entry for every holder).
pending_invite_count:
type: integer
description: Pending invites issued for this role. Present only on roles you can manage. While above zero, `DELETE /role/{id}` and `POST /role/{id}/delete` need `reassign_to`, which re-points them.
group_mapping_count:
type: integer
description: SCIM group mappings that assign this role. Present only on roles you can manage. While above zero, `DELETE /role/{id}` is refused (`409 role_mapped`) until the mappings change.
can_reassign_holders:
type: boolean
description: 'Whether you may move this role''s holders and pending invites to another role (`DELETE /role/{id}?reassign_to=` or `POST /role/{id}/delete`): you hold `member:manage` and do not hold this role yourself.'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: session or API token
description: '`Authorization: Bearer <token>` with an org-scoped API token (`elch_{org_id}_{64 hex}`), created in the Elaichi app under Settings → API tokens. The Elaichi app itself signs in with a session cookie, which is also accepted but is not meant for integrations. Two other credential families exist and are NOT interchangeable with this one: an MCP OAuth access token (`POST /mcp` only) and a SCIM token (`/scim/v2/*` only).'
mcpOAuth:
type: oauth2
description: MCP OAuth access token, used only by `POST /mcp` — not interchangeable with `bearerAuth`. Discovered per RFC 9728/8414 at `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`.
flows:
authorizationCode:
authorizationUrl: https://api.elaichi.ai/oauth/authorize
tokenUrl: https://api.elaichi.ai/oauth/token
scopes:
mcp:read: Anything you can already see in Elaichi. Never secret values. It can also ask an admin for access to something restricted for you.
mcp:write: Create and change teams, roles, toolboxes, templates and connections, and share access.
mcp:destructive: Permanently delete data and remove access in Elaichi. This cannot be undone.
mcp:tools: Run tools from the toolboxes you choose next, deleting in your connected apps included. It can also ask an admin for access to something restricted for you.
openid: That you are signed in, and the user ID we use for you. Nothing else.
email: Your email address, and whether it is verified. Apps use this to check your domain.