Guides

Search

Turn a question — plain English, structured filters, or both — into companies, people, or documents, with the response explaining how it was interpreted.

Search turns a question like "industrial automation companies in Germany with 200+ engineers" into rows of companies, people, or documents. One endpoint, POST /v1/search, covers all nine categories. You can:

  • ask in plain English and let the engine infer the constraints,
  • send exact filters when you already know them,
  • or combine both.

Use search when you are finding records you don't yet have. If you already have companies that fit your target profile and want more like them, use Similar Companies. If you already have ids or domains and want full records, use Record Lookup.

Use cases#

Three situations cover most of what teams build on this endpoint.

Prospecting in plain English

You know who you're looking for, but not how the index encodes it. You get a ranked page of company rows, plus the filters the engine inferred from your sentence.

Building a precise segment

You know exactly which constraints define the segment. You get a deterministic, repeatable segment with an exact total.

Watching a market

You want to know when something happens in a space. You get a page of matching documents to diff on a schedule.

Each tab shows the request to send and the outcome you hold when it returns.

You are building a prospect list and know who you're looking for, but not how the index encodes it. mode: "auto" reads your words and turns them into filters:

curl -X POST https://api.sproutsdata.ai/v1/search \
  -H "X-API-Key: $SPROUTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "query": "industrial automation companies in Germany",
        "mode": "auto",
        "category": "company",
        "limit": 10
      }'

Outcome: a ranked page of company rows, each with id, name, industry, size, and a relevance score. resolved_search.filters shows exactly which constraints the engine inferred from your sentence. Feed the rows into your pipeline, or copy the inferred filters into a filter-only search to make the segment repeatable.

How it works#

POST /v1/search searches one category of the corpus: companies, persons, or one of seven document verticals. Send a natural-language query, structured filters, or both.

Requires the dsp.search.read scope. A Basic (fast) search costs 0.5 credits for its first 10 results and 0.08 per extra result; an AI (auto) search 0.595 and 0.085. A search that finds nothing costs nothing. See Credits & usage.

Parameters

ParameterTypeDefaultNotes
querystring ≤ 2000—Natural language. Optional if filters is given
modefast | auto | deepfastHow the query is interpreted — see below
categorystringcompanyWhat to search; also the type of every returned row
filtersobject—Structured constraints; shape depends on category
include_domainsstring[] ≤ 100[]company, person, job_opening only
exclude_domainsstring[] ≤ 100[]company, person, job_opening only
sortclause[] ≤ 3—Explicit ordering, strongest key first — see Sorting
limit1–10010Page size. The default is what a search's base price covers
cursorstring—From a previous response's next_cursor

Unknown parameters are rejected with a 422, never ignored. A parameter the API silently dropped would let you believe you asked for something you didn't.

Modes

The difference between modes is what your words mean, not how many results come back:

modeBehavior
fastMatch my words. Your query is matched directly (vector retrieval, or keyword matching where vectors are unavailable), with no LLM in the path. filters used exactly as sent
autoUnderstand my intent. An extraction agent reads query and infers filters from it
deepEnumerate the matches. An agentic engine works through the query and reports how much of the set it reached in coverage. Works differently from the other two — see Deep mode

fast with "Series B fintech in Germany" matches those words. auto turns them into funding_stage, industry, and company_country filters and searches on those. The cost of auto is an LLM call in the path: expect roughly twice the latency of fast (seconds, not milliseconds, either way).

Deep mode

deep hands the query to an agentic engine that enumerates matching records and measures its own coverage. It behaves differently from fast and auto, so check these points before you build on it:

  • It needs an explicit category, and it searches companies and people: send "company" or "person". Omitting category is a 422, because auto-classification runs only under mode: "auto".
  • It returns one page. There is no next_cursor, and a cursor sent with mode: "deep" is refused. coverage describes what the page represents; when coverage.truncated_by is set, the enumeration stopped early, so narrow the query rather than looking for a second page.
  • It runs at minimal effort. A search request has a 30-second budget, so deep mode runs at the engine's minimal effort tier and says so with a mode_downgraded entry in degradation[]. For deeper enumeration, start an agent run instead.
  • filters, include_domains, exclude_domains and sort are not applied yet. Deep mode currently reads query, category and limit only. The other parameters are accepted but have no effect, so do not rely on them to narrow a deep search: state the constraint in query, or use fast or auto.

A deep search is billed as dsp.search.deep, at the agent price of the effort it ran (today Quick, 1.02 credits), and returns up to limit rows (default 10) — see Credits & usage.

Categories

The value you send is the value on every row (results[].category).

categoryCorpusKind
companyEntity indexEntity
personEntity indexEntity
patentPatentsDocument
news_articleNewsDocument
trendTrendsDocument
market_reportMarket reportsDocument
funding_roundFunding roundsDocument
academic_reportResearch papersDocument
job_openingJob openingsDocument

Domain filters

include_domains and exclude_domains (max 100 each) restrict results by company domain. They apply to exactly three categories: company, person, and job_opening. A job opening is a document, but it genuinely carries the posting company's domain. Every other category is a document with no domain of its own, and sending domain filters there is a 422:

{
  "code": "request.invalid",
  "extra": { "errors": [{ "msg": "Value error, `include_domains` / `exclude_domains` apply to company, job_opening, person, not `patent`" }] }
}

This is refused rather than reinterpreted. "Patents whose mentioned company has this domain" would be a different query, and answering it silently would be worse than not answering.

Filters

filters narrows a search with structured constraints instead of words. Its shape depends on category. An unknown key is a 422; for company/person the error names the offending field.

{
  "category": "company",
  "filters": {
    "industry": { "include": ["Software & Technology"] },
    "employee_count": { "gte": 500, "lte": 5000 },
    "geo": { "places": [{ "value": "germany", "level": "country" }] }
  },
  "limit": 10
}

Every filter for all nine categories, its value shape, and the nesting rules that trip people up are in the filters guide. To find out what a given filter can be set to, use GET /v1/search/filters/{name}. It is free.

Not available yet

These return clean 501s rather than pretending:

FeatureStatus
Document categories where their index isn't wiredGate on GET /v1/coverage

Best practices#

  • On zero results, show resolved_search.filters. Especially with mode: "auto": the honest message is "these constraints matched nothing; drop one", not a blank state.
  • Render 3+ when total_is_estimate is true. An estimated total is a floor, not a count.
  • Handle pagination_truncated before anything else in degradation[]. It is what keeps has_more: false from reading as "end of results" when more matches exist.
  • Prefer fast when the words are already precise. It skips the LLM in the path (about half the latency of auto) and uses your filters exactly as sent.
  • Gate category pickers on GET /v1/coverage. Which categories have a live index is a runtime fact, not a constant.

See also#