docs: bedroom developer documentation — complete docs/developers/ set
Adds the full bedroom-developer-docs OpenSpec change implementation: - docs/developers/README.md — index page - docs/developers/quick-start.md — bootstrap to working token in 7 steps - docs/developers/concepts.md — AgentIdP, AGNTCY, lifecycle, OAuth 2.0, free tier - docs/developers/guides/README.md — guide index - docs/developers/guides/register-an-agent.md — all fields, validation, common errors - docs/developers/guides/manage-credentials.md — generate, list, rotate, revoke - docs/developers/guides/issue-and-revoke-tokens.md — OAuth 2.0 flow, introspect, revoke - docs/developers/guides/query-audit-logs.md — filters, pagination, 90-day retention - docs/developers/api-reference.md — all 14 endpoints, all error codes, curl examples Also commits deferred OpenSpec housekeeping from previous session: - Archives phase-1-mvp-implementation change to openspec/changes/archive/ - Adds bedroom-developer-docs change artifacts (30/30 tasks complete) - Syncs 4 delta specs to openspec/specs/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
12
docs/developers/guides/README.md
Normal file
12
docs/developers/guides/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Guides
|
||||
|
||||
Step-by-step walkthroughs for each AgentIdP workflow.
|
||||
|
||||
| Guide | What it covers |
|
||||
|-------|----------------|
|
||||
| [Register an Agent](register-an-agent.md) | All registration fields, validation rules, common errors and fixes |
|
||||
| [Manage Credentials](manage-credentials.md) | Generate, list, rotate, and revoke credentials |
|
||||
| [Issue and Revoke Tokens](issue-and-revoke-tokens.md) | OAuth 2.0 Client Credentials flow, JWT structure, introspect, revoke |
|
||||
| [Query Audit Logs](query-audit-logs.md) | Filters, pagination, event structure, 90-day retention |
|
||||
|
||||
All guides assume you have a running local server and a valid Bearer token. See the [Quick Start](../quick-start.md) if you haven't done that yet.
|
||||
203
docs/developers/guides/issue-and-revoke-tokens.md
Normal file
203
docs/developers/guides/issue-and-revoke-tokens.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Issue and Revoke Tokens
|
||||
|
||||
This guide covers the complete token lifecycle: issuing, using, inspecting, and revoking JWT access tokens.
|
||||
|
||||
---
|
||||
|
||||
## Issue a token
|
||||
|
||||
`POST /api/v1/token`
|
||||
|
||||
This is the OAuth 2.0 Client Credentials grant. Your agent exchanges its `client_id` and `client_secret` for a signed JWT access token.
|
||||
|
||||
> **Important**: This endpoint uses `application/x-www-form-urlencoded` encoding, not JSON.
|
||||
|
||||
```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 .
|
||||
```
|
||||
|
||||
Response (`200 OK`):
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMWIyYzNkNC1lNWY2LTc4OTAtYWJjZC1lZjEyMzQ1Njc4OTAiLCJjbGllbnRfaWQiOiJhMWIyYzNkNC1lNWY2LTc4OTAtYWJjZC1lZjEyMzQ1Njc4OTAiLCJzY29wZSI6ImFnZW50czpyZWFkIGFnZW50czp3cml0ZSIsImp0aSI6InV1aWQtaGVyZSIsImlhdCI6MTc0MzE1MTIwMCwiZXhwIjoxNzQzMTU0ODAwfQ.signature",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"scope": "agents:read agents:write"
|
||||
}
|
||||
```
|
||||
|
||||
The token expires in `3600` seconds (1 hour). Request a new one before it expires.
|
||||
|
||||
### Request fields
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `grant_type` | Yes | Must be `client_credentials` |
|
||||
| `client_id` | Yes | Your agent's `agentId` (UUID) |
|
||||
| `client_secret` | Yes | The secret from credential generation |
|
||||
| `scope` | No | Space-separated list of requested scopes. If omitted, all scopes are granted. |
|
||||
|
||||
### Available scopes
|
||||
|
||||
| Scope | What it allows |
|
||||
|-------|----------------|
|
||||
| `agents:read` | Read agent records |
|
||||
| `agents:write` | Create, update, decommission agents |
|
||||
| `tokens:read` | Introspect tokens |
|
||||
| `audit:read` | Query audit logs |
|
||||
|
||||
Request only the scopes your agent needs.
|
||||
|
||||
---
|
||||
|
||||
## What's inside the JWT
|
||||
|
||||
A JWT has three base64-encoded parts separated by dots: header, payload, and signature. The payload contains your agent's identity claims.
|
||||
|
||||
Decode the payload to inspect it (for development only — never trust an unverified token in production):
|
||||
|
||||
```bash
|
||||
# Extract the middle part (payload) of your token and decode it
|
||||
TOKEN_PAYLOAD=$(echo "$TOKEN" | cut -d. -f2)
|
||||
echo "$TOKEN_PAYLOAD" | base64 --decode 2>/dev/null | jq .
|
||||
```
|
||||
|
||||
Claims in the payload:
|
||||
|
||||
| Claim | Description |
|
||||
|-------|-------------|
|
||||
| `sub` | Subject — your agent's `agentId` |
|
||||
| `client_id` | The `agentId` that authenticated |
|
||||
| `scope` | Scopes granted by this token |
|
||||
| `jti` | JWT ID — unique identifier for this token (used for revocation) |
|
||||
| `iat` | Issued at (Unix timestamp in seconds) |
|
||||
| `exp` | Expires at (Unix timestamp in seconds) |
|
||||
|
||||
---
|
||||
|
||||
## Use the token
|
||||
|
||||
Include the token in the `Authorization` header of every API request:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:3000/api/v1/agents \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Introspect a token
|
||||
|
||||
`POST /api/v1/token/introspect`
|
||||
|
||||
Check whether a token is currently active (valid, not expired, not revoked). Requires a Bearer token with `tokens:read` scope.
|
||||
|
||||
```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 .
|
||||
```
|
||||
|
||||
Response for an active token:
|
||||
|
||||
```json
|
||||
{
|
||||
"active": true,
|
||||
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"client_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"scope": "agents:read agents:write",
|
||||
"token_type": "Bearer",
|
||||
"iat": 1743151200,
|
||||
"exp": 1743154800
|
||||
}
|
||||
```
|
||||
|
||||
Response for an inactive (expired or revoked) token:
|
||||
|
||||
```json
|
||||
{
|
||||
"active": false
|
||||
}
|
||||
```
|
||||
|
||||
> The introspect endpoint always returns `200 OK` — even for inactive tokens. You must check the `active` field to determine token validity.
|
||||
|
||||
---
|
||||
|
||||
## Revoke a token
|
||||
|
||||
`POST /api/v1/token/revoke`
|
||||
|
||||
Immediately invalidates a token, preventing it from being used for any subsequent requests. Requires a Bearer token.
|
||||
|
||||
```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 .
|
||||
```
|
||||
|
||||
Response (`200 OK`):
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
**Notes on revocation**:
|
||||
- Revocation is immediate — the token is rejected on the next request
|
||||
- Revoking an already-revoked or expired token is not an error (idempotent per RFC 7009)
|
||||
- An agent can revoke its own tokens; revoking another agent's token requires an admin-scoped token
|
||||
- Revoking a token does not affect the credential that issued it — new tokens can still be obtained using the same credentials
|
||||
|
||||
---
|
||||
|
||||
## Token errors
|
||||
|
||||
### `401 invalid_client` — wrong credentials
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "invalid_client",
|
||||
"error_description": "Client authentication failed. Invalid client_id or client_secret."
|
||||
}
|
||||
```
|
||||
|
||||
Check that `client_id` matches the agent's `agentId` and `client_secret` is the current active secret.
|
||||
|
||||
### `403 unauthorized_client` — agent suspended or monthly limit reached
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "unauthorized_client",
|
||||
"error_description": "Agent is currently suspended and cannot obtain tokens."
|
||||
}
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "unauthorized_client",
|
||||
"error_description": "Free tier monthly token limit of 10,000 requests has been reached."
|
||||
}
|
||||
```
|
||||
|
||||
For suspension: reactivate the agent first. For the monthly limit: the counter resets on the first day of the next calendar month.
|
||||
|
||||
### `400 unsupported_grant_type`
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "unsupported_grant_type",
|
||||
"error_description": "Only 'client_credentials' grant type is supported."
|
||||
}
|
||||
```
|
||||
|
||||
Only `client_credentials` is supported. Do not use `authorization_code`, `password`, or other grant types.
|
||||
167
docs/developers/guides/manage-credentials.md
Normal file
167
docs/developers/guides/manage-credentials.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Manage Credentials
|
||||
|
||||
A credential is a `client_id` + `client_secret` pair that your agent uses to get access tokens. This guide covers all four credential operations.
|
||||
|
||||
All credential endpoints are under `/api/v1/agents/{agentId}/credentials` and require a Bearer token with `agents:write` scope.
|
||||
|
||||
---
|
||||
|
||||
## Generate credentials
|
||||
|
||||
`POST /api/v1/agents/{agentId}/credentials`
|
||||
|
||||
Creates a new credential for the agent. The `clientSecret` is returned **once only**.
|
||||
|
||||
```bash
|
||||
curl -s -X POST "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}' | jq .
|
||||
```
|
||||
|
||||
To set an expiry date (optional):
|
||||
|
||||
```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-03-28T00:00:00.000Z" }' | jq .
|
||||
```
|
||||
|
||||
Response (`201 Created`):
|
||||
|
||||
```json
|
||||
{
|
||||
"credentialId": "c9d8e7f6-a5b4-3210-fedc-ba9876543210",
|
||||
"clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"clientSecret": "sk_live_7f3a2b1c9d8e4f0a6b5c3d2e1f0a9b8c",
|
||||
"status": "active",
|
||||
"createdAt": "2026-03-28T09:00:00.000Z",
|
||||
"expiresAt": "2027-03-28T00:00:00.000Z",
|
||||
"revokedAt": null
|
||||
}
|
||||
```
|
||||
|
||||
> **Save the `clientSecret` immediately.** It is shown once. The server stores a bcrypt hash and cannot recover the plaintext. If you lose it, rotate the credential to get a new one.
|
||||
|
||||
An agent can hold **multiple active credentials** at the same time. This supports zero-downtime rotation: generate a new credential, update all consumers to use it, then revoke the old one.
|
||||
|
||||
**Restrictions**:
|
||||
- The agent must be in `active` status. Suspended and decommissioned agents cannot generate credentials.
|
||||
|
||||
---
|
||||
|
||||
## List credentials
|
||||
|
||||
`GET /api/v1/agents/{agentId}/credentials`
|
||||
|
||||
Returns all credentials for the agent (both active and revoked). The `clientSecret` is **never** returned in list responses.
|
||||
|
||||
```bash
|
||||
curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"credentialId": "c9d8e7f6-a5b4-3210-fedc-ba9876543210",
|
||||
"clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"status": "active",
|
||||
"createdAt": "2026-03-28T09:00:00.000Z",
|
||||
"expiresAt": "2027-03-28T00:00:00.000Z",
|
||||
"revokedAt": null
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"limit": 20
|
||||
}
|
||||
```
|
||||
|
||||
### Pagination
|
||||
|
||||
```bash
|
||||
curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials?page=1&limit=50" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
```
|
||||
|
||||
### Filter by status
|
||||
|
||||
```bash
|
||||
# Active credentials only
|
||||
curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials?status=active" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
|
||||
# Revoked credentials only
|
||||
curl -s "http://localhost:3000/api/v1/agents/$AGENT_ID/credentials?status=revoked" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rotate a credential
|
||||
|
||||
`POST /api/v1/agents/{agentId}/credentials/{credentialId}/rotate`
|
||||
|
||||
Rotation immediately invalidates the current `clientSecret` and generates a new one — the `credentialId` stays the same. Use this for periodic secret rotation or emergency rotation if a secret is compromised.
|
||||
|
||||
```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 .
|
||||
```
|
||||
|
||||
Response (`200 OK`):
|
||||
|
||||
```json
|
||||
{
|
||||
"credentialId": "c9d8e7f6-a5b4-3210-fedc-ba9876543210",
|
||||
"clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"clientSecret": "sk_live_9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d",
|
||||
"status": "active",
|
||||
"createdAt": "2026-03-28T09:00:00.000Z",
|
||||
"expiresAt": null,
|
||||
"revokedAt": null
|
||||
}
|
||||
```
|
||||
|
||||
**What changes after rotation**:
|
||||
- The `clientSecret` is a new value — the old secret is immediately invalid
|
||||
- The `credentialId` is the same — no changes needed to references by ID
|
||||
- Any tokens issued using the old secret remain valid until they expire naturally (tokens are not revoked by credential rotation)
|
||||
|
||||
**What cannot be rotated**: A `revoked` credential cannot be rotated. Generate a new credential instead.
|
||||
|
||||
---
|
||||
|
||||
## Revoke a credential
|
||||
|
||||
`DELETE /api/v1/agents/{agentId}/credentials/{credentialId}`
|
||||
|
||||
Permanently revokes a credential. The credential can no longer be used to obtain new tokens.
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
Successful response: `204 No Content` (empty body).
|
||||
|
||||
**Effects of revocation**:
|
||||
- The credential status is set to `revoked`
|
||||
- The credential cannot be used to call `POST /token`
|
||||
- Any tokens that were issued using this credential remain valid until they expire — to immediately invalidate tokens, revoke them explicitly using `POST /token/revoke`
|
||||
- The credential record is retained for audit purposes
|
||||
- Revocation is **irreversible** — a revoked credential cannot be re-activated
|
||||
|
||||
**Revocation vs decommission**:
|
||||
- Revoking a credential affects that credential only; the agent stays active
|
||||
- Decommissioning an agent (`DELETE /api/v1/agents/{agentId}`) revokes all credentials simultaneously and permanently retires the agent
|
||||
183
docs/developers/guides/query-audit-logs.md
Normal file
183
docs/developers/guides/query-audit-logs.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# Query Audit Logs
|
||||
|
||||
The audit log is an immutable, append-only record of every significant action on the AgentIdP platform. This guide covers how to query it, what filters are available, and how retention works.
|
||||
|
||||
Requires: `Authorization: Bearer <token>` with `audit:read` scope.
|
||||
|
||||
---
|
||||
|
||||
## What gets logged
|
||||
|
||||
Every action below is automatically recorded. You cannot create, modify, or delete audit events — the log is read-only via the API.
|
||||
|
||||
| Action | Triggered by |
|
||||
|--------|-------------|
|
||||
| `agent.created` | Successful `POST /agents` |
|
||||
| `agent.updated` | Successful `PATCH /agents/{agentId}` |
|
||||
| `agent.decommissioned` | Successful `DELETE /agents/{agentId}` |
|
||||
| `agent.suspended` | Status changed to `suspended` |
|
||||
| `agent.reactivated` | Status changed from `suspended` to `active` |
|
||||
| `token.issued` | Successful `POST /token` |
|
||||
| `token.revoked` | Successful `POST /token/revoke` |
|
||||
| `token.introspected` | Successful `POST /token/introspect` |
|
||||
| `credential.generated` | Successful `POST /agents/{agentId}/credentials` |
|
||||
| `credential.rotated` | Successful `POST /agents/{agentId}/credentials/{credentialId}/rotate` |
|
||||
| `credential.revoked` | Successful `DELETE /agents/{agentId}/credentials/{credentialId}` |
|
||||
| `auth.failed` | Failed authentication attempt on `POST /token` |
|
||||
|
||||
---
|
||||
|
||||
## Query the audit log
|
||||
|
||||
`GET /api/v1/audit`
|
||||
|
||||
Returns a paginated list of audit events, most recent first.
|
||||
|
||||
```bash
|
||||
curl -s "http://localhost:3000/api/v1/audit" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"eventId": "f1e2d3c4-b5a6-7890-cdef-123456789012",
|
||||
"agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"action": "token.issued",
|
||||
"outcome": "success",
|
||||
"ipAddress": "127.0.0.1",
|
||||
"userAgent": "curl/7.88.1",
|
||||
"metadata": {
|
||||
"scope": "agents:read agents:write",
|
||||
"expiresAt": "2026-03-28T10:00:00.000Z"
|
||||
},
|
||||
"timestamp": "2026-03-28T09:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"total": 47,
|
||||
"page": 1,
|
||||
"limit": 50
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Audit event structure
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `eventId` | UUID | Immutable unique ID for this event |
|
||||
| `agentId` | UUID | The agent that triggered the event |
|
||||
| `action` | string | What happened (see table above) |
|
||||
| `outcome` | string | `success` or `failure` |
|
||||
| `ipAddress` | string | Client IP (IPv4 or IPv6) |
|
||||
| `userAgent` | string | HTTP User-Agent from the request |
|
||||
| `metadata` | object | Action-specific details (varies by action) |
|
||||
| `timestamp` | ISO 8601 | When the event occurred |
|
||||
|
||||
### `metadata` by action
|
||||
|
||||
| Action | Metadata fields |
|
||||
|--------|----------------|
|
||||
| `token.issued` | `scope`, `expiresAt` |
|
||||
| `credential.generated` | `credentialId` |
|
||||
| `credential.rotated` | `credentialId` |
|
||||
| `agent.created` | `agentType`, `owner` |
|
||||
| `auth.failed` | `reason`, `clientId` |
|
||||
|
||||
---
|
||||
|
||||
## Filters
|
||||
|
||||
All filter parameters are optional and can be combined (logical AND).
|
||||
|
||||
### Filter by agent
|
||||
|
||||
```bash
|
||||
curl -s "http://localhost:3000/api/v1/audit?agentId=$AGENT_ID" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
```
|
||||
|
||||
### Filter by action
|
||||
|
||||
```bash
|
||||
curl -s "http://localhost:3000/api/v1/audit?action=token.issued" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
```
|
||||
|
||||
### Filter by outcome
|
||||
|
||||
```bash
|
||||
# Failed authentication attempts only
|
||||
curl -s "http://localhost:3000/api/v1/audit?outcome=failure" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
```
|
||||
|
||||
### Filter by date range
|
||||
|
||||
```bash
|
||||
curl -s "http://localhost:3000/api/v1/audit?fromDate=2026-03-01T00:00:00.000Z&toDate=2026-03-28T23:59:59.999Z" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
```
|
||||
|
||||
### Combine filters
|
||||
|
||||
```bash
|
||||
# All failed token requests for a specific agent today
|
||||
curl -s "http://localhost:3000/api/v1/audit?agentId=$AGENT_ID&action=auth.failed&outcome=failure&fromDate=2026-03-28T00:00:00.000Z" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pagination
|
||||
|
||||
Default page size is 50, maximum is 200.
|
||||
|
||||
```bash
|
||||
curl -s "http://localhost:3000/api/v1/audit?page=2&limit=100" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
```
|
||||
|
||||
Use `total`, `page`, and `limit` from the response to calculate the number of pages:
|
||||
|
||||
```
|
||||
total_pages = ceil(total / limit)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get a single event
|
||||
|
||||
`GET /api/v1/audit/{eventId}`
|
||||
|
||||
```bash
|
||||
curl -s "http://localhost:3000/api/v1/audit/$EVENT_ID" \
|
||||
-H "Authorization: Bearer $TOKEN" | jq .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retention — 90 days
|
||||
|
||||
On the free tier, audit events are retained for 90 days. Events older than 90 days are automatically purged.
|
||||
|
||||
- Querying for dates outside the 90-day window returns an empty result set — not an error
|
||||
- Requesting a specific `eventId` for a purged event returns `404 Not Found`
|
||||
- The `fromDate` filter cannot be set to a date older than 90 days; doing so returns `400 RETENTION_WINDOW_EXCEEDED`
|
||||
|
||||
To check the earliest available date:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "RETENTION_WINDOW_EXCEEDED",
|
||||
"message": "Free tier audit log retention is 90 days. Requested date is outside the retention window.",
|
||||
"details": {
|
||||
"retentionDays": 90,
|
||||
"earliestAvailable": "2025-12-28T00:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
172
docs/developers/guides/register-an-agent.md
Normal file
172
docs/developers/guides/register-an-agent.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Register an Agent
|
||||
|
||||
This guide covers everything about registering a new agent identity, including all fields, validation rules, and how to fix common errors.
|
||||
|
||||
---
|
||||
|
||||
## The registration request
|
||||
|
||||
`POST /api/v1/agents`
|
||||
|
||||
Requires: `Authorization: Bearer <token>` with `agents:write` scope.
|
||||
|
||||
### Request fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `email` | string (email) | Yes | Unique identifier for this agent. Must be a valid email format and unique across all registered agents. |
|
||||
| `agentType` | string (enum) | Yes | Functional classification of the agent. See values below. |
|
||||
| `version` | string (semver) | Yes | Semantic version of the agent software (e.g. `1.0.0`, `2.3.1-beta`). |
|
||||
| `capabilities` | string[] | Yes | One or more capability strings in `resource:action` format. Minimum 1. |
|
||||
| `owner` | string | Yes | Team or organisation that owns this agent. 1–128 characters. |
|
||||
| `deploymentEnv` | string (enum) | Yes | Target deployment environment. See values below. |
|
||||
|
||||
### `agentType` values
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `screener` | Screens or filters content |
|
||||
| `classifier` | Classifies or categorises inputs |
|
||||
| `orchestrator` | Coordinates other agents or workflows |
|
||||
| `extractor` | Extracts structured data |
|
||||
| `summarizer` | Produces summaries |
|
||||
| `router` | Routes requests to other agents |
|
||||
| `monitor` | Monitors systems or outputs |
|
||||
| `custom` | Any type not covered above |
|
||||
|
||||
### `deploymentEnv` values
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `development` | Local or dev environment |
|
||||
| `staging` | Pre-production testing |
|
||||
| `production` | Live production workloads |
|
||||
|
||||
### `capabilities` format
|
||||
|
||||
Each capability is a string matching `resource:action`. Examples:
|
||||
|
||||
```
|
||||
resume:read
|
||||
email:send
|
||||
candidate:score
|
||||
document:classify
|
||||
data:*
|
||||
```
|
||||
|
||||
The `*` wildcard in the action position means all actions on that resource. Capabilities are informational in Phase 1.
|
||||
|
||||
---
|
||||
|
||||
## Example — register a screener agent
|
||||
|
||||
```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", "candidate:score"],
|
||||
"owner": "talent-acquisition-team",
|
||||
"deploymentEnv": "production"
|
||||
}' | jq .
|
||||
```
|
||||
|
||||
Successful response (`201 Created`):
|
||||
|
||||
```json
|
||||
{
|
||||
"agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"email": "screener-001@talent.ai",
|
||||
"agentType": "screener",
|
||||
"version": "1.0.0",
|
||||
"capabilities": ["resume:read", "email:send", "candidate:score"],
|
||||
"owner": "talent-acquisition-team",
|
||||
"deploymentEnv": "production",
|
||||
"status": "active",
|
||||
"createdAt": "2026-03-28T09:00:00.000Z",
|
||||
"updatedAt": "2026-03-28T09:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
The `agentId` is assigned by the system — it is immutable and never changes.
|
||||
|
||||
---
|
||||
|
||||
## Immutable fields
|
||||
|
||||
After registration, the following fields **cannot be changed**:
|
||||
|
||||
- `agentId` — system-assigned, permanent
|
||||
- `email` — the agent's stable identity
|
||||
- `createdAt` — registration timestamp
|
||||
|
||||
To update any other field, use `PATCH /api/v1/agents/{agentId}`.
|
||||
|
||||
---
|
||||
|
||||
## Common errors and fixes
|
||||
|
||||
### `400 VALIDATION_ERROR` — invalid email format
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Request validation failed.",
|
||||
"details": { "field": "email", "reason": "Must be a valid email address." }
|
||||
}
|
||||
```
|
||||
|
||||
**Fix**: Use a valid email format, e.g. `my-agent@myproject.ai`.
|
||||
|
||||
---
|
||||
|
||||
### `400 VALIDATION_ERROR` — invalid version format
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Request validation failed.",
|
||||
"details": { "field": "version", "reason": "Must be a valid semantic version string." }
|
||||
}
|
||||
```
|
||||
|
||||
**Fix**: Use semantic versioning — `1.0.0`, `2.1.3`, `1.0.0-beta.1`. The format is `MAJOR.MINOR.PATCH`.
|
||||
|
||||
---
|
||||
|
||||
### `400 VALIDATION_ERROR` — invalid capability format
|
||||
|
||||
Capabilities must match `resource:action` — lowercase letters, numbers, hyphens, and underscores only.
|
||||
|
||||
**Fix**: Use `resume:read` not `Resume:Read` or `read-resume`.
|
||||
|
||||
---
|
||||
|
||||
### `409 AGENT_ALREADY_EXISTS` — duplicate email
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "AGENT_ALREADY_EXISTS",
|
||||
"message": "An agent with this email address is already registered.",
|
||||
"details": { "email": "screener-001@talent.ai" }
|
||||
}
|
||||
```
|
||||
|
||||
**Fix**: Choose a different email address. Each agent must have a unique email.
|
||||
|
||||
---
|
||||
|
||||
### `403 FREE_TIER_LIMIT_EXCEEDED` — 100 agent limit reached
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "FREE_TIER_LIMIT_EXCEEDED",
|
||||
"message": "Free tier limit of 100 registered agents has been reached.",
|
||||
"details": { "limit": 100, "current": 100 }
|
||||
}
|
||||
```
|
||||
|
||||
**Fix**: Decommission agents you no longer need before registering new ones.
|
||||
Reference in New Issue
Block a user