# Theseus API Endpoints

Base URL: `http://0.0.0.0:3080` (configurable via `--port`).

## Response Envelope

All responses wrapped in `ApiResponse<T>`:

```json
{ "success": true, "data": { ... }, "error": null }
```

Error responses may include additional fields (`error_code`, `error_details`, `error_messages`):

```json
{
  "success": false,
  "error": "Database operation failed while processing edge.",
  "error_code": "db.unique_violation",
  "error_details": { "operation": "edges.insert_edge", "entity": "edge", "hint": "..." },
  "error_messages": { "en": "...", "zh": "处理 edge 时数据库操作失败。" }
}
```

Error codes: `db.*`, `request.*`, `auth.*`, `resource.*`, `server.*`.

## Authentication

Optional, enabled when an API key is configured. Three methods (checked in order):

| Method | Example |
|--------|---------|
| Header | `x-api-key: <key>` |
| Bearer | `Authorization: Bearer <key>` |
| Query | `?api_key=<key>` |

Missing/invalid → 401. No key configured → server is open.

## Configuration

Priority: CLI flags > env vars > `theseus.toml` (`./`, then `~/.config/theseus/`).

| Field | CLI Flag | Env Var | TOML Key |
|-------|----------|---------|----------|
| API auth key | `--api-key` | `THESEUS_API_KEY` | `api_key` |
| DeepSeek key | `--deepseek-api-key` | `THESEUS_DEEPSEEK_API_KEY` | `deepseek_api_key` |
| CORS origins | — | `THESEUS_CORS_ORIGINS` (comma-sep) | `cors_origins = ["https://example.com"]` |

CORS defaults to `http://127.0.0.1:3080` if not configured.

## Short ID Resolution

All `:project_id`, `:node_id`, `:edge_id` path params accept full UUID or prefix (≥6 chars, unique).
Returns 400 if ambiguous/too short. Functions: `resolve_project_id()` (lib.rs:602), `resolve_edge_id()` (lib.rs:662), `resolve_node_id()` (lib.rs:723).

---

## Health & Index

| Method | Path | Description |
|--------|------|-------------|
| GET | `/` | Serves embedded `index.html` |
| GET | `/health` | Returns `{ status: "ok" }` |

## Projects (`src/routes/projects.rs`)

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/projects` | Create project. Body: `{ name }`. Returns 201. Duplicate → 409. |
| GET | `/api/projects` | List all projects. |
| GET | `/api/projects/stats` | Per-project node/edge counts. |
| GET | `/api/projects/:project_id` | Get single project. |
| DELETE | `/api/projects/:project_id` | Delete project and all children. |

## Nodes (`src/routes/nodes.rs`)

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/projects/:pid/nodes` | Create node. Body: `{ label, node_type, properties, after?, edge_label? }`. Returns 201. If project has nodes, `after` required (no isolated nodes). |
| GET | `/api/projects/:pid/nodes` | List nodes. Query: `?node_type=&verdict=&evidence=`. |
| GET | `/api/projects/:pid/nodes/:nid` | Get single node. |
| PUT | `/api/projects/:pid/nodes/:nid` | Update. Body: `{ label?, node_type?, properties?, set_verdict?, set_evidence? }`. |
| DELETE | `/api/projects/:pid/nodes/:nid` | Delete. Returns 409 if edges exist; `?force=true` overrides. |
| POST | `/api/projects/:pid/nodes/:nid/upload` | Upload screenshot (multipart). |
| GET | `/api/projects/:pid/nodes/search?q=` | In-project label/content search. |
| POST | `/api/projects/:pid/nodes/check-duplicate` | Body: `{ label, content, node_type }`. Returns `Vec<DuplicateCandidate>`. |

**Validation**: empty label → 400; invalid `node_type` → 400; `verdict` ∈ [pending, success, failed, in_progress]; `evidence` ∈ [confirmed, unverified, hypothesis]; `properties` must be JSON object.

## Edges (`src/routes/edges.rs`)

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/projects/:pid/edges` | Create. Body: `{ source_node_id?, source_label?, target_node_id?, target_label?, label? }`. Match by UUID or label. Self-loop → 400; duplicate → 409. |
| GET | `/api/projects/:pid/edges` | List all edges. |
| PUT | `/api/projects/:pid/edges/:eid` | Update label. Body: `{ label }`. Empty → 400. |
| DELETE | `/api/projects/:pid/edges/:eid` | Delete edge. |

## Credentials (`src/routes/credentials.rs`)

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/projects/:pid/credentials` | Create. Body: `{ label, credential_type, username?, secret?, source_node_id?, status, notes? }`. |
| GET | `/api/projects/:pid/credentials` | List credentials. |
| PUT | `/api/projects/:pid/credentials/:cid` | Update credential. |
| DELETE | `/api/projects/:pid/credentials/:cid` | Delete credential. |

`credential_type` ∈ [password, ssh_key, aksk, token]; `status` ∈ [unverified, verified, expired].

## Evidence Files (`src/routes/evidence.rs`)

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/projects/:pid/nodes/:nid/evidence` | Upload evidence (multipart). |
| GET | `/api/projects/:pid/nodes/:nid/evidence` | List evidence files. |
| GET | `/api/projects/:pid/nodes/:nid/evidence/:filename` | Download file. Path traversal blocked. |
| DELETE | `/api/projects/:pid/nodes/:nid/evidence/:filename` | Delete file + DB record. |

## Timeline (`src/routes/timeline.rs`)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/projects/:pid/timeline` | Chronological nodes with incoming/outgoing edge labels. |

## Search (`src/routes/search.rs`)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/search?q=` | Cross-project LIKE search on label + content. LIMIT 50. |

## Analysis (`src/routes/analysis.rs`)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/projects/:pid/analysis` | Graph analysis: BFS attack paths, health checks, typed suggestions. `?ai=true` for DeepSeek commentary (requires key). |

## Check & Report

| Method | Path | File | Description |
|--------|------|------|-------------|
| GET | `/api/projects/:pid/check` | `check.rs` | Deterministic graph health checks. |
| GET | `/api/projects/:pid/report` | `report.rs` | AI Markdown report (requires DeepSeek key). |
| GET | `/api/projects/:pid/daily-report?date=` | `daily_report.rs` | Nodes by CST date. Defaults to today. |

## Import (`src/routes/import.rs`)

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/projects/:pid/import` | Bulk import. Nodes deduped by label; edges matched by label. Returns `{ nodes_created, nodes_skipped, edges_created, edges_skipped, errors }`. |

## Chat & Agent

| Method | Path | File | Description |
|--------|------|------|-------------|
| POST | `/api/projects/:pid/tell` | `chat.rs` | NL → AI creates nodes/edges. Body: `{ message }`. |
| POST | `/api/projects/:pid/ask` | `chat.rs` | NL query over project context. Returns `{ answer, context_nodes }`. |
| POST | `/api/agent/run` | `agent.rs` | Multi-turn tool-calling agent. Body: `{ project_id, entry, message, max_turns? }`. |

All require DeepSeek API key. `agent/run` returns `{ answer, turns, tool_calls, created_nodes, created_edges }`.

## WebSocket (`src/routes/ws.rs`)

| Method | Path | Description |
|--------|------|-------------|
| WS | `/api/ws` | Broadcast messages after mutations. Format: `node_created:<pid>`, `graph:<pid>`, etc. Reload graph on `graph:<pid>`. |

## Static Files

| Method | Path | Description |
|--------|------|-------------|
| GET | `/static/*` | Serves CSS/JS from `static/` directory. |
