# API

Base URL: `https://api.ravenoutreach.com`. All endpoints are under `/v1`. The OpenAPI 3.1 description is at [`/v1/openapi.json`](https://api.ravenoutreach.com/v1/openapi.json).

## Conventions

| | |
|---|---|
| Auth | `Authorization: Bearer <key or token>`; see [authentication](/docs/authentication) |
| Versioning | Responses carry `Api-Version: 2026-11-01`. You can send the same header to pin a version. |
| Rate limit | 20 requests per second per key. Every response has `X-RateLimit-Limit`, `-Remaining`, `-Reset` and `-Policy`. A 429 has `Retry-After`. |
| Long polls | `wait=N` (up to 55 seconds) on team, message and lead reads answers as soon as something changes |
| Paging | Lead lists return `next_cursor`, also sent in `X-Next-Cursor`. Pass it back as `cursor` until it's absent. |
| Retries | Send `Idempotency-Key: <uuid>` on a POST, PATCH or DELETE you might retry. For 24 hours, a retry with the same key gets the first response back, marked `Idempotent-Replayed: true`. A retry while the first call is still running gets 409. Creating a team with the same brief within 10 minutes also returns the first team, even without a key. |
| Errors | One envelope with a `code` and a `fix`; see [errors](/docs/errors) |

## Endpoints

| Method and path | What it does |
|---|---|
| `GET /v1/account` | The account, its credit by kind, held credit, this month's usage, `top_up_url` |
| `POST /v1/teams` | Starts a team. Body: `brief` (required), `product`, `name`, `daily_budget_usd`, `spend_limit_usd`, `researchers`, `verifiers`, `auto_add_campaigns`. Returns 201. Returns 402 if the account can't pay for planning. |
| `GET /v1/teams` | The account's teams (`include_archived=true` for all) |
| `GET /v1/teams/{id}` | One team. `wait` and `wait_for` (`status`, `plan`, `leads` or `messages`) long-poll for a change. |
| `PATCH /v1/teams/{id}` | `name`, `daily_budget_usd`, `spend_limit_usd`, `auto_add_campaigns` |
| `POST /v1/teams/{id}/pause`, `/resume`, `/archive` | Stops or restarts spending |
| `GET /v1/teams/{id}/plan` | The current plan, as JSON and as markdown |
| `POST /v1/teams/{id}/plan/approve` | Body: `{"version": N}`, the version the user reviewed. Starts paid research. |
| `POST /v1/teams/{id}/messages` | Body: `{"body": "…"}`. Sends a message to the supervisor. Returns 202. |
| `GET /v1/teams/{id}/messages` | Messages and replies. `after` and `wait` wait for new ones. |
| `GET /v1/teams/{id}/campaigns` | The team's lead lists and their progress |
| `GET /v1/campaigns/{id}`, `PATCH /v1/campaigns/{id}` | One campaign. PATCH changes `status` (`active` or `paused`) or `target_verified`. |
| `GET /v1/leads` | Leads, oldest first. Filter with `team_id` or `campaign_id`, plus `since`, `cursor`, `limit` (up to 1000), `format`, `max_tokens` and `wait`. |
| `GET /v1/leads/{id}` | One lead |
| `DELETE /v1/leads/{id}` | Removes a lead from lists and exports. The person won't be researched again. |
| `GET /v1/teams/{id}/runs` | Agent runs: role, status, cost, summary |
| `GET /v1/teams/{id}/candidates` | Everyone found and what verification decided (`status=rejected` shows why) |

## Leads

| Field | Meaning |
|---|---|
| `email` | Published on the person's own site and checked on `source_url` |
| `first_name`, `last_name`, `company`, `title`, `website` | As the site shows them |
| `source_url` | The page the email is on |
| `evidence` | The text around the email on that page, or `mailto:…` when it's only a link |
| `found_via` | What led to the site: `search:<query>` or `list:<site>` |
| `fit_reason` | Why the person fits the campaign |
| `line` | One personal sentence, written per the campaign's brief |
| `campaign_id`, `team_id`, `verified_at` | Where the lead belongs and when it was verified |

### Formats

`format` sets the shape of `GET /v1/leads`:

| Format | Shape |
|---|---|
| `json` (default) | `{"leads": [...], "next_cursor": "..."}` |
| `jsonl` | One lead per line |
| `csv` | A header row, then one lead per row |
| `markdown` | A compact table for language models, cut to fit `max_tokens` (default 4000) |
