# Research

`POST https://api.serpkite.com/v1/research` · Credits: 30 (lite), 75 (standard) or 200 (deep) per report, charged only when it is written

Async deep research: Claude plans Google searches, reads and ranks the pages, fills gaps and writes a Markdown report with numbered citations to its sources.

Research is an async task for questions one search can't answer. Claude Haiku 5.5 plans Google queries for your `input`, the pages they find are read and ranked, a decision model (OpenAI's Decisions API) checks whether the notes already cover the task and drops off-topic sources, Claude reviews the gaps and plans follow-up rounds, then writes a Markdown report that cites numbered sources. With `output_schema` the result also has a JSON value in `output`.

**Effort:** `lite` runs 3 first-round queries in 1 round and reads 2 pages per query, for 30 credits; `standard` runs 5 queries over 2 rounds with 3 pages each, for 75; `deep` runs 8 queries over 3 rounds with 4 pages each, for 200. See the [Deep research guide](https://serpkite.com/docs/guides/deep-research).

`POST /v1/research` returns `202` with a task. Poll `GET /v1/research/{id}` (`progress` while it runs, `result` when it ends; kept 24 hours) or receive the signed [`research.completed`](https://serpkite.com/docs/webhooks#other-events) webhook. `DELETE /v1/research/{id}` cancels it: a queued task is refunded at once, a running one stops at its next round and is charged 1 credit per search already run.

The flat price is reserved when the task is queued and charged only when the report is written; research that fails is refunded in full.

## Request body

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `input` **required** | string |  | The research task in plain words, up to 8,000 characters (`q` is accepted as an alias). |
| `effort` | string | `standard` | How much to search and read: lite 30, standard 75, deep 200 credits. One of: `lite`, `standard`, `deep`. |
| `output_schema` | object |  | A JSON Schema (at most 16 KB); the report is also returned as a JSON value in `result.output`. |
| `country` | string |  | Search from this country (two-letter code). |
| `language` | string |  | Search in this language. |
| `include_domains` | string[] |  | Search only these domains (up to 20). |
| `exclude_domains` | string[] |  | Never use these domains (up to 20). |
| `webhook_url` | string |  | Receives the signed `research.completed` event. Defaults to the account webhook URL. |

## Example request

cURL:

```bash
curl https://api.serpkite.com/v1/research \
  -H "Authorization: Bearer $SERPKITE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":"compare espresso machines under $500 for a small office","effort":"standard"}'
```

TypeScript:

```ts
import { SerpKite } from "serpkite";

const sk = new SerpKite(); // reads SERPKITE_API_KEY
const res = await sk.research({ input: "compare espresso machines under $500 for a small office", effort: "standard" });
const done = await sk.waitResearch(res);
console.log(done.status, done.result?.report);
```

Python:

```python
from serpkite import SerpKite

sk = SerpKite()  # reads SERPKITE_API_KEY
res = sk.research("compare espresso machines under $500 for a small office", effort="standard")
done = sk.wait_research(res.id)
print(done.status, done.result)
```

## Response fields

| Field | Type | Description |
| --- | --- | --- |
| `id` | string | The task's ID. `poll_url` is `https://api.serpkite.com/v1/research/{id}`. |
| `status` | string | `queued`, `running`, `completed`, `failed` or `canceled`. |
| `credits_reserved / credits_used` | number | The flat price reserved at enqueue, and what was charged when the task ended. |
| `progress` | object | While running: `round`, `rounds`, `queries`, `sources`, `step` (`searching`, `reviewing`, `writing`). |
| `result` | object | When it ends: `input`, `effort`, `report` (Markdown with `[n]` markers), `citations[]`, `sources[]` (`index`, `title`, `link`, `domain`, `published_at`, `query`), `queries[]`, `stats` (`searches`, `sources`, `rounds`), `llm`, and `output` / `output_error` with `output_schema`. |
| `error` | object \| null | Set when the task failed (refunded) or was canceled. |

## Example response

```json
{
  "id": "01926a3e-8c4d-7e1f-a2b3-c4d5e6f7a8b9",
  "kind": "research",
  "status": "completed",
  "created_at": "2026-10-24T12:00:00Z",
  "started_at": "2026-10-24T12:00:01Z",
  "completed_at": "2026-10-24T12:01:38Z",
  "credits_reserved": 75,
  "credits_used": 75,
  "webhook_status": null,
  "progress": {
    "round": 2,
    "rounds": 2,
    "queries": 8,
    "sources": 14,
    "step": "writing"
  },
  "result": {
    "input": "compare espresso machines under $500 for a small office",
    "effort": "standard",
    "report": "## Summary\n\nFor a small office, a heat-exchanger machine with a 2-litre tank is the safest pick under $500 [1][3]…",
    "citations": [
      {
        "index": 1,
        "title": "The best espresso machines under $500",
        "link": "https://www.example.com/best-espresso-under-500",
        "domain": "example.com"
      }
    ],
    "sources": [
      {
        "index": 1,
        "title": "The best espresso machines under $500",
        "link": "https://www.example.com/best-espresso-under-500",
        "domain": "example.com",
        "query": "best espresso machine under $500 office"
      }
    ],
    "queries": [
      "best espresso machine under $500 office",
      "office espresso machine tank size"
    ],
    "stats": {
      "searches": 8,
      "sources": 14,
      "rounds": 2
    },
    "llm": {
      "provider": "anthropic",
      "model": "claude-haiku-5-5",
      "input_tokens": 41200,
      "output_tokens": 1650
    }
  },
  "error": null
}
```

## Errors

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `invalid_request` | A parameter is missing or invalid. |
| 401 | `unauthorized` | The API key is missing, invalid or revoked. |
| 402 | `insufficient_credits` | Your balance is too low. Buy a pack or wait for the monthly free grant. |
| 429 | `rate_limited` | Too many requests per second for your plan. Retry after the Retry-After header. |
| 503 | `upstream_error` | Google could not be fetched or parsed. Not billed; retry after Retry-After. |
| 503 | `ai_unavailable` | An AI step (answer, summary, schema extraction, research) could not run. The model part is not billed; on /v1/answer the search that ran is. |

All errors: https://serpkite.com/docs/errors

## Notes

- Research tasks share the per-account task limit with crawls: at most 20 queued or running at once (`429` beyond that).
- The input, the planned queries and the page text are sent to Anthropic (writing) and OpenAI (decision checks) under their API terms, which exclude training on API data. See [Privacy and data retention](https://serpkite.com/docs/privacy-and-data-retention#ai-features).
- The remote MCP server has `research` and `research_result` tools.

## Related

- [Deep research guide](https://serpkite.com/docs/guides/deep-research)
- [Answer](https://serpkite.com/docs/endpoints/answer)
- [Webhooks](https://serpkite.com/docs/webhooks)