# Search in plain English

> POST /v1/search. One sentence in, a ranked shortlist out, with the evidence behind every pick. Blocking or streamed as server-sent events.

Canonical page: https://www.refolk.ai/developers/agentic-search



Describe who you are looking for in a sentence and get back a ranked shortlist
of real people, with the evidence behind every pick. This is the search the
product is built around, unchanged and unabridged.

### `POST /api/v1/search`

One sentence in, a ranked shortlist out. Blocking by default, or streamed as server-sent events.

**Cost:** ~10 reserved, reconciled down

## The request

**Body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `query` | `string` | optional | One sentence describing who you want. Include the constraints that matter: role, seniority, location, what they have built. Naming a count is honoured. Use this or `messages`. |
| `messages` | `array` | optional | A conversation of `{ role, content }` objects, for follow-ups. Roles are `user` and `assistant`. Use this or `query`. |
| `stream` | `boolean` | optional | Return server-sent events instead of one JSON body. Defaults to `false`. |
| `rerank` | `boolean` | optional | Rank and trim to a shortlist. Defaults to `true`. Set `false` to get every unique person the sources returned, unranked. |

**curl**

```bash
curl https://www.refolk.ai/api/v1/search \
  -H "Authorization: Bearer $REFOLK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "Series A fintech CTOs in London who came from a bank"}'
```

**TypeScript**

```typescript
const res = await fetch("https://www.refolk.ai/api/v1/search", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.REFOLK_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    query: "Series A fintech CTOs in London who came from a bank",
  }),
});
const { answer, people, credits } = await res.json();
```

**Python**

```python
res = requests.post(
    "https://www.refolk.ai/api/v1/search",
    headers={"Authorization": f"Bearer {os.environ['REFOLK_API_KEY']}"},
    json={"query": "Series A fintech CTOs in London who came from a bank"},
    timeout=180,
)
data = res.json()
```

Run this request in the playground: https://www.refolk.ai/developers/playground?endpoint=search.

```json
{"query": "Series A fintech CTOs in London who came from a bank"}
```

## Follow-ups

Pass the conversation back and the second question is read in the light of the
first, exactly as it is in the app.

```json
{
  "messages": [
    { "role": "user", "content": "Founders of seed-stage fintechs in London" },
    { "role": "assistant", "content": "Found 25." },
    { "role": "user", "content": "Only the ones who were engineers first" }
  ]
}
```

Fifty messages and a hundred thousand characters are the ceiling. Past either,
start a new conversation: the whole history is read on every turn, so a long
one is paid for again each time.

## The response

**Fields**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `answer` | `string` | required | A short written summary of the run. |
| `people` | `array` | required | The shortlist. Each carries a `name`, a stable `key`, whatever role and location resolved, a `reason` for the match, and `signals`, which is the evidence behind it. |
| `companies` | `array` | required | Populated when the question was about companies instead. A query about fintechs in Berlin returns these. |
| `repos` | `array` | required | Populated when the question was about repositories. |
| `credits` | `object` | required | `charged` and `balance` for this run. |

Profile photos come back as absolute URLs on this site rather than wherever the
image is hosted, so they can be rendered anywhere without leaking where they
came from.

## Streaming

A search takes tens of seconds. If something with a screen is waiting on it,
stream it: one JSON object per `data:` line, in the order things happened.

**TypeScript**

```typescript
const res = await fetch("https://www.refolk.ai/api/v1/search", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.REFOLK_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ query: "Rust maintainers in Europe", stream: true }),
});

const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();
let buffer = "";
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += value;
  const lines = buffer.split("\n\n");
  buffer = lines.pop() ?? "";
  for (const line of lines) {
    if (!line.startsWith("data: ")) continue;
    const event = JSON.parse(line.slice(6));
    if (event.type === "people") console.log(event.people.length, "found");
  }
}
```

**Python**

```python
import json, requests

with requests.post(
    "https://www.refolk.ai/api/v1/search",
    headers={"Authorization": f"Bearer {os.environ['REFOLK_API_KEY']}"},
    json={"query": "Rust maintainers in Europe", "stream": True},
    stream=True,
    timeout=300,
) as res:
    for line in res.iter_lines(decode_unicode=True):
        if not line or not line.startswith("data: "):
            continue
        event = json.loads(line[6:])
        if event["type"] == "people":
            print(len(event["people"]), "found")
```

**curl**

```bash
curl -N https://www.refolk.ai/api/v1/search \
  -H "Authorization: Bearer $REFOLK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "Rust maintainers in Europe", "stream": true}'
```

The event types, in the order you will meet them:

| Type | Meaning |
| --- | --- |
| `step` | A stage of the search started. |
| `step_done` | It finished, with `durationMs`, and `error` if it failed. |
| `token` | A fragment of the written answer. |
| `people_partial` | What one source found, before the merge. Superseded by `people`. |
| `people`, `companies`, `repos` | The results. |
| `balance` | What the run cost, once it has settled. |
| `done` | The run finished. |
| `error` | The run failed, with a message. |

> **Hanging up does not cancel a search**
>
> Both shapes cost the same, because they are the same search. Disconnecting
> mid-stream does not stop the run or refund it: it has already been paid for,
> and it runs to its own end so the charge can be reconciled honestly.

## How long to wait

Thirty to ninety seconds is normal, and a hard search can take longer. Set the
client timeout at three minutes or more. Two searches per key may run at once;
a third is refused with a `429` rather than queued.



---

## More of the API documentation

- [Quickstart](https://www.refolk.ai/developers/quickstart.md): Create a key, run your first search, and read what comes back. A working request in under two minutes, in curl, TypeScript, and Python.
- [Authentication](https://www.refolk.ai/developers/authentication.md): How API keys work: creating one, sending it, rotating it, and what happens when one leaks. Keys are shown once and hashed at rest.
- [Credits and limits](https://www.refolk.ai/developers/credits-and-limits.md): What each call costs, how a search is reserved and reconciled, what the rate limits are, and how to check a balance before a long run.
- [Playground](https://www.refolk.ai/developers/playground.md): Run any endpoint from the browser, signed in, on your own credits. Prefilled examples, the real response, and the equivalent curl for your own code.
- [Search with filters](https://www.refolk.ai/developers/structured-search.md): POST /v1/people/search. An exact filter set in, a deterministic page of people out, one credit a call. Every field, every allowed value, and how to paginate.
- [Errors](https://www.refolk.ai/developers/errors.md): One error shape for every endpoint, a closed set of types, and which of them are worth retrying. What gets refunded when a call fails.
- [MCP server](https://www.refolk.ai/developers/mcp.md): Give Claude, Cursor, VS Code, or any MCP client the ability to search for people mid-conversation. One URL, one header, four tools.
- [OpenAPI document](https://www.refolk.ai/api/v1/openapi.json): the machine-readable specification.

Authenticate with `Authorization: Bearer rfk_live_...`. Keys are created at https://www.refolk.ai/hire/api.