# API Reference

AgentPaaS exposes a RESTful API via FastAPI. All endpoints (except `/health` and `/`) require authentication via the `Authorization` header with a bearer API key.

Base URL: `http://localhost:8000` (development) or your production domain.

Interactive docs: `GET /docs` (Swagger UI) | `GET /redoc` (ReDoc)

---

## Authentication

All authenticated endpoints require:

```
Authorization: Bearer ap_xxxxxxxxxxxx
```

API keys are scoped. Common scopes: `agents:read`, `agents:write`, `agents:execute`, `keys:*`, `billing:read`, `admin:*`.

Rate limiting applies per API key (sliding window, 60 requests/minute default). Exceeding the limit returns `429 Too Many Requests` with a `Retry-After: 60` header.

---

## Agent CRUD

### POST /api/v1/agents

Create a new agent. The config is validated by compiling it through `lambdagent.from_config()`.

**Request:**

```json
{
  "name": "my-research-agent",
  "description": "A research agent for summarization",
  "config": {
    "agentId": "research-01",
    "name": "researcher",
    "type": "react",
    "systemPrompt": "You are a research assistant.",
    "model": { "provider": "anthropic", "name": "claude-sonnet-4-20250514" },
    "react": { "maxSteps": 10 }
  },
  "tags": ["research", "production"],
  "environment": "production"
}
```

**Response (201):**

```json
{
  "agent_id": "ag_abc123",
  "version": 1,
  "created_at": "2026-04-05T12:00:00Z",
  "endpoint": "/api/v1/agents/ag_abc123"
}
```

**Errors:** `400 INVALID_CONFIG` -- config failed compilation.

---

### GET /api/v1/agents

List all active agents for the authenticated tenant.

**Query Parameters:**

| Param | Type | Description |
|-------|------|-------------|
| `tag` | string | Filter by tag (optional) |

**Response (200):**

```json
{
  "agents": [
    {
      "id": "ag_abc123",
      "name": "my-research-agent",
      "description": "...",
      "current_version": 3,
      "tags": ["research"],
      "environment": "production",
      "status": "active",
      "created_at": "...",
      "updated_at": "..."
    }
  ],
  "count": 1
}
```

---

### GET /api/v1/agents/{agent_id}

Get agent details including current version config.

**Response (200):** Full agent record with `config` field populated from the current version.

**Errors:** `404 AGENT_NOT_FOUND`

---

### PUT /api/v1/agents/{agent_id}

Update agent config. Automatically increments the version number. If the config hash is unchanged, no new version is created.

**Required scope:** `agents:write`

**Request:**

```json
{
  "config": { "...updated config..." },
  "changelog": "Increased maxSteps to 20"
}
```

**Response (200):**

```json
{
  "agent_id": "ag_abc123",
  "version": 4,
  "updated_at": "2026-04-05T12:30:00Z"
}
```

**Errors:** `400 INVALID_CONFIG`, `404 AGENT_NOT_FOUND`

---

### DELETE /api/v1/agents/{agent_id}

Soft delete an agent (sets status to `deleted`).

**Required scope:** `agents:write`

**Response (200):**

```json
{ "message": "Agent deleted", "agent_id": "ag_abc123" }
```

---

## Execution

### POST /api/v1/agents/{agent_id}/run

Synchronous agent execution. Compiles the config, runs the agent, and returns the result.

**Required scope:** `agents:execute`

**Headers:**

| Header | Description |
|--------|-------------|
| `X-Idempotency-Key` | Optional. If provided, duplicate requests return the cached result. |

**Request:**

```json
{
  "input": "Summarize the latest advances in quantum computing",
  "parameters": {
    "model.temperature": 0.5,
    "react.maxSteps": 15
  },
  "context": {}
}
```

The `parameters` field supports dot-notation for deep overrides (e.g., `model.temperature` sets `config.model.temperature`).

**Response (200):**

```json
{
  "run_id": "run_xyz789",
  "status": "completed",
  "output": "Quantum computing has seen...",
  "usage": {
    "input_tokens": 1234,
    "output_tokens": 567,
    "total_tokens": 1801,
    "steps": 5,
    "duration_ms": 8320
  }
}
```

**Errors:** `404 AGENT_NOT_FOUND`, `429 RATE_LIMITED`, `500 EXECUTION_ERROR` (with `run_id` for debugging)

---

### POST /api/v1/agents/{agent_id}/run/stream

Server-Sent Events (SSE) streaming execution. Emits real-time step events as the agent runs.

**Response:** `text/event-stream`

**Event types:**

| Event | Data | Description |
|-------|------|-------------|
| `think` | `{ "step": 1, "content": "...", "duration_ms": 500 }` | Agent reasoning step |
| `think_chunk` | `{ "content": "partial text" }` | Streaming LLM chunk (ClaudeLam) |
| `tool_call` | `{ "step": 2, "tool": "search", "content": "query" }` | Tool invocation |
| `tool_result` | `{ "step": 2, "content": "result..." }` | Tool output |
| `error` | `{ "message": "..." }` | Tool or execution error |
| `answer` | `{ "content": "final answer" }` | Final agent answer |
| `done` | `{ "status": "completed", "output": "...", "steps": 5 }` | Execution complete |

**Example client (JavaScript):**

```javascript
const es = new EventSource("/api/v1/agents/ag_abc123/run/stream", {
  method: "POST",
  headers: { "Authorization": "Bearer ap_xxx" },
  body: JSON.stringify({ input: "Hello" }),
});
es.addEventListener("think", (e) => console.log("Think:", JSON.parse(e.data)));
es.addEventListener("done", (e) => { console.log("Done:", JSON.parse(e.data)); es.close(); });
```

---

### POST /api/v1/agents/{agent_id}/runs/record

Record an externally-executed run (e.g., from Claude Code CLI). Does NOT trigger lambdagent execution.

**Request:**

```json
{
  "input": "...",
  "output": "...",
  "status": "completed",
  "duration_ms": 5000,
  "steps": 3,
  "source": "claude-code-cli"
}
```

**Response (201):**

```json
{ "run_id": "run_abc", "status": "completed", "source": "claude-code-cli" }
```

---

### GET /api/v1/agents/{agent_id}/runs

List recent runs for an agent.

**Query Parameters:** `limit` (default 20)

**Response (200):**

```json
{ "runs": [ { "id": "run_xyz", "status": "completed", "duration_ms": 8320, "...": "..." } ] }
```

---

## Versions

### GET /api/v1/agents/{agent_id}/versions

List all versions of an agent, ordered newest first.

**Required scope:** `agents:read`

**Response (200):**

```json
{
  "versions": [
    {
      "agent_id": "ag_abc123",
      "version": 3,
      "config": "...",
      "config_hash": "sha256...",
      "changelog": "Increased maxSteps",
      "created_by": "usr_xxx",
      "created_at": "...",
      "is_current": true
    }
  ]
}
```

---

### POST /api/v1/agents/{agent_id}/rollback

Rollback to a previous version.

**Request:**

```json
{ "target_version": 2 }
```

**Response (200):**

```json
{ "agent_id": "ag_abc123", "version": 2, "message": "Rolled back" }
```

**Errors:** `404 VERSION_NOT_FOUND`

---

## Analysis

Static analysis endpoints for agent configs. These do not require an agent to be registered -- they accept raw configs.

### POST /api/v1/analyze/lint

Lint an agent config for structural defects (26 rules: L001-L026).

**Request:**

```json
{
  "config": { "type": "react", "systemPrompt": "...", "react": { "maxSteps": 50 } },
  "framework": "auto"
}
```

**Response (200):**

```json
{
  "framework": "native",
  "errors": [
    { "rule": "L004a", "level": "ERROR", "message": "No terminate tool found (Y combinator has no base case)" }
  ],
  "warnings": [
    { "rule": "L010", "level": "WARN", "message": "maxSteps > 10; recommend runtime.engine: cek" }
  ],
  "info": [
    { "rule": "L025", "level": "INFO", "message": "Detected framework: native lambdagent" }
  ]
}
```

---

### POST /api/v1/analyze/type-check

Type-check an agent pipeline using the T-Compose rule.

**Response (200):**

```json
{
  "type_safe": true,
  "errors": []
}
```

Or when type errors are found:

```json
{
  "type_safe": false,
  "errors": [
    { "stage": 1, "output_type": "Int", "input_type": "Json({name: string})" }
  ]
}
```

---

### POST /api/v1/analyze/cost

Estimate worst-case execution cost via graded types.

**Response (200):**

```json
{
  "tokens_upper_bound": 45000,
  "latency_sec": 12.5,
  "cost_usd": 0.135,
  "success_probability": 0.92
}
```

---

### POST /api/v1/analyze/parallel-safety

Check store independence for parallel agents (Paper II Proposition 30).

**Response (200):**

```json
{
  "safe": true,
  "conflicts": []
}
```

---

### POST /api/v1/analyze/full

Run all analysis passes (lint + type-check + cost + parallel-safety) in a single request.

**Response (200):**

```json
{
  "lint": { "framework": "...", "errors": [], "warnings": [], "info": [] },
  "types": { "type_safe": true, "errors": [] },
  "cost": { "tokens_upper_bound": 45000, "latency_sec": 12.5, "cost_usd": 0.135, "success_probability": 0.92 },
  "parallel": { "safe": true, "conflicts": [] }
}
```

---

## API Keys (Auth)

### POST /api/v1/auth/keys

Create a new API key for the current tenant.

**Request:**

```json
{
  "name": "production-key",
  "scopes": ["agents:read", "agents:execute"],
  "rate_limit": 120,
  "expires_at": "2027-01-01T00:00:00Z"
}
```

**Response (201):**

```json
{
  "key_id": "key_abc123",
  "api_key": "ap_a1b2c3d4e5f6...",
  "warning": "Store this key securely. It will not be shown again."
}
```

---

### GET /api/v1/auth/keys

List all API keys for the current tenant (key values are not returned, only prefixes).

**Response (200):**

```json
{
  "keys": [
    {
      "id": "key_abc123",
      "key_prefix": "ap_a1b2",
      "name": "production-key",
      "scopes": ["agents:read", "agents:execute"],
      "rate_limit": 120,
      "status": "active",
      "last_used_at": "...",
      "created_at": "..."
    }
  ]
}
```

---

### DELETE /api/v1/auth/keys/{key_id}

Revoke an API key (sets status to `revoked`).

**Response (200):**

```json
{ "message": "Key revoked" }
```

---

## Admin

All admin endpoints require the `admin:*` scope.

### POST /api/v1/admin/tenants

Create a new tenant with an admin user and initial API key.

**Request:**

```json
{
  "name": "Acme Corp",
  "plan": "pro",
  "admin_email": "admin@acme.com"
}
```

**Response (201):**

```json
{
  "tenant_id": "tn_xyz",
  "user_id": "usr_abc",
  "api_key": "ap_...",
  "warning": "Store this API key securely."
}
```

---

### GET /api/v1/admin/tenants/{tenant_id}

Get tenant details.

**Errors:** `403 FORBIDDEN` (non-admin), `404 TENANT_NOT_FOUND`

---

### PUT /api/v1/admin/tenants/{tenant_id}/quota

Set tenant resource quotas.

**Request:**

```json
{
  "tokens_monthly": 5000000,
  "concurrency": 20,
  "agents": 100
}
```

**Response (200):**

```json
{ "message": "Quota updated" }
```

---

### GET /api/v1/admin/tenants/{tenant_id}/usage

Get tenant usage statistics.

**Response (200):**

```json
{
  "runs": 1234,
  "input_tokens": 5678900,
  "output_tokens": 2345600,
  "total_duration_ms": 98765432
}
```

---

## Billing

### GET /api/v1/billing/usage

Query usage data for the current tenant.

**Query Parameters:**

| Param | Type | Description |
|-------|------|-------------|
| `group_by` | string | `agent` (default), `model`, or `day` |

**Response (200):**

```json
{
  "usage": [
    {
      "agent_id": "ag_abc123",
      "runs": 42,
      "input_tokens": 123456,
      "output_tokens": 65432,
      "total_ms": 345000
    }
  ],
  "tenant_id": "tn_xyz"
}
```

---

## Status

### GET /api/v1/status

Platform-wide status overview: total agents, runs, success rate, average latency.

### GET /api/v1/status/agents

All agents status summary.

### GET /api/v1/status/agents/{agent_id}

Detailed status for a single agent.

### GET /api/v1/status/agents/{agent_id}/health

Agent health score.

---

## Metrics

### GET /api/v1/metrics/overview

Platform metrics snapshot for monitoring dashboards.

---

## Health

### GET /health

Unauthenticated health check endpoint.

**Response (200):**

```json
{ "status": "ok", "version": "0.1.0" }
```

---

### GET /

Root endpoint with service information.

**Response (200):**

```json
{
  "service": "AgentPaaS",
  "version": "0.1.0",
  "docs": "/docs",
  "description": "Agent Platform as a Service -- Every agent is a Lambda term."
}
```

---

## Common Error Format

All errors follow a consistent structure:

```json
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description"
  }
}
```

| HTTP Status | Code | Description |
|-------------|------|-------------|
| 400 | `INVALID_CONFIG` | Agent config failed compilation |
| 401 | `UNAUTHORIZED` | Missing or invalid API key |
| 403 | `FORBIDDEN` | Insufficient scope for this operation |
| 404 | `AGENT_NOT_FOUND` | Agent does not exist or is deleted |
| 404 | `VERSION_NOT_FOUND` | Requested version does not exist |
| 404 | `JOB_NOT_FOUND` | Async job not found |
| 421 | `HTTPS_REQUIRED` | HTTPS required in production |
| 429 | `RATE_LIMITED` | Rate limit exceeded |
| 500 | `EXECUTION_ERROR` | Agent execution failed (includes `run_id`) |

---

## Security Headers

All responses include:

- `X-Content-Type-Options: nosniff`
- `X-Frame-Options: DENY`
- `X-XSS-Protection: 1; mode=block`
- `Referrer-Policy: strict-origin-when-cross-origin`
- `X-Response-Time-Ms: <ms>`
- `X-Request-ID: <uuid>` (for log correlation)

Production-only:
- `Strict-Transport-Security: max-age=31536000; includeSubDomains`
- `Content-Security-Policy: default-src 'self'`

---

## Jobs (Async Execution)

### GET /api/v1/jobs/{job_id}

Get async job status and result.

**Response (200):**

```json
{
  "job_id": "job_abc",
  "status": "completed",
  "result": "...",
  "created_at": "..."
}
```

Status values: `pending`, `running`, `completed`, `failed`, `cancelled`.

---

### POST /api/v1/jobs/{job_id}/cancel

Cancel a running or pending async job.

**Response (200):**

```json
{ "message": "Job cancelled", "job_id": "job_abc" }
```
