Files
sentryagent-idp/docs/developers/guides/issue-and-revoke-tokens.md
SentryAgent.ai Developer 8cabc0191c docs: commit all Phase 6 documentation updates and OpenSpec archives
- devops docs: 8 files updated for Phase 6 state; field-trial.md added (946-line runbook)
- developer docs: api-reference (50+ endpoints), quick-start, 5 existing guides updated, 5 new guides added
- engineering docs: all 12 files updated (services, architecture, SDK guide, testing, overview)
- OpenSpec archives: phase-7-devops-field-trial, developer-docs-phase6-update, engineering-docs-phase6-update
- VALIDATOR.md + scripts/start-validator.sh: V&V Architect tooling added
- .gitignore: exclude session artifacts, build artifacts, and agent workspaces

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 02:24:24 +00:00

5.8 KiB

Issue and Revoke Tokens

This guide covers the complete token lifecycle: issuing, using, inspecting, and revoking JWT access tokens.


Issue a token

POST /api/v1/token

This is the OAuth 2.0 Client Credentials grant. Your agent exchanges its client_id and client_secret for a signed JWT access token.

Important

: This endpoint uses application/x-www-form-urlencoded encoding, not JSON.

curl -s -X POST http://localhost:3000/api/v1/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" \
  -d "scope=agents:read agents:write" | jq .

Response (200 OK):

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMWIyYzNkNC1lNWY2LTc4OTAtYWJjZC1lZjEyMzQ1Njc4OTAiLCJjbGllbnRfaWQiOiJhMWIyYzNkNC1lNWY2LTc4OTAtYWJjZC1lZjEyMzQ1Njc4OTAiLCJzY29wZSI6ImFnZW50czpyZWFkIGFnZW50czp3cml0ZSIsImp0aSI6InV1aWQtaGVyZSIsImlhdCI6MTc0MzE1MTIwMCwiZXhwIjoxNzQzMTU0ODAwfQ.signature",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "agents:read agents:write"
}

The token expires in 3600 seconds (1 hour). Request a new one before it expires.

Request fields

Field Required Description
grant_type Yes Must be client_credentials
client_id Yes Your agent's agentId (UUID)
client_secret Yes The secret from credential generation
scope No Space-separated list of requested scopes. If omitted, all scopes are granted.

Available scopes

Scope What it allows
agents:read Read agent identity records
agents:write Create, update, and decommission agents
tokens:read Introspect tokens
audit:read Query audit logs and verify audit chain integrity
webhooks:read List webhook subscriptions and delivery history
webhooks:write Create, update, and delete webhook subscriptions
admin:orgs Manage organizations and federation partners

Request only the scopes your agent needs.


What's inside the JWT

A JWT has three base64-encoded parts separated by dots: header, payload, and signature. The payload contains your agent's identity claims.

Decode the payload to inspect it (for development only — never trust an unverified token in production):

# Extract the middle part (payload) of your token and decode it
TOKEN_PAYLOAD=$(echo "$TOKEN" | cut -d. -f2)
echo "$TOKEN_PAYLOAD" | base64 --decode 2>/dev/null | jq .

Claims in the payload:

Claim Description
sub Subject — your agent's agentId
client_id The agentId that authenticated
scope Scopes granted by this token
jti JWT ID — unique identifier for this token (used for revocation)
iat Issued at (Unix timestamp in seconds)
exp Expires at (Unix timestamp in seconds)

Use the token

Include the token in the Authorization header of every API request:

curl -s http://localhost:3000/api/v1/agents \
  -H "Authorization: Bearer $TOKEN" | jq .

Introspect a token

POST /api/v1/token/introspect

Check whether a token is currently active (valid, not expired, not revoked). Requires a Bearer token with tokens:read scope.

curl -s -X POST http://localhost:3000/api/v1/token/introspect \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=$TOKEN_TO_CHECK" | jq .

Response for an active token:

{
  "active": true,
  "sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "client_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "scope": "agents:read agents:write",
  "token_type": "Bearer",
  "iat": 1743151200,
  "exp": 1743154800
}

Response for an inactive (expired or revoked) token:

{
  "active": false
}

The introspect endpoint always returns 200 OK — even for inactive tokens. You must check the active field to determine token validity.


Revoke a token

POST /api/v1/token/revoke

Immediately invalidates a token, preventing it from being used for any subsequent requests. Requires a Bearer token.

curl -s -X POST http://localhost:3000/api/v1/token/revoke \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=$TOKEN_TO_REVOKE" | jq .

Response (200 OK):

{}

Notes on revocation:

  • Revocation is immediate — the token is rejected on the next request
  • Revoking an already-revoked or expired token is not an error (idempotent per RFC 7009)
  • An agent can revoke its own tokens; revoking another agent's token requires an admin-scoped token
  • Revoking a token does not affect the credential that issued it — new tokens can still be obtained using the same credentials

Token errors

401 invalid_client — wrong credentials

{
  "error": "invalid_client",
  "error_description": "Client authentication failed. Invalid client_id or client_secret."
}

Check that client_id matches the agent's agentId and client_secret is the current active secret.

403 unauthorized_client — agent suspended or monthly limit reached

{
  "error": "unauthorized_client",
  "error_description": "Agent is currently suspended and cannot obtain tokens."
}

Or:

{
  "error": "unauthorized_client",
  "error_description": "Free tier monthly token limit of 10,000 requests has been reached."
}

For suspension: reactivate the agent first. For the monthly limit: the counter resets on the first day of the next calendar month.

400 unsupported_grant_type

{
  "error": "unsupported_grant_type",
  "error_description": "Only 'client_credentials' grant type is supported."
}

Only client_credentials is supported. Do not use authorization_code, password, or other grant types.