# 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. ```bash 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`): ```json { "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): ```bash # 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: ```bash 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. ```bash 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: ```json { "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: ```json { "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. ```bash 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`): ```json {} ``` **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 ```json { "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 ```json { "error": "unauthorized_client", "error_description": "Agent is currently suspended and cannot obtain tokens." } ``` Or: ```json { "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` ```json { "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.