> For the complete documentation index, see [llms.txt](https://docs.algenta.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.algenta.ai/deploy-and-operate/teams-rbac.md).

# Teams & roles (RBAC)

Invite team members and set roles, understand the owner/admin/member/viewer permission matrix, and read the org audit log and its content-hashed artifacts.

An Algenta organization has members, each with one role, and every privileged action is gated by that role. This page covers inviting and managing members, the exact capabilities each role grants, and how to read the audit log — including the tamper-evident, content-hashed artifact records.

Roles are ordered highest to lowest: **owner → admin → member → viewer**. A role grants everything below it, so an owner can do anything an admin can, and so on.

{% hint style="info" %}
Managing members and viewing audit logs require the **admin** role or higher; requesting or tearing down a cloud deployment requires **owner**. All examples send `Authorization: Bearer $ALGENTA_API_KEY`; swap `https://api.algenta.ai` for `http://localhost:8000` on a self-hosted engine.
{% endhint %}

## Invite and manage members

{% stepper %}
{% step %}

### Invite a member

Send an invite with an email and a role. Valid roles are `owner`, `admin`, `member`, and `viewer`; `role` defaults to `member` if omitted.

```bash
curl -sS -X POST "https://api.algenta.ai/v1/team/invite" \
  -H "Authorization: Bearer $ALGENTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "teammate@example.com", "role": "member" }'
```

The `201` response returns the `invite_id` along with the invite's `email`, `role`, `status`, and `expires_at`. An unknown role is rejected with `422` `invalid_role`.
{% endstep %}

{% step %}

### List the team

Read the current members. Pass `page` and `limit` for a paginated envelope; omit both to get a plain array.

```bash
curl -sS "https://api.algenta.ai/v1/team?page=1&limit=25" \
  -H "Authorization: Bearer $ALGENTA_API_KEY"
```

Each member carries `user_id`, `name`, `email`, `role`, and `status` (for example `active` or `suspended`).
{% endstep %}

{% step %}

### Change a role or remove a member

Update a member's role, or remove them from the org.

```bash
curl -sS -X PATCH "https://api.algenta.ai/v1/team/$USER_ID/role" \
  -H "Authorization: Bearer $ALGENTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "role": "admin" }'
```

```bash
curl -sS -X DELETE "https://api.algenta.ai/v1/team/$USER_ID" \
  -H "Authorization: Bearer $ALGENTA_API_KEY"
```

Removing a member returns `204`. Removal suspends the member (it never deletes them) and, in the same step, revokes their API keys, revokes the devices registered under those keys, revokes pending invitations they sent or that are addressed to them, and stops any GitHub App installation they connected from admitting GitHub organization admins until an admin connects it again. Removing a member who is already suspended — for example by a seat downgrade, which revokes the same things — succeeds and revokes anything they still held. A seat downgrade suspends only the members beyond the seats the organization pays for (owners and the longest-standing members keep theirs); renewals and billing updates within those seats suspend nobody. Re-inviting a removed member reactivates them once they accept, but their old API keys stay revoked — they mint new ones.

Changing a member's role to `member` or `viewer` also stops any GitHub App installation they connected from admitting GitHub organization admins until an admin connects it again. A member's GitHub identity never acts above their role: signing in with another account linked to the same GitHub identity gives no more access than the member's own seat. This holds for every GitHub identity the member has signed in with, not only the first, and a removed member gets no access through any of them.

Several guardrails protect the org:

* You cannot change **your own** role (`403` `self_role_change_forbidden`).
* Only an **owner** may grant the `owner` role (`403` `owner_grant_forbidden`).
* The **last active owner** cannot be demoted or removed (`409` `last_owner`), so an org can never end up ownerless.
* Accepting an invitation needs the invitee's own signed-in session with a confirmed email equal to the invited address, and the inviter must still be able to grant it (`409` `invite_inviter_inactive`). An invitation sent before a member's removal never reactivates them (`409` `invite_predates_removal`).
* Team changes that race each other are serialized; if one still loses a database race it returns `409` `team_update_conflict` with `details.retryable: true` — retry it.
  {% endstep %}
  {% endstepper %}

{% hint style="success" %}
**Expected result** — `POST /v1/team/invite` returns `201` with an `invite_id`, the new member appears in `GET /v1/team`, and a role change is reflected on the member's `role` field. Attempts that violate a guardrail return a `403` or `409` with a specific error `code`.
{% endhint %}

## Role permission matrix

Each role grants a superset of the roles beneath it. The table below shows where each capability's minimum role sits.

| Capability                                      | viewer | member | admin | owner |
| ----------------------------------------------- | :----: | :----: | :---: | :---: |
| Run simulations, queries, and reads             |   yes  |   yes  |  yes  |  yes  |
| Manage your own API keys                        |   no   |   yes  |  yes  |  yes  |
| Revoke another member's API key                 |   no   |   no   |  yes  |  yes  |
| Revoke a service key (e.g. the GitHub App's)    |   no   |   no   |   no  |  yes  |
| Invite / remove members, change roles           |   no   |   no   |  yes  |  yes  |
| View audit logs and egress policy               |   no   |   no   |  yes  |  yes  |
| View deployment cost                            |   no   |   no   |  yes  |  yes  |
| Activate a server deployment license            |   no   |   no   |  yes  |  yes  |
| Move a deployment to a different deployment key |   no   |   no   |   no  |  yes  |
| Request / deprovision a deployment              |   no   |   no   |   no  |  yes  |
| Grant the `owner` role                          |   no   |   no   |   no  |  yes  |
| Billing checkout and portal                     |   no   |   no   |   no  |  yes¹ |

¹ In an organization created by a GitHub App install that has no active owner, a verified admin of the installed GitHub organization may also check out and open the billing portal; GitHub is asked again on every such request.

When a call is made below the required role, the API returns `403` with the required and current roles in the error details. Role-gated changes always check the caller's role as it is now, not as it was cached when their key was last used. Only a member's own credential passes a role check: a device license token is refused (`403` `license_principal_not_permitted`), and only a member's own API key can manage the team (`403` `team_actor_not_permitted`).

Issuing a credential — registering a device, heartbeating or renewing its license, activating a deployment, or creating an API key — also re-checks the key and its member in the database first, so a removed member is refused (`401`) by every API server at once, and a credential created while the member is being removed is revoked with the rest. Requests that pass no role gate and issue no credential — reads, and writes such as changing the execution policy or a connector — can lag a removal, role change or key revocation briefly on another API server: up to 30 seconds for an API key that server had cached (the server that handled the change refuses at once). A website session's verified identity facts can lag up to the session token's expiry (at most 1 hour), and a confirmed GitHub organization admin up to 60 seconds (every mutation re-asks GitHub). A device license is checked against the device registry on every request (cached up to 30 seconds per API server): once its device, its API key or its member is revoked or removed it is refused with `401` `license_revoked`, and while its organization is suspended it is refused with `403` `organization_suspended`, as the organization's API keys are.

## Read the audit log

Every privileged action is recorded. Two endpoints expose it: the live database log and the immutable, content-hashed artifact store. Both require the **admin** role and return the same paginated `AuditLogResponse` shape.

```bash
curl -sS "https://api.algenta.ai/v1/audit-logs?action=invite_member&result=success&page=1&limit=25" \
  -H "Authorization: Bearer $ALGENTA_API_KEY"
```

```json
{
  "entries": [
    {
      "id": "a1b2c3d4-...",
      "timestamp": "2026-07-05T09:12:44Z",
      "actor_email": "admin@example.com",
      "action": "invite_member",
      "resource_type": "team_member",
      "resource_id": "f2a1c0de-...",
      "ip_address": "203.0.113.11",
      "result": "success",
      "content_hash": null,
      "metadata": {}
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 25,
  "pages": 1
}
```

Both endpoints accept the same filters — `actor_email`, `action`, `resource_type`, `result` (`success` or `failure`), plus lineage filters `policy_snapshot_id`, `schema_snapshot_id`, `manifest_version`, and `request_hash` — with `page` (default `1`) and `limit` (default `25`, max `100`).

<details>

<summary>Content-hashed audit artifacts</summary>

`GET /v1/audit-logs/artifacts` reads the durable, immutable artifact store rather than the live table. Its entries carry a populated `content_hash`, making each record tamper-evident, and it accepts one extra filter, `content_hash`, to fetch a specific record. Use it when you need a verifiable, exportable audit trail; use `GET /v1/audit-logs` for recent operational activity.

```bash
curl -sS "https://api.algenta.ai/v1/audit-logs/artifacts?action=simulate&limit=50" \
  -H "Authorization: Bearer $ALGENTA_API_KEY"
```

</details>

## Next steps

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Audit and tighten egress</strong></td><td>Prove what the deployment can and did reach over the network.</td><td><a href="/deploy-and-operate/privacy-egress.md">Audit &amp; tighten egress</a></td></tr><tr><td><strong>Manage deployments and nodes</strong></td><td>Provision isolated cloud deployments and register worker nodes.</td><td><a href="/deploy-and-operate/deployments-and-nodes.md">Deployments &amp; nodes</a></td></tr><tr><td><strong>Manage API keys</strong></td><td>Mint, scope, and revoke the keys your team authenticates with.</td><td><a href="/getting-started/authentication.md">Authentication &amp; API keys</a></td></tr></tbody></table>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.algenta.ai/deploy-and-operate/teams-rbac.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
