Deep research API / Quickstart
Your first research run.
Create an API key in the Farside Research app, price the question, start the run, then read its status and cited result. The web app, the REST API and MCP share one workspace, so a run you start here shows up in the app and the other way round.
- 01 / SUBMITYour question.Price it, then start it with an idempotency key.
- 02 / FOLLOWThe work in progress.Poll the run or stream its events.
- 03 / EXAMINEThe cited result.Read the synthesis and the graded sources.
Every example reads two shell variables. Set them once:
export FARSIDE_API_KEY=fsr_live_your_key_here
# A fresh key per new piece of work. Reuse it only to retry that same request.
export IDEMPOTENCY_KEY=$(python3 -c 'import uuid; print(uuid.uuid4())')curl -X POST https://api.farsideresearch.com/v1/estimate \
-H "Authorization: Bearer $FARSIDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"question":"How is database licensing changing?","depth":"standard","research_type":"technical"}'funding_status is ready when your balance covers the run. Otherwise it says why not (insufficient_credits, credit_cap_exceeded or billing_suspended) and funding_shortfall gives the gap in credits.
curl -X POST https://api.farsideresearch.com/v1/runs \
-H "Authorization: Bearer $FARSIDE_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{"question":"How is database licensing changing?","depth":"standard","research_type":"technical"}' A new run answers 201 (or 202 when it is queued behind your other runs) with the run object. Keep its id. Credits for the run are reserved now and settled when it finishes.
curl https://api.farsideresearch.com/v1/runs/RUN_ID \
-H "Authorization: Bearer $FARSIDE_API_KEY" Poll every 15 to 30 seconds until status is completed, failed or cancelled. A completed run carries the cited report in synthesis (Markdown with numbered citations) plus quality_score and the source counts. Add ?include=sources for the top 50 kept sources inline.
curl -N https://api.farsideresearch.com/v1/runs/RUN_ID/events \
-H "Authorization: Bearer $FARSIDE_API_KEY"curl "https://api.farsideresearch.com/v1/runs/RUN_ID/sources?limit=20" \
-H "Authorization: Bearer $FARSIDE_API_KEY"API keys and authentication
In the app, open Developers, then API keys, and click Create API key. The secret starts with fsr_live_ and is shown once (keys created before November 2026 start with sk_ and keep working). If you lose it, rotate the key to get a new secret; the old one stops working.
Send the key as Authorization: Bearer fsr_live_... or as X-API-Key: fsr_live_... on REST calls. The MCP endpoint accepts the Bearer header only.
| Scope | What it allows |
|---|---|
read | Read runs, sources, reports and exports, list work, and price a run. Never spends credits. |
run | Start, cancel and recover runs, plus every action that can spend credits: questions, extensions, rabbit holes, dossiers and workflows. |
admin | Everything above, plus webhooks, namespaces and output profiles. |
New keys get read and run. A key can also carry an expiry (1 to 3,650 days) and a monthly credit cap; once a key reaches its cap, paid work through that key stops while reads keep working. Each plan limits how many active keys a workspace can hold (one on the free trial). Every operation in the API reference lists the scope it needs.
Runs, depth and retries
| Depth | Standard | Deep |
|---|---|---|
| Credits | 25 | 120 |
| Research rounds | 4 | 6 |
| Estimated sources | 8 to 24 | 24 to 54 |
| Estimated time | 7 to 18 minutes | 30 to 60 minutes |
| Included | Cited report | Cited report, follow-on investigations and an 8-document dossier |
Credit prices come from the live price list on the pricing page. Source and time ranges are estimates. Deep research is not part of the free trial; starting one there answers 403 feature_not_available.
questionRequired. Up to 2,000 characters. Specific questions tied to a decision get the most useful reports.depthstandard (default) or deep.research_typegeneral (default), competitive_intel, market_research, market_sizing, technical, content_strategy, social_strategy, academic, investigative or strategic.decision_context, intended_audienceOptional. What the research should help decide, and who will read it.preferred_domains, exclude_domains, time_rangeOptional. Steer the search: up to 30 preferred and 50 excluded domains, and day, week, month or year.namespace_id, tags, metadataOptional. Organize runs; metadata is your own JSON, up to 4 KB, returned unchanged.
Status
A run moves through pending or queued, then running, and ends as completed, failed or cancelled. A run started with "skip_clarifying": false waits in clarifying until you answer POST /v1/runs/RUN_ID/clarify. Cancel with POST /v1/runs/RUN_ID/cancel; the run stops before its next research round.
Idempotency
POST /v1/runs requires an Idempotency-Key header (any string up to 255 characters; a UUID works well). Repeat a request with the same key and the same body after a timeout and you get the original response back, with no second run and no second charge. The same key with a different body answers 409 idempotency_conflict. While the first request is still being admitted, a retry answers 409 idempotency_in_flight with Retry-After: 2. Use a new key for new work.
Streaming events
GET /v1/runs/RUN_ID/events is a server-sent event stream. Each frame has an id, an event type such as run.started, round.completed or run.completed, and a JSON data line. Comment lines (: heartbeat) keep the connection open.
- ResumeReconnect with
Last-Event-IDset to the last id you saw and the stream continues after it. - ResetA
stream.resetframe means the stream skipped ahead. Read the run withGET /v1/runs/RUN_IDand carry on. - Close it yourselfThe stream stays open after the run ends. Stop reading after
run.completed,run.failedorrun.cancelled.
Follow the investigation further
A finished run is a starting point. Ask its evidence a question, extend it with more rounds, follow a rabbit hole into one thread, or build a multi-document dossier. Each paid action takes its own Idempotency-Key (a new one per action), and its price is on the pricing page.
# One key per question; reuse it only to retry this same question.
export ASK_KEY=$(python3 -c 'import uuid; print(uuid.uuid4())')
curl -X POST https://api.farsideresearch.com/v1/runs/RUN_ID/ask \
-H "Authorization: Bearer $FARSIDE_API_KEY" \
-H "Idempotency-Key: $ASK_KEY" \
-H "Content-Type: application/json" \
-d '{"question":"Which licensing change affects self-hosted users most?","allow_search":false}' The answer comes back with numbered citations to the run's sources, plus credits_charged and free_question. Questions use your plan's free questions for the day first; "allow_search": true also searches the web for newer pages and is always charged. Asking a run that has not completed answers 409 run_not_completed.
Read the evidence trail
A report keeps its recommendation beside the available citations, source quality, conflicting evidence, and unresolved gaps. Review the source record before you carry the conclusion into the next decision.
- Claim-to-source linksFollow the support behind a recommendation.
- Conflicting evidenceSee the counter-signal instead of losing it in synthesis.
- Open gapsKeep unanswered questions visible for the next brief.
Reopen or export the report
Reading a report does not spend credits. Reopen the retained result when the decision needs review, or export it. GET /v1/runs/RUN_ID/export/FORMAT takes markdown, pdf, json or sources.csv (plus dossier.md, dossier.pdf and dossier.zip once a dossier is finished). It answers with a download_url that lasts 15 minutes, or with a status_url to poll while the file is built. Exports are free; your plan has to include the format.
Webhooks
Instead of polling, register an HTTPS endpoint in Developers, then Webhooks (or POST /v1/webhooks with an admin key) and pick the events you want, such as run.completed and run.failed. The signing secret starts with whsec_ and is shown once. Webhooks are part of paid plans.
{
"id": "evt_6f1c0a9e2b7d4c1f8e3a5b9d0c2e4f61",
"type": "run.completed",
"created_at": "2026-10-29T14:03:11Z",
"api_version": "v1",
"data": {
"run": {
"id": "RUN_ID",
"quality_score": 82
}
}
} Each delivery carries X-Fathom-Event, X-Fathom-Delivery-Id and X-Signature: t=TIMESTAMP,v1=HEX, where HEX is the HMAC-SHA256 of TIMESTAMP. followed by the raw request body, keyed with your secret. Check it before you trust the payload, and reject old timestamps.
import hashlib, hmac, time
def verify(secret: str, signature_header: str, body: bytes, max_age: int = 300) -> bool:
parts = dict(item.split("=", 1) for item in signature_header.split(","))
timestamp, received = parts["t"], parts["v1"]
if abs(time.time() - int(timestamp)) > max_age:
return False
expected = hmac.new(secret.encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received) Answer with any 2xx to acknowledge. Anything else is retried, up to 8 attempts with delays growing from one minute to a day. Treat the delivery id as the key for ignoring repeats.
Credits
Every paid action has a published credit price. Credits are reserved when the action starts, then settled when it finishes; a run that fails gives back what it reserved. When the balance can't cover a new run the API answers 402 insufficient_credits, and error.detail names required, available and shortfall. Reading runs, sources and exports costs nothing. The credits guide covers plans, rollover, overage and top-ups.
Errors and rate limits
Every error has the same shape: a stable code to branch on, a readable message, optional detail, and the request_id to quote if you contact us.
{
"error": {
"code": "insufficient_credits",
"message": "Not enough credits to start this run",
"detail": {
"required": 25,
"available": 10,
"shortfall": 15
},
"request_id": "6c1d1f0e-2a7b-4f3e-9b8a-0d4e5f6a7b8c"
}
}| Status | Code | What to do |
|---|---|---|
| 400 | idempotency_key_required | Add an Idempotency-Key header to the paid request. |
| 401 | invalid_api_key | The key is wrong, expired, rotated or revoked. |
| 402 | insufficient_credits | Add credits or wait for the next plan period. detail has the shortfall. |
| 402 | subscription_past_due | Update the payment method in the app; reads keep working. |
| 403 | insufficient_scope | The key lacks the scope this operation lists. |
| 403 | feature_not_available | Your plan does not include this, for example Deep research on the trial. |
| 404 | run_not_found | The id is wrong or belongs to another workspace. |
| 409 | idempotency_conflict | The key was used with a different body. Use a new key. |
| 409 | run_not_completed | Wait for the run to complete before asking it questions. |
| 422 | validation_error | detail.errors lists each field that failed and why. |
| 429 | rate_limit_exceeded | Wait for Retry-After seconds, then try again. |
Limits are per workspace and depend on the plan. Rate-limited responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds). A 429 rate_limit_exceeded adds Retry-After in seconds; wait that long before trying again.
| Plan | New runs per hour | Reads per minute | Questions per minute |
|---|---|---|---|
| Free trial | 3 | 30 | 3 |
| Solo | 8 | 60 | 5 |
| Starter | 15 | 120 | 10 |
| Pro | 60 | 300 | 30 |
| Team | 200 | 600 | 60 |
| Enterprise | 1,000 | 1,200 | 120 |
If an operation is not present in the supported reference, keep it out of the launch path and start with a new report brief.