> ## Documentation Index
> Fetch the complete documentation index at: https://agno-v2.mintlify.site/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:

```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
```

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`](https://agno-v2.mintlify.site/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:

```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
```

### 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

```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
```

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](https://agno-v2.mintlify.site/agent-os/security/authorization/user-isolation.md) 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](https://agno-v2.mintlify.site/agent-os/security/authorization/scopes.md). |
| 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. |
| `404` | Revoking a service account ID that does not exist. |
| `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.

## Control Plane

Manage service accounts from the [AgentOS control plane](https://os.agno.com/manage-os/service-accounts). View active tokens, mint new ones, and revoke compromised tokens for any connected AgentOS.

<video autoPlay muted controls src="https://mintcdn.com/agno-v2/Nt69oS6x_Hq3z-zA/videos/service-accounts.mp4?fit=max&auto=format&n=Nt69oS6x_Hq3z-zA&q=85&s=c977caaada58a25bdb7b666427de2d49" />

## Next Steps

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.