> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agno.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Service Accounts

> Mint, scope, and revoke the opaque agno_pat_ tokens that machine identities use to authenticate with AgentOS.

Service accounts are machine identities for AgentOS. Coding agents, chat apps, and CI pipelines authenticate with opaque `agno_pat_...` tokens instead of JWTs.

Mint a token with a credential that can create service accounts: a JWT holding `service_accounts:write` (or the admin scope), or the OS security key:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST http://localhost:7777/service-accounts \
    -H "Authorization: Bearer $ADMIN_JWT" \
    -H "Content-Type: application/json" \
    -d '{"name": "claude-code"}'
  ```

  ```python Python theme={null}
  import httpx

  response = httpx.post(
      "http://localhost:7777/service-accounts",
      headers={"Authorization": f"Bearer {admin_jwt}"},
      json={"name": "claude-code"},
  )
  token = response.json()["token"]
  ```

  ```bash CLI theme={null}
  agno tokens create claude-code
  ```
</CodeGroup>

The `201` response is the only time you'll see the plaintext `token`, so store it somewhere safe. There's no way to get it back later. [`agno tokens`](/agent-os/cli/tokens) wraps this API for the terminal.

The machine sends the token as a standard bearer header:

```bash theme={null}
curl -X POST http://localhost:7777/agents/my-agent/runs \
  -H "Authorization: Bearer agno_pat_..." \
  -d "message=hello" -d "stream=false"
```

Service accounts require a database on your AgentOS (`AgentOS(db=...)`). Tokens are stored there, next to sessions and memories. Minting always requires a real credential, so anonymous requests on an open instance get a `401`.

## Token Properties

| Property | Behavior                                                                                    |
| -------- | ------------------------------------------------------------------------------------------- |
| Format   | `agno_pat_<base62>`. The fixed prefix makes leaked tokens easy for secret scanners to find. |
| Storage  | SHA-256 hash only. The plaintext is returned once, at creation.                             |
| Name     | Lowercase slug (letters, digits, `_`, `-`; max 63 chars), e.g. `claude-code`.               |
| Expiry   | 90 days by default. Set `expires_in_days` (1 to 3650) or `never_expires: true`.             |
| Rotation | Names are unique among active accounts. Revoke, then mint again under the same name.        |

## Scopes

A token minted without scopes gets run and read access:

```
agents:run, teams:run, workflows:run, sessions:read, config:read
```

`config:read` lets the token discover what it can run (`GET /config`).

Custom scopes are passed as `{scope, effect}` objects. Token scopes are grants, so only `effect: "allow"` is accepted:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST http://localhost:7777/service-accounts \
    -H "Authorization: Bearer $ADMIN_JWT" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "ci-runner",
      "scopes": [
        {"scope": "agents:run", "effect": "allow"},
        {"scope": "sessions:write", "effect": "allow"}
      ],
      "allow_privileged_scopes": true,
      "expires_in_days": 30
    }'
  ```

  ```bash CLI theme={null}
  agno tokens create ci-runner -s agents:run -s sessions:write --privileged --expires 30d
  ```
</CodeGroup>

### Privileged Scopes

Some scopes require `allow_privileged_scopes: true` at mint time, so a privileged token is always deliberate:

| Scope class                    | Examples                                           |
| ------------------------------ | -------------------------------------------------- |
| Any `write` or `delete` action | `sessions:write`, `knowledge:delete`               |
| The admin scope                | `agent_os:admin`                                   |
| Any `service_accounts` scope   | `service_accounts:write` (tokens that mint tokens) |

A scoped caller can only grant scopes it already holds. A caller with `service_accounts:write` but without `knowledge:delete` cannot mint a token carrying `knowledge:delete`, so minting never escalates privileges. Admin callers and the OS security key (an unscoped root) can grant anything.

## List and Revoke

<CodeGroup>
  ```bash cURL theme={null}
  curl http://localhost:7777/service-accounts \
    -H "Authorization: Bearer $ADMIN_JWT"

  curl -X DELETE http://localhost:7777/service-accounts/<id> \
    -H "Authorization: Bearer $ADMIN_JWT"
  ```

  ```python Python theme={null}
  import httpx

  headers = {"Authorization": f"Bearer {admin_jwt}"}
  base = "http://localhost:7777"

  accounts = httpx.get(f"{base}/service-accounts", headers=headers).json()["data"]
  httpx.delete(f"{base}/service-accounts/{accounts[0]['id']}", headers=headers)
  ```

  ```bash CLI theme={null}
  agno tokens list
  agno tokens revoke claude-code
  ```
</CodeGroup>

Listing is paginated (`limit`, `page`, `sort_by`, `sort_order`, `include_revoked`) and returns metadata plus a display prefix (`token_prefix`) only. Neither the hash nor the plaintext is ever returned.

Revocation is one-way and idempotent. A revoked name can be reused by minting a new account.

### Revocation Timing

Successful verifications are cached in-process, so token auth does not hit the database on every request. Revocation takes effect immediately on the worker that processes the `DELETE`, and within `service_account_cache_ttl_seconds` (default 30) on other workers. Token expiry is always honored, even on a cache hit.

For strict instant revocation, disable the cache. Every request then verifies against the database:

```python theme={null}
from agno.os import AgentOS
from agno.os.settings import AgnoAPISettings

agent_os = AgentOS(
    agents=[agent],
    db=db,
    settings=AgnoAPISettings(service_account_cache_ttl_seconds=0),
)
```

## Attribution and Data Access

Requests authenticated with a token run as the principal `sa:<name>`. Sessions, memories, and traces created through a `claude-code` token show `sa:claude-code` as the user.

`sa:` is a reserved namespace. A JWT whose `sub` claims an `sa:` identity is rejected with `401`, so a human token can never impersonate a machine identity.

Service accounts always self-scope: they read and write only data attributed to their own principal, even when [user isolation](/agent-os/security/authorization/user-isolation) is off. For a cross-user debugging token, grant the admin scope; `agent_os:admin` bypasses self-scoping.

## Enforcement Surfaces

Tokens pass through the same auth layer as JWTs and cover the full AgentOS surface:

| Surface          | Behavior                                                                                                  |
| ---------------- | --------------------------------------------------------------------------------------------------------- |
| REST API         | Every route is checked against the token's scopes.                                                        |
| MCP (`/mcp`)     | Tool calls are checked against the scopes of their equivalent REST route.                                 |
| Interfaces (A2A) | A2A routes map to `agents/teams/workflows` scopes. See [Scopes](/agent-os/security/authorization/scopes). |
| WebSockets       | Same verification and scope enforcement as REST.                                                          |

Service account scopes are ACL data stored in your database, so AgentOS enforces them in every authentication mode, including `security_key` and `none`. JWT scopes are enforced only when authorization is enabled.

## Failure Modes

| Status | Cause                                                                                                                                                         |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | Unknown, expired, or revoked token. Also: minting without a credential, sending a token to an AgentOS without a database, or a JWT claiming an `sa:` subject. |
| `403`  | The token verified but lacks a scope the route requires (the detail lists the required scopes). Also: minting scopes the caller does not hold.                |
| `400`  | Invalid scope strings, or privileged scopes without `allow_privileged_scopes: true`.                                                                          |
| `409`  | An active account with that name already exists. Revoke it to rotate.                                                                                         |
| `429`  | Too many failed token lookups from one client address.                                                                                                        |
| `503`  | The database is unreachable or does not support service accounts.                                                                                             |

<Note>
  On an instance with no auth configured, a token that cannot be verified is ignored and the request proceeds anonymously. A token that verifies always attributes the request, and its scopes always apply.
</Note>

## Next Steps

| Task                             | Guide                                                                                                                                                 |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Mint tokens from the terminal    | [agno tokens](/agent-os/cli/tokens)                                                                                                                   |
| See the full scope reference     | [Scopes](/agent-os/security/authorization/scopes)                                                                                                     |
| Understand per-user data scoping | [User Isolation](/agent-os/security/authorization/user-isolation)                                                                                     |
| Run the cookbook example         | [agent\_os\_with\_service\_accounts.py](https://github.com/agno-agi/agno/blob/main/cookbook/05_agent_os/middleware/agent_os_with_service_accounts.py) |
