Files
sentryagent-idp/docs/developers/api-reference.md
SentryAgent.ai Developer 61ea975c79 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>
2026-03-28 14:13:03 +00:00

584 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 .
```