Skip to main content

OnFire Research API

OnFire Research is a retrieval-augmented document platform. You collect source material into a notebook — PDFs, web pages, audio, video, pasted text — and then generate finished work from it: reports, decks, spreadsheets, audio overviews, mind maps, quizzes.

Everything the web app at research.onfire.so does is available over this API.

Base URLhttps://research.onfire.so/api
AuthJWT bearer token, or an API key on three specific routes
FormatJSON, except file upload which is multipart/form-data
Interactive reference/api/docs — live OpenAPI/Swagger UI
Health check/api/healthz

The shape of the API

Three concepts, in the order you use them:

  1. Notebook — a container. Sources go in; everything you generate is grounded in them.
  2. Source — one ingested document. Ingestion is asynchronous: a source is ingesting until it has been chunked and embedded, then ready.
  3. Generation job — a template applied to a notebook. Also asynchronous: you submit, get a job_id, and poll until done. The result is an artifact with a signed download URL.

The two asynchronous steps are the thing to design around. Nothing useful comes out of generating against a notebook whose sources are still ingesting.


Quickstart

This walks the full path — log in, create a notebook, add three kinds of source, generate a report, download it. Every command is runnable as written.

1. Get a token

export TOKEN=$(curl -s -X POST https://research.onfire.so/auth/login_user \
-H "Content-Type: application/json" \
-d '{"p_email": "[email protected]", "p_password": "your-password"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)[0]['access_token'])")

echo "${TOKEN:0:24}…"

The token is valid for 24 hours.

A failed login returns HTTP 200

Bad credentials come back as 200 with success: false and access_token: null. The command above will print None rather than erroring. Check the success field — see Authentication.

2. Create a notebook

export NOTEBOOK_ID=$(curl -s -X POST https://research.onfire.so/api/notebooks/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "API quickstart"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")

echo "notebook: $NOTEBOOK_ID"

3. Add sources

Three routes, three kinds of input. Run any or all.

Upload a filemultipart/form-data, not JSON:

curl -s -X POST "https://research.onfire.so/api/notebooks/$NOTEBOOK_ID/files/" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@./quarterly-report.pdf"

Paste raw text:

curl -s -X POST "https://research.onfire.so/api/notebooks/$NOTEBOOK_ID/files/ingest-text" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"text": "Revenue grew 24% year over year, driven by enterprise renewals.",
"title": "Q2 headline numbers"
}'

Scrape a web page:

curl -s -X POST "https://research.onfire.so/api/notebooks/$NOTEBOOK_ID/files/ingest-url" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/2026-market-report"}'

Each returns a file record with "status": "ingesting".

4. Wait for ingestion

Sources must reach ready before they can be retrieved against.

until curl -s "https://research.onfire.so/api/notebooks/$NOTEBOOK_ID/files/" \
-H "Authorization: Bearer $TOKEN" \
| python3 -c "
import sys, json
files = json.load(sys.stdin)
pending = [f for f in files if f['status'] == 'ingesting']
print(f\"{len(files) - len(pending)}/{len(files)} ready\", file=sys.stderr)
sys.exit(1 if pending else 0)
"; do sleep 3; done

echo "all sources ready"

5. Pick a template

curl -s "https://research.onfire.so/api/notebooks/$NOTEBOOK_ID/templates" \
-H "Authorization: Bearer $TOKEN" \
| python3 -m json.tool | head -30

Nineteen system templates are available. research_summary produces a Word document and is a good default. See the full list with credit costs.

6. Generate

export JOB_ID=$(curl -s -X POST "https://research.onfire.so/api/notebooks/$NOTEBOOK_ID/generate" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"template_id": "research_summary",
"topic": "Key findings across my sources"
}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")

echo "job: $JOB_ID"

Returns 202 Accepted immediately. topic is required — it fills the prompt and is the retrieval query against your sources, so make it specific.

Credits are charged now, not on success

The charge happens when the job is accepted. A job that later fails does not refund. See Errors & Limits.

7. Poll the job

while true; do
STATUS=$(curl -s "https://research.onfire.so/api/notebooks/$NOTEBOOK_ID/jobs/$JOB_ID" \
-H "Authorization: Bearer $TOKEN" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
echo "status: $STATUS"
[ "$STATUS" = "done" ] && break
[ "$STATUS" = "error" ] && { echo "generation failed"; exit 1; }
sleep 3
done

Statuses are queued, running, done, error — those four and no others. Expect 60–120 seconds for a document, longer for audio.

8. Download the artifact

DOWNLOAD_URL=$(curl -s "https://research.onfire.so/api/notebooks/$NOTEBOOK_ID/jobs/$JOB_ID" \
-H "Authorization: Bearer $TOKEN" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['download_url'])")

curl -L -o research-summary.docx "$DOWNLOAD_URL"
open research-summary.docx # or xdg-open on Linux

The URL is pre-signed and carries its own authorization — do not attach your bearer token to it. It expires after 24 hours; re-request the artifact for a fresh one rather than caching the link.


Where to go next

If you want toRead
Set up a machine client, or understand the JWT and API-key splitAuthentication
Ingest files, URLs, YouTube or raw text; organise with foldersNotebooks & Sources
Understand templates, models, credit costs and job lifecycleGeneration
List, inspect and download results, including composite bundlesArtifacts
Handle failures, retries and rate limits correctlyErrors & Limits
Turn a file URL into a document, with a webhook callbackConversion API — rolling out

Three things that surprise people

API keys work on almost nothing — and never for file upload. Only ingest-text, enrich-search, the Sonix routes and the workflow routes accept X-API-Key. Everywhere else the header is ignored and you get 401 Missing Bearer token, which looks like a bad key but means the route only speaks JWT. In particular, uploading a file is JWT-only: automations that push documents must log in. See Authentication and Automating ingestion.

topic is doing two jobs. It is not just a title — it is the retrieval query that decides which of your sources reach the model. A vague topic produces a vague document even when your sources are excellent.

Trailing slashes matter. Collection routes are registered with one (/notebooks/, /files/, /artifacts/). Without it you get a 307 redirect — harmless, but it doubles the request count on anything you poll.