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:

curl -X POST http://localhost:7777/service-accounts \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{"name": "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 wraps this API for the terminal.

The machine sends the token as a standard bearer header:

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

PropertyBehavior
Formatagno_pat_<base62>. The fixed prefix makes leaked tokens easy for secret scanners to find.
StorageSHA-256 hash only. The plaintext is returned once, at creation.
NameLowercase slug (letters, digits, _, -; max 63 chars), e.g. claude-code.
Expiry90 days by default. Set expires_in_days (1 to 3650) or never_expires: true.
RotationNames are unique among active accounts. Revoke, then mint again under the same name.

Scopes

A token minted without scopes gets these grants:

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

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

These defaults do not include agents:read, teams:read, or workflows:read. Protected REST run polling, history, and checkpoint reads require the relevant resource's read and run grants. Add those grants if your client polls a background run.

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

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
  }'

Privileged Scopes

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

Scope classExamples
Any write or delete actionsessions:write, knowledge:delete
The admin scopeagent_os:admin
Any service_accounts scopeservice_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

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"

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:

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 self-scope private user data to their own principal, even when user isolation is off. Some domains also expose shared rows with no owner, such as shared knowledge, while preventing non-admin mutation of those rows. For a cross-user debugging token, grant the admin scope; agent_os:admin bypasses self-scoping.

Account metadata belongs to its creator; this ownership is separate from the token's execution principal sa:<name>. Scoped callers can see workspace-level unowned accounts but cannot revoke them.

Enforcement Surfaces

Agno accepts service-account tokens on these protected surfaces:

SurfaceBehavior
REST APIMapped routes enforce token grants. Unmapped custom routes have no automatic scope requirement.
MCP (/mcp)Built-in tools and exposed components enforce native resource scopes. Arbitrary callable tools need their own finer-grained authorization policy.
Interfaces (A2A, AG-UI)Default A2A paths support per-resource grants. Custom A2A prefixes and AG-UI require global or wildcard family grants. Self-authenticating webhook interfaces use their own credentials. See Scopes.
WebSocketsSame verification and scope enforcement as REST.

REST routes using Agno's authentication dependency verify PATs and enforce their database-backed grants in every authentication mode, including security_key and none. For MCP, configure JWT, security-key authentication, mcp_auth, or an appropriate auth middleware. A database/verifier alone does not activate PAT authentication on an otherwise anonymous MCP mount; sending a PAT there does not make the request authenticated. JWT scopes are enforced only when authorization is enabled.

With mcp_auth, FastMCP's provider handles MCP authentication while retaining Agno PAT and configured JWT verification. Public discovery routes, including the enabled Server Card, do not require a PAT.

Failure Modes

This table describes REST and default authentication behavior. With mcp_auth, failed or unavailable PAT lookup is reported through FastMCP as failed authentication (401), including lookup throttling and database unavailability.

StatusCause
401Unknown, 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.
403The token verified but lacks a scope the route requires (the detail lists the required scopes). Also: minting scopes the caller does not hold.
400Invalid scope strings, or privileged scopes without allow_privileged_scopes: true.
409An active account with that name already exists. Revoke it to rotate.
404Revoking a service account ID that does not exist.
429Too many failed token lookups from one client address.
503Token lookup cannot reach the database or the adapter does not support service accounts.
500A service-account creation/storage operation fails unexpectedly.

On REST routes with no auth configured, a token that cannot be verified is ignored and the request proceeds anonymously. When the route's authentication dependency verifies a token, it attributes the request and applies its grants. The anonymous MCP limitation above still applies.

Control Plane

Manage service accounts from the AgentOS control plane. View active tokens, mint new ones, and revoke compromised tokens for any connected AgentOS.

Next Steps

TaskGuide
Mint tokens from the terminalagno tokens
See the full scope referenceScopes
Understand per-user data scopingUser Isolation
Run the cookbook exampleagent_os_with_service_accounts.py