Deep multi-source research jobs with resolving citations. Asynchronous: submit a job, get a handle back instantly, then poll (or take a webhook) until it's done. Most jobs finish in 5–15 minutes and read up to ~40 sources, returning a synthesized report plus the sources it stands on; a deep or source-heavy job can run longer.
Copy one work order and paste it into Claude Code, Cursor, or any coding agent — it finds the right place in your codebase and wires STELQ Research in, so your users get grounded, cited answers at runtime. Every payload references $STELQ_KEY from your environment, never a live secret.
One request to POST /v1/research/run. Set $STELQ_KEY in your environment and run this as-is — it is generated from the live contract, not transcribed.
# 1) Submit the job — charged by depth on submit (quick $0.08 / balanced $0.25 / deep $0.40), returns a job handle.
curl -X POST https://api.stelq.com/v1/research/run \
-H "Authorization: Bearer $STELQ_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"Map AI-native search startups and their moats","depth":"balanced","citations":true}'
# 2) Poll until status == "completed" (usually 5–15 min, ~40 sources).
curl https://api.stelq.com/v1/research/run/$JOB_ID \
-H "Authorization: Bearer $STELQ_KEY"Every request carries a bearer token in the Authorization header. Copied agent payloads reference the $STELQ_KEY environment variable, so set it in your agent's environment rather than pasting a live secret into a prompt or a chat log.
Authorization: Bearer $STELQ_KEY
| Field | Type | Notes |
|---|---|---|
| queryrequired | string | The research question, in natural language. prompt is accepted as an alias. |
| depth | enum | How thorough the job is: quick, balanced, or deep (default balanced). quick is bounded and the most predictable; deep reads the most sources and takes longest. This is the only effort control most callers need. speed_mode is accepted as an alias. |
| max_tool_calls | integer | Optional, advanced. An upper bound on research steps (4–120), applied within the chosen depth — higher reads more sources and runs slower, and depth still caps it. The budget knob you'll know from other research APIs. Omit to let depth decide. |
| max_sources | integer | Optional, legacy. A soft floor on sources considered (clamped ≤50); it never reduces a run below what your depth already gathers. Prefer depth (and max_tool_calls for finer control). |
| citations | boolean | Return resolving source citations on the result (default true). |
| webhook | url (https) | Optional. We POST a signed completion event to this URL when the job finishes, so you can skip polling. Must be https. Polling stays the source of truth — see Webhooks. |
{
"query": "Map AI-native search startups and their moats",
"depth": "balanced",
"citations": true
}Lifecycle: queued → in_progress → completed (the terminal states are completed, failed, or cancelled). The submit call returns { id, status: "queued" } and is the billable event — The depth sets the price — quick $0.08, balanced $0.25, deep $0.40 — charged once on submit; polling is free. Poll GET /v1/research/run/{id} every 2–5 seconds until the status is terminal, then read result.outputText (fast, render-ready), result.reportMarkdown (long-form), and result.citations (source URLs). result.dossier carries the full structured sources for advanced use; error is populated only on failed/cancelled. A 502 on submit is safe to retry and is not charged; a 402 means top up your balance. To skip polling, pass a webhook and we'll notify you on completion — but polling always remains the source of truth.
{
"id": "rsch_8K2mQ9vX3pLw",
"status": "completed",
"result": {
"outputText": "AI-native search startups cluster around three moats: live indexing, citation fidelity, and agent-native delivery…",
"reportMarkdown": "# AI-native search startups\n\n## Moats\n…full long-form report…",
"citations": [
"https://eur-lex.europa.eu/eli/reg/2024/1689",
"https://example.com/ai-search-landscape"
],
"dossier": {
"…": "full structured retrieval packet — sources, synthesis, threads, gaps, stats"
}
}
}Pass a webhook (https URL) on submit and STELQ POSTs a small signed event there the moment the job reaches a terminal state — so you don't have to poll. The event is a signal, not the result body: read data.result_url to fetch the finished job. Different jobs can point at different URLs (the URL is your return address for that one job). Webhooks are an optimization on top of polling, never a replacement.
{
"id": "evt_01JX7Z9C2QH4",
"type": "research.run.completed",
"api_version": "2026-06-01",
"occurred_at": "2026-06-14T10:32:04Z",
"attempt": 1,
"data": {
"job_id": "rsch_8K2mQ9vX3pLw",
"status": "completed",
"result_url": "https://api.stelq.com/v1/research/run/rsch_8K2mQ9vX3pLw"
}
}Signature headers: webhook-id · webhook-timestamp · webhook-signature
Verify before you trust the body: recompute HMAC-SHA256 over {webhook-id}.{webhook-timestamp}.{rawBody} with your account signing secret (whsec_…, revealed any time under API Keys in the console), then constant-time compare it against the webhook-signature header (format v1,<base64>). Reject timestamps older than ~5 minutes, and always verify against the raw request bytes — not re-serialized JSON.
data.job_id (+ type) and process each completion exactly once.result_url.research.run.completed or research.run.failed. Treat it as final.Submit returns a job handle instantly — the answer arrives on the poll above.
{
"id": "rsch_8K2mQ9vX3pLw",
"status": "queued"
}| Status | Meaning |
|---|---|
| 400 | Invalid request — a field is missing or malformed (the body says which). |
| 401 | Missing or invalid API key. |
| 402 | Insufficient balance — top up credits to continue. |
| 429 | Rate limited — back off and retry after the Retry-After header. |
| 502 | Upstream temporarily unavailable — safe to retry; not charged. |
Research is queue-backed: submit as many jobs as you need and they run as worker capacity frees up — submit returns instantly with a handle either way. If you submit faster than the queue accepts, you'll get a 429 with a Retry-After header; back off and retry.