# 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 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": "" }` **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": "", "agentId": "" }` **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": "" }` **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 . ```