Lifecycle
Create a run
POST /v1/runs accepts prompt (required), workspace_id, conversation_id, and output. Any other field is rejected.
The agent does not ask clarifying questions: nobody is there to answer. Put every decision in the prompt. If a fact is missing that would change the target, the agent stops and says what is missing instead of guessing. To correct a choice, start another run on the same conversation_id.
- Without
workspace_idorconversation_id, Retriever creates a new workspace and a new conversation. - With
workspace_idonly, the run opens a new conversation in that workspace. The agent can read the workspace’s tables, but it has not seen the earlier conversations. - With
conversation_id, the run continues that conversation. The agent keeps its history and its tables. Only one run can be active per conversation, otherwise409 conversation_busy. - A conversation that a run opens is named a few seconds later from the prompt, followed by
- API conversation; a workspace that a run creates is named the same way, followed by- API workspace. If no name can be generated, the start of the prompt is used, followed by the same suffix. - The
202response includesLocationandRetry-After: 5, and the run’sworkspace_idandconversation_id: keep them for follow-ups.
New conversation or follow-up?
Continue the same conversation when you refine or extend a result: more rows, other roles, a wider scope, a new column. The agent sees what it was asked and what it found, and its tables. Ask it to add to the existing table, without repeating the context. Leads already in the table being filled are left out of the searches that add to it and not billed, across sources: a person by LinkedIn profile, a company by domain. Leads of your other tables can come back. Not covered: a search for the right person at a given company, and contacts found row by row in a table; posts and web results are matched only when the same source returns them again. For people or companies that must never come back, use the exclusion list instead of listing names in the prompt. It is shared by your whole organization and filters what sources bring into every later table, not only this one. It does not filter Go, direct service calls, contacts found row by row in a table, rows the agent writes itself from a service call, or lists you import into a table. See Continue a run with a new prompt. Open a new conversation, in the sameworkspace_id, for a request that does not depend on an earlier one: another company, another list. Also use separate conversations to run requests at the same time, because a conversation runs one request at a time.
If a run is still active and you want to change its brief, cancel it (POST /v1/runs/{id}/cancel), wait until GET /v1/runs/{id} shows cancelled (usually 15 to 30 seconds), then continue its conversation with the new brief. Before that, a new run in the conversation gets 409 conversation_busy. If it still does not show cancelled after a few minutes, start the new brief in a new conversation of the same workspace. Until the old run stops, it can still write to that workspace’s tables and use credits, and two runs for the same need are both billed.
Runs per organization
An organization executes at most 5 runs at the same time by default (queue.limit gives the current value). A run counts while it is running or recovering. Runs started from MCP (retriever_build_list_start) share the same slots.
- A new run is always accepted with
202. It is never refused because of this limit. - When all your organization’s slots are taken, by executing runs or by older queued runs, the new run stays
queued. - It starts on its own when one of your runs ends. Do not create it again: a second run is billed as a second run.
- Your queued runs start oldest first. Runs of other organizations do not wait for yours.
- A queued run already holds its conversation: a follow-up run on the same
conversation_idgets409 conversation_busy, and the app shows the conversation as running. - Go jobs (
/v1/go/runs) and conversations run from the app do not use these slots. - Time spent
queueddoes not count toward the maximum run duration (3 hours by default). That duration starts atstarted_at, which isnullwhile the run is queued.
queued run read with GET /v1/runs/{id} or GET /v1/runs has a queue field that says why it waits. It is null once the run has left the queue.
202 of POST /v1/runs, queue can be null or already out of date: GET /v1/runs/{id} is the reference.
Idempotency
Send anIdempotency-Key header (non-empty) to replay creation without duplicates:
- Same key and same body:
202with the original run. - Same key and different body:
409 idempotency_conflict. - The key is tied to the user, not the API key: two API keys for the same user share the same namespace.
Poll a run
CallGET /v1/runs/{id} until completed, failed, or cancelled:
- Respect
Retry-After(15 seconds while the run is active). - Send
If-None-Matchwith the receivedETag: the API returns304with no body if nothing changed.
Credits
GET /v1/runs/{id} returns credits.charged: the credits the run was charged so far, read from your credit history. It counts:
- the services and data sources the run called, including the cells of the columns it launched (AI columns included),
- the research agent that fills a column row by row,
- browser sessions,
- AI-written sequence messages written during the run: the first message of each lead the run enrolls in an active sequence. Every other AI sequence message, including those of a draft sequence the agent activates, is written when its step is due and is not charged on the run.
- While the run is active, the amount grows. A new charge also changes the
ETag. - The amount can keep growing after the run ends, while cells of the columns the run launched are still running. A charge can also be recorded just after the end, most often for a call cut by a timeout, or aborted by a Stop or a cancel, whose answer landed just after the cut.
- On a Stop or a cancel, the cells still running go back to their previous value at once. A call they had already sent can still be billed on the run when its answer lands. A failed or cancelled run shows what it used so far, and can still grow a little after it stopped.
- No field marks the amount as final. Read the rows of the run’s tables (
artifacts,GET /v1/tables/{id}/rows?include_hidden=true: hidden columns run and bill too) until no cell hasstatusrunning, then read the run again later. A cell that readsfailedmay still be retried automatically, up to two times, depending on its service and on why it failed. While it waits for its retry it readsfailed, and nothing in the API tells a failed cell that will be retried from a final one. A retried cell waits its turn in the queue, which can take more than a few minutes, and the amount grows again if the retry gets an answer. Treat the amount as provisional until it has stopped changing over a longer window. Waiting for no running cell also waits for cells that other activity on the same tables started. - If your credit history cannot be read at that moment, the run is returned without
credits: read it again. - The list
GET /v1/runsand the create and cancel responses do not includecredits.
Reconciling with GET /credits
credits.charged on a run or on a service job will not always match the change of your balance.
- The balance belongs to your whole organization.
- The balance drops when a call reserves its worst-case price.
chargedcounts only what was settled, and the unused part of a reservation comes back. - Other spending also moves the balance: direct
POST /servicescalls, Go, MCP, the app, scheduled runs, and your teammates. Grants and top-ups move it too. Sequence messages written outside a run are not on it: every AI sequence message except the first message of a lead enrolled in an active sequence during the run is written when its step is due, outside any run, including the messages of a draft sequence the agent activates. - A run’s
chargedcan keep growing after the run ends, while cells of the columns it launched are still running. A run’s or a job’schargedcan also grow after it ends when a charge is recorded late, most often for a call cut by a timeout whose answer landed just after the cut (on a run, also a call aborted by a Stop or a cancel). A job’s charge can also land after its end when its write took longer than the job waits for it, or when the job was taken over by another worker. Late charges usually land within seconds, but this is not guaranteed: read it again a few minutes after the end. - Service jobs only (a run reads your credit history directly): each charge is added to the job’s total when it settles. A failed addition is not tried again, because a retry could count the charge twice. The job’s
chargedthen reads lower than the balance change in three cases: the addition to the job’s total fails; the charge is written to your credit history but the reply of that write is lost, so the job never learns of the charge; or the server is stopped after the charge and before recording it. charged: nullon a service job means its first call ran on a server from before this field. A job created before it but first run after shows a number.- While this field was being released, a job first claimed by a new server and then retried by an older one can show less than it cost.
Choose output
- Schemas are JSON Schema draft 7, self-contained, validated without coercion.
- For
tables, the list and eachrow_schemaare optional. Keys must be unique. - Declare
["string", "null"]for any field that may be missing: an overly strict schema blocks publication and the run fails.
GET /v1/runs/{id}/output returns:
- JSON:
typeisjsonandvalueholds the value. - Tables:
typeistablesandtableslists each table (key,source_table_id,columns,row_count,links).
GET /v1/runs/{id}/output/tables/{key}/rows. They include id, source_row_id, values, cells, and fit, and do not change afterward.
Artifacts
artifacts lists tables created (created) or updated (updated) by the run:
- Artifacts point to live tables: their content may change after the run.
- The list may include intermediate tables, or be empty.
- To identify the final table without guessing, use a
tables-typeoutputcontract.
Export
POST /v1/runs/{id}/exports with table_key and format (csv or json) generates the export during the request. The 201 response means it is ready. links.self is an absolute URL. Opening it downloads the file. That URL does not need an API key: the export id is enough, and it stays valid while the export exists. GET /v1/runs/{id}/exports/{exportId} is the same file for a caller who already has an API key.
- The export contains business values, without cell metadata.
- In CSV, experience and education lists are flattened to text. JSON keeps structure.