Skip to content

Agent identity

An agent is a first-class client type. Registering one through the management API gives it an org-bound identity, a specific set of tool scopes, and — optionally — a DID it can prove with a verifiable credential.

This is the difference between an agent and a dynamically registered client: agents are provisioned by someone holding an API key, so they can be bound to an organization and granted tool scopes. Anonymous registration can do neither.

Requires agents:write.

POST /v1/organizations/org_01J…/agents
Authorization: Bearer sk_live_…
{
"name": "Research bot",
"scopes": ["tool:search", "tool:weather"],
"agent_did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
}
{
"client_id": "agent_8f3b2c…",
"client_secret": "cs_…",
"scopes": ["tool:search", "tool:weather"],
"agent_did": "did:key:z6Mk…",
"token_endpoint": "https://acme.oauth.work/token",
"grant_type": "client_credentials"
}

The client_secret is shown once.

Scopes must be tool scopesmcp:*, tool:*, or a2a:*. Anything else is refused:

{ "error": "invalid_scope", "detail": "invalid agent scopes: admin (allowed: mcp:*, tool:*, a2a:*)" }

Omitting scopes defaults to ["mcp:tools"], which is broad — it satisfies every tool on the demo MCP server. Name the specific tools an agent needs instead. That list is the whole of what the agent can ever be authorized for: it bounds not only its own tokens but also what it can receive through delegation.

agent_did is optional. When set, it becomes the token sub and the subject of any agent identity credential the agent mints.

Agents use the client-credentials grant with client_secret_basic:

POST /token
Authorization: Basic <base64(agent_8f3b2c…:cs_…)>
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=tool:search&resource=https://tools.acme.com/mcp
{
"access_token": "eyJ…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "tool:search"
}

Requesting more scopes than the agent holds narrows to the intersection rather than failing, so read scope in the response. Requesting a resource the agent is not allowed fails with invalid_target.

The token’s actor_type is agent, and sub is the agent’s DID when it has one, otherwise its client id — so a resource server can tell an agent’s token from a user’s without inspecting the client registry.

Add a DPoP proof to the token request and the resulting token is key-bound, which is worth doing for anything that will be passed between services.

For a stronger credential than a shared secret, register with private_key_jwt instead — see client authentication.

POST /v1/organizations/org_01J…/agents/agent_8f3b2c…/rotate_secret
Authorization: Bearer sk_live_…
{ "client_id": "agent_8f3b2c…", "client_secret": "cs_…" }

The old secret stops working immediately — there is no overlap window. Deploy the new secret before rotating, or plan for the gap. Already-issued access tokens are unaffected and remain valid until they expire; rotation prevents new ones from being minted, it does not revoke old ones. To cut those off too, revoke them.

Writes an agent_secret_rotated audit event.

GET /v1/organizations/:orgId/agents # agents:read
DELETE /v1/organizations/:orgId/agents/:clientId # agents:write

Listing never returns secrets. Deleting stops the agent from obtaining new tokens.

An agent’s token can mint a verifiable credential attesting to what it is — useful when the agent has to prove its identity to a party that does not share your authorization server.

POST /credentials/issue
Authorization: Bearer <agent access token>
{ "format": "sd-jwt-vc" }

The platform recognizes the token’s actor_type and issues an AgentIdentityCredential rather than a Work Credential:

{
"agentId": "agent_8f3b2c…",
"actorType": "agent",
"organization": "org_01J…",
"scope": "tool:search"
}

It is signed by the tenant’s key and resolves through the tenant’s did:web document. Pass holderDid to bind it to a did:key the agent holds, so the agent can prove possession rather than merely presenting a bearer document.

A token that came from delegation mints a DelegationCredential instead, attesting that the actor chain may act for the original subject.

See verifiable credentials for formats and verification.

The platform hosts a demo MCP resource server at /mcp that enforces these scopes per tool, so you can exercise the whole path before pointing agents at your own servers:

GET /mcp/tools
{
"tools": [
{ "name": "search", "description": "Search the (mock) knowledge base", "required_scope": "tool:search" },
{ "name": "weather", "description": "Get the (mock) weather", "required_scope": "tool:weather" },
{ "name": "email.send", "description": "Send a (mock) email", "required_scope": "tool:email.send" }
]
}

Calling a tool requires a token that is audience-bound to the resource and carries the tool’s scope. See MCP authorization.