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:
SentryAgent.ai Developer
2026-03-28 14:13:03 +00:00
parent d3530285b9
commit 61ea975c79
29 changed files with 2397 additions and 0 deletions

View File

@@ -0,0 +1,583 @@
# 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](#errors)
- [Agent Registry](#agent-registry) — 5 endpoints
- [OAuth 2.0 Tokens](#oauth-20-tokens) — 3 endpoints
- [Credential Management](#credential-management) — 4 endpoints
- [Audit Log](#audit-log) — 2 endpoints
---
## Errors
All error responses use this envelope:
```json
{
"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, 1128 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**:
```bash
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**:
```bash
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**:
```bash
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 `status` to `decommissioned` is **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**:
```bash
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**:
```bash
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**:
```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 .
```
---
### 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**:
```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 .
```
---
### 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**:
```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 .
```
---
## 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**:
```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-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**:
```bash
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**:
```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 .
```
---
### 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**:
```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"
```
---
## 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](guides/query-audit-logs.md)) |
| `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**:
```bash
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**:
```bash
curl -s "http://localhost:3000/api/v1/audit/$EVENT_ID" \
-H "Authorization: Bearer $TOKEN" | jq .
```