Research
Create, list, read, and cancel durable Research runs over HTTP.
Endpoints under /api/v1/research. Envelope: api-contracts.v1. Conceptual behavior:
Research.
There are no HTTP routes for web.search, web.read, or web.crawl. Those are preview
capabilities only.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/research | List runs |
| POST | /api/v1/research | Create / dedupe a run (202/200) |
| GET | /api/v1/research/{id} | Get one run |
| POST | /api/v1/research/{id}/cancel | Request cancel |
POST create and cancel require Idempotency-Key.
List
GET /api/v1/research
| Query | Notes |
|---|---|
status | Optional. One of queued, running, succeeded, failed, canceled. Else 400. |
cursor | Opaque { createdAt, id } base64url. Invalid: 400. |
limit | Optional integer 1–100. Omitted → store default 25. |
data: { runs, nextCursor }. Dates on the run and sources are ISO-8601.
Create
POST /api/v1/research
{
"query": "release notes",
"sourceUrls": ["https://example.com/notes"],
"limits": { "maxPages": 4, "maxDomains": 2 }
}
First insert: 202. Idempotent replay (same key and payload): 200,
Idempotency-Replayed: true, deduplicated: true, dispatch: { dispatched: false }.
data:
| Field | Notes |
|---|---|
run | Serialized run (may already be failed if direct dispatch ran) |
deduplicated | boolean |
dispatch | { dispatched: true, queued: boolean, mode: "direct" \| "bullmq" } or { dispatched: false } |
Run id: research_ + first 32 hex of sha256(workspaceId + NUL + Idempotency-Key).
Zod/parse failures: 400 validation_failed. Idempotency payload clash: 409 conflict.
Get and cancel
GET /api/v1/research/{id} — { run }.
POST /api/v1/research/{id}/cancel — { run }. Missing id: 404. Terminal statuses are not
converted to 409; the current row is returned.
Run object
| Field | Type / values |
|---|---|
id | string |
workspaceId | string |
createdById | string (API key user) |
query | string |
sourceUrls | string[] |
limits | object (stored limits only; may be {}) |
status | queued | running | succeeded | failed | canceled |
resultDocumentId | string or null (research_document_{id} on success) |
error | string or null |
cancelRequestedAt | ISO or null |
startedAt | ISO or null |
finishedAt | ISO or null |
createdAt | ISO |
updatedAt | ISO |
sources | array of snapshots |
Source fields: id, runId, canonicalUrl, title, fetchedAt, sha256, snapshotText,
snapshotFileId (null unless a caller stored one; the executor does not), createdAt.
Gap: this API does not return the result document body. With the Prisma store, read it via
Documents using resultDocumentId.
Error map
| HTTP | error.code | Typical cause |
|---|---|---|
| 400 | idempotency_key_invalid | Missing/invalid key on create or cancel |
| 400 | validation_failed | Body, status, cursor, or limit |
| 401 | authentication_required | Bearer |
| 403 | authorization_failed | Non-workspace key |
| 404 | resource_not_found | Run not in this workspace |
| 409 | conflict | Idempotency key reused with different payload |
Create/execute failures that the store records as failed still return 202 with that run when
dispatch ran in-process; they are not mapped to 500 by the route.
