API Reference

One endpoint, one job: POST api.serpdive.com/v1/search takes a question and returns answer-ready page content, extracted, cleaned, and sized for an LLM. This page is the entire API surface: if it's not here, you don't need it.
For AI agents:/docs.md·/llms.txt·/openapi.jsonthis whole reference, fetchable in one hop.

01Quickstart

Grab an API key from the dashboard (free: 1,000 credits a month, no card), install the official SDK for your language, and search:

Python
# pip install serpdive
from serpdive import SerpDive

client = SerpDive()  # reads SERPDIVE_API_KEY
response = client.search("what happened between Trump and FIFA", answer=True)

print(response.answer)
for result in response.results:
    print(result.url, result.date, result.content)
TypeScript
// npm install serpdive
import { SerpDive } from "serpdive";

const client = new SerpDive(); // reads SERPDIVE_API_KEY
const response = await client.search("what happened between Trump and FIFA", {
  answer: true,
});

console.log(response.answer);
for (const result of response.results) {
  console.log(result.url, result.date, result.content);
}

Prefer plain HTTPS? The whole integration is one POST, from any language:

cURL
curl -X POST https://api.serpdive.com/v1/search \
  -H "Authorization: Bearer $SERPDIVE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "query": "what happened between Trump and FIFA",
        "model": "mako" }'

The playground generates these snippets with your key preselected, and shows the live response next to them.

02Official SDKs

Two official clients, thin by design: they speak the exact /v1/search contract, add types, retries and nothing else.

LanguageInstallSource
Python 3.9+pip install serpdiveserpdive/serpdive-python
TypeScript / JavaScriptnpm install serpdiveserpdive/serpdive-js

Both read the key from the SERPDIVE_API_KEY environment variable (or take it explicitly), return typed responses, raise typed errors carrying the stable codes from the errors table, and retry transient failures (502/503) automatically, which is always safe because failed searches are never billed.

The Python client ships sync (SerpDive) and async (AsyncSerpDive). The TypeScript client has zero dependencies and runs anywhere fetch exists: Node 18+, Bun, Deno, edge runtimes.

03MCP server

SERPdive is also a hosted MCP server at https://mcp.serpdive.com: one tool, serpdive_search, that gives Claude Code, Claude Desktop, Cursor and any other MCP client the same extracted, answer-ready web results as the API. No install, nothing to run.

One command wires it into every agent on your machine, along with a skill that tells the agent which model to pick and how to keep the response small:

Every agent at once
npx -y serpdive-cli init --key sd_live_YOUR_KEY

It finds Claude Code, Claude Desktop, Cursor, Windsurf, Codex, VS Code, Gemini CLI and OpenCode, and skips the ones you do not have. status and --dry-run change nothing; other MCP servers already in your config are left untouched. Or wire it by hand:

Claude Code
claude mcp add --transport http serpdive https://mcp.serpdive.com \
  --header "Authorization: Bearer sd_live_YOUR_KEY"
JSON config
{
  "mcpServers": {
    "serpdive": {
      "url": "https://mcp.serpdive.com/?key=sd_live_YOUR_KEY"
    }
  }
}

Auth is your regular API key, either as an Authorization: Bearer header or as a ?key= parameter on the server URL for clients that only take a URL. Searches through MCP are billed exactly like API calls, same credits, same rate limits.

04Authentication

Every request carries your key in the Authorization header: Bearer sd_live_…. Keys are created, revealed and revoked in API Keys; revocation takes effect immediately. At creation a key can optionally get an expiration date (it stops authenticating past it, same 401 as a revoked key) and a monthly credit limit of its own — a guardrail for a key you deploy somewhere you don't fully control. A missing, expired or unknown key gets a 401 with missing_api_key or invalid_api_key: the search never runs, and nothing is billed.

05LangChain

Official package: `langchain-serpdive`. One tool class, sync and async, built on the Python SDK.

Python
# pip install langchain-serpdive
from langchain_serpdive import SerpdiveSearch

tool = SerpdiveSearch()  # reads SERPDIVE_API_KEY
tool.invoke({"query": "latest developments in solid state batteries"})

# or hand [SerpdiveSearch()] to any LangChain / LangGraph agent as its tools list

06LlamaIndex

Official package: `llama-index-tools-serpdive`. Results come back as Documents (text = extracted page content, metadata = url/title/date), ready for agents or a RAG pipeline.

Python
# pip install llama-index-tools-serpdive
from llama_index_tools_serpdive import SerpdiveToolSpec

spec = SerpdiveToolSpec()  # reads SERPDIVE_API_KEY
documents = spec.serpdive_search("latest developments in solid state batteries")

# in an agent: tools=SerpdiveToolSpec().to_tool_list()

07Vercel AI SDK

The TypeScript SDK plugs straight into an AI SDK tool: the model decides when to search, the response is already LLM-sized.

TypeScript
// npm install ai serpdive zod
import { generateText, tool, stepCountIs } from "ai";
import { z } from "zod";
import { SerpDive } from "serpdive";

const serpdive = new SerpDive(); // reads SERPDIVE_API_KEY

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5",
  prompt: "What changed in the EU AI Act this month?",
  stopWhen: stepCountIs(5),
  tools: {
    serpdive_search: tool({
      description:
        "Search the live web for current information. Returns extracted, " +
        "answer-ready page content (url, title, date, text), not links.",
      inputSchema: z.object({
        query: z.string().describe("The search, in any language"),
      }),
      execute: async ({ query }) => serpdive.search(query),
    }),
  },
});

08OpenAI function calling

No package needed: declare the function, run the search when the model calls it, feed the JSON back.

Python
# pip install openai serpdive
import json
from openai import OpenAI
from serpdive import SerpDive

client, serpdive = OpenAI(), SerpDive()

tools = [{
    "type": "function",
    "name": "serpdive_search",
    "description": "Search the live web for current information. Returns "
                   "extracted, answer-ready page content, not links.",
    "parameters": {
        "type": "object",
        "properties": {"query": {"type": "string"}},
        "required": ["query"],
    },
}]

response = client.responses.create(
    model="gpt-5", input="What changed in the EU AI Act this month?", tools=tools
)
for item in response.output:
    if item.type == "function_call":
        result = serpdive.search(json.loads(item.arguments)["query"])
        # send result.raw back as the function_call_output, then re-run

09Anthropic tool use

Same idea with the Claude API — or skip the plumbing entirely: Claude clients can use our hosted MCP server directly.

Python
# pip install anthropic serpdive
import anthropic
from serpdive import SerpDive

client, serpdive = anthropic.Anthropic(), SerpDive()

tools = [{
    "name": "serpdive_search",
    "description": "Search the live web for current information. Returns "
                   "extracted, answer-ready page content, not links.",
    "input_schema": {
        "type": "object",
        "properties": {"query": {"type": "string"}},
        "required": ["query"],
    },
}]

message = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "What changed in the EU AI Act this month?"}],
)
for block in message.content:
    if block.type == "tool_use":
        result = serpdive.search(block.input["query"])
        # send result.raw back as a tool_result block, then re-run

10The request

POST /v1/search with a JSON body. Four parameters; everything else is decided for you:

ParameterTypeDescription
querystring, requiredThe question, as you'd ask it. Up to 300 characters; longer queries are truncated, not rejected.
model"krill" | "mako" | "moby"Retrieval depth. krill (free, unlimited under fair use) returns the shortest useful set of sentences (~700 tokens, fewer sources), served at low priority and without the answer field; mako (default) returns the fact-carrying sentences of each source; moby returns the full readable content of every page. Unknown values fall back to mako.
answerbooleanOpt-in direct answer. true adds a written answer built from the sources: concise on Mako, detailed with [n] citations on Moby. Included in the price, never extra credits; expect a few hundred milliseconds on top. Krill never writes one — the field is ignored there.
max_resultsinteger, 1–10A hard cap on how many results you get back, keeping the best-ranked ones. It trims the response — and your downstream token bill — but does not speed up the search: the engine always does its full read. When omitted, the engine returns everything it considers relevant.

Localization is automatic. The query's language picks where we search: a German query searches the German-language web, a Japanese one the Japanese web, whoever and wherever the caller is. There is no country knob to set.

Set a generous client timeout. Mako answers in a few seconds; Moby reads whole pages and can take substantially longer on heavy sources. We recommend an 80-second client timeout; it's what our own playground uses.

11The response

A successful search returns 200 with this shape, nothing else, ever:

200 OK
{
  "query": "what happened between Trump and FIFA",
  "model": "mako",
  "response_time_ms": 2641,
  "results": [
    {
      "url": "https://www.reuters.com/sports/soccer/…",
      "title": "Trump, FIFA's Infantino unveil …",
      "date": "2026-07-11",
      "content": "The announcement came during a joint press conference … [the fact-carrying sentences of the page]"
    },
    {
      "url": "https://www.bbc.com/sport/football/…",
      "title": "What the deal means for the 2026 World Cup",
      "content": "…"
    }
  ]
}
FieldTypeDescription
querystringYour query, echoed untouched.
modelstringThe model that answered: krill, mako or moby.
response_time_msnumberWall-clock time we took, in milliseconds.
answerstring | nullThe written answer. Only present when you sent answer: true; null if none could be built from the sources.
extra_infoobjectA structured direct-answer block, only present when the query has one: weather, exchange rates, definitions, live scores… Its type field says which kind.
resultsarrayThe extracted sources, best first. Each has url, title, content (the extraction: sentences on Mako, the full readable page on Moby) and date when a publication date is known — always ISO YYYY-MM-DD, absent otherwise.

Every result carries content that speaks to the query — never a bare link, never a placeholder. A source we could get nothing usable from is simply absent rather than padded out. Tracking parameters (utm_*, click IDs…) are stripped from URLs, so what you cite is clean.

Asking for the answer looks like this:

Request with answer
{ "query": "who won the 2026 Champions League final",
  "model": "moby",
  "answer": true }

12Errors

Every failure returns a JSON body with two fields: error, a stable machine-readable code, and message, a human sentence that says what to do about it:

401 Unauthorized
{
  "error": "invalid_api_key",
  "message": "This API key is invalid or was revoked. Manage your keys at https://serpdive.com/dashboard/keys"
}
StatusCodeMeaning
400invalid_jsonThe body isn't valid JSON.
400missing_queryNo query in the body.
401missing_api_key / invalid_api_keyNo usable key in the Authorization header.
429rate_limit_exceededMore than 5 requests per second or 200 per minute. Honor the retry-after header and slow down.
429monthly_quota_exceededYour monthly credits are used up (or, on Pay as you go, you reached the spend limit you set in Billing); they come back when your monthly cycle renews (your signup or billing date). The blocked call costs nothing.
429key_limit_exceededThis key hit the monthly credit limit it was created with; it resumes when your monthly cycle renews. Other keys keep working. The blocked call costs nothing.
429too_many_concurrent_requestsToo many searches in flight at once on this account: 1 on Krill, 5 on Mako and Moby. This bounds simultaneous work, not arrival rate — wait for one to return, then retry. Nothing is billed.
502search_failedThe search couldn't complete. Safe to retry.
503server_busyMomentarily at full capacity. 503s carry a retry-after header: honor it, then retry.

Failed searches are never billed. Any non-200 response costs zero credits, whatever the reason.

13Krill, Mako & Moby

ModelCostWhat you get
krillfreeThe free tier, unlimited under fair use: the shortest useful set (~700 tokens, fewer sources), no written answer, one search at a time, served at low priority. Right when token budget matters more than depth.
mako1 creditThe lean default for agents: only the fact-carrying sentences of each source (~1k tokens total), answering in a few seconds. The answer, when requested, is one concise paragraph.
moby1.5 creditsThe deep read: the whole readable text of every source (up to ~15k tokens), for when your downstream model needs the full story. The answer, when requested, is detailed with [n] citations.

Rule of thumb: start with Mako. Reach for Moby when the task is synthesis: briefs, comparisons, anything where a missing paragraph changes the conclusion. Krill is for volume without a budget — it answers most questions, on fewer sources and without the written answer.

14Credits & limits

A Krill search is free and never touches your quota — that is what makes it unlimited. A Mako search costs 1 credit, a Moby search 1.5. The answer is always included, and failures are free. Every account gets 1,000 credits a month; they renew monthly on your signup date (billing date on paid plans) and don't roll over. Live consumption is on your Usage page, plans and pricing on Billing.

When the month’s credits run out, further requests return 429 monthly_quota_exceeded until they reset; blocked calls are never billed. On Pay as you go there is no monthly cap by default — usage is metered and invoiced at the end of each billing cycle instead; you can set an optional monthly spend limit in Billing and requests 429 past it, same as a quota. A key created with its own monthly limit returns 429 key_limit_exceeded once that limit is spent — the rest of your keys keep working. Rate limits are 5 requests per second and 200 per minute, on every plan; beyond them requests return 429 rate_limit_exceeded with a retry-after header, and cost nothing. Separately, an account can hold 5 searches in flight at once on Mako and Moby, 1 on Krill — that bounds simultaneous work rather than arrival rate, and going past it returns 429 too_many_concurrent_requests. Searching at serious scale? Talk to us.