# ContextFetch API reference > The $1 / 1,000 neural search & Markdown API for AI agents. ContextFetch is a web search API for AI agents at $1.00 per 1,000 searches, page Markdown included ($15 buys 15,000 credits that never expire): 86% less than Exa Auto/Fast ($7.00/1k) and 75% less than Exa Instant ($4.00/1k) at list prices. Results can carry highlights and a cross-encoder-pruned context: on average 69% fewer tokens than the full page (median 79%) in a benchmark on 29 synthetic docs; not yet measured on live pages. exa_py and tavily-python work after a base-URL change; agents get 25 free searches per day with no key. Base URL: https://api.contextfetch.dev Website: https://contextfetch.dev Discovery: https://contextfetch.dev/llms.txt, https://contextfetch.dev/.well-known/mcp.json, https://contextfetch.dev/robots.txt All request and response bodies are JSON unless noted. Example values (ids, scores, timings, token counts) are illustrative. ## Authentication Send a key in any of these places: - Header Authorization: Bearer YOUR_API_KEY (preferred; what tavily-python and @tavily/core send) - Header x-api-key: YOUR_API_KEY (what exa_py and exa-js send) - Body field api_key (Tavily requests only) Keys are a short prefix, then _live_, then 32 lowercase hex characters. ContextFetch stores only the SHA-256 hash of a key. Keyless tier: send no Authorization or x-api-key header, or Authorization: Bearer guest (or Bearer keyless), for 25 requests per IP per UTC day (IPv6: per /64). Every billable response carries the header X-Credits-Remaining: free requests left today for keyless calls, credits left for a paid key. ## Credits and cost - 1 credit per call to POST /v1/search (aliases /search, /exa/search), POST /v1/findSimilar (alias /findSimilar), POST /tavily/search and GET /r/{url}. - POST /v1/contents (alias /contents) costs 1 credit per URL that loads (maximum 10 URLs per call). - A paid request that fails (fetch error, search backend outage, server error) is refunded. Keyless requests count against the daily limit even when they fail. - costDollars.total = credits used x 0.001 ($1.00 per 1,000 credits at the Instant pack rate). ## POST /v1/search (aliases: POST /search, POST /exa/search) Exa-compatible search. Accepts camelCase or snake_case field names. - query (string, required) - numResults / num_results (int, default 10, max 25) - type (string): "auto", "neural", "keyword" or any other value Exa SDKs send; all run the same pipeline - includeDomains / include_domains, excludeDomains / exclude_domains (string arrays) - startPublishedDate / start_published_date (ISO date) - contents (object): text, highlights and pruned_context (or prunedContext), each true/false or an options object as Exa SDKs send it. Without contents, results have no text, highlights or prunedContext. Request: ~~~bash curl -s https://api.contextfetch.dev/v1/search \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "vector database benchmarks 2026", "numResults": 3, "excludeDomains": ["pinterest.com"], "contents": {"text": true, "highlights": true, "pruned_context": true} }' ~~~ Response: ~~~json { "requestId": "4f9d2c0e8b5a4b0f9a1e6c2d7b3f8a10", "results": [ { "id": "0b6f3c1e-5d2a-5f4b-9c8e-7a1d2e3f4b5c", "url": "https://example.com/blog/vector-database-benchmarks", "title": "Vector database benchmarks", "score": 0.93, "publishedDate": "2026-02-11", "text": "# Vector database benchmarks\n\nWe measured recall and p99 latency for...", "highlights": ["We measured recall and p99 latency for..."], "highlightScores": [0.97], "prunedContext": "We measured recall and p99 latency for...\n\n| Engine | Recall@10 | p99 |\n|---|---|---|\n| ... | ... | ... |" } ], "costDollars": {"total": 0.001}, "searchTimeMs": 412, "contextfetch": { "cache": "miss", "timingsMs": {"recall": 727, "fetch": 953, "rerank": 2331, "total": 3488}, "tokens": {"raw": 14200, "pruned": 1850, "savedPct": 87.0}, "sources": {"searxng": 102, "stackoverflow": 10, "hn": 10, "github": 10, "wikipedia": 2, "local": 0} } } ~~~ text is the page as clean Markdown (tables preserved). highlightScores are 0 to 1. prunedContext keeps the chunks that scored at or above the relevance threshold, in original order, and always at least the best chunk; chunks are 400 to 700 characters and never split a fenced code block or a Markdown table. contextfetch is an extra top-level field (SDKs ignore it): cache: "hit" when the same request was answered from the query cache, else "miss". timingsMs: Server-side milliseconds per stage and in total. tokens: Markdown tokens of the returned results and of their prunedContext (ceil(chars / 4)); pruned is null unless highlights or pruned context were requested. sources: Candidates each recall source returned for this query. ## POST /v1/contents (alias: POST /contents) Fetch pages as Markdown, with optional query-aware highlights. Only pages that load are billed; a failed URL comes back in statuses with status "error" and the reason in error.message. - urls or ids (string array, max 10; Exa SDKs may send either) - text (bool) - highlights (bool or {query, numSentences, highlightsPerUrl}): highlights are 400 to 700 character Markdown chunks, not sentences, so numSentences is accepted but ignored Request: ~~~bash curl -s https://api.contextfetch.dev/v1/contents \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "urls": ["https://docs.python.org/3/library/asyncio-task.html"], "text": true, "highlights": {"query": "task groups", "numSentences": 3, "highlightsPerUrl": 2} }' ~~~ Response: ~~~json { "requestId": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "results": [ { "id": "c7e1a2b3-4d5e-5f60-8a9b-0c1d2e3f4a5b", "url": "https://docs.python.org/3/library/asyncio-task.html", "title": "Coroutines and Tasks", "text": "# Coroutines and Tasks\n\n...", "highlights": ["...the chunk most relevant to task groups..."], "highlightScores": [0.95], "prunedContext": "## Task Groups\n\n..." } ], "costDollars": {"total": 0.001} } ~~~ ## POST /v1/findSimilar (alias: POST /findSimilar) Scrapes the URL, extracts its core topic terms and runs a semantic search that excludes the source domain. Returns the same shape as POST /v1/search. ~~~bash curl -s https://api.contextfetch.dev/v1/findSimilar \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/blog/vector-database-benchmarks", "numResults": 5, "contents": {"text": true, "highlights": true}}' ~~~ ## POST /tavily/search Tavily-compatible search. tavily-python and @tavily/core use the base URL https://api.contextfetch.dev/tavily and post to {base}/search. - api_key (string, optional if a header is sent) - query (string, required) - search_depth ("basic" or "advanced"; both cost 1 credit) - max_results (int) - include_raw_content (bool): adds raw_content as clean Markdown - include_domains, exclude_domains (string arrays) - include_answer (bool): adds answer, quoted from the most relevant passages, or null when none is relevant enough. ContextFetch runs no generative model, so this is not an LLM-written answer. Request: ~~~bash curl -s https://api.contextfetch.dev/tavily/search \ -H "Content-Type: application/json" \ -d '{ "api_key": "YOUR_API_KEY", "query": "vector database benchmarks 2026", "search_depth": "basic", "max_results": 3, "include_raw_content": true }' ~~~ Response: ~~~json { "query": "vector database benchmarks 2026", "results": [ { "title": "Vector database benchmarks", "url": "https://example.com/blog/vector-database-benchmarks", "content": "We measured recall and p99 latency for...", "raw_content": "# Vector database benchmarks\n\n...", "score": 0.93 } ], "response_time": 0.84 } ~~~ ## GET /r/{url} Reader: any public URL as Markdown (Content-Type: text/markdown). Optional ?q= prunes the page to the chunks relevant to a question. GitHub blob URLs are rewritten to raw files and arXiv PDF links to their HTML versions; for documentation root URLs ContextFetch first checks whether the site publishes /llms.txt. ~~~bash curl -si "https://api.contextfetch.dev/r/https://docs.python.org/3/library/asyncio-task.html?q=task+groups" \ -H "Authorization: Bearer YOUR_API_KEY" ~~~ Response headers: ~~~text HTTP/2 200 content-type: text/markdown; charset=utf-8 x-token-savings: 91 x-raw-tokens: 18240 x-output-tokens: 1650 ~~~ X-Token-Savings is the percentage saved: Markdown versus raw HTML, or pruned versus full Markdown when q is given. Token counts use ceil(characters / 4). ## GET /health ~~~json {"status": "ok", "redis": "remote", "qdrant": "remote", "searxng": true, "indexed_docs": 128734} ~~~ redis is "remote" or "fakeredis"; qdrant is "remote" or "memory". ## GET /buy/{pack} A link for a human, not an API call. GET https://api.contextfetch.dev/buy/{instant|builder|scale} answers 303 straight to Stripe Checkout (card, Apple Pay, Google Pay); there is no intermediate page. After payment the browser lands on https://contextfetch.dev/success?session_id=..., which shows the key and copies it to the clipboard. - https://api.contextfetch.dev/buy/instant: Instant, $15 for 15,000 credits - https://api.contextfetch.dev/buy/builder: Builder, $50 for 60,000 credits - https://api.contextfetch.dev/buy/scale: Scale, $150 for 200,000 credits Optional auto-reload (off unless asked): https://api.contextfetch.dev/buy/builder?auto_reload=1&threshold=1000&reload_pack=builder&monthly_max_usd=500 saves the card and turns auto-reload on with that threshold, reload pack and monthly cap. ## GET /billing/session/{session_id} Used by the success page. Returns the new key once the payment webhook has provisioned it. The plaintext key stays retrievable for 24 hours. Before provisioning: HTTP 404 {"status": "pending"}. Poll with backoff. ## Payment loop for agents (HTTP 402) 1. Call without a key. Read X-Credits-Remaining for the free requests left today. 2. When the allowance or a key's credits run out, the API answers HTTP 402 (432 on /tavily/* routes) with the header X-Payment-URL: https://api.contextfetch.dev/buy/instant and this body: ~~~json { "error": "keyless_limit", "message": "Daily free tier limit (25 requests) reached. Reload 15,000 searches for $15.", "checkout_url": "https://contextfetch.dev/#buy", "pricing": { "pack_size": 15000, "price_usd": 15, "unit_cost_usd": 0.001 }, "packs": [ { "id": "instant", "name": "Instant", "credits": 15000, "price_usd": 15, "cost_per_1k": 1, "recommended": false, "checkout_url": "https://api.contextfetch.dev/buy/instant" }, { "id": "builder", "name": "Builder", "credits": 60000, "price_usd": 50, "cost_per_1k": 0.83, "recommended": true, "checkout_url": "https://api.contextfetch.dev/buy/builder" }, { "id": "scale", "name": "Scale", "credits": 200000, "price_usd": 150, "cost_per_1k": 0.75, "recommended": false, "checkout_url": "https://api.contextfetch.dev/buy/scale" } ], "action": { "type": "payment_link", "url": "https://api.contextfetch.dev/buy/instant", "prompt_to_user": "ContextFetch credits ran out. Reload 15,000 searches for $15: https://api.contextfetch.dev/buy/instant" } } ~~~ 3. Show your human action.prompt_to_user and stop retrying. action.url and every packs[].checkout_url are our own /buy links, one click to Stripe Checkout. checkout_url (https://contextfetch.dev/#buy) stays for older clients. 4. After payment the success page shows the key and copies it to the clipboard. Retry with Authorization: Bearer . ## GET /v1/balance Key in the Authorization or x-api-key header. A ?key= query parameter is not accepted, so keys never land in access logs. Costs no credits. MCP tool: contextfetch_balance. ~~~bash curl -s https://api.contextfetch.dev/v1/balance -H "Authorization: Bearer YOUR_API_KEY" ~~~ ~~~json { "credits": 14873, "auto_reload": { "enabled": true, "threshold": 1000, "pack": "builder", "monthly_max_usd": 500, "spent_this_month_usd": 50, "last_error": null }, "burn_rate_per_day": 412 } ~~~ burn_rate_per_day is the average credits deducted per day over the last 7 days. last_error is set when an auto-reload payment was declined or needed authentication; auto-reload is then off. Unknown key: HTTP 401 {"error": "invalid_api_key"}. ## POST /v1/billing/auto-reload Body {"enabled": true, "threshold": 1000, "pack": "builder", "monthly_max_usd": 500} with the key in a header. Returns the same shape as GET /v1/balance. MCP tool: contextfetch_auto_reload. - enabled (bool, required); threshold (int, 100 to 100,000, default 1,000); pack ("instant", "builder", "scale", default "builder"); monthly_max_usd (default 500). - After a deduction leaves the balance strictly below threshold, ContextFetch charges the saved card for pack once, if this UTC month's auto-reload spend plus the pack price stays within monthly_max_usd. Credits arrive when the payment succeeds. - Needs a card saved at checkout (buy with auto_reload=1); otherwise HTTP 409 with a buy link. - Through the API a key can turn auto-reload off, lower values or change the pack, but monthly_max_usd can never go above $500; a higher cap needs the browser success page. ## POST /billing/webhook Stripe webhook endpoint, not for API clients. ## Errors ~~~json {"error": "invalid_api_key"} ~~~ - 400 {"error": "url_not_allowed"}: the URL is not public http(s) (private, loopback, link-local and similar addresses are rejected, including after redirects) - 401 {"error": "invalid_api_key"} - 402 {"error": "insufficient_credits" or "keyless_limit", "message", "checkout_url", "pricing", "packs", "action"} with header X-Payment-URL: https://api.contextfetch.dev/buy/instant - 409 {"error": "no_payment_method", "message": "..."}: auto-reload requested without a saved card; the message carries a buy link - 422: request validation error (for example a query over 2,000 characters) - 502 {"error": "fetch_failed", "message": "..."}: the target page could not be fetched - 503 {"error": "recall_unavailable", "message": "..."}: web search backends are down; retry shortly - On /tavily/* routes errors use Tavily's shape, {"detail": {"error": "..."}}, and running out of credits is HTTP 432 as on Tavily, so tavily-python shows the message Fetch limits: http/https only, at most 5 redirects, 5 MB body, 15 s timeout. ## MCP server Package: @contextfetch/mcp (https://www.npmjs.com/package/@contextfetch/mcp). Works keyless. ~~~bash claude mcp add contextfetch -- npx -y @contextfetch/mcp claude mcp add contextfetch -e CONTEXTFETCH_API_KEY=YOUR_API_KEY -- npx -y @contextfetch/mcp ~~~ Claude Desktop, Cursor and other clients: ~~~json {"mcpServers":{"contextfetch":{"command":"npx","args":["-y","@contextfetch/mcp"],"env":{"CONTEXTFETCH_API_KEY":"YOUR_API_KEY"}}}} ~~~ Environment: CONTEXTFETCH_API_KEY (optional; keyless when unset), CONTEXTFETCH_API_BASE (default https://api.contextfetch.dev). Machine-readable manifest with each tool's JSON Schema: https://contextfetch.dev/.well-known/mcp.json Tools: - contextfetch_search {query, num_results = 5, include_domains?, exclude_domains?}: Search the web and get LLM-ready Markdown. Each result has its title, URL and only the passages relevant to the query (cross-encoder pruned context), so it costs far fewer tokens than raw pages. Use for current events, documentation, APIs, research and fact checking. - contextfetch_extract_url {url, query?}: Fetch a public web page (docs, articles, GitHub files, arXiv papers) as clean Markdown with tables preserved. Pass `query` to get only the passages relevant to it instead of the whole page. - contextfetch_find_similar {url, num_results = 5}: Find pages similar to a given URL (alternatives, related docs, competing libraries), excluding the source domain. Returns titles, URLs and the most relevant passages as Markdown. - contextfetch_deep_research {query, sub_queries?, num_results_per_query = 5}: Research a question in one call: runs up to 3 web searches (sub-queries) in parallel, dedupes the sources across them and returns one Markdown evidence pack with each source's title, URL, score and query-pruned context. Each sub-query costs 1 credit. Pass your own `sub_queries` (up to 3 distinct angles) for better coverage; if omitted they are derived from `query`. - contextfetch_balance {}: Show the API key's remaining credits, daily burn rate (7-day average) and auto-reload settings, including the last auto-reload error if a payment failed. Needs CONTEXTFETCH_API_KEY; free. Check it before long jobs. - contextfetch_auto_reload {enabled, threshold?, pack?, monthly_max_usd?}: Turn auto-reload on or off, or change it: when credits drop below `threshold`, the saved card buys `pack` again, up to `monthly_max_usd` per month. Needs CONTEXTFETCH_API_KEY and a card saved at checkout. The monthly cap cannot be raised above $500 here. When credits or the keyless allowance run out, tools return the payment link (https://api.contextfetch.dev/buy/instant) as an error result instead of crashing. ## SDK examples Python, exa_py: ~~~python # pip install exa-py from exa_py import Exa exa = Exa(api_key="YOUR_API_KEY", base_url="https://api.contextfetch.dev") res = exa.search( "vector database benchmarks 2026", num_results=5, contents={"text": True, "highlights": True}, ) for r in res.results: print(r.url, r.highlights) ~~~ TypeScript, exa-js: ~~~ts // npm i exa-js import Exa from "exa-js"; const exa = new Exa("YOUR_API_KEY", "https://api.contextfetch.dev"); const res = await exa.search("vector database benchmarks 2026", { numResults: 5, contents: { text: true, highlights: true }, }); for (const r of res.results) console.log(r.url, r.highlights); ~~~ Python, tavily-python: ~~~python # pip install tavily-python from tavily import TavilyClient tavily = TavilyClient( api_key="YOUR_API_KEY", # or "keyless" for the free tier api_base_url="https://api.contextfetch.dev/tavily", ) res = tavily.search("vector database benchmarks 2026", max_results=5, include_raw_content=True) for r in res["results"]: print(r["url"], r["score"]) ~~~ TypeScript, @tavily/core: ~~~ts // npm i @tavily/core import { tavily } from "@tavily/core"; const tvly = tavily({ apiKey: "YOUR_API_KEY", apiBaseURL: "https://api.contextfetch.dev/tavily", }); const res = await tvly.search("vector database benchmarks 2026", { searchDepth: "basic", maxResults: 5 }); for (const r of res.results) console.log(r.url, r.score); ~~~ ## Pricing - Instant (pack id "instant"): $15 for 15,000 credits, $1.00 per 1,000. Buy: https://api.contextfetch.dev/buy/instant - Builder (pack id "builder"): $50 for 60,000 credits, $0.83 per 1,000 (most popular). Buy: https://api.contextfetch.dev/buy/builder - Scale (pack id "scale"): $150 for 200,000 credits, $0.75 per 1,000. Buy: https://api.contextfetch.dev/buy/scale - One-time payment. Credits never expire. Full Markdown and pruning included. - Keyless: 25 requests per IP per UTC day, no signup. - Optional auto-reload on any pack, off by default, capped per month (see POST /v1/billing/auto-reload). ## Comparison (list prices as of 2026-10-16) - ContextFetch: $1.00 ($0.75 on the $150 pack). Markdown: Yes, on request (contents), no extra charge. Pruned context: Yes: cross-encoder prunedContext + highlights. Keyless MCP: Yes: 25 searches/day per IP via npx. - Exa: $7.00 auto/fast, $4.00 instant (up to 10 results). Markdown: Extra: $1.00 per 1,000 pages per content type. Pruned context: Highlights, $1.00 per 1,000 pages. Keyless MCP: Yes: hosted, rate-limited. Sources: https://exa.ai/pricing, https://exa.ai/docs/reference/exa-mcp - Tavily: $8.00 basic, $16.00 advanced (pay-as-you-go). Markdown: Optional raw_content. Pruned context: Relevance-ranked content snippets. Keyless MCP: No: API key or OAuth sign-in. Sources: https://www.tavily.com/pricing, https://docs.tavily.com/documentation/api-credits, https://docs.tavily.com/documentation/mcp - Firecrawl: $1.20 (Scale, billed yearly) to $7.60 (Hobby, billed monthly); 2 credits per 10 results. Markdown: Extra: +1 credit per scraped page. Pruned context: Highlight format, +4 credits per page. Keyless MCP: Yes: hosted, daily limits. Sources: https://www.firecrawl.dev/pricing, https://docs.firecrawl.dev/features/search, https://docs.firecrawl.dev/mcp-server - Serper: $1.00 ($50 pack) down to $0.30 at volume; credits expire after 6 months. Markdown: No: Google SERP JSON and snippets. Pruned context: No. Keyless MCP: No official server (community servers need a key). Sources: https://serper.dev/ Latency and result quality are not compared unless measured with your own keys. Exa, Tavily, Firecrawl and Serper are trademarks of their respective owners. ContextFetch is an independent product and is not affiliated with, endorsed by or sponsored by any of them.