- devops docs: 8 files updated for Phase 6 state; field-trial.md added (946-line runbook) - developer docs: api-reference (50+ endpoints), quick-start, 5 existing guides updated, 5 new guides added - engineering docs: all 12 files updated (services, architecture, SDK guide, testing, overview) - OpenSpec archives: phase-7-devops-field-trial, developer-docs-phase6-update, engineering-docs-phase6-update - VALIDATOR.md + scripts/start-validator.sh: V&V Architect tooling added - .gitignore: exclude session artifacts, build artifacts, and agent workspaces Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1579 lines
48 KiB
Markdown
1579 lines
48 KiB
Markdown
# API Reference
|
||
|
||
Complete reference for all SentryAgent.ai AgentIdP endpoints.
|
||
|
||
## Base URL
|
||
|
||
http://localhost:3000/api/v1
|
||
|
||
The port is configured via the PORT environment variable (default: 3000).
|
||
|
||
## Authentication
|
||
|
||
All endpoints require a JWT Bearer token in the Authorization header unless noted otherwise:
|
||
|
||
Authorization: Bearer <access_token>
|
||
|
||
Obtain a token via POST /api/v1/token using your agent's client_id and client_secret.
|
||
|
||
Endpoints marked **No auth** do not require a Bearer token. Endpoints marked **Unauthenticated** are
|
||
intentionally public.
|
||
|
||
## Table of Contents
|
||
|
||
- [Errors](#errors)
|
||
- [Agent Registry](#section-1--agent-registry)
|
||
- [Credentials](#section-2--credentials)
|
||
- [OAuth 2.0 / Tokens](#section-3--oauth-20--tokens)
|
||
- [Audit Log](#section-4--audit-log)
|
||
- [Organizations](#section-5--organizations)
|
||
- [Analytics](#section-6--analytics)
|
||
- [API Tiers](#section-7--api-tiers)
|
||
- [Compliance](#section-8--compliance)
|
||
- [Webhooks](#section-9--webhooks)
|
||
- [Federation](#section-10--federation)
|
||
- [DID / OIDC](#section-11--did--oidc)
|
||
- [A2A Delegation](#section-12--a2a-delegation)
|
||
- [Marketplace](#section-13--marketplace)
|
||
|
||
---
|
||
|
||
## 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 |
|
||
| `ORG_NOT_FOUND` | 404 | Organization with the given `orgId` does not exist |
|
||
| `ORG_ALREADY_EXISTS` | 409 | An organization with this slug already exists |
|
||
| `ORG_SUSPENDED` | 403 | Organization is suspended — operations are blocked |
|
||
| `MEMBER_ALREADY_EXISTS` | 409 | Agent is already a member of this organization |
|
||
| `DELEGATION_NOT_FOUND` | 404 | Delegation chain with the given `chainId` does not exist |
|
||
| `DELEGATION_EXPIRED` | 403 | Delegation token has expired |
|
||
| `DELEGATION_REVOKED` | 403 | Delegation token has been revoked |
|
||
| `DELEGATION_SCOPE_EXCEEDED` | 403 | Requested scopes exceed the delegator's own scopes |
|
||
| `TIER_UPGRADE_NOT_REQUIRED` | 400 | Target tier is not higher than the current tier |
|
||
| `WEBHOOK_NOT_FOUND` | 404 | Webhook subscription with the given `id` does not exist |
|
||
| `PARTNER_NOT_FOUND` | 404 | Federation partner with the given `id` does not exist |
|
||
| `COMPLIANCE_DISABLED` | 404 | AGNTCY compliance endpoints are disabled on this instance |
|
||
|
||
### 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.
|
||
|
||
---
|
||
|
||
## Section 1 — Agent Registry
|
||
|
||
### POST /agents — Register a new agent
|
||
|
||
**Description**: 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 for the agent |
|
||
| `agentType` | enum | Yes | `screener` \| `classifier` \| `orchestrator` \| `extractor` \| `summarizer` \| `router` \| `monitor` \| `custom` |
|
||
| `version` | string | Yes | Semantic version string, e.g. `1.0.0` |
|
||
| `capabilities` | string[] | Yes | One or more `resource:action` strings (min 1) |
|
||
| `owner` | string | Yes | Owning team or organisation, 1–128 characters |
|
||
| `deploymentEnv` | enum | Yes | `development` \| `staging` \| `production` |
|
||
| `organization_id` | string | No | UUID of the org to scope the agent to. Required on multi-tenant instances. |
|
||
|
||
**Response** `201 Created`:
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `agentId` | UUID | System-assigned immutable identifier |
|
||
| `email` | string | Unique email identifier |
|
||
| `agentType` | string | Agent type |
|
||
| `version` | string | Semantic version |
|
||
| `capabilities` | string[] | Capability list |
|
||
| `owner` | string | Owning team |
|
||
| `deploymentEnv` | string | Deployment environment |
|
||
| `status` | string | Always `active` on creation |
|
||
| `createdAt` | ISO 8601 | Registration timestamp |
|
||
| `updatedAt` | ISO 8601 | Last update timestamp |
|
||
|
||
**Error responses**:
|
||
|
||
| Code | HTTP | Error code |
|
||
|------|------|-----------|
|
||
| Validation failure | 400 | `VALIDATION_ERROR` |
|
||
| Invalid token | 401 | `UNAUTHORIZED` |
|
||
| Missing scope | 403 | `INSUFFICIENT_SCOPE` |
|
||
| Free tier limit | 403 | `FREE_TIER_LIMIT_EXCEEDED` |
|
||
| Email taken | 409 | `AGENT_ALREADY_EXISTS` |
|
||
| Rate limit | 429 | `RATE_LIMIT_EXCEEDED` |
|
||
|
||
**curl 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
|
||
|
||
**Description**: 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 (`active` \| `suspended` \| `decommissioned`) |
|
||
|
||
**Response** `200 OK`: `{ data: Agent[], total: number, page: number, limit: number }`
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl 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
|
||
|
||
**Description**: Returns the full identity record for a single agent.
|
||
**Auth**: Bearer token with `agents:read` scope.
|
||
|
||
**Path parameters**: `agentId` (UUID)
|
||
|
||
**Response** `200 OK`: Full agent object (same fields as POST response).
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 404 `AGENT_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### PATCH /agents/{agentId} — Update agent metadata
|
||
|
||
**Description**: Partially updates agent metadata. Immutable fields (`agentId`, `email`, `createdAt`) cannot be changed.
|
||
**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 full list) |
|
||
| `owner` | string | Updated owner |
|
||
| `deploymentEnv` | enum | Updated deployment environment |
|
||
| `status` | enum | `active` \| `suspended` \| `decommissioned`. Setting `decommissioned` is irreversible. |
|
||
|
||
**Response** `200 OK`: Full updated agent object.
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR` / `IMMUTABLE_FIELD`, 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE` / `AGENT_DECOMMISSIONED`, 404 `AGENT_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl 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
|
||
|
||
**Description**: Permanently decommissions an agent (soft delete). All active credentials are immediately revoked. Irreversible.
|
||
**Auth**: Bearer token with `agents:write` scope.
|
||
|
||
**Response** `204 No Content` (empty body).
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 404 `AGENT_NOT_FOUND`, 409 `AGENT_ALREADY_DECOMMISSIONED`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl 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"
|
||
```
|
||
|
||
---
|
||
|
||
## Section 2 — Credentials
|
||
|
||
### POST /agents/{agentId}/credentials — Generate credentials
|
||
|
||
**Description**: 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. Omit for non-expiring credential. |
|
||
|
||
**Response** `201 Created`:
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `credentialId` | UUID | Unique credential identifier |
|
||
| `clientId` | UUID | Same as `agentId` |
|
||
| `clientSecret` | string | Plaintext secret (shown once only — store immediately) |
|
||
| `status` | string | `active` |
|
||
| `createdAt` | ISO 8601 | Creation timestamp |
|
||
| `expiresAt` | ISO 8601 \| null | Expiry date or null |
|
||
| `revokedAt` | ISO 8601 \| null | Always null on creation |
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE` / `AGENT_NOT_ACTIVE`, 404 `AGENT_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl 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
|
||
|
||
**Description**: Returns all credentials for an agent (active and revoked). The `clientSecret` is never returned.
|
||
**Auth**: Bearer token with `agents:read` scope.
|
||
|
||
**Query parameters**: `page` (default 1), `limit` (default 20, max 100), `status` (`active` \| `revoked`)
|
||
|
||
**Response** `200 OK`: `{ data: Credential[], total: number, page: number, limit: number }`
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 404 `AGENT_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl 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
|
||
|
||
**Description**: Generates a new `clientSecret` for the same `credentialId`. The old secret is immediately invalidated.
|
||
**Auth**: Bearer token with `agents:write` scope.
|
||
|
||
**Request body** (`application/json`) — optional: `{ "expiresAt": "ISO 8601" }`
|
||
|
||
**Response** `200 OK`: Full credential object with new `clientSecret` (shown once only).
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 404 `AGENT_NOT_FOUND` / `CREDENTIAL_NOT_FOUND`, 409 `CREDENTIAL_ALREADY_REVOKED`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl 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
|
||
|
||
**Description**: Permanently revokes a credential. Irreversible.
|
||
**Auth**: Bearer token with `agents:write` scope.
|
||
|
||
**Response** `204 No Content`.
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 404 `AGENT_NOT_FOUND` / `CREDENTIAL_NOT_FOUND`, 409 `CREDENTIAL_ALREADY_REVOKED`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl 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"
|
||
```
|
||
|
||
---
|
||
|
||
## Section 3 — OAuth 2.0 / Tokens
|
||
|
||
### POST /token — Issue an access token
|
||
|
||
**Description**: Issues a signed RS256 JWT via the OAuth 2.0 Client Credentials grant.
|
||
**Auth**: No Bearer token — credentials are in the request body.
|
||
**Content-Type**: `application/x-www-form-urlencoded`
|
||
|
||
**Request fields** (form-encoded):
|
||
|
||
| Field | Required | Description |
|
||
|-------|----------|-------------|
|
||
| `grant_type` | Yes | Must be `client_credentials` |
|
||
| `client_id` | Yes | Agent's `agentId` (UUID) |
|
||
| `client_secret` | Yes | Credential secret |
|
||
| `scope` | No | Space-separated scopes. If omitted, all scopes are granted. |
|
||
|
||
**Response** `200 OK`:
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `access_token` | string | Signed RS256 JWT |
|
||
| `token_type` | string | Always `Bearer` |
|
||
| `expires_in` | integer | Lifetime in seconds (3600) |
|
||
| `scope` | string | Granted scopes (space-separated) |
|
||
|
||
**Error responses**:
|
||
|
||
| Code | HTTP | Error |
|
||
|------|------|-------|
|
||
| Bad request / bad grant | 400 | `{ "error": "unsupported_grant_type" }` |
|
||
| Bad credentials | 401 | `{ "error": "invalid_client" }` |
|
||
| Agent suspended or monthly limit | 403 | `{ "error": "unauthorized_client" }` |
|
||
| Rate limit | 429 | `RATE_LIMIT_EXCEEDED` |
|
||
|
||
**curl 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
|
||
|
||
**Description**: Checks whether a token is active. Always returns `200 OK` — check the `active` field.
|
||
**Auth**: Bearer token with `tokens:read` scope.
|
||
**Content-Type**: `application/x-www-form-urlencoded`
|
||
|
||
**Request fields**: `token` (required), `token_type_hint` (optional, `access_token`)
|
||
|
||
**Response** `200 OK` (active): `{ "active": true, "sub": "...", "client_id": "...", "scope": "...", "token_type": "Bearer", "iat": 0, "exp": 0 }`
|
||
**Response** `200 OK` (inactive): `{ "active": false }`
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl 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
|
||
|
||
**Description**: Immediately invalidates a token. Idempotent.
|
||
**Auth**: Bearer token.
|
||
**Content-Type**: `application/x-www-form-urlencoded`
|
||
|
||
**Request fields**: `token` (required), `token_type_hint` (optional)
|
||
|
||
**Response** `200 OK`: `{}` (empty object)
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 403 `FORBIDDEN`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl 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 .
|
||
```
|
||
|
||
---
|
||
|
||
## Section 4 — Audit Log
|
||
|
||
### GET /audit — Query audit log
|
||
|
||
**Description**: 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` | string | — | Filter by action type (e.g. `token.issued`, `agent.created`) |
|
||
| `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** `200 OK`: `{ data: AuditEvent[], total: number, page: number, limit: number }`
|
||
|
||
**AuditEvent fields**: `eventId` (UUID), `agentId` (UUID), `action` (string), `outcome` (string), `ipAddress` (string), `userAgent` (string), `metadata` (object), `timestamp` (ISO 8601)
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR` / `RETENTION_WINDOW_EXCEEDED`, 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl 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
|
||
|
||
**Description**: Returns a single audit event by its immutable `eventId`.
|
||
**Auth**: Bearer token with `audit:read` scope.
|
||
|
||
**Path parameters**: `eventId` (UUID)
|
||
|
||
**Response** `200 OK`: Single AuditEvent object.
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 404 `AUDIT_EVENT_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/audit/$EVENT_ID" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /audit/verify — Verify audit chain integrity
|
||
|
||
**Description**: Verifies the cryptographic hash chain of all audit events. Returns `verified: true` if the chain is intact. Rate limited to 30 req/min (computationally intensive).
|
||
**Auth**: Bearer token with `audit:read` scope.
|
||
|
||
**Query parameters**: `fromDate` (ISO 8601, optional), `toDate` (ISO 8601, optional)
|
||
|
||
**Response** `200 OK`:
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `verified` | boolean | `true` if chain is intact, `false` if tampering detected |
|
||
| `checkedCount` | integer | Number of events checked |
|
||
| `fromDate` | ISO 8601 \| null | Verification window start |
|
||
| `toDate` | ISO 8601 \| null | Verification window end |
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/audit/verify" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
## Section 5 — Organizations
|
||
|
||
### POST /organizations — Create an organization
|
||
|
||
**Description**: Creates a new tenant organization. Agents can be scoped to an organization via `organization_id`.
|
||
**Auth**: Bearer token (OPA scope enforcement — `admin:orgs` or equivalent policy).
|
||
|
||
**Request body** (`application/json`):
|
||
|
||
| Field | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `name` | string | Yes | Display name, 1–255 characters |
|
||
| `slug` | string | Yes | URL-safe identifier — lowercase letters, digits, hyphens only |
|
||
| `planTier` | enum | No | `free` (default) \| `pro` \| `enterprise` |
|
||
| `maxAgents` | integer | No | Override the plan default agent limit |
|
||
| `maxTokensPerMonth` | integer | No | Override the plan default monthly token limit |
|
||
|
||
**Response** `201 Created`:
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `organizationId` | UUID | System-assigned organization identifier |
|
||
| `name` | string | Display name |
|
||
| `slug` | string | URL-safe slug |
|
||
| `planTier` | string | Current plan tier |
|
||
| `maxAgents` | integer | Agent limit |
|
||
| `maxTokensPerMonth` | integer | Monthly token limit |
|
||
| `status` | string | `active` |
|
||
| `createdAt` | ISO 8601 | Creation timestamp |
|
||
| `updatedAt` | ISO 8601 | Last update timestamp |
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 409 `ORG_ALREADY_EXISTS`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:3000/api/v1/organizations \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"name": "Acme AI",
|
||
"slug": "acme-ai",
|
||
"planTier": "pro"
|
||
}' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /organizations — List organizations
|
||
|
||
**Description**: Returns a paginated list of organizations.
|
||
**Auth**: Bearer token (OPA scope enforcement).
|
||
|
||
**Query parameters**: `page` (default 1), `limit` (default 20, max 100), `status` (`active` \| `suspended` \| `deleted`)
|
||
|
||
**Response** `200 OK`: `{ data: Organization[], total: number, page: number, limit: number }`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/organizations?status=active" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /organizations/{orgId} — Get organization by ID
|
||
|
||
**Description**: Returns the full record for a single organization.
|
||
**Auth**: Bearer token (OPA scope enforcement).
|
||
|
||
**Path parameters**: `orgId` (UUID)
|
||
|
||
**Response** `200 OK`: Full organization object.
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 404 `ORG_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/organizations/$ORG_ID" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### PATCH /organizations/{orgId} — Update organization
|
||
|
||
**Description**: Partially updates an organization. The `slug` is immutable after creation.
|
||
**Auth**: Bearer token (OPA scope enforcement).
|
||
|
||
**Request body** (`application/json`) — all fields optional:
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `name` | string | New display name |
|
||
| `planTier` | enum | `free` \| `pro` \| `enterprise` |
|
||
| `maxAgents` | integer | New agent limit |
|
||
| `maxTokensPerMonth` | integer | New token limit |
|
||
| `status` | enum | `active` \| `suspended` (use DELETE to set `deleted`) |
|
||
|
||
**Response** `200 OK`: Full updated organization object.
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 404 `ORG_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X PATCH "http://localhost:3000/api/v1/organizations/$ORG_ID" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{ "planTier": "enterprise" }' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### DELETE /organizations/{orgId} — Delete organization
|
||
|
||
**Description**: Soft-deletes an organization. Sets status to `deleted`.
|
||
**Auth**: Bearer token (OPA scope enforcement).
|
||
|
||
**Response** `204 No Content`.
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 404 `ORG_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X DELETE "http://localhost:3000/api/v1/organizations/$ORG_ID" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-o /dev/null -w "%{http_code}\n"
|
||
```
|
||
|
||
---
|
||
|
||
### POST /organizations/{orgId}/members — Add a member
|
||
|
||
**Description**: Adds an existing agent to an organization with a specified role.
|
||
**Auth**: Bearer token (OPA scope enforcement).
|
||
|
||
**Request body** (`application/json`):
|
||
|
||
| Field | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `agentId` | UUID | Yes | The agent to add |
|
||
| `role` | enum | Yes | `member` \| `admin` |
|
||
|
||
**Response** `201 Created`:
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `memberId` | UUID | Membership record identifier |
|
||
| `organizationId` | UUID | Organization |
|
||
| `agentId` | UUID | Agent |
|
||
| `role` | string | `member` or `admin` |
|
||
| `joinedAt` | ISO 8601 | Membership creation timestamp |
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 404 `ORG_NOT_FOUND` / `AGENT_NOT_FOUND`, 409 `MEMBER_ALREADY_EXISTS`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X POST "http://localhost:3000/api/v1/organizations/$ORG_ID/members" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"agentId": "'$AGENT_ID'",
|
||
"role": "member"
|
||
}' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
## Section 6 — Analytics
|
||
|
||
All analytics endpoints are scoped to the authenticated agent's `organization_id`.
|
||
|
||
### GET /analytics/tokens — Token issuance trend
|
||
|
||
**Description**: Returns daily token issuance counts for the past N days, scoped to the current organization.
|
||
**Auth**: Bearer token.
|
||
|
||
**Query parameters**:
|
||
|
||
| Parameter | Type | Default | Max | Description |
|
||
|-----------|------|---------|-----|-------------|
|
||
| `days` | integer | 30 | 90 | Number of days to return |
|
||
|
||
**Response** `200 OK`:
|
||
|
||
```json
|
||
{
|
||
"tenantId": "org-uuid",
|
||
"days": 30,
|
||
"data": [
|
||
{ "date": "2026-03-01", "count": 142 },
|
||
{ "date": "2026-03-02", "count": 198 }
|
||
]
|
||
}
|
||
```
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR` (days > 90), 401 `UNAUTHORIZED`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/analytics/tokens?days=30" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /analytics/agents/activity — Agent activity heatmap
|
||
|
||
**Description**: Returns agent request counts grouped by day-of-week and hour (UTC), for the current organization.
|
||
**Auth**: Bearer token.
|
||
|
||
**Response** `200 OK`:
|
||
|
||
```json
|
||
{
|
||
"tenantId": "org-uuid",
|
||
"data": [
|
||
{ "dow": 1, "hour": 9, "count": 54 },
|
||
{ "dow": 1, "hour": 10, "count": 87 }
|
||
]
|
||
}
|
||
```
|
||
|
||
`dow` is 0 (Sunday) through 6 (Saturday). `hour` is 0–23 UTC.
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/analytics/agents/activity" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /analytics/agents — Per-agent usage summary
|
||
|
||
**Description**: Returns token issuance counts per agent for the current calendar month, for the current organization.
|
||
**Auth**: Bearer token.
|
||
|
||
**Response** `200 OK`:
|
||
|
||
```json
|
||
{
|
||
"tenantId": "org-uuid",
|
||
"month": "2026-03",
|
||
"data": [
|
||
{ "agentId": "uuid", "tokenCount": 312 },
|
||
{ "agentId": "uuid2", "tokenCount": 87 }
|
||
]
|
||
}
|
||
```
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/analytics/agents" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
## Section 7 — API Tiers
|
||
|
||
### GET /tiers/status — Get current tier status
|
||
|
||
**Description**: Returns the organization's current plan tier, configured limits, and live usage counters.
|
||
**Auth**: Bearer token with a valid `organization_id` claim.
|
||
|
||
**Response** `200 OK`:
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `tier` | string | `free` \| `pro` \| `enterprise` |
|
||
| `limits.maxAgents` | integer | Maximum agents allowed |
|
||
| `limits.maxCallsPerDay` | integer | Maximum API calls per day |
|
||
| `limits.maxTokensPerDay` | integer | Maximum token issuances per day |
|
||
| `usage.agentCount` | integer | Current active agent count |
|
||
| `usage.callsToday` | integer | API calls made today |
|
||
| `usage.tokensToday` | integer | Tokens issued today |
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/tiers/status" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### POST /tiers/upgrade — Initiate tier upgrade
|
||
|
||
**Description**: Creates a Stripe Checkout Session to upgrade the organization to a higher plan tier. Returns a one-time checkout URL to redirect the user to.
|
||
**Auth**: Bearer token with a valid `organization_id` claim.
|
||
|
||
**Request body** (`application/json`):
|
||
|
||
| Field | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `target_tier` | enum | Yes | `pro` \| `enterprise` — must be higher than current tier |
|
||
|
||
**Response** `200 OK`:
|
||
|
||
```json
|
||
{ "checkoutUrl": "https://checkout.stripe.com/pay/cs_live_..." }
|
||
```
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR` / `TIER_UPGRADE_NOT_REQUIRED`, 401 `UNAUTHORIZED`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:3000/api/v1/tiers/upgrade \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{ "target_tier": "pro" }' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
## Section 8 — Compliance
|
||
|
||
### GET /compliance/controls — SOC 2 control status (public)
|
||
|
||
**Description**: Returns the live status of all SOC 2 Trust Services Criteria controls. No authentication required.
|
||
**Auth**: None.
|
||
|
||
**Response** `200 OK` (`Cache-Control: public, max-age=60`):
|
||
|
||
```json
|
||
{
|
||
"controls": [
|
||
{ "id": "CC6.1", "name": "Logical Access Controls", "status": "pass", "lastChecked": "2026-04-04T00:00:00.000Z" },
|
||
{ "id": "CC7.2", "name": "System Monitoring", "status": "pass", "lastChecked": "2026-04-04T00:00:00.000Z" }
|
||
]
|
||
}
|
||
```
|
||
|
||
Each control: `id` (string), `name` (string), `status` (`pass` \| `fail` \| `unknown`), `lastChecked` (ISO 8601)
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/compliance/controls" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /compliance/report — AGNTCY compliance report
|
||
|
||
**Description**: Generates an AGNTCY compliance report for the authenticated tenant. Cached in Redis for 5 minutes. Sets `X-Cache: HIT` when served from cache.
|
||
**Auth**: Bearer token.
|
||
|
||
**Response** `200 OK`:
|
||
|
||
```json
|
||
{
|
||
"tenantId": "org-uuid",
|
||
"generatedAt": "2026-04-04T00:00:00.000Z",
|
||
"agntcyConformance": true,
|
||
"agentCount": 12,
|
||
"verifiedAgentCount": 12,
|
||
"auditChainIntegrity": true,
|
||
"from_cache": false
|
||
}
|
||
```
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 404 `COMPLIANCE_DISABLED`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/compliance/report" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /compliance/agent-cards — Export AGNTCY agent cards
|
||
|
||
**Description**: Exports all active agents for the authenticated tenant as AGNTCY-standard agent card JSON objects.
|
||
**Auth**: Bearer token.
|
||
|
||
**Response** `200 OK`: Array of agent card objects.
|
||
|
||
Each card: `did` (string), `name` (string), `agentType` (string), `capabilities` (string[]), `owner` (string), `version` (string), `deploymentEnv` (string), `identityProvider` (string), `issuedAt` (ISO 8601)
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 404 `COMPLIANCE_DISABLED`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/compliance/agent-cards" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
## Section 9 — Webhooks
|
||
|
||
All webhook endpoints require Bearer token authentication and are scoped to the authenticated agent's `organization_id`. Required scopes are enforced via OPA policy.
|
||
|
||
### POST /webhooks — Create a subscription
|
||
|
||
**Description**: Creates a new webhook subscription for the organization. The `signingSecret` is returned once only.
|
||
**Auth**: Bearer token. OPA enforces `webhooks:write`.
|
||
|
||
**Request body** (`application/json`):
|
||
|
||
| Field | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `name` | string | Yes | Human-readable subscription name |
|
||
| `url` | string | Yes | HTTPS endpoint that will receive events |
|
||
| `events` | string[] | Yes | One or more event types to subscribe to (see event type list below) |
|
||
|
||
**Available event types**: `agent.created`, `agent.updated`, `agent.suspended`, `agent.reactivated`, `agent.decommissioned`, `credential.generated`, `credential.rotated`, `credential.revoked`, `token.issued`, `token.revoked`
|
||
|
||
**Response** `201 Created`:
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `id` | UUID | Subscription identifier |
|
||
| `organization_id` | UUID | Owning organization |
|
||
| `name` | string | Subscription name |
|
||
| `url` | string | Target endpoint URL |
|
||
| `events` | string[] | Subscribed event types |
|
||
| `active` | boolean | `true` |
|
||
| `signingSecret` | string | HMAC-SHA256 signing secret (shown once) |
|
||
| `failure_count` | integer | `0` |
|
||
| `created_at` | ISO 8601 | Creation timestamp |
|
||
| `updated_at` | ISO 8601 | Last update timestamp |
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:3000/api/v1/webhooks \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"name": "prod-events",
|
||
"url": "https://my-app.example.com/hooks/sentryagent",
|
||
"events": ["agent.created", "token.issued"]
|
||
}' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /webhooks — List subscriptions
|
||
|
||
**Description**: Returns all webhook subscriptions for the organization. Signing secrets are never returned.
|
||
**Auth**: Bearer token. OPA enforces `webhooks:read`.
|
||
|
||
**Response** `200 OK`: Array of subscription objects (without `signingSecret`).
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/webhooks" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /webhooks/{id} — Get subscription by ID
|
||
|
||
**Description**: Returns a single subscription by its UUID.
|
||
**Auth**: Bearer token. OPA enforces `webhooks:read`.
|
||
|
||
**Path parameters**: `id` (UUID)
|
||
|
||
**Response** `200 OK`: Single subscription object (without `signingSecret`).
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 404 `WEBHOOK_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/webhooks/$WEBHOOK_ID" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### PATCH /webhooks/{id} — Update subscription
|
||
|
||
**Description**: Partially updates a webhook subscription.
|
||
**Auth**: Bearer token. OPA enforces `webhooks:write`.
|
||
|
||
**Request body** (`application/json`) — all fields optional: `name` (string), `url` (string), `events` (string[]), `active` (boolean)
|
||
|
||
**Response** `200 OK`: Updated subscription object.
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 404 `WEBHOOK_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X PATCH "http://localhost:3000/api/v1/webhooks/$WEBHOOK_ID" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{ "active": false }' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### DELETE /webhooks/{id} — Delete subscription
|
||
|
||
**Description**: Permanently deletes a webhook subscription and all its delivery records.
|
||
**Auth**: Bearer token. OPA enforces `webhooks:write`.
|
||
|
||
**Response** `204 No Content`.
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 404 `WEBHOOK_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X DELETE "http://localhost:3000/api/v1/webhooks/$WEBHOOK_ID" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-o /dev/null -w "%{http_code}\n"
|
||
```
|
||
|
||
---
|
||
|
||
### GET /webhooks/{id}/deliveries — List delivery history
|
||
|
||
**Description**: Returns a paginated list of delivery attempts for a subscription.
|
||
**Auth**: Bearer token. OPA enforces `webhooks:read`.
|
||
|
||
**Query parameters**: `limit` (default 20), `offset` (default 0)
|
||
|
||
**Response** `200 OK`:
|
||
|
||
```json
|
||
{
|
||
"deliveries": [...],
|
||
"total": 47,
|
||
"limit": 20,
|
||
"offset": 0
|
||
}
|
||
```
|
||
|
||
Each delivery: `id` (UUID), `subscription_id` (UUID), `event_type` (string), `payload` (object), `status` (`pending` \| `delivered` \| `failed` \| `dead_letter`), `http_status_code` (integer \| null), `attempt_count` (integer), `next_retry_at` (ISO 8601 \| null), `delivered_at` (ISO 8601 \| null), `created_at` (ISO 8601), `updated_at` (ISO 8601)
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 404 `WEBHOOK_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/webhooks/$WEBHOOK_ID/deliveries?limit=20&offset=0" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
## Section 10 — Federation
|
||
|
||
All partner management endpoints require the `admin:orgs` scope (enforced via OPA). The verify
|
||
endpoint requires any authenticated agent.
|
||
|
||
### POST /federation/trust — Register a trusted partner
|
||
|
||
**Description**: Registers a new trusted federation partner (a remote IdP whose tokens this instance will accept).
|
||
**Auth**: Bearer token. OPA enforces `admin:orgs`.
|
||
|
||
**Request body** (`application/json`): Implementation-defined fields for partner registration including `name` (string), `issuer` (string — partner's token issuer URL), `jwksUri` (string — partner's JWKS endpoint).
|
||
|
||
**Response** `201 Created`: Partner record with `id`, `name`, `issuer`, `jwksUri`, `createdAt`.
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:3000/api/v1/federation/trust \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"name": "PartnerCorp IdP",
|
||
"issuer": "https://idp.partnercorp.com",
|
||
"jwksUri": "https://idp.partnercorp.com/.well-known/jwks.json"
|
||
}' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /federation/partners — List partners
|
||
|
||
**Description**: Returns all registered federation partners.
|
||
**Auth**: Bearer token. OPA enforces `admin:orgs`.
|
||
|
||
**Response** `200 OK`: Array of partner records.
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/federation/partners" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /federation/partners/{id} — Get partner by ID
|
||
|
||
**Description**: Returns a single federation partner record.
|
||
**Auth**: Bearer token. OPA enforces `admin:orgs`.
|
||
|
||
**Path parameters**: `id` (UUID)
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 403 `INSUFFICIENT_SCOPE`, 404 `PARTNER_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/federation/partners/$PARTNER_ID" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### PATCH /federation/partners/{id} — Update partner
|
||
|
||
**Description**: Partially updates a federation partner record.
|
||
**Auth**: Bearer token. OPA enforces `admin:orgs`.
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X PATCH "http://localhost:3000/api/v1/federation/partners/$PARTNER_ID" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{ "name": "Updated Partner Name" }' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### DELETE /federation/partners/{id} — Delete partner
|
||
|
||
**Description**: Removes a federation partner. This instance will no longer accept tokens from the partner's issuer.
|
||
**Auth**: Bearer token. OPA enforces `admin:orgs`.
|
||
|
||
**Response** `204 No Content`.
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X DELETE "http://localhost:3000/api/v1/federation/partners/$PARTNER_ID" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-o /dev/null -w "%{http_code}\n"
|
||
```
|
||
|
||
---
|
||
|
||
### POST /federation/verify — Verify a federated token
|
||
|
||
**Description**: Verifies a token issued by a trusted federation partner. Returns the decoded claims if the token is valid and the issuer is trusted.
|
||
**Auth**: Bearer token (any authenticated agent — no `admin:orgs` required).
|
||
|
||
**Request body** (`application/json`): `{ "token": "<partner-issued-jwt>" }`
|
||
|
||
**Response** `200 OK`: `{ "valid": true, "claims": { ... } }` or `{ "valid": false, "reason": "..." }`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:3000/api/v1/federation/verify \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{ "token": "'$PARTNER_TOKEN'" }' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
## Section 11 — DID / OIDC
|
||
|
||
### GET /agents/{agentId}/did — Get agent DID document
|
||
|
||
**Description**: Returns the W3C DID Core 1.0 document for an agent. Unauthenticated — publicly accessible.
|
||
**Auth**: None.
|
||
|
||
**Response** `200 OK`: W3C DID Document.
|
||
|
||
```json
|
||
{
|
||
"@context": ["https://www.w3.org/ns/did/v1"],
|
||
"id": "did:web:localhost%3A3000:agents:a1b2c3d4",
|
||
"controller": "did:web:localhost%3A3000:agents:a1b2c3d4",
|
||
"verificationMethod": [{
|
||
"id": "did:web:localhost%3A3000:agents:a1b2c3d4#key-1",
|
||
"type": "JsonWebKey2020",
|
||
"controller": "did:web:localhost%3A3000:agents:a1b2c3d4",
|
||
"publicKeyJwk": { "kty": "RSA", "n": "...", "e": "AQAB" }
|
||
}],
|
||
"authentication": ["did:web:localhost%3A3000:agents:a1b2c3d4#key-1"],
|
||
"agntcy": {
|
||
"agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||
"agentType": "screener",
|
||
"capabilities": ["resume:read"],
|
||
"deploymentEnv": "production",
|
||
"owner": "talent-team",
|
||
"version": "1.0.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID/did" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /agents/{agentId}/did/resolve — Resolve agent DID
|
||
|
||
**Description**: Returns the full W3C DID Resolution Result format including metadata.
|
||
**Auth**: Bearer token + OPA policy.
|
||
|
||
**Response** `200 OK`:
|
||
|
||
```json
|
||
{
|
||
"didDocument": { ... },
|
||
"didDocumentMetadata": {
|
||
"created": "2026-03-28T09:00:00.000Z",
|
||
"updated": "2026-03-28T09:00:00.000Z",
|
||
"deactivated": false
|
||
},
|
||
"didResolutionMetadata": {
|
||
"contentType": "application/did+ld+json",
|
||
"retrieved": "2026-04-04T00:00:00.000Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID/did/resolve" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /agents/{agentId}/did/card — Get AGNTCY agent card
|
||
|
||
**Description**: Returns the AGNTCY-format agent card for an agent. Unauthenticated.
|
||
**Auth**: None.
|
||
|
||
**Response** `200 OK`:
|
||
|
||
```json
|
||
{
|
||
"did": "did:web:localhost%3A3000:agents:a1b2c3d4",
|
||
"name": "screener-001@talent.ai",
|
||
"agentType": "screener",
|
||
"capabilities": ["resume:read"],
|
||
"owner": "talent-team",
|
||
"version": "1.0.0",
|
||
"deploymentEnv": "production",
|
||
"identityProvider": "https://sentryagent.ai",
|
||
"issuedAt": "2026-04-04T00:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID/did/card" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /.well-known/openid-configuration — OIDC discovery document
|
||
|
||
**Description**: Returns the OIDC Provider discovery document. Unauthenticated. Mounted at the server root (not under `/api/v1`).
|
||
**Auth**: None.
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/.well-known/openid-configuration" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /.well-known/jwks.json — JWKS endpoint
|
||
|
||
**Description**: Returns the JSON Web Key Set (public keys used to verify ID tokens). Unauthenticated.
|
||
**Auth**: None.
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/.well-known/jwks.json" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /agent-info — Agent identity claims
|
||
|
||
**Description**: Returns identity claims for the authenticated agent (equivalent to UserInfo in OIDC). Mounted at the server root.
|
||
**Auth**: Bearer token.
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/agent-info" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### POST /api/v1/oidc/token — OIDC token exchange (GitHub Actions)
|
||
|
||
**Description**: Exchanges a GitHub OIDC JWT for a SentryAgent.ai access token. Unauthenticated — the GitHub OIDC token is the credential. Trust-policy enforcement happens inside the controller.
|
||
**Auth**: None (GitHub OIDC JWT in body).
|
||
|
||
**Request body** (`application/json`): `{ "github_token": "<github-oidc-jwt>", "agentId": "<uuid>" }`
|
||
|
||
**Response** `200 OK`: `{ "access_token": "...", "token_type": "Bearer", "expires_in": 3600 }`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:3000/api/v1/oidc/token \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"github_token": "'$GITHUB_OIDC_TOKEN'",
|
||
"agentId": "'$AGENT_ID'"
|
||
}' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### POST /api/v1/oidc/trust-policies — Create trust policy
|
||
|
||
**Description**: Registers a trust policy that allows GitHub Actions workflows matching specific claims to exchange tokens.
|
||
**Auth**: Bearer token with `agents:write` scope.
|
||
|
||
**Request body** (`application/json`): Repository, branch, and claim constraints (implementation-defined fields).
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:3000/api/v1/oidc/trust-policies \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"agentId": "'$AGENT_ID'",
|
||
"repository": "my-org/my-repo",
|
||
"branch": "main"
|
||
}' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /api/v1/oidc/trust-policies — List trust policies
|
||
|
||
**Description**: Returns all trust policies for an agent.
|
||
**Auth**: Bearer token with `agents:write` scope.
|
||
|
||
**Query parameters**: `agentId` (UUID, required)
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/oidc/trust-policies?agentId=$AGENT_ID" \
|
||
-H "Authorization: Bearer $TOKEN" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### DELETE /api/v1/oidc/trust-policies/{id} — Delete trust policy
|
||
|
||
**Description**: Deletes a trust policy by its UUID.
|
||
**Auth**: Bearer token with `agents:write` scope.
|
||
|
||
**Response** `204 No Content`.
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X DELETE "http://localhost:3000/api/v1/oidc/trust-policies/$POLICY_ID" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-o /dev/null -w "%{http_code}\n"
|
||
```
|
||
|
||
---
|
||
|
||
## Section 12 — A2A Delegation
|
||
|
||
### POST /oauth2/token/delegate — Create a delegation chain
|
||
|
||
**Description**: Creates a delegation chain that grants a delegatee agent a subset of the delegator's scopes for a limited time.
|
||
**Auth**: Bearer token.
|
||
|
||
**Request body** (`application/json`):
|
||
|
||
| Field | Type | Required | Description |
|
||
|-------|------|----------|-------------|
|
||
| `delegateeAgentId` | UUID | Yes | The agent that receives delegated authority |
|
||
| `scopes` | string[] | Yes | Scopes to delegate — must be a strict subset of the caller's own scopes |
|
||
| `ttlSeconds` | integer | Yes | Delegation lifetime in seconds. Min: 60, Max: 86400 |
|
||
|
||
**Response** `201 Created`:
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `delegationToken` | string | Signed delegation token (HMAC-SHA256) |
|
||
| `chainId` | UUID | Delegation chain identifier |
|
||
| `delegatorAgentId` | UUID | Agent granting the delegation |
|
||
| `delegateeAgentId` | UUID | Agent receiving the delegation |
|
||
| `scopes` | string[] | Delegated scopes |
|
||
| `expiresAt` | ISO 8601 | Expiry timestamp |
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR` / `DELEGATION_SCOPE_EXCEEDED`, 401 `UNAUTHORIZED`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:3000/api/v1/oauth2/token/delegate \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"delegateeAgentId": "'$DELEGATEE_AGENT_ID'",
|
||
"scopes": ["agents:read"],
|
||
"ttlSeconds": 3600
|
||
}' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### POST /oauth2/token/verify-delegation — Verify a delegation token
|
||
|
||
**Description**: Verifies a delegation token and returns the chain details. Returns `valid: false` (not an error) for expired or revoked tokens.
|
||
**Auth**: Bearer token.
|
||
|
||
**Request body** (`application/json`): `{ "delegationToken": "<delegation-jwt>" }`
|
||
|
||
**Response** `200 OK`:
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `valid` | boolean | Whether the delegation is currently valid |
|
||
| `chainId` | UUID | Chain identifier |
|
||
| `delegatorAgentId` | UUID | Delegating agent |
|
||
| `delegateeAgentId` | UUID | Receiving agent |
|
||
| `scopes` | string[] | Delegated scopes |
|
||
| `issuedAt` | ISO 8601 | Issue timestamp |
|
||
| `expiresAt` | ISO 8601 | Expiry timestamp |
|
||
| `revokedAt` | ISO 8601 \| null | Revocation timestamp, or null |
|
||
|
||
**Error responses**: 400 `VALIDATION_ERROR`, 401 `UNAUTHORIZED`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:3000/api/v1/oauth2/token/verify-delegation \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{ "delegationToken": "'$DELEGATION_TOKEN'" }' | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### DELETE /oauth2/token/delegate/{chainId} — Revoke a delegation chain
|
||
|
||
**Description**: Immediately revokes a delegation chain. The delegatee can no longer use the delegation token. Only the delegator can revoke their own chains.
|
||
**Auth**: Bearer token.
|
||
|
||
**Path parameters**: `chainId` (UUID)
|
||
|
||
**Response** `204 No Content`.
|
||
|
||
**Error responses**: 401 `UNAUTHORIZED`, 403 `FORBIDDEN`, 404 `DELEGATION_NOT_FOUND`, 429 `RATE_LIMIT_EXCEEDED`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s -X DELETE "http://localhost:3000/api/v1/oauth2/token/delegate/$CHAIN_ID" \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-o /dev/null -w "%{http_code}\n"
|
||
```
|
||
|
||
---
|
||
|
||
## Section 13 — Marketplace
|
||
|
||
The Marketplace is feature-flagged via `MARKETPLACE_ENABLED` env var. When disabled, all endpoints
|
||
return `404 NOT_FOUND`. All marketplace endpoints are **unauthenticated** — no Bearer token required.
|
||
|
||
### GET /marketplace/agents — List public agents
|
||
|
||
**Description**: Returns a paginated list of publicly-listed agents.
|
||
**Auth**: None.
|
||
|
||
**Query parameters**: `page` (default 1), `limit` (default 20, max 100), `q` (text search), `capability` (filter by capability string), `publisher` (filter by owner)
|
||
|
||
**Response** `200 OK`: `{ data: PublicAgent[], total: number, page: number, limit: number }`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/marketplace/agents?q=screener&limit=20" | jq .
|
||
```
|
||
|
||
---
|
||
|
||
### GET /marketplace/agents/{agentId} — Get public agent
|
||
|
||
**Description**: Returns a single public agent with its DID document included. Returns 404 if the agent is private or inactive.
|
||
**Auth**: None.
|
||
|
||
**Path parameters**: `agentId` (UUID)
|
||
|
||
**Response** `200 OK`: Public agent object including `didDocument` field.
|
||
|
||
**Error responses**: 404 `AGENT_NOT_FOUND`
|
||
|
||
**curl example**:
|
||
|
||
```bash
|
||
curl -s "http://localhost:3000/api/v1/marketplace/agents/$AGENT_ID" | jq .
|
||
```
|