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.
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.
You know exactly which constraints define the segment. You get a deterministic, repeatable segment with an exact total.
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
| Parameter | Type | Default | Notes |
|---|---|---|---|
query | string ≤ 2000 | — | Natural language. Optional if filters is given |
mode | fast | auto | deep | fast | How the query is interpreted — see below |
category | string | company | What to search; also the type of every returned row |
filters | object | — | Structured constraints; shape depends on category |
include_domains | string[] ≤ 100 | [] | company, person, job_opening only |
exclude_domains | string[] ≤ 100 | [] | company, person, job_opening only |
sort | clause[] ≤ 3 | — | Explicit ordering, strongest key first — see Sorting |
limit | 1–100 | 10 | Page size. The default is what a search's base price covers |
cursor | string | — | 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:
mode | Behavior |
|---|---|
fast | Match 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 |
auto | Understand my intent. An extraction agent reads query and infers filters from it |
deep | Enumerate 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". Omittingcategoryis a422, because auto-classification runs only undermode: "auto". - It returns one page. There is no
next_cursor, and acursorsent withmode: "deep"is refused.coveragedescribes what the page represents; whencoverage.truncated_byis 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
minimaleffort tier and says so with amode_downgradedentry indegradation[]. For deeper enumeration, start an agent run instead. filters,include_domains,exclude_domainsandsortare not applied yet. Deep mode currently readsquery,categoryandlimitonly. The other parameters are accepted but have no effect, so do not rely on them to narrow a deep search: state the constraint inquery, or usefastorauto.
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).
category | Corpus | Kind |
|---|---|---|
company | Entity index | Entity |
person | Entity index | Entity |
patent | Patents | Document |
news_article | News | Document |
trend | Trends | Document |
market_report | Market reports | Document |
funding_round | Funding rounds | Document |
academic_report | Research papers | Document |
job_opening | Job openings | Document |
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:
| Feature | Status |
|---|---|
| Document categories where their index isn't wired | Gate on GET /v1/coverage |
Best practices#
- On zero results, show
resolved_search.filters. Especially withmode: "auto": the honest message is "these constraints matched nothing; drop one", not a blank state. - Render
3+whentotal_is_estimateistrue. An estimatedtotalis a floor, not a count. - Handle
pagination_truncatedbefore anything else indegradation[]. It is what keepshas_more: falsefrom reading as "end of results" when more matches exist. - Prefer
fastwhen the words are already precise. It skips the LLM in the path (about half the latency ofauto) and uses yourfiltersexactly as sent. - Gate category pickers on
GET /v1/coverage. Which categories have a live index is a runtime fact, not a constant.
See also#
Cursor semantics, expiry, depth limits.
Chaining hits into full records.
The catalogue behind a sort control.
Every response field.