Research
research.start hands Amdahl one question and returns one cited answer: a read-only job that investigates it over your calls, CRM accounts and deals. Start it, poll research.get, answer its clarifying question if it asks one.
Research takes one question about your customers and returns one answer, with the evidence it rests on. A read-only agent works the question server-side: it queries your call transcripts, CRM accounts and deals, reads the themes your customers raise, counts distinct companies behind each claim, and writes the answer with each claim cited to a table, a quote or a theme it actually read.
Reach for it when a question needs several reads and a written synthesis ("why are mid-market deals stalling this quarter?"). For one fast lookup, use Search. For market research beyond your own data, or a written deliverable saved to the workspace, start a Chat.
| Operation | REST | MCP | Scope | |
|---|---|---|---|---|
| Start a job | research.start | POST /research | research → start | conversations:write |
| Read a job | research.get | GET /research/:id | research → status | conversations:read |
| Answer its question | research.respond | POST /research/:research_id/respond | research → respond | conversations:write |
| Stop a job | research.cancel | POST /research/:research_id/cancel | research → cancel | conversations:write |
A Customer agent key carries both scopes. A Read only key can read a job but cannot start one.
/researchauth requiredStart a research job. Returns a research_id at once.
/research/:idauth requiredRead a job: its status, the question it is waiting on, and once complete the cited answer. Pass wait_ms to wait for it to settle.
What a job can do
A research job reads this workspace's own data: the interactions warehouse, deals and deal qualification, the conversation themes, and saved documents. It writes nothing, sends nothing, and fetches nothing from outside your workspace: no web or market search, and no lookup of a social profile. The role the job runs under enforces that, not its instructions, so a question cannot talk it into a write or an outside fetch.
Start a job
{
"question": "Why are mid-market deals stalling this quarter?",
"context": "For the Q4 board deck. We changed pricing in August.",
"allow_questions": true
}question(required): the question, in plain language.context(optional): what the job should know. Who the answer is for, what you already know, the decision it feeds. It is read as background, never as the question.allow_questions(optional,truewhen omitted): lets the job pause to ask you a clarifying question when the question is ambiguous in a way that changes the answer. Sendfalsewhen nobody will answer; the job then proceeds on its best reading and states the assumption in its answer.
Any other field is refused with 400 invalid_argument, naming it. The response comes back at once:
{
"data": {
"success": true,
"research_id": "8b0f2c4e-1d7a-4f0e-9a51-2f3c8d6e7b10",
"chat_id": "f3a9e1d2-5b6c-4a7d-8e9f-0a1b2c3d4e5f",
"status": "queued"
}
}chat_id is the Chat the job runs in, so it also appears in the console's Chat list.
Read a job
GET /research/:id?wait_ms=30000 holds the connection for up to 30 seconds until the job settles, then returns the same body an immediate read would. Call it again while status is running. A typical job settles in one to a few minutes.
{
"data": {
"research_id": "8b0f2c4e-1d7a-4f0e-9a51-2f3c8d6e7b10",
"chat_id": "f3a9e1d2-5b6c-4a7d-8e9f-0a1b2c3d4e5f",
"status": "complete",
"question": "Why are mid-market deals stalling this quarter?\n\nContext from the caller: For the Q4 board deck. We changed pricing in August.",
"question_for_you": null,
"answer": {
"text": "Security review is where mid-market deals stall: 6 of the 11 stalled deals this quarter are waiting on it...",
"partial": false,
"partial_reason": null,
"content_blocks": [],
"follow_ups": ["Which security questions come up most often?"]
},
"answered_questions": [],
"error": null,
"created_at": "2026-10-02T15:04:11.000Z",
"started_at": "2026-10-02T15:04:12.000Z",
"completed_at": "2026-10-02T15:06:40.000Z"
}
}status | What it means | What to do |
|---|---|---|
running | The job is working, or queued to start. | Read again with wait_ms. |
needs_input | The job paused on a clarifying question, in question_for_you. | Answer it with research.respond. |
complete | answer holds the result. | Read answer. |
failed | The job could not answer. error says so in one sentence. | Start a new job. |
canceled | The job was stopped. | Nothing. |
answer is null until the job is complete, so a draft is never read as the result. A read of an id that is malformed, missing, in another workspace, or a Chat run that is not a research job returns 404.
The answer
textis the answer as markdown. It is never empty on a complete job that found anything: when the job wrote no closing prose,textlists each of thecontent_blocksfindings by label, with its insight or value.content_blocksis the evidence the answer presents: tables carrying the query that produced them, verbatim customer quotes with their source, and theme findings. These are the same blocks a Chat answer carries.follow_upslists questions the job suggests asking next.
Partial answers
Every job has a fixed budget of model usage and steps. A job that reaches it does not stop empty-handed: it writes its best answer from what it already found, says which parts of the question it did not get to, and completes with partial: true. partial_reason is usage_limit or turn_limit. Report a partial answer as partial. If that last step produces no answer at all, the job ends failed rather than passing off an earlier draft.
Clarifying questions
When status is needs_input, question_for_you holds the question:
{
"kind": "multiple_choice",
"question": "Which product line should I focus on?",
"options": [
{ "id": "opt_1", "label": "Core platform" },
{ "id": "opt_2", "label": "Analytics add-on" },
{ "id": "other", "label": "Other" }
]
}Answer it with POST /research/:research_id/respond:
{ "answer": "Analytics add-on" }Send your answer as text: on a multiple-choice question, the label of a choice selects it, and any other text is sent as the Other write-in. Pass option_id to pick a choice by id; the answer text then travels only with other, and a free-form question ignores option_id. The job resumes and reads as running again. Answering a job that is not waiting on a question returns 409 conflict.
Stop a job
POST /research/:research_id/cancel stops a job that is running or waiting on its question. A job that already ended returns 409 conflict and is left as it is.
Over MCP
The research tool carries all four calls as actions:
research { action: "start", question: "Why are mid-market deals stalling this quarter?" }
research { action: "status", research_id: "<research_id>", wait_ms: 30000 }
research { action: "respond", research_id: "<research_id>", answer: "Analytics add-on" }A job is also readable as the resource research://<id>.
Monthly cap
Each job started with an API key or an external OAuth token, MCP included, counts toward a monthly cap of 100 per workspace. A start is counted the moment it is accepted, before the job runs, so a start that later fails still counts. Past the cap, research.start returns 429 quota_exceeded with the limit and usage in details, and no job starts; simultaneous requests can pass it by at most a start or two. The cap resets at the start of the next month; ask Amdahl support to raise it. Jobs started from the Amdahl console do not count.