docs: bedroom developer documentation — complete docs/developers/ set
Adds the full bedroom-developer-docs OpenSpec change implementation: - docs/developers/README.md — index page - docs/developers/quick-start.md — bootstrap to working token in 7 steps - docs/developers/concepts.md — AgentIdP, AGNTCY, lifecycle, OAuth 2.0, free tier - docs/developers/guides/README.md — guide index - docs/developers/guides/register-an-agent.md — all fields, validation, common errors - docs/developers/guides/manage-credentials.md — generate, list, rotate, revoke - docs/developers/guides/issue-and-revoke-tokens.md — OAuth 2.0 flow, introspect, revoke - docs/developers/guides/query-audit-logs.md — filters, pagination, 90-day retention - docs/developers/api-reference.md — all 14 endpoints, all error codes, curl examples Also commits deferred OpenSpec housekeeping from previous session: - Archives phase-1-mvp-implementation change to openspec/changes/archive/ - Adds bedroom-developer-docs change artifacts (30/30 tasks complete) - Syncs 4 delta specs to openspec/specs/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
167
docs/developers/guides/manage-credentials.md
Normal file
167
docs/developers/guides/manage-credentials.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# 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.
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user