> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dataforb2b.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Reasoning Search

> Multi-turn AI agent that builds and runs the best possible search for you

An AI agent reasons about your query, probes the database in real time, asks clarifying questions when needed, then runs the best final search. Unlike a [one-shot search](/api-reference/text-to-filters#one-shot-search), Reasoning Search is conversational: you can answer questions and refine results across turns using the same `session_id`.

## Credit Cost

| Item                             | Credits                 |
| -------------------------------- | ----------------------- |
| Agent fee (per completed search) | 2 credits               |
| Result (`enrich_live: false`)    | 0.75 credits per result |
| Result (`enrich_live: true`)     | 1.5 credits per result  |

Clarification turns (`status: "needs_input"`) are **free** — the agent fee is only charged when a search actually runs.

***

## Request Body

<ParamField body="query" type="string">
  Natural language query (max 3000 characters). Required on the first call. On follow-up calls with a `session_id`, use it to refine the previous search (e.g. `"now only in San Francisco"`).
</ParamField>

<ParamField body="session_id" type="string">
  Session identifier returned by a previous call. Send it back to answer questions or refine the same search.
</ParamField>

<ParamField body="answers" type="object">
  Answers to the questions of a `needs_input` turn, as `{question_id: answer}`.
</ParamField>

<ParamField body="category" type="string" default="people">
  What to search: `"people"` or `"companies"`.
</ParamField>

<ParamField body="max_results" type="integer" default="25">
  Number of results for the final search (1–100).
</ParamField>

<ParamField body="enrich_live" type="boolean" default="false">
  If `true`, results are enriched with fresh live data (1.5 credits per result instead of 0.75).
</ParamField>

<Info>
  **Pagination:** this endpoint always returns the first `max_results` results. To get the next pages, take the `applied_filters` from the response and call [`/search/people`](/api-reference/search-people) or [`/search/companies`](/api-reference/search-company) with them, setting `offset` (e.g. `offset: 25`, `offset: 50`, ...). You only pay the agent fee once — pagination is billed at the standard per-result rate.
</Info>

***

## Flow

<Steps>
  <Step title="Send your query">
    `POST /search/reasoning` with `query`. The response is either `complete` (results) or `needs_input` (questions).
  </Step>

  <Step title="Answer questions (if asked)">
    Send back `session_id` + `answers`. The agent continues and returns results.
  </Step>

  <Step title="Refine (optional)">
    Send `session_id` + a new `query` to refine the same search — the agent remembers the conversation.
  </Step>
</Steps>

***

## Show the agent's reasoning live (recommended)

`POST /search/reasoning/stream` takes the exact same body but returns Server-Sent Events, so your users see the agent think, probe the database and build the search in real time — instead of staring at a spinner for 20–60 seconds.

Events arrive in this order:

| Event       | Payload                                                                                                  | When                           |
| ----------- | -------------------------------------------------------------------------------------------------------- | ------------------------------ |
| `step`      | `{type: "thought" \| "action" \| "observation", ...}`                                                    | Each reasoning step, live      |
| `questions` | `{session_id, reasoning, questions}`                                                                     | The agent needs clarification  |
| `result`    | `{session_id, reasoning, applied_filters, category, total, count, has_next_page, credits_used, results}` | Final results                  |
| `error`     | `{message}`                                                                                              | Something went wrong           |
| `done`      | `{}`                                                                                                     | Always last — close the stream |

The three `step` types map naturally to UI elements:

* **`thought`** — `{type, text}`: the agent's narration. Render as a status line.
* **`action`** — `{type, tool, filters?, resolve?}`: a database probe about to run (`resolve_value`, `find_companies`, `probe_count`, `preview_results`, then a final `search`). `filters` shows the exact filters being tested.
* **`observation`** — `{type, tool, result}`: the probe's outcome, e.g. `{"count": 183}` for a `probe_count`.

Since SSE over `POST` isn't supported by `EventSource`, consume the stream with `fetch`:

```javascript theme={null}
const res = await fetch('https://api.dataforb2b.ai/search/reasoning/stream', {
  method: 'POST',
  headers: { api_key: 'YOUR_api_key', 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: 'CTOs of AI startups in Paris', max_results: 25 })
});

const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = '';

while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += value;
  // SSE frames are separated by a blank line
  const frames = buffer.split('\n\n');
  buffer = frames.pop(); // keep the incomplete frame

  for (const frame of frames) {
    const event = frame.match(/^event: (.+)$/m)?.[1];
    const data = JSON.parse(frame.match(/^data: (.+)$/m)?.[1] ?? '{}');

    if (event === 'step' && data.type === 'thought') showStatus(data.text);
    if (event === 'step' && data.type === 'observation') showProbe(data.tool, data.result);
    if (event === 'questions') askUser(data.session_id, data.questions);
    if (event === 'result') showResults(data);
    if (event === 'error') showError(data.message);
  }
}
```

```python Python theme={null}
import json, requests

with requests.post(
    "https://api.dataforb2b.ai/search/reasoning/stream",
    headers={"api_key": "YOUR_api_key", "Content-Type": "application/json"},
    json={"query": "CTOs of AI startups in Paris", "max_results": 25},
    stream=True,
) as r:
    event = None
    for line in r.iter_lines(decode_unicode=True):
        if line.startswith("event: "):
            event = line[7:]
        elif line.startswith("data: "):
            data = json.loads(line[6:])
            if event == "step":
                print(f"[{data['type']}] {data.get('text') or data.get('tool')}")
            elif event == "result":
                print(f"Done: {data['total']} results, {data['credits_used']} credits")
```

A real stream looks like this:

```text theme={null}
event: step
data: {"type": "action", "tool": "resolve_value", "resolve": {"type": "category", "q": "artificial intelligence"}}

event: step
data: {"type": "observation", "tool": "resolve_value", "result": {"matches": [{"value": "artificial intelligence", "popularity": "10k-100k"}]}}

event: step
data: {"type": "action", "tool": "find_companies", "filters": {"op": "and", "conditions": [...]}}

event: step
data: {"type": "observation", "tool": "find_companies", "result": {"total": 680, "returned": 680}}

event: step
data: {"type": "action", "tool": "probe_count", "filters": {"op": "and", "conditions": [...]}}

event: step
data: {"type": "observation", "tool": "probe_count", "result": {"count": 183, "is_capped": false}}

event: step
data: {"type": "action", "tool": "search"}

event: result
data: {"session_id": "...", "reasoning": "Found 680 AI startups in Paris, then filtered CTOs (183 matches).", "total": 203, "count": 25, "credits_used": 20.75, "results": [...]}

event: done
data: {}
```

***

## Response

<ResponseField name="status" type="string">
  `"complete"` (results below) or `"needs_input"` (answer the `questions`).
</ResponseField>

<ResponseField name="session_id" type="string">
  Send it back to answer questions or refine the search.
</ResponseField>

<ResponseField name="reasoning" type="string">
  Summary of what the agent searched for and why.
</ResponseField>

<ResponseField name="questions" type="array">
  Only when `status` is `needs_input`. Each question has `id`, `text` and optional `suggestions` (display them as choice chips in your UI).
</ResponseField>

<ResponseField name="applied_filters" type="object">
  Only when `status` is `complete`. The final filters built by the agent. You can reuse them directly on `/search/people` or `/search/companies` to paginate.
</ResponseField>

<ResponseField name="total" type="integer">
  Total number of matching results.
</ResponseField>

<ResponseField name="count" type="integer">
  Number of results returned.
</ResponseField>

<ResponseField name="credits_used" type="number">
  Total credits charged (agent fee + results).
</ResponseField>

<ResponseField name="results" type="array">
  Profiles or companies, same structure as `/search/people` / `/search/companies`.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.dataforb2b.ai/search/reasoning \
    -H "api_key: YOUR_api_key" \
    -H "Content-Type: application/json" \
    -d '{
      "query": "Heads of Sales at French fintech scale-ups",
      "category": "people",
      "max_results": 25
    }'
  ```

  ```python Python theme={null}
  import requests

  BASE = "https://api.dataforb2b.ai"
  HEADERS = {"api_key": "YOUR_api_key", "Content-Type": "application/json"}

  resp = requests.post(f"{BASE}/search/reasoning", headers=HEADERS, json={
      "query": "Heads of Sales at French fintech scale-ups",
      "category": "people",
      "max_results": 25,
  }).json()

  # Answer clarifying questions if the agent asks
  while resp["status"] == "needs_input":
      answers = {q["id"]: q["suggestions"][0] for q in resp["questions"]}
      resp = requests.post(f"{BASE}/search/reasoning", headers=HEADERS, json={
          "session_id": resp["session_id"],
          "answers": answers,
      }).json()

  print(f"{resp['total']} results — {resp['credits_used']} credits")
  ```

  ```javascript JavaScript theme={null}
  const BASE = 'https://api.dataforb2b.ai';
  const headers = { api_key: 'YOUR_api_key', 'Content-Type': 'application/json' };

  let resp = await fetch(`${BASE}/search/reasoning`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      query: 'Heads of Sales at French fintech scale-ups',
      category: 'people',
      max_results: 25
    })
  }).then(r => r.json());

  // Answer clarifying questions if the agent asks
  while (resp.status === 'needs_input') {
    const answers = Object.fromEntries(
      resp.questions.map(q => [q.id, q.suggestions?.[0] ?? ''])
    );
    resp = await fetch(`${BASE}/search/reasoning`, {
      method: 'POST',
      headers,
      body: JSON.stringify({ session_id: resp.session_id, answers })
    }).then(r => r.json());
  }

  console.log(`${resp.total} results — ${resp.credits_used} credits`);
  ```
</RequestExample>

<ResponseExample>
  ```json Clarification (needs_input) theme={null}
  {
    "status": "needs_input",
    "session_id": "rs_8f2c1a9b",
    "reasoning": "Fintech scale-up can mean different sizes — let me confirm.",
    "questions": [
      {
        "id": "company_size",
        "text": "What company size do you consider a scale-up?",
        "suggestions": ["51-200", "201-500", "501-1000"]
      }
    ]
  }
  ```

  ```json Results (complete) theme={null}
  {
    "status": "complete",
    "session_id": "4d84740e1a654105b4af9ffe403f6c3f",
    "reasoning": "Targeted Heads/VPs/Directors of Sales (and close variants like CRO) at French fintech companies via category + country + title variants. 55 matches.",
    "questions": null,
    "applied_filters": {
      "op": "and",
      "conditions": [
        {
          "op": "or",
          "conditions": [
            {"column": "current_title", "type": "like", "value": "head of sales"},
            {"column": "current_title", "type": "like", "value": "vp sales"},
            {"column": "current_title", "type": "like", "value": "chief revenue officer"}
          ]
        },
        {"column": "current_company_category", "type": "=", "value": "fintech"},
        {"column": "profile_country", "type": "=", "value": "FR"}
      ]
    },
    "total": 55,
    "count": 25,
    "has_next_page": true,
    "credits_used": 20.75,
    "results": [
      {
        "id": "prof_PVZvTCRqnvSOWWfQOav9QQI130aGcZuo4Lez",
        "first_name": "Loic",
        "last_name": "Jaume",
        "headline": "Head of Sales at Linedata",
        "location": "Greater Paris Metropolitan Region",
        "country": "FR",
        "industry": "Computer Software",
        "summary": null,
        "profile_language": "en_US",
        "profile_picture_url": null,
        "links": {
          "linkedin": "https://www.linkedin.com/in/loic-jaume-3894a98",
          "twitter": null,
          "github": null
        },
        "experience": ["..."],
        "education": ["..."],
        "skills": ["..."],
        "certifications": ["..."],
        "languages": ["..."],
        "metrics": {"followers": 1200, "skills_count": 14},
        "has_personal_email": false
      }
    ]
  }
  ```
</ResponseExample>
