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.

The search suggests it

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.

You need the whole set

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.

Rows must meet extra requirements

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.

effortNameCredits per successful run
minimalQuick1.02
lowBasic2.125
medium (default)Standard8.5
highDetailed42.5
xhighMaximum85
autoAutomaticThe 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#

  1. Start the run

    POST /v1/agents/runs needs the dsp.agents.write scope and an Idempotency-Key header. 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"]
          }'
    FieldNotes
    queryRequired, 1 to 4,000 characters
    categorycompany (default) or person
    effortminimal, low, medium (default), high, xhigh or auto
    criteriaUp 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. coverage is still measured against the whole matching set

    An unknown field is a 422.

    The answer is a 202 with a handle:

    { "run_id": "…", "status": "pending", "meta": { "request_id": "…", "environment": "live", "as_of": "…", "credential": "api_key" } }

    Sending the same Idempotency-Key again returns the original run's handle with 200 instead of 202, 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.

  2. Follow its progress

    There are two ways to follow a run. Both need dsp.agents.read or dsp.agents.write.

    Poll the run. GET /v1/agents/runs/{run_id} returns its status: pending, running, then one of completed, failed or cancelled. 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}/events is 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's id is a sequence number for that run: if the connection drops, reconnect with a Last-Event-ID header carrying the last id you 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 404 before the stream opens. A failure after the stream has opened arrives as a frame, not as a status code.

  3. 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:

    FieldWhat 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 in criteria, and constraints_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 coverage
    degradationWays the run is less than it should be. On a test key it carries billing_bypassed
    stop_reason, errorWhy the run stopped, and the error when it failed
    credits_chargedWhat the run cost, once settled. null until then, never 0 for "not known yet"
    effort, effort_ranThe tier you asked for and the tier it ran at. effort_ran is null until the run has settled

    Read constraints_unmet before you rely on universe_exhausted. When part of the question could not be expressed, universe_exhausted stays false, because "everything that matches" can't be true of a question that was only partly asked.

  4. Page through the results

    GET /v1/agents/runs/{run_id}/results returns 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"
    • limit is 1 to 100 (default 25). Pass next_cursor back as the cursor query parameter until has_more is false. 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's id, it is not a lookup id; see chaining from search.
    • total is the size of the answer. When the run measured its matching set exactly, that is the full universe, which can be larger than the rows stored for the run.
    • columns lists the extra columns when you asked for enrichments. 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.

CodeWhenWhat to do
quota.concurrency_exceededMore than 5 runs are being started at the same moment by one credential. Carries Retry-After: 30Wait for the Retry-After seconds, then retry with the same Idempotency-Key
quota.entity_ceilingThe 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 limitContact us to raise the ceiling

Common errors#

StatusCodeWhenWhat to do
400request.invalidPOST /v1/agents/runs without an Idempotency-Key, or with one longer than 255 charactersSend a unique key per question
402credits.insufficientYour balance can't cover the holdBuy credits. Don't retry unchanged
403auth.insufficient_scopeThe credential lacks dsp.agents.write (start, cancel) or dsp.agents.read (reads)Create a key with the scope
404request.invalidThe run id doesn't exist, or belongs to another workspaceCheck the run_id
422request.invalidA field failed validation, an unknown field was sent, or the agent rejected the requestFix the request; detail says what was wrong
429quota.*See Limits on starting runsWait, or contact us
502upstream.unavailableThe agent engine answered with an errorRetry the same request later. Retrying a start with the same Idempotency-Key is safe
503upstream.unavailableThe agent engine couldn't be reachedRetry shortly

Every error has the same problem shape, and every code is on Error codes.

Best practices#

  • Send one Idempotency-Key per 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 coverage before the rows. universe_exhausted, truncated_by and constraints_unmet tell you whether you have the whole answer or part of it.
  • Show summary and exclusions beside 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 medium and change it based on coverage, not on the number of rows.