Fig. 01 / A research runSubmit / follow / examine

Developers

Research belongs
in your workflow.

Build the evidence into your product. Connect your assistant. One engine, from the first question to the next investigation.

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.

  1. 01 / SUBMITYour question.Price it, then start it with an idempotency key.
  2. 02 / FOLLOWThe work in progress.Poll the run or stream its events.
  3. 03 / EXAMINEThe cited result.Read the synthesis and the graded sources.

Every example reads two shell variables. Set them once:

0. Set your key
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())')
1. Price the question (free)
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.

2. Start the run
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.

3. Read the result
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.

4. Stream progress instead of polling
curl -N https://api.farsideresearch.com/v1/runs/RUN_ID/events \
  -H "Authorization: Bearer $FARSIDE_API_KEY"
5. Read the graded sources
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.

ScopeWhat it allows
readRead runs, sources, reports and exports, list work, and price a run. Never spends credits.
runStart, cancel and recover runs, plus every action that can spend credits: questions, extensions, rabbit holes, dossiers and workflows.
adminEverything 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

DepthStandardDeep
Credits25120
Research rounds46
Estimated sources8 to 2424 to 54
Estimated time7 to 18 minutes30 to 60 minutes
IncludedCited reportCited 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-ID set to the last id you saw and the stream continues after it.
  • ResetA stream.reset frame means the stream skipped ahead. Read the run with GET /v1/runs/RUN_ID and carry on.
  • Close it yourselfThe stream stays open after the run ends. Stop reading after run.completed, run.failed or run.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.

Ask a completed run a question
# 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.

What arrives at your endpoint
{
  "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.

Check the signature (Python)
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 body
{
  "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"
  }
}
StatusCodeWhat to do
400idempotency_key_requiredAdd an Idempotency-Key header to the paid request.
401invalid_api_keyThe key is wrong, expired, rotated or revoked.
402insufficient_creditsAdd credits or wait for the next plan period. detail has the shortfall.
402subscription_past_dueUpdate the payment method in the app; reads keep working.
403insufficient_scopeThe key lacks the scope this operation lists.
403feature_not_availableYour plan does not include this, for example Deep research on the trial.
404run_not_foundThe id is wrong or belongs to another workspace.
409idempotency_conflictThe key was used with a different body. Use a new key.
409run_not_completedWait for the run to complete before asking it questions.
422validation_errordetail.errors lists each field that failed and why.
429rate_limit_exceededWait 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.

PlanNew runs per hourReads per minuteQuestions per minute
Free trial3303
Solo8605
Starter1512010
Pro6030030
Team20060060
Enterprise1,0001,200120

If an operation is not present in the supported reference, keep it out of the launch path and start with a new report brief.