Research
A research run collects the stored data for one listed company and has the model write a sectioned report that ends in a decision line (a model-sampled view, not advice). For a short request about the stored data, use the copilot; for model calls with your own messages and tools, use model inference.
Routes
| task | route | entitlement |
|---|---|---|
| start a run and stream it | POST /v1/research/agentic/analyze | ai |
| list your runs | GET /v1/research/runs | ai |
| read a run's status and usage | GET /v1/research/runs/{run_id} | ai |
| stop a run | POST /v1/research/runs/{run_id}/cancel | ai |
| read or delete a run's stored report | GET / DELETE /v1/research/runs/{run_id}/report | ai |
| read the shared daily report for a security | GET /v1/securities/{symbol}/research | data |
Entitlements come with your tier (entitlements); a streamed example is in Python examples.
Start a run
POST /v1/research/agentic/analyze takes symbol (the company's ticker, or MIC:SYMBOL to name the venue), date
(the analysis date, YYYY-MM-DD), and optionally context. Every field is described in the
API reference.
Any other field is refused, and so is a date that is not a real date or is in the future (422 invalid_request),
before the stream starts. A symbol that does not name a maintained listing is refused with a typed error before the
stream starts, for example 404 not_registered, 404 not_maintained, or 409 for a bare ticker listed on two venues.
A run is AI work, charged as set out in charges for AI work.
The research stream
The stream sends server-sent events in this order:
| event | when | what it carries |
|---|---|---|
run | first | the run object: its id (run_…), status, report_available, and usage |
progress | while data is collected | one source at a time: step of total, source, and status (collecting, then read, absent, or failed) |
inputs | when collection ends | the report's inputs |
model_started | once, when the model call starts | started_at, reserved_credits, and charge_if_interrupted |
reasoning | while the model reasons before answering | {"status": "in_progress"}, at most one every 5 seconds |
unnamed data: frames | while the model answers | the report text, as OpenAI chat-completion deltas |
evidence | when the model's stream ends | the report's evidence and verified_at (RFC 3339 UTC), when it was verified present |
completion | last | the API's verdict on the report, and the run's settled usage |
When the model's stream reached its end, data: [DONE] follows completion as the last frame. It says only that the
transport ended; the verdict is completion. The model's reasoning is never streamed.
Source outcomes and missing values
Each source ends read, absent, or failed. absent means no data is available for that source: the data is
missing, and the read did not fail.
In the report text, the financial statements mark a line with no value as NaN (a placeholder in the text, not a JSON
value). A NaN is never zero and never an estimate. missing_values.fields lists every such line with its statement,
periods, and reason:
| reason | meaning |
|---|---|
not_held | no value is available for that line in that period |
no_longer_published | the company stopped reporting the line's concept |
conflicting_assertions, unreliable | the period is held, but its value is withheld |
The verdict
completion has complete, reason, and message. complete is true only when the model finished normally and the
answer is a whole report with its conclusion and exactly one decision line. Otherwise reason says why, for example
length_limit, no_conclusion, no_decision, model_timeout, cancelled, or
report_unavailable. Treat the report as complete only when a received completion event has complete: true.
A run that cannot finish sends a typed error frame before completion: {"error": …, "message": …} with error
model_timeout, model_unavailable, or model_error; or an event: error frame with evidence_withdrawn when an
article the report was being written from is withdrawn, or evidence_unverified when its evidence could not be
verified in time (no unverified text is delivered). The error frame carries the run's settled usage too.
A stream that ends with neither completion nor error was dropped. Read the run to see how it settled.
Charging fields
model_started marks the instant the model call started, which decides how an interrupted run settles
(settlement); charge_if_interrupted gives the charge of an
interruption after it. Every terminal frame carries the settled usage; charged_credits is the charge, final once
settled is true.
Follow or stop a run
GET /v1/research/runs lists your runs, newest first, by limit (default 20, at most 100) and after
(lists). GET /v1/research/runs/{run_id} reads a run's status and settled usage.
POST /v1/research/runs/{run_id}/cancel stops a run that is still in progress. The
stream ends with completion {"complete": false, "reason": "cancelled", …}. The route answers with the run: 200
once it is settled, 202 while the stop is still settling (read it again). A run that already ended is returned
unchanged. Closing the connection also stops the run. The run's status depends on what stopped it:
| the model when the stop arrived | the run's status |
|---|---|
| still working, stopped by a cancel | cancelled |
| still working, stopped by a disconnect | failed |
| already finished | its own verdict: completed with the report stored, or incomplete |
The run and report routes count as data requests and serve only your account's runs: another
account's run id is 404 not_found, like an unknown one.
The stored report
complete: true confirms that the complete report and its settlement are stored. A storage failure returns
complete: false, reason: report_unavailable. report_available on the run says whether there is a report to read.
GET /v1/research/runs/{run_id}/report returns the report and its decision, the completion verdict the stream
ended with, known_at (the evidence cutoff), and the report fields. validity is judged each time
you read the report.
A run without a stored report is 404 no_report: one that failed, was canceled, disconnected, or ended incomplete, a
run the copilot started as a tool, or a report you deleted.
DELETE /v1/research/runs/{run_id}/report deletes the report (204); the run's record and usage stay. A stored report
is kept until you delete it. Reports belong to your account: any of its keys can read and delete them, and revoking a
key does not remove them.
Runs started by the copilot
When the copilot runs research as a tool, that research is its own run, charged separately. Its run
object names the copilot run in copilot_run ({"id": …}; null for a run you started). Stop it by canceling the
copilot run (POST /v1/agents/copilot/runs/{run_id}/cancel): the research cancel route answers 409 conflict while
such a run is in progress. A copilot-started run stores no report.
Shared daily research
GET /v1/securities/{symbol}/research returns the newest stored report for one security in the research cohort. It
is a data request: it needs the data entitlement, draws on your data pool, uses no AI credits, and never starts a
run. A shared report is the same for every caller. For a fresh report, or one on a date of your choosing, start your
own run.
data_session is the trading session whose close the evidence runs through, and prepared_for the next session.
decision and report are model output, not advice. freshness.stale is true when a later session has completed and
its report is not stored yet (latest_completed_session names it).
A security outside the cohort is 404 not_supported. A cohort member whose first report is not prepared yet is
503 temporarily_unavailable.
Report fields
A run's stored report and the shared daily report carry evidence, inputs, validity, and method. Every field is
described in the API reference.
inputs.sourceshas one entry per source with itsstate(read,absent, orfailed);inputs.missing_valueslists every line marked with the placeholder (missing values).validity.stateisvalid,withdrawn(an article or the thesis has been withdrawn or changed since), orunverifiable; in the last two casesreportanddecisionare null andreasonandmessagesay why. A later correction of an input named invalidity.unverified_inputsdoes not change the state.method.input_id: two reports with the same value were written from the same input and configuration.method.decision_statusissampled: another run can reach a different decision.
Research over MCP
Over the hosted MCP server, a research run is the run_agentic_research tool, and
list_research_runs, retrieve_research_run, cancel_research_run, and retrieve_research_report list, read, and stop
runs (the research tool). Deleting a stored report is HTTP only.