# Manage Credentials A credential is a `client_id` + `client_secret` pair that your agent uses to get access tokens. This guide covers all four credential operations. > **Multi-tenant note**: Credentials issued for an agent that belongs to an organization will > produce tokens carrying an `organization_id` claim. This claim is required by analytics, > webhooks, tier enforcement, and A2A delegation. Ensure your agent is registered with > `organization_id` before issuing credentials for production use. All credential endpoints are under `/api/v1/agents/{agentId}/credentials` and require a Bearer token with `agents:write` scope. --- ## Generate credentials `POST /api/v1/agents/{agentId}/credentials` Creates a new credential for the agent. The `clientSecret` is returned **once only**. ```bash curl -s -X POST "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{}' | jq . ``` To set an expiry date (optional): ```bash curl -s -X POST "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "expiresAt": "2027-03-28T00:00:00.000Z" }' | jq . ``` Response (`201 Created`): ```json { "credentialId": "c9d8e7f6-a5b4-3210-fedc-ba9876543210", "clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "clientSecret": "sk_live_7f3a2b1c9d8e4f0a6b5c3d2e1f0a9b8c", "status": "active", "createdAt": "2026-03-28T09:00:00.000Z", "expiresAt": "2027-03-28T00:00:00.000Z", "revokedAt": null } ``` > **Save the `clientSecret` immediately.** It is shown once. The server stores a bcrypt hash and cannot recover the plaintext. If you lose it, rotate the credential to get a new one. An agent can hold **multiple active credentials** at the same time. This supports zero-downtime rotation: generate a new credential, update all consumers to use it, then revoke the old one. **Restrictions**: - The agent must be in `active` status. Suspended and decommissioned agents cannot generate credentials. --- ## List credentials `GET /api/v1/agents/{agentId}/credentials` Returns all credentials for the agent (both active and revoked). The `clientSecret` is **never** returned in list responses. ```bash curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials" \ -H "Authorization: Bearer $TOKEN" | jq . ``` Response: ```json { "data": [ { "credentialId": "c9d8e7f6-a5b4-3210-fedc-ba9876543210", "clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "active", "createdAt": "2026-03-28T09:00:00.000Z", "expiresAt": "2027-03-28T00:00:00.000Z", "revokedAt": null } ], "total": 1, "page": 1, "limit": 20 } ``` ### Pagination ```bash curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials?page=1&limit=50" \ -H "Authorization: Bearer $TOKEN" | jq . ``` ### Filter by status ```bash # Active credentials only curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials?status=active" \ -H "Authorization: Bearer $TOKEN" | jq . # Revoked credentials only curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials?status=revoked" \ -H "Authorization: Bearer $TOKEN" | jq . ``` --- ## Rotate a credential `POST /api/v1/agents/{agentId}/credentials/{credentialId}/rotate` Rotation immediately invalidates the current `clientSecret` and generates a new one — the `credentialId` stays the same. Use this for periodic secret rotation or emergency rotation if a secret is compromised. ```bash curl -s -X POST \ "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials/$CREDENTIAL_ID/rotate" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{}' | jq . ``` Response (`200 OK`): ```json { "credentialId": "c9d8e7f6-a5b4-3210-fedc-ba9876543210", "clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "clientSecret": "sk_live_9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d", "status": "active", "createdAt": "2026-03-28T09:00:00.000Z", "expiresAt": null, "revokedAt": null } ``` **What changes after rotation**: - The `clientSecret` is a new value — the old secret is immediately invalid - The `credentialId` is the same — no changes needed to references by ID - Any tokens issued using the old secret remain valid until they expire naturally (tokens are not revoked by credential rotation) **What cannot be rotated**: A `revoked` credential cannot be rotated. Generate a new credential instead. --- ## Revoke a credential `DELETE /api/v1/agents/{agentId}/credentials/{credentialId}` Permanently revokes a credential. The credential can no longer be used to obtain new tokens. ```bash curl -s -X DELETE \ "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials/$CREDENTIAL_ID" \ -H "Authorization: Bearer $TOKEN" \ -o /dev/null -w "%{http_code}\n" ``` Successful response: `204 No Content` (empty body). **Effects of revocation**: - The credential status is set to `revoked` - The credential cannot be used to call `POST /token` - Any tokens that were issued using this credential remain valid until they expire — to immediately invalidate tokens, revoke them explicitly using `POST /token/revoke` - The credential record is retained for audit purposes - Revocation is **irreversible** — a revoked credential cannot be re-activated **Revocation vs decommission**: - Revoking a credential affects that credential only; the agent stays active - Decommissioning an agent (`DELETE /api/v1/agents/{agentId}`) revokes all credentials simultaneously and permanently retires the agent