Agent API Reference

API key auth, job lifecycle endpoints, SSE, webhooks, retries, and error taxonomy.

# Agent API (v1)

This reference is written for:
- Autonomous agents (implementation details, exact fields, retries, idempotency).
- Human developers/operators (quick examples and flow explanations).

Base path: `/api/v1`  
Auth: `Authorization: Bearer <api_key>` on protected routes.

## 1. Integration Flow (Recommended)

1. Ensure account
- `POST /agent/accounts/ensure`

2. Activate account
- `POST /agent/accounts/activate`

3. Create initial API key
- `POST /agent/keys` with `email` + `activation_code`

4. Create content job
- `POST /content/jobs` with `Idempotency-Key`

5. Track progress
- Poll `GET /content/jobs/{id}` or stream `GET /content/jobs/{id}/events`

6. Fetch result
- `GET /content/jobs/{id}/result`

7. Optional webhook notifications
- `POST /agent/webhooks` and verify signature

8. Rotate/revoke keys automatically
- `POST /agent/keys/{id}/rotate` then `POST /agent/keys/{old_id}/revoke`

## 2. Auth and Common Headers

Required auth header:
```http
Authorization: Bearer cak_live_...
```

Required for job creation:
```http
Idempotency-Key: your-unique-operation-id
```

Rate-limit headers (on rate-limited routes):
- `X-RateLimit-Limit`
- `X-RateLimit-Remaining`
- `X-RateLimit-Reset` (Unix seconds)

## 3. Accounts

### `POST /agent/accounts/ensure`

Request:
```json
{
  "email": "agent@example.com",
  "tenant_name": "Client Workspace",
  "agent_name": "Content Agent"
}
```

Possible responses:
- Existing active account:
```json
{
  "ok": true,
  "request_id": "uuid",
  "account_status": "active",
  "email": "agent@example.com",
  "user_id": "usr_...",
  "tenant_id": "tn_...",
  "tenant_name": "Client Workspace"
}
```

- New/pending account:
```json
{
  "ok": true,
  "request_id": "uuid",
  "account_status": "pending_email_verification",
  "verification_delivery": "email_pending_provider",
  "activation_code_preview": "dev-only-in-non-production",
  "activation_expires_at": "2026-02-26T12:00:00.000Z"
}
```

Rate limits:
- Public bootstrap endpoints are rate-limited per email+client key.

### `POST /agent/accounts/activate`

Request:
```json
{
  "email": "agent@example.com",
  "activation_code": "..."
}
```

Response:
```json
{
  "ok": true,
  "request_id": "uuid",
  "account_status": "active",
  "user_id": "usr_...",
  "tenant_id": "tn_...",
  "signup_request_id": "signup_request_id"
}
```

## 4. API Keys

### `POST /agent/keys`

Mode A: initial key issuance (no bearer key yet)
```json
{
  "name": "agent-primary",
  "email": "agent@example.com",
  "activation_code": "...",
  "expires_in_days": 30
}
```

Mode B: additional key issuance (authenticated, scope `keys:write`)
```json
{
  "name": "agent-rotated",
  "scopes": ["jobs:write", "jobs:read", "webhooks:read", "keys:write"],
  "expires_in_days": 30
}
```

Response:
```json
{
  "ok": true,
  "request_id": "uuid",
  "key_id": "ck_...",
  "api_key": "cak_live_...",
  "prefix": "cak_live_...",
  "scopes": ["jobs:write", "jobs:read", "webhooks:read", "keys:write"],
  "expires_at": "2026-03-26T12:00:00.000Z"
}
```

### `GET /agent/keys`
Lists keys owned by the authenticated user.

### `POST /agent/keys/{id}/rotate`
Creates replacement key and returns both old/new metadata.

### `POST /agent/keys/{id}/revoke`
Revokes a key immediately.

## 5. Content Jobs

### `POST /content/jobs`
Queues a generation run.

Request body: `GenerationRequestV2`
- Optional non-production testing fields:
  - `testing.force_error_stage`
  - `testing.force_error_code`
  - `testing.force_error_message`

Response:
```json
{
  "ok": true,
  "request_id": "uuid",
  "job_id": "job_...",
  "run_id": "run_...",
  "topic_id": "topic_...",
  "status": "queued"
}
```

Idempotency behavior:
- Same `Idempotency-Key` + same authenticated owner + same route returns cached response.
- Use one unique key per logical create operation.

### `GET /content/jobs/{id}`
`id` supports either `job_id` or `run_id`.

Response fields:
- `status`
- `progress` with stage counters
- `partial_content`
- `error` (terminal error/cancel details)
- timestamps + queue/processing timings

### `DELETE /content/jobs/{id}`
Cancels queued or processing job.

### `GET /content/jobs/{id}/result`

If not ready:
```json
{
  "ok": true,
  "request_id": "uuid",
  "status": "processing",
  "result": null,
  "partial_result": {
    "progress": {},
    "content": {}
  }
}
```

If ready:
- Returns `result.content`, `result.outputs`, and `result.source_provenance`.

If error/canceled:
- Returns `result: null` with structured `error`.

### `GET /content/jobs/{id}/events` (SSE)

Request header:
```http
Accept: text/event-stream
```

Event types:
- `job.update`
- `job.done`
- `job.error`
- heartbeat comments `: keepalive`

Use SSE for live updates, and poll fallback when stream reconnect repeatedly fails.

## 6. Webhooks

### `POST /agent/webhooks`
Register endpoint.

```json
{
  "url": "https://client.example.com/hooks/content",
  "events": ["job.ready", "job.error"],
  "enabled": true
}
```

Response includes generated `secret` (persist securely).

Security rules:
- URL must use `http` or `https`.
- In production, webhook targets resolving to private/local/reserved IP ranges are blocked.
- In non-production, private/local targets are allowed only when `ALLOW_PRIVATE_WEBHOOK_URLS=true`.

### `GET /agent/webhooks`
List endpoints.

### `PATCH /agent/webhooks/{id}`
Update URL, events, enabled state, or secret.

### `POST /agent/webhooks/{id}/test`
Send test delivery immediately through webhook processor.

### Signature Verification

Headers:
- `X-Webhook-Id`
- `X-Webhook-Timestamp`
- `X-Webhook-Signature` (`v1=<hex>`)
- `X-Webhook-Event`

Signature:
- `HMAC_SHA256(secret, "<timestamp>.<raw_json_body>")`

## 7. Error Taxonomy

Standard envelope:
```json
{
  "ok": false,
  "request_id": "uuid",
  "error": {
    "code": "auth.invalid_api_key",
    "message": "Invalid API key",
    "retryable": false,
    "details": {}
  }
}
```

Common error codes:
- `auth.invalid_api_key`
- `auth.expired_api_key`
- `auth.insufficient_scope`
- `auth.rate_limited`
- `input.validation_failed`
- `quota.user_limit_exceeded`
- `job.not_found`
- `job.conflict`
- `job.pipeline_failed`
- `system.internal_error`

Job-level terminal error object:
```json
{
  "stage": "facts",
  "code": "job.pipeline_failed",
  "message": "Run processing failed",
  "timestamp": "2026-02-26T14:20:48.867Z",
  "retryable": false,
  "details": {
    "step": 6,
    "total": 11,
    "stage_step": 1,
    "stage_total": 2
  }
}
```

## 8. Retry Guidance

Retry with backoff when:
- HTTP `429`
- HTTP `503` (if introduced by deployment edge/proxy)
- `error.retryable = true`

Do not retry unchanged when:
- `401` invalid/expired key (rotate/re-auth instead)
- `403` missing scope
- `400` validation failure

## 9. Automatic Key Rotation (Agent Policy)

1. Monitor active key expiry.
2. At `<= 7 days` remaining, call `POST /agent/keys/{id}/rotate`.
3. Switch all traffic to new key.
4. Verify one create + one read call.
5. Revoke old key.

## 10. Test-Only Fault Injection

Purpose:
- Deterministically validate job error contracts without relying on random provider/runtime failures.

Request shape (inside `POST /content/jobs` body):
```json
{
  "testing": {
    "force_error_stage": "input",
    "force_error_code": "job.pipeline_failed",
    "force_error_message": "Forced test error"
  }
}
```

Rules:
- Non-production only.
- Requires `ENABLE_TEST_FAULT_INJECTION=true` on the server.
- If disabled, API returns `400` with `input.validation_failed`.

## 11. Related Docs
- Runtime contract: `content-v2.md`
- Local/LAN operations runbook: `docs/agent-local-network-runbook.md`