> ## Documentation Index
> Fetch the complete documentation index at: https://docs.awaithumans.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# API overview

> REST API reference. Most users go through the SDK; this page is for edge cases.

The awaithumans server exposes a small REST API. Most users never touch it directly — the [Python](/sdk/python) and [TypeScript](/sdk/typescript) SDKs wrap every endpoint. This reference is for:

* Operators debugging an issue
* Building a non-SDK client (Go, Rust, etc.)
* Wiring custom integrations

## Base URL

```
http://localhost:3001/api      # dev
https://reviews.acme.com/api   # production
```

All endpoints under `/api/`. Static dashboard files live at `/`, `/setup`, etc. — those aren't part of the REST API.

## Authentication

Two auth modes:

**Admin bearer token** — for agents / automation:

```
Authorization: Bearer <AWAITHUMANS_ADMIN_API_TOKEN>
```

**Session cookie** — for the dashboard (set by `POST /api/auth/login`):

```
Cookie: awaithumans_session=<signed-cookie-value>
```

Public endpoints (no auth required): `/api/auth/*`, `/api/setup/*`, `/api/health`, channel webhook receivers (`/api/channels/slack/interactions`, `/api/channels/email/action/*`, etc.) — these self-authenticate via signed payloads.

## Content types

* Request bodies: `application/json` (except channel webhooks, which use `application/x-www-form-urlencoded` per their respective vendor specs)
* Response bodies: `application/json`

## Error format

Every error response has the shape:

```json theme={"system"}
{
  "error_code": "TASK_NOT_FOUND",
  "message": "Task 'tsk_abc' not found.",
  "docs": "https://docs.awaithumans.dev/troubleshooting#task-not-found"
}
```

The `error_code` is stable across versions; the message and docs URL may change.

| Status | Meaning                                                                     |
| ------ | --------------------------------------------------------------------------- |
| 400    | Malformed request body                                                      |
| 401    | Auth required or session invalid                                            |
| 403    | Authenticated but not authorized (e.g. non-operator hitting admin endpoint) |
| 404    | Task / user / identity not found                                            |
| 409    | Idempotency conflict, terminal state, or already-completed                  |
| 410    | Magic-link token already consumed                                           |
| 422    | Validation error (schema mismatch, malformed config)                        |
| 429    | Rate limit hit                                                              |
| 500    | Server-side error (verifier provider unavailable, etc.)                     |
| 502    | Vendor outage (Slack, OpenAI, etc.)                                         |
| 503    | Service not configured (e.g. Slack OAuth without credentials)               |

## OpenAPI spec

Interactive OpenAPI docs at `/api/docs` (FastAPI's Swagger UI). The raw spec is at `/api/openapi.json`.

For SDK code-gen against your own clients:

```bash theme={"system"}
curl http://localhost:3001/api/openapi.json > openapi.json
# Then feed to openapi-generator-cli or similar.
```

## Rate limits

| Endpoint                   | Limit             | Window | Key   |
| -------------------------- | ----------------- | ------ | ----- |
| `POST /api/auth/login`     | 10 / 5min / IP    | 5 min  | IP    |
| `POST /api/auth/login`     | 20 / 5min / email | 5 min  | Email |
| `POST /api/setup/operator` | 30 / 5min / IP    | 5 min  | IP    |

No rate limit on task routes — the bearer token holder is implicitly trusted. If you need to rate-limit your own agent's task creation, do it in your code (Stripe-style outbox pattern recommended).

## Idempotency

Every `POST /api/tasks` carries a required `idempotency_key`. The contract is **Stripe-style** — same key returns the same task, regardless of status, for the lifetime of the row.

* **First call with a fresh key:** the task is inserted and the response is `201 Created`.
* **Subsequent calls with the same key:** the existing task is returned with `201 Created` **plus an `Idempotent-Replayed: true` response header**. Notifications (email / Slack) do NOT re-fire on replay — that would re-page the human for already-in-flight work.

Clients that need to distinguish a fresh creation from a replay read the header:

```bash theme={"system"}
curl -i -X POST http://localhost:3001/api/tasks \
  -H "Authorization: Bearer $AWAITHUMANS_ADMIN_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"task":"Approve refund","idempotency_key":"refund-A-4721", ...}'

HTTP/1.1 201 Created
content-type: application/json
idempotent-replayed: true       ← present only on replay
...
```

Why the status stays `201` (instead of flipping to `200` on replay): strict HTTP semantics reserve `201` for newly-created resources, but Stripe — the model this contract is named after — keeps the original status code on replay and signals replay-vs-fresh via header. That convention breaks zero clients that check `status == 201` for success while still being explicit for clients that care.

To trigger a fresh task for the same logical event (e.g. retry a refund review after an earlier one was rejected), pass a distinct key — convention is to suffix: `"refund-A-4721:retry-1"`.

## Endpoints by resource

### Tasks (admin / operator / assignee)

| Method   | Path                       | Description                                                         |
| -------- | -------------------------- | ------------------------------------------------------------------- |
| `POST`   | `/api/tasks`               | [Create a task](/api/create-task). Admin / operator only.           |
| `GET`    | `/api/tasks`               | List tasks. Operator: all. Assignee: scoped to own.                 |
| `GET`    | `/api/tasks/{id}`          | [Get one task](/api/poll-task). Operator / admin / assignee.        |
| `POST`   | `/api/tasks/{id}/complete` | [Submit response](/api/complete-task). Operator / admin / assignee. |
| `POST`   | `/api/tasks/{id}/cancel`   | Cancel. Operator / admin only.                                      |
| `GET`    | `/api/tasks/{id}/poll`     | Long-poll until terminal. Operator / admin / assignee.              |
| `GET`    | `/api/tasks/{id}/audit`    | Audit trail. Operator / admin only.                                 |
| `DELETE` | `/api/tasks/{id}`          | Hard delete. Operator / admin only.                                 |

### Auth

| Method | Path               | Description                                |
| ------ | ------------------ | ------------------------------------------ |
| `POST` | `/api/auth/login`  | Email + password → session cookie. Public. |
| `POST` | `/api/auth/logout` | Clear session cookie.                      |
| `GET`  | `/api/auth/me`     | Session introspection.                     |

### Setup (first-run bootstrap)

| Method | Path                  | Description                      |
| ------ | --------------------- | -------------------------------- |
| `GET`  | `/api/setup/status`   | Is first-run setup needed?       |
| `POST` | `/api/setup/operator` | Create first operator. One-shot. |

### Users (admin / operator)

| Method   | Path              | Description                        |
| -------- | ----------------- | ---------------------------------- |
| `GET`    | `/api/users`      | List directory users.              |
| `POST`   | `/api/users`      | Create.                            |
| `GET`    | `/api/users/{id}` | Get one.                           |
| `PATCH`  | `/api/users/{id}` | Update.                            |
| `DELETE` | `/api/users/{id}` | Soft-delete (sets `active=false`). |

### Channels

| Method     | Path                                 | Description                      |
| ---------- | ------------------------------------ | -------------------------------- |
| `POST`     | `/api/channels/slack/interactions`   | Slack webhook (signed).          |
| `GET/POST` | `/api/channels/slack/oauth/*`        | OAuth install flow.              |
| `GET/POST` | `/api/channels/slack/installations*` | OAuth install CRUD (admin).      |
| `GET/POST` | `/api/channels/email/identities*`    | Sender identity CRUD (admin).    |
| `GET/POST` | `/api/channels/email/action/{token}` | Magic-link action page (signed). |

### Stats

| Method | Path         | Description                                                         |
| ------ | ------------ | ------------------------------------------------------------------- |
| `GET`  | `/api/stats` | Task counts by status, completion rate, last 30d. Operator / admin. |

### Health

| Method | Path          | Description              |
| ------ | ------------- | ------------------------ |
| `GET`  | `/api/health` | Readiness probe. Public. |

## Cross-version stability

* The `/api/tasks/*` shape is stable for v1.x — breaking changes are a v2 event.
* Error codes (`error_code` field) are stable across all v0.x.
* The dashboard and channel webhook routes are internal — third-party clients should NOT depend on their shapes.
