Guides
Agent runs
Start a research agent on a question no single search answers, follow it to the end, read what it found and what it cost.
An agent run hands a question to a research agent that works through it in several steps, enumerates the matching companies or people, and measures how much of the matching set it reached. You start it with one request, follow its progress, and page through the answer once it has finished.
Use a run when a single search can't answer the question. If you can state the target as a query or as filters, Search is faster and cheaper. If you have companies that fit and want more like them, use Similar Companies. This page walks through a run from start to finish; every field and error is in the agent runs reference.
When to use a run instead of a search#
A search response can carry suggestion.kind: "agentic": no single search answers the question, whatever the category, because it needs several steps or several datasets joined. You get the same question answered by a run.
A deep search (mode: "deep") runs at the lowest effort and returns one page. You get a run that works at the effort you choose, measures the matching set, and pages through all of it.
Some requirements are hard to express as filters. You get each row checked against up to 10 criteria written in plain words, with a pass rate per criterion.
A run can also fill extra columns you name (enrichments) and leave out records you already hold (exclusion).
Pick an effort tier#
effort sets how hard the run works and what it costs. You're charged only when the run succeeds; a run that fails or is cancelled costs nothing.
effort | Name | Credits per successful run |
|---|---|---|
minimal | Quick | 1.02 |
low | Basic | 2.125 |
medium (default) | Standard | 8.5 |
high | Detailed | 42.5 |
xhigh | Maximum | 85 |
auto | Automatic | The price of the tier it ran |
auto lets the agent choose. It never chooses xhigh, and today it runs at high. While it decides, it holds 85 credits (see How you're charged); once it has run, effort_ran says which tier it used and that tier's price is what you pay.
Start with medium. Move up when coverage shows the run stopped before it reached the whole set, and down when a smaller tier already reaches it.
How a run goes#
- Start the run
POST /v1/agents/runsneeds thedsp.agents.writescope and anIdempotency-Keyheader. The key is required here: a run holds credits and can take minutes, so a retry has to be recognisable. Use one unique value per question, and send the same value again only to retry that question.curl -X POST https://api.sproutsdata.ai/v1/agents/runs \ -H "X-API-Key: $SPROUTS_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 6f1c2e9a-5b7d-4c1e-9a3f-2d8b7e4c1a90" \ -d '{ "query": "B2B payments companies in Europe that sell to marketplaces", "category": "company", "effort": "medium", "criteria": ["Offers split payments or payouts to sellers"], "enrichments": ["Payout methods offered"] }'Field Notes queryRequired, 1 to 4,000 characters categorycompany(default) orpersoneffortminimal,low,medium(default),high,xhighorautocriteriaUp to 10 requirements, in plain words. The agent decides which it can check with a filter and which it judges enrichmentsUp to 20 column names to fill. A column no dataset can support comes back empty with a reason, never invented exclusionUp to 1,000 ids you already hold, to leave out of the answer. coverageis still measured against the whole matching setAn unknown field is a
422.The answer is a
202with a handle:{ "run_id": "…", "status": "pending", "meta": { "request_id": "…", "environment": "live", "as_of": "…", "credential": "api_key" } }Sending the same
Idempotency-Keyagain returns the original run's handle with200instead of202, whatever the body says. It starts nothing and charges nothing. Run keys never expire and are shared across your workspace, so a key belongs to one run for good. - Follow its progress
There are two ways to follow a run. Both need
dsp.agents.readordsp.agents.write.Poll the run.
GET /v1/agents/runs/{run_id}returns itsstatus:pending,running, then one ofcompleted,failedorcancelled. Poll every few seconds until the status is one of the last three.curl https://api.sproutsdata.ai/v1/agents/runs/$RUN_ID \ -H "X-API-Key: $SPROUTS_API_KEY"Stream its events.
GET /v1/agents/runs/{run_id}/eventsis a server-sent event stream (text/event-stream), one frame per step. It replays the frames you missed, then follows the run until it ends, with heartbeats during quiet stretches. Each frame'sidis a sequence number for that run: if the connection drops, reconnect with aLast-Event-IDheader carrying the lastidyou received, and the stream resumes after it.curl -N https://api.sproutsdata.ai/v1/agents/runs/$RUN_ID/events \ -H "X-API-Key: $SPROUTS_API_KEY" \ -H "Accept: text/event-stream"A missing run is a
404before the stream opens. A failure after the stream has opened arrives as a frame, not as a status code. - Read the outcome
Once the status is
completed,GET /v1/agents/runs/{run_id}describes what the run found and how far to trust it:Field What it tells you coverageHow much of the matching set the answer represents: universe(records matching the resolved question, counted),enumerated(how many the answer carries),enumeration_recall,universe_exhausted(every match was returned and the count agrees),truncated_by(the limit that stopped it early), per-criterion results incriteria, andconstraints_unmet(parts of the question the run could not express)summaryTwo or three sentences on what came back and the bar that was actually applied exclusionsWhat was left out, and why: a failed criterion, a constraint no dataset can express, accounts a joined dataset cannot see insights,insight_sourcesCited statements about the market behind the question, from research reports, papers and trends, with the documents they rest on. They are separate from the rows and never change coveragedegradationWays the run is less than it should be. On a test key it carries billing_bypassedstop_reason,errorWhy the run stopped, and the error when it failed credits_chargedWhat the run cost, once settled. nulluntil then, never0for "not known yet"effort,effort_ranThe tier you asked for and the tier it ran at. effort_ranisnulluntil the run has settledRead
constraints_unmetbefore you rely onuniverse_exhausted. When part of the question could not be expressed,universe_exhaustedstaysfalse, because "everything that matches" can't be true of a question that was only partly asked. - Page through the results
GET /v1/agents/runs/{run_id}/resultsreturns the rows in the standard envelope. It is free: the run was paid for when it settled.curl "https://api.sproutsdata.ai/v1/agents/runs/$RUN_ID/results?limit=100" \ -H "X-API-Key: $SPROUTS_API_KEY"limitis 1 to 100 (default 25). Passnext_cursorback as thecursorquery parameter untilhas_moreisfalse. A cursor from one run is refused on another.- Each row has an
id: the company's domain when it has one, otherwise a 16-character hash. Like a search hit'sid, it is not a lookup id; see chaining from search. totalis the size of the answer. When the run measured its matching set exactly, that is the fulluniverse, which can be larger than the rows stored for the run.columnslists the extra columns when you asked forenrichments. Each value is on its row, under the column's name.
Listing and cancelling runs#
GET /v1/agents/runs lists your workspace's runs, newest first, with run_id, status, query, created_at and finished_at. It takes limit (1 to 100, default 25) and has no cursor. The list reads the platform's own record of each run, which is brought up to date when the run is read, so open a run with GET /v1/agents/runs/{run_id} for its live status.
POST /v1/agents/runs/{run_id}/cancel stops a run and releases its hold, so a cancelled run costs nothing. It needs dsp.agents.write; a read-only credential can follow a run but not stop it. Cancelling is safe to repeat. A run that has already finished stays finished, and the cancel response reads it like GET does, which settles the charge for a run that succeeded.
How you're charged#
A run's cost isn't known until it ends, so starting one places a hold for the tier price (85 credits for auto) instead of charging it, much like a hold on a payment card. If your balance can't cover the hold, the start fails with 402 credits.insufficient and no run begins.
The charge is settled the first time the finished run is read with GET /v1/agents/runs/{run_id}, or by a cancel:
- A run that succeeded is charged the price of the tier it ran, and the rest of the hold is released.
- A run that failed or was cancelled releases the hold and is charged nothing.
- Reading results, listing runs and the events stream don't settle the charge.
The hold lasts 35 minutes from when you start the run. If nobody has read the finished run by then, the hold lapses and the run is never charged: it reports credits_charged: 0, and usage history shows it as unsettled. To see the final status and cost of every run, poll GET /v1/agents/runs/{run_id} until the status is completed, failed or cancelled.
The full price list is in Credits & usage.
Test keys#
A test key (sk_dsp_test_…) runs real agents on live data and is never charged. No hold is placed, credits_charged is 0 once the run has finished, and degradation carries a billing_bypassed entry that says so. Use a test key while you build, then switch to a live key.
Limits on starting runs#
Two limits apply when you start a run. Both answer 429, and neither applies to reading runs.
| Code | When | What to do |
|---|---|---|
quota.concurrency_exceeded | More than 5 runs are being started at the same moment by one credential. Carries Retry-After: 30 | Wait for the Retry-After seconds, then retry with the same Idempotency-Key |
quota.entity_ceiling | The credential has been shown 5,000,000 distinct records (the default ceiling) in the last 30 days, through searches and run results. It is checked when a run starts. extra carries seen and limit | Contact us to raise the ceiling |
Common errors#
| Status | Code | When | What to do |
|---|---|---|---|
| 400 | request.invalid | POST /v1/agents/runs without an Idempotency-Key, or with one longer than 255 characters | Send a unique key per question |
| 402 | credits.insufficient | Your balance can't cover the hold | Buy credits. Don't retry unchanged |
| 403 | auth.insufficient_scope | The credential lacks dsp.agents.write (start, cancel) or dsp.agents.read (reads) | Create a key with the scope |
| 404 | request.invalid | The run id doesn't exist, or belongs to another workspace | Check the run_id |
| 422 | request.invalid | A field failed validation, an unknown field was sent, or the agent rejected the request | Fix the request; detail says what was wrong |
| 429 | quota.* | See Limits on starting runs | Wait, or contact us |
| 502 | upstream.unavailable | The agent engine answered with an error | Retry the same request later. Retrying a start with the same Idempotency-Key is safe |
| 503 | upstream.unavailable | The agent engine couldn't be reached | Retry shortly |
Every error has the same problem shape, and every code is on Error codes.
Best practices#
- Send one
Idempotency-Keyper question and keep it. Retrying with the same key can never start or charge a second run. - Poll
GET /v1/agents/runs/{run_id}to the end. It is what settles the run and gives you its final status and cost, even when you follow progress on the events stream. - Read
coveragebefore the rows.universe_exhausted,truncated_byandconstraints_unmettell you whether you have the whole answer or part of it. - Show
summaryandexclusionsbeside the rows. They say what bar was applied and what was left out, which a row count alone can't. - Choose the tier on evidence. Start at
mediumand change it based oncoverage, not on the number of rows.