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>
16 KiB
API Reference
Complete reference for all 14 endpoints across the four SentryAgent.ai AgentIdP services.
Base URL
http://localhost:3000/api/v1
The port is configured via the PORT environment variable (default: 3000).
All endpoints are currently unversioned within the path prefix /api/v1. API versioning will be introduced in Phase 2.
Authentication
All endpoints require a JWT Bearer token in the Authorization header:
Authorization: Bearer <access_token>
Obtain a token via POST /token using your agent's client_id and client_secret.
Table of Contents
- Errors
- Agent Registry — 5 endpoints
- OAuth 2.0 Tokens — 3 endpoints
- Credential Management — 4 endpoints
- Audit Log — 2 endpoints
Errors
All error responses use this envelope:
{
"code": "ERROR_CODE",
"message": "Human-readable description.",
"details": {}
}
The details field is optional and provides additional context (e.g. which field failed validation).
Error codes
| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR |
400 | Request body or query parameter failed validation |
UNAUTHORIZED |
401 | Missing, expired, or invalid Bearer token |
FORBIDDEN |
403 | Valid token but insufficient scope |
AGENT_NOT_FOUND |
404 | Agent with the given agentId does not exist |
CREDENTIAL_NOT_FOUND |
404 | Credential with the given credentialId does not exist |
AUDIT_EVENT_NOT_FOUND |
404 | Audit event with the given eventId does not exist (or outside retention window) |
AGENT_ALREADY_EXISTS |
409 | An agent with this email is already registered |
AGENT_ALREADY_DECOMMISSIONED |
409 | Agent has already been decommissioned |
CREDENTIAL_ALREADY_REVOKED |
409 | Credential has already been revoked |
RATE_LIMIT_EXCEEDED |
429 | 100 req/min limit exceeded |
FREE_TIER_LIMIT_EXCEEDED |
403 | Free tier resource limit reached |
INSUFFICIENT_SCOPE |
403 | Token is missing a required scope |
IMMUTABLE_FIELD |
400 | Attempt to modify a field that cannot be changed |
AGENT_NOT_ACTIVE |
403 | Operation requires agent to be in active status |
AGENT_DECOMMISSIONED |
403 | Cannot modify a decommissioned agent |
RETENTION_WINDOW_EXCEEDED |
400 | Requested audit date is outside the 90-day retention window |
INTERNAL_SERVER_ERROR |
500 | Unexpected server error |
Rate limit headers
Every response includes rate limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests per minute (100) |
X-RateLimit-Remaining |
Requests remaining in current window |
X-RateLimit-Reset |
Unix timestamp when the window resets |
On 429 responses, wait until X-RateLimit-Reset before retrying.
Agent Registry
POST /agents — Register a new agent
Creates a new AI agent identity. The agentId is system-assigned.
Auth: Bearer token with agents:write scope.
Request body (application/json):
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Unique email-format identifier |
agentType |
enum | Yes | screener | classifier | orchestrator | extractor | summarizer | router | monitor | custom |
version |
string | Yes | Semantic version (e.g. 1.0.0) |
capabilities |
string[] | Yes | resource:action strings, min 1 |
owner |
string | Yes | Owning team/org, 1–128 chars |
deploymentEnv |
enum | Yes | development | staging | production |
Response codes:
| Code | Meaning |
|---|---|
201 |
Agent registered successfully |
400 |
Validation error |
401 |
Invalid token |
403 |
Insufficient scope or free tier limit reached |
409 |
Email already registered |
429 |
Rate limit exceeded |
Example:
curl -s -X POST http://localhost:3000/api/v1/agents \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "screener-001@talent.ai",
"agentType": "screener",
"version": "1.0.0",
"capabilities": ["resume:read", "email:send"],
"owner": "talent-team",
"deploymentEnv": "production"
}' | jq .
GET /agents — List agents
Returns a paginated list of registered agents.
Auth: Bearer token with agents:read scope.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number (1-based) |
limit |
integer | 20 | Results per page (max 100) |
owner |
string | — | Filter by owner (exact match) |
agentType |
enum | — | Filter by agent type |
status |
enum | — | Filter by status |
Response codes:
| Code | Meaning |
|---|---|
200 |
List returned |
400 |
Invalid query parameters |
401 |
Invalid token |
403 |
Insufficient scope |
429 |
Rate limit exceeded |
Example:
curl -s "http://localhost:3000/api/v1/agents?page=1&limit=20&status=active" \
-H "Authorization: Bearer $TOKEN" | jq .
GET /agents/{agentId} — Get agent by ID
Returns the full identity record for a single agent.
Auth: Bearer token with agents:read scope.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
agentId |
UUID | The agent's immutable identifier |
Response codes:
| Code | Meaning |
|---|---|
200 |
Agent record returned |
401 |
Invalid token |
403 |
Insufficient scope |
404 |
Agent not found |
429 |
Rate limit exceeded |
Example:
curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID" \
-H "Authorization: Bearer $TOKEN" | jq .
PATCH /agents/{agentId} — Update agent metadata
Partially updates agent metadata. Only provided fields are changed. Immutable fields (agentId, email, createdAt) cannot be updated.
Auth: Bearer token with agents:write scope.
Request body (application/json) — all fields optional:
| Field | Type | Description |
|---|---|---|
agentType |
enum | Updated agent type |
version |
string | Updated semantic version |
capabilities |
string[] | Updated capabilities (replaces the full list) |
owner |
string | Updated owner |
deploymentEnv |
enum | Updated deployment environment |
status |
enum | Updated status (active | suspended | decommissioned) |
Setting
statustodecommissionedis irreversible. The agent cannot be reactivated.
Response codes:
| Code | Meaning |
|---|---|
200 |
Agent updated, full record returned |
400 |
Validation error or attempt to modify immutable field |
401 |
Invalid token |
403 |
Insufficient scope or agent is decommissioned |
404 |
Agent not found |
429 |
Rate limit exceeded |
Example:
curl -s -X PATCH "http://localhost:3000/api/v1/agents/$AGENT_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "version": "1.5.0", "status": "suspended" }' | jq .
DELETE /agents/{agentId} — Decommission an agent
Permanently decommissions an agent (soft delete). All active credentials are immediately revoked. This operation is irreversible.
Auth: Bearer token with agents:write scope.
Response codes:
| Code | Meaning |
|---|---|
204 |
Agent decommissioned (no body) |
401 |
Invalid token |
403 |
Insufficient scope |
404 |
Agent not found |
409 |
Agent already decommissioned |
429 |
Rate limit exceeded |
Example:
curl -s -X DELETE "http://localhost:3000/api/v1/agents/$AGENT_ID" \
-H "Authorization: Bearer $TOKEN" \
-o /dev/null -w "%{http_code}\n"
OAuth 2.0 Tokens
POST /token — Issue an access token
Issues a signed RS256 JWT via the OAuth 2.0 Client Credentials grant.
Auth: Client credentials in the request body (no Bearer token required for this endpoint).
Content-Type: This endpoint uses
application/x-www-form-urlencoded, not JSON.
Request fields (form-encoded):
| Field | Required | Description |
|---|---|---|
grant_type |
Yes | Must be client_credentials |
client_id |
Yes | Your agent's agentId (UUID) |
client_secret |
Yes | The credential secret |
scope |
No | Space-separated scopes. If omitted, all scopes are granted. |
Response codes:
| Code | Meaning |
|---|---|
200 |
Token issued |
400 |
Malformed request, invalid scope, or unsupported grant type |
401 |
Invalid client_id or client_secret |
403 |
Agent suspended or monthly token limit reached |
429 |
Rate limit exceeded |
Note on 429: The X-RateLimit-* headers are returned on all responses, including 429.
Example:
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 .
POST /token/introspect — Introspect a token
Checks whether a token is active. Returns { "active": false } for expired or revoked tokens — always 200 OK.
Auth: Bearer token with tokens:read scope.
Content-Type:
application/x-www-form-urlencoded
Request fields:
| Field | Required | Description |
|---|---|---|
token |
Yes | The JWT to introspect |
token_type_hint |
No | Optional hint — access_token |
Response codes:
| Code | Meaning |
|---|---|
200 |
Result returned (check active field) |
400 |
Missing token parameter |
401 |
Caller's Bearer token is invalid |
403 |
Caller's token lacks tokens:read scope |
429 |
Rate limit exceeded |
Example:
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 .
POST /token/revoke — Revoke a token
Immediately invalidates a token. Idempotent — revoking an already-revoked token returns 200.
Auth: Bearer token (agent can revoke its own tokens).
Content-Type:
application/x-www-form-urlencoded
Request fields:
| Field | Required | Description |
|---|---|---|
token |
Yes | The JWT to revoke |
token_type_hint |
No | Optional hint — access_token |
Response codes:
| Code | Meaning |
|---|---|
200 |
Token revoked (or was already inactive) |
400 |
Missing token parameter |
401 |
Caller's Bearer token is invalid |
403 |
Insufficient permissions to revoke this token |
429 |
Rate limit exceeded |
Example:
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 .
Credential Management
POST /agents/{agentId}/credentials — Generate credentials
Creates a new client_id + client_secret pair. The clientSecret is returned once only.
Auth: Bearer token with agents:write scope.
Request body (application/json) — optional:
| Field | Type | Required | Description |
|---|---|---|---|
expiresAt |
ISO 8601 | No | Optional expiry date. Must be a future date. If omitted, credential does not expire. |
Response codes:
| Code | Meaning |
|---|---|
201 |
Credential created — save clientSecret now |
400 |
Invalid expiresAt |
401 |
Invalid token |
403 |
Insufficient scope or agent not active |
404 |
Agent not found |
429 |
Rate limit exceeded |
Example:
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-01-01T00:00:00.000Z" }' | jq .
GET /agents/{agentId}/credentials — List credentials
Returns all credentials (active and revoked). The clientSecret is never returned.
Auth: Bearer token with agents:read scope.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
limit |
integer | 20 | Results per page (max 100) |
status |
enum | — | Filter by active or revoked |
Response codes:
| Code | Meaning |
|---|---|
200 |
List returned |
400 |
Invalid query parameters |
401 |
Invalid token |
403 |
Insufficient scope |
404 |
Agent not found |
429 |
Rate limit exceeded |
Example:
curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials?status=active" \
-H "Authorization: Bearer $TOKEN" | jq .
POST /agents/{agentId}/credentials/{credentialId}/rotate — Rotate a credential
Replaces the clientSecret for the same credentialId. The old secret is immediately invalidated.
Auth: Bearer token with agents:write scope.
Request body (application/json) — optional:
| Field | Type | Required | Description |
|---|---|---|---|
expiresAt |
ISO 8601 | No | New expiry for the rotated credential |
Response codes:
| Code | Meaning |
|---|---|
200 |
Credential rotated — save new clientSecret now |
400 |
Invalid expiresAt |
401 |
Invalid token |
403 |
Insufficient scope |
404 |
Agent or credential not found |
409 |
Credential is already revoked |
429 |
Rate limit exceeded |
Example:
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 .
DELETE /agents/{agentId}/credentials/{credentialId} — Revoke a credential
Permanently revokes a credential. The credential can no longer obtain tokens. Irreversible.
Auth: Bearer token with agents:write scope.
Response codes:
| Code | Meaning |
|---|---|
204 |
Credential revoked (no body) |
401 |
Invalid token |
403 |
Insufficient scope |
404 |
Agent or credential not found |
409 |
Credential already revoked |
429 |
Rate limit exceeded |
Example:
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"
Audit Log
GET /audit — Query audit log
Returns a paginated, filtered list of audit events (most recent first).
Auth: Bearer token with audit:read scope.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
limit |
integer | 50 | Results per page (max 200) |
agentId |
UUID | — | Filter by agent |
action |
enum | — | Filter by action type (see Audit Log guide) |
outcome |
enum | — | success or failure |
fromDate |
ISO 8601 | — | Events at or after this timestamp (max 90 days ago) |
toDate |
ISO 8601 | — | Events at or before this timestamp |
Response codes:
| Code | Meaning |
|---|---|
200 |
Events returned |
400 |
Invalid parameters or date outside retention window |
401 |
Invalid token |
403 |
Token lacks audit:read scope |
429 |
Rate limit exceeded |
Example:
curl -s "http://localhost:3000/api/v1/audit?agentId=$AGENT_ID&action=token.issued&limit=50" \
-H "Authorization: Bearer $TOKEN" | jq .
GET /audit/{eventId} — Get audit event by ID
Returns a single audit event by its immutable eventId.
Auth: Bearer token with audit:read scope.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
eventId |
UUID | The audit event's identifier |
Response codes:
| Code | Meaning |
|---|---|
200 |
Audit event returned |
401 |
Invalid token |
403 |
Token lacks audit:read scope |
404 |
Event not found or outside 90-day retention window |
429 |
Rate limit exceeded |
Example:
curl -s "http://localhost:3000/api/v1/audit/$EVENT_ID" \
-H "Authorization: Bearer $TOKEN" | jq .