# Answer (/ai/answer) `POST https://api.keirolabs.cloud/api/v2/answer` Cost: 1 credits paid · 2 free · Latency: ~4 s Provides an authoritative, LLM-synthesized direct answer to your question with cited sources. Powered by a multi-provider reasoning stack with Keiro retrieval and citation synthesis layered on top. Supports both Server-Sent Events (SSE) streaming and standard JSON responses. **When to use it.** Use Answer when you need a direct, synthesized response to a question — like asking an expert. Returns a text answer with cited sources. Ideal for chatbots, Q&A systems, and grounding LLM outputs. **When not to.** Don't use Answer if you need raw search results (use Search), page content (use Search+Content), or multi-step autonomous research with charts (use Web Agent). ## Request **cURL** ```bash curl -N -X POST https://api.keirolabs.cloud/api/v2/answer \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "what is the context window of claude opus?"}' ``` **Python** ```python import os, requests res = requests.post( "https://api.keirolabs.cloud/api/v2/answer", headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"}, json={ "query": "what is the context window of claude opus?" }, stream=True, ) for line in res.iter_lines(): print(line.decode()) ``` **Node.js** ```js const res = await fetch("https://api.keirolabs.cloud/api/v2/answer", { method: "POST", headers: { Authorization: `Bearer ${process.env.KEIRO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "query": "what is the context window of claude opus?" }), }); for await (const chunk of res.body) process.stdout.write(chunk); ``` ## Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | yes | The question to answer. No hard length limit, but keep under 1,000 chars for best synthesis quality. | | `stream` | boolean | no | Stream the response via Server-Sent Events (SSE). Default: true. When true, the response uses text/event-stream and emits chunk events with the running answer text, followed by a final done event. | | `noCache` | boolean | no | Bypass the search cache and force a fresh web search. Default: false. | ## Response ```json title="200 OK" { "answer": "The 2024 US Presidential Election was won by Donald Trump (R), defeating incumbent Kamala Harris (D). Trump secured 312 electoral votes to Harris's 226, with a decisive popular-vote margin of ~2.3%.", "sources": [ { "title": "2024 United States presidential election - Wikipedia", "url": "", "domain": "wikipedia", "snippet": "..." }, { "title": "Federal Election Commission official results", "url": "", "domain": "fec.gov", "snippet": "..." } ], "stats": { "sources_count": 2, "latency_ms": 3820, "credits_used": 5, "attempt": "ai-search" }, "creditsRemaining": 1095 } ``` | Name | Type | Required | Description | | --- | --- | --- | --- | | `answer` | string | no | LLM-synthesized answer text with inline citations. | | `sources` | array | no | List of sources cited in the answer. | | `sources[].title` | string | no | Title of the source page. | | `sources[].url` | string | no | URL of the source. | | `sources[].domain` | string | no | Domain name of the source. | | `sources[].snippet` | string | no | Relevant excerpt from the source. | | `stats.sources_count` | integer | no | Number of sources used. | | `stats.latency_ms` | integer | no | Total processing time. | | `stats.credits_used` | number | no | Credits charged (2 on Free, 1 on paid plans). | | `creditsRemaining` | number | no | Your remaining credit balance. | ## Errors | Status | Code | Cause | Fix | | --- | --- | --- | --- | | `400` | `VALIDATION_ERROR` | Missing query | Provide a non-empty query string. | | `401` | `UNAUTHORIZED` | Missing or invalid API key | Check your Authorization: Bearer header. | | `402` | `INSUFFICIENT_CREDITS` | Not enough credits (need 2, or 1 on paid plans) | Top up or upgrade at /dashboard/billing. | | `429` | `RATE_LIMIT_EXCEEDED` | Too many requests | Answer is rate-limited at 10/min (free) to 200/min (enterprise). | | `502` | `SEARCH_UNAVAILABLE` | LLM provider failure | Credits auto-refunded. Retry with backoff. | ## Rate limits | Tier | Limit | | --- | --- | | Free | 10/min | | Starter | 30/min | | Pro | 60/min | | Startup | 120/min | | Enterprise | 200/min | Over the limit you get a `429`. See [Rate limits](/rate-limits) for headers and backoff. # Web Agent (/ai/web-agent) `POST https://api.keirolabs.cloud/api/webagents/research/stream` Cost: 7 credits + 1 per 1k tokens · Latency: Streams Autonomous research agent that streams formatted markdown, validates facts, and returns structured data via Server-Sent Events (SSE). **When to use it.** Use when you want an agentic system that streams its thought process, fact checks, and outputs multiple structured components like timelines and tables. **When not to.** Do not use if you only need a quick SERP or a single extracted article. ## Request **cURL** ```bash curl -N -X POST https://api.keirolabs.cloud/api/webagents/research/stream \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "Compare React 19 and Vue 3.5 for a large dashboard", "research_depth": "medium", "outputs": ["summary", "sources"]}' ``` **Python** ```python import os, requests res = requests.post( "https://api.keirolabs.cloud/api/webagents/research/stream", headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"}, json={ "query": "Compare React 19 and Vue 3.5 for a large dashboard", "research_depth": "medium", "outputs": [ "summary", "sources" ] }, stream=True, ) for line in res.iter_lines(): print(line.decode()) ``` **Node.js** ```js const res = await fetch("https://api.keirolabs.cloud/api/webagents/research/stream", { method: "POST", headers: { Authorization: `Bearer ${process.env.KEIRO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "query": "Compare React 19 and Vue 3.5 for a large dashboard", "research_depth": "medium", "outputs": [ "summary", "sources" ] }), }); for await (const chunk of res.body) process.stdout.write(chunk); ``` ## Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | yes | The topic to research. | | `research_depth` | string | no | low \| medium \| high. Default: medium | | `research_mode` | string | no | auto \| academic \| financial \| compare \| fact_check | | `outputs` | array | no | Array of expected outputs (e.g., ["summary", "detailed_answer", "claims", "sources", "table", "timeline", "premise_validation"]) | ## Response ```json title="200 OK" { "type": "chunk", "section": "summary", "data": "React is a UI library backed by Meta. Vue is a progressive framework...", "metadata": { "token_usage": { "total_tokens": 15420 } } } ``` | Name | Type | Required | Description | | --- | --- | --- | --- | | `type` | string | no | Event type ("start", "chunk", "error", "done"). | | `section` | string | no | The output section being streamed (e.g. "summary", "timeline"). | | `data` | string/object | no | The chunk content (string for markdown, object for JSON arrays like timeline). | | `metadata` | object | no | Available on "done" event. Contains token_usage and credits. | ## Other routes - `POST /api/webagents/research`: Non-streaming (synchronous) version. Wait for the entire task to complete and return a single JSON object. Use this for automated background processing where real-time UI updates are not needed. ## Errors | Status | Code | Cause | Fix | | --- | --- | --- | --- | | `400` | `VALIDATION_ERROR` | Missing query | Provide a query. | | `401` | `UNAUTHORIZED` | Missing or invalid API key | Check your Authorization header. | ## Rate limits | Tier | Limit | | --- | --- | | Free | 3/min | | Starter | 10/min | | Pro | 20/min | | Startup | 40/min | | Enterprise | 80/min | Over the limit you get a `429`. See [Rate limits](/rate-limits) for headers and backoff. # Authentication (/authentication) Every request needs your key in the `Authorization` header: ```http Authorization: Bearer keiro_your_api_key Content-Type: application/json ``` Create and revoke keys in [Dashboard → API Keys](https://platform.keirolabs.cloud/dashboard/keys). A key is shown once when you create it. > **Keep keys server-side** > Never ship a key in browser or mobile code. Read it from an environment variable on your server and call Keiro from there. A missing or wrong key returns `401 UNAUTHORIZED` and costs nothing. # Batch (/bulk/batch) `POST https://api.keirolabs.cloud/api/v2/search/batch` Cost: 0.5 credits paid · 1 free per query · Latency: Async Submit up to 75 search queries in a single async job. The POST returns a job ID immediately; poll the GET endpoint until the job reaches completed (or failed/partial). Polling is free of charge and rate-limited separately. Jobs are accessible for 1 hour after submission. **When to use it.** Use Batch when you need to run many searches (1-75) asynchronously. Submit the job, poll for results. Costs 1 credit/query (0.5 on Essential and Pro, free on Startup). Ideal for bulk SERP analysis, keyword research, and data pipelines. **When not to.** Don't use Batch for a single query (use Search — simpler and faster). Don't use if you need content extraction (Batch only returns SERP results, not page content). ## Request **cURL** ```bash curl -X POST https://api.keirolabs.cloud/api/v2/search/batch \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"queries": ["python web scraping", "golang concurrency", "react hooks tutorial"], "maxResults": 10}' ``` **Python** ```python import os, requests res = requests.post( "https://api.keirolabs.cloud/api/v2/search/batch", headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"}, json={ "queries": [ "python web scraping", "golang concurrency", "react hooks tutorial" ], "maxResults": 10 }, ) print(res.json()) ``` **Node.js** ```js const res = await fetch("https://api.keirolabs.cloud/api/v2/search/batch", { method: "POST", headers: { Authorization: `Bearer ${process.env.KEIRO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "queries": [ "python web scraping", "golang concurrency", "react hooks tutorial" ], "maxResults": 10 }), }); console.log(await res.json()); ``` ## Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `queries` | string[] | yes | Array of queries (1-75 per submission, server-clamped). Each query is 1-500 chars. Empty/invalid entries are filtered out before processing. | | `maxResults` | integer | no | Results per query. Default: 10. Max: 50 (server-clamped). | ## Response ```json title="200 OK" { "id": "29938885db05eca3", "status": "processing", "total": 3, "message": "batch submitted, poll GET /api/v2/search/batch/29938885db05eca3", "creditsCharged": 0.5, "creditsRemaining": 1136 } ``` | Name | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | no | Job ID. Poll GET /api/v2/search/batch/{id} to check status. | | `status` | string | no | Job status: "processing", "completed", "failed", or "partial". | | `total` | integer | no | Total number of queries in the batch. | | `message` | string | no | Human-readable status message with polling URL. | | `creditsCharged` | number | no | Credits charged (0.5/query for paid, 1/query for free). | | `creditsRemaining` | number | no | Your remaining credit balance. | ## Polling for results Submitting returns a job id. Poll `GET /api/v2/search/batch/:id` until the job is done. ```bash curl https://api.keirolabs.cloud/api/v2/search/batch/$JOB_ID \ -H "Authorization: Bearer $KEIRO_API_KEY" ``` ## Errors | Status | Code | Cause | Fix | | --- | --- | --- | --- | | `400` | `VALIDATION_ERROR` | Missing queries array or too many queries | Provide 1-75 queries as a string array. | | `401` | `UNAUTHORIZED` | Missing or invalid API key | Check your Authorization: Bearer header. | | `402` | `INSUFFICIENT_CREDITS` | Not enough credits for all queries | Need 1 credit per query (0.5 on Essential and Pro; free on Startup). | | `403` | `PLAN_UPGRADE_REQUIRED` | Batch requires a paid plan (or a balance of 1,000+ credits) | Upgrade to Starter or buy more credits at /dashboard/billing. | | `429` | `RATE_LIMIT_EXCEEDED` | Too many batch submissions | Batch is rate-limited per hour. Check Retry-After header. | ## Rate limits | Tier | Limit | | --- | --- | | Free | Not available | | Starter | 10/hour | | Pro | 25/hour | | Startup | 50/hour | | Enterprise | 200/hour | Over the limit you get a `429`. See [Rate limits](/rate-limits) for headers and backoff. # Extract (/content/extract) `POST https://api.keirolabs.cloud/api/v2/extract` Cost: 2 credits · Latency: 1s - 5s Extract clean markdown/text content from a specific URL. Steps up through progressively heavier rendering when a page blocks simple requests, so it gets past the defenses that stop a plain HTTP client. Supports semantic truncation by objective, optional vector embeddings, and structured JSON extraction via a user-supplied JSON Schema. **When to use it.** Use Extract when you have a specific URL and need its clean content as markdown. Supports JS-rendered pages (deep mode), structured extraction (JSON Schema), and semantic filtering (objective). **When not to.** Don't use Extract if you need to search first (use Search or Search+Content — they search AND extract in one call). ## Request **cURL** ```bash curl -X POST https://api.keirolabs.cloud/api/v2/extract \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://stripe.com/pricing", "mode": "lite"}' ``` **Python** ```python import os, requests res = requests.post( "https://api.keirolabs.cloud/api/v2/extract", headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"}, json={ "url": "https://stripe.com/pricing", "mode": "lite" }, ) print(res.json()) ``` **Node.js** ```js const res = await fetch("https://api.keirolabs.cloud/api/v2/extract", { method: "POST", headers: { Authorization: `Bearer ${process.env.KEIRO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "url": "https://stripe.com/pricing", "mode": "lite" }), }); console.log(await res.json()); ``` ## Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | The fully-qualified URL to extract. Must be http(s); SSRF-blocked internal targets return 403. | | `mode` | enum | no | Extraction mode. Options: "medium" (default - full extraction + RAG enrichment), "deep" (renders JavaScript-heavy pages for sites that need it), "heavy" (maximum-effort rendering for the hardest pages, at full price). | | `objective` | string | no | Semantic objective. Only text relevant to this goal is kept; everything else is dropped to reduce noise. | | `json_schema` | object | no | JSON Schema for structured extraction. Only honored when mode is not "light". No extra charge. | | `expand_content` | boolean | no | Clicks "Read More"/"Show more" links before extracting. Default: false. | | `max_chars` | integer | no | Alias for max_chars_source. Max characters extracted. Default: 7,000. | | `max_chars_source` | integer | no | Max characters per source. Default: 7,000. | ## Response ```json title="200 OK" { "data": { "results": [ { "url": "https://news.ycombinator.com", "status": 200, "size_bytes": 28412, "time_taken": 1.83, "text": "**Hacker News**\n\n1. Show HN: I built a distributed lock service in Rust...", "json_schema": { "data": { "title": "Hacker News", "items": [ { "rank": 1, "title": "Show HN: I built a distributed lock service in Rust", "url": "..." } ] } } } ], "latencyMs": 1830 }, "creditsRemaining": 1137, "credits_used": 2 } ``` | Name | Type | Required | Description | | --- | --- | --- | --- | | `data.results` | array | no | List of extraction results (one per URL). | | `data.results[].url` | string | no | The URL that was extracted. | | `data.results[].status` | integer | no | HTTP status code from the fetch (200 = success). | | `data.results[].text` | string | no | Clean markdown content extracted from the page. | | `data.results[].json_schema` | object | no | Structured data extracted using your JSON Schema (if provided). | | `data.results[].size_bytes` | integer | no | Size of extracted content in bytes. | | `data.results[].time_taken` | number | no | Extraction time in seconds. | | `data.latencyMs` | integer | no | Total request latency. | | `credits_used` | number | no | Credits charged: 2, json_schema included. | | `creditsRemaining` | number | no | Your remaining credit balance. | ## Other routes - `POST /api/web-crawler`: Legacy alias for backward compatibility. Same handler, same cost. ## Errors | Status | Code | Cause | Fix | | --- | --- | --- | --- | | `400` | `VALIDATION_ERROR` | Missing or invalid URL | Provide a valid http(s) URL. | | `401` | `UNAUTHORIZED` | Missing or invalid API key | Check your Authorization: Bearer header. | | `402` | `INSUFFICIENT_CREDITS` | Not enough credits | Buy more credits or upgrade at /dashboard/billing. Nothing was charged. | | `403` | `FORBIDDEN` | URL is blocked (SSRF protection) | Internal/private URLs are blocked. Use public URLs only. | | `429` | `RATE_LIMIT_EXCEEDED` | Too many requests | Check X-RateLimit-Remaining header. | | `502` | `SEARCH_UNAVAILABLE` | Page fetch or extraction failed | Credits auto-refunded. Try mode="deep" for JS-heavy pages. | ## Rate limits | Tier | Limit | | --- | --- | | Free | 10/min | | Starter | 30/min | | Pro | 60/min | | Startup | 120/min | | Enterprise | 200/min | Over the limit you get a `429`. See [Rate limits](/rate-limits) for headers and backoff. # Search + Content (/content/search-content) `POST https://api.keirolabs.cloud/api/v2/search/content` Cost: 1.5 credits paid · 3 free · Latency: ~3 s Execute a search and scrape clean, markdown-formatted text from the top results in a single, unified call. The perfect foundation for RAG pipelines, LLM summarization, and AI agents. Returns enriched content with TLDR, tags, entities, and optional vector embeddings. **When to use it.** Use Search+Content when you need the actual page text (markdown) for RAG, summarization, or AI agents. Returns clean content + TLDR + tags + optional embeddings in a single call. **When not to.** Don't use if you only need URLs (use Search — cheaper), or if you need an LLM-synthesized answer (use Answer). ## Request **cURL** ```bash curl -X POST https://api.keirolabs.cloud/api/v2/search/content \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "how to reverse a linked list in python", "maxResults": 4, "mode": "medium"}' ``` **Python** ```python import os, requests res = requests.post( "https://api.keirolabs.cloud/api/v2/search/content", headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"}, json={ "query": "how to reverse a linked list in python", "maxResults": 4, "mode": "medium" }, ) print(res.json()) ``` **Node.js** ```js const res = await fetch("https://api.keirolabs.cloud/api/v2/search/content", { method: "POST", headers: { Authorization: `Bearer ${process.env.KEIRO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "query": "how to reverse a linked list in python", "maxResults": 4, "mode": "medium" }), }); console.log(await res.json()); ``` ## Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | yes | The search query (1-500 chars). | | `maxResults` | integer | no | Number of pages to scrape. Default: 3. Max: 5 (server-clamped). | | `mode` | enum | no | Extraction depth/method. Options: "medium" (full extraction + RAG enrichment, default), "deep" (renders JavaScript-heavy sites). | | `objective` | string | no | Semantic objective extracts only text relevant to this goal, trimming noise from the page. | | `country` | string | no | ISO-3166-1 alpha-2 country code to bias results. (Alias: location.) | | `max_chars_source` | integer | no | Max characters extracted per source. Range: 1,000-10,000. Default: 7,000. | | `max_chars_total` | integer | no | Max total characters across all sources. Range: 5,000-50,000. Default: 30,000. | | `embeddings.enabled` | boolean | no | Generate vector embeddings for each content chunk. Default: false. Only honored in medium or deep mode. | | `embeddings.dimensions` | integer | no | Matryoshka dimension truncation. Options: 384, 512, 768, 1024. Default: 768. | | `embeddings.chunkSize` | integer | no | Characters per chunk for embedding. Range: 1500-2000. Default: 1500. Values outside this range are clamped. | ## Response ```json title="200 OK" { "query": "how to reverse a linked list in python", "results": [ { "title": "Reverse a Linked List - GeeksforGeeks", "url": "https://www.geeksforgeeks.org/python/reverse-a-linked-list/", "snippet": "Step-by-step guide with iterative and recursive solutions.", "content": "Reverse a Linked List\nGiven the head of a linked list, the iterative approach uses three pointers...", "metadata": { "word_count": 1200 }, "enrichment": { "tldr": "Reverse a linked list by flipping next-pointers iteratively in O(n) time and O(1) space.", "tags": [ "python", "linked-list", "algorithms" ], "entities": { "key_entities": [ "pointer", "head", "next" ] } }, "embeddings": { "model": "baai/bge-m3", "dimensions": 768, "chunkSize": 1500, "chunkCount": 3, "totalChunks": 3, "chunks": [ { "index": 0, "embedding": [ 0.0123, -0.0456, 0.0789, "..." ] }, { "index": 1, "embedding": [ 0.0234, -0.0567, 0.089, "..." ] }, { "index": 2, "embedding": [ 0.0345, -0.0678, 0.0901, "..." ] } ], "truncated": false, "error": null }, "error": "" } ], "count": 3, "latencyMs": 2706, "creditsCharged": 1.5, "creditsRemaining": 1138.5 } ``` | Name | Type | Required | Description | | --- | --- | --- | --- | | `results` | array | no | List of search results with extracted content. | | `results[].title` | string | no | Page title. | | `results[].url` | string | no | Full URL of the page. | | `results[].content` | string | no | Clean markdown-formatted page content (main text, no nav/ads). | | `results[].enrichment` | object | no | RAG enrichment: TLDR summary, tags, and named entities. | | `results[].enrichment.tldr` | string | no | Auto-generated one-line summary of the page. | | `results[].enrichment.tags` | array | no | Topic tags extracted from the content. | | `results[].embeddings` | object | no | Vector embeddings (only if embeddings.enabled=true). | | `results[].embeddings.chunks` | array | no | Array of { index, embedding } objects, one per chunk. | | `results[].error` | string | no | Empty string if extraction succeeded; error message if it failed. | | `creditsCharged` | number | no | Credits deducted (1.5 for paid tiers, 3 for free). | | `creditsRemaining` | number | no | Your remaining credit balance. | ## Errors | Status | Code | Cause | Fix | | --- | --- | --- | --- | | `400` | `VALIDATION_ERROR` | Missing query or invalid mode | Ensure query is provided and mode is "medium" or "deep". | | `401` | `UNAUTHORIZED` | Missing or invalid API key | Check your Authorization: Bearer header. | | `402` | `INSUFFICIENT_CREDITS` | Not enough credits | Buy more credits or upgrade your plan. | | `429` | `RATE_LIMIT_EXCEEDED` | Too many requests | Check X-RateLimit-Remaining header. Wait for Retry-After seconds. | | `502` | `SEARCH_UNAVAILABLE` | Extraction or search failure | Credits auto-refunded. Retry or try mode="medium" instead of "deep". | ## Rate limits | Tier | Limit | | --- | --- | | Free | 10/min | | Starter | 20/min | | Pro | 40/min | | Startup | 80/min | | Enterprise | 120/min | Over the limit you get a `429`. See [Rate limits](/rate-limits) for headers and backoff. # Sitemap (/content/sitemap) `POST https://api.keirolabs.cloud/api/v2/sitemap` Cost: 1 credit · Latency: ~1–5 s Every URL a site publishes: reads robots.txt, follows sitemap indexes (and .gz files), and returns each page URL with its lastmod. Filter by path or by date. **When to use it.** Use Sitemap to list every page on a site before extracting: docs crawls, competitor page monitoring, finding new blog posts by date. **When not to.** Don't use Sitemap for page content (use Extract) or for sites that publish no sitemap. ## Request **cURL** ```bash curl -X POST https://api.keirolabs.cloud/api/v2/sitemap \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "stripe.com", "path_prefix": "/blog", "limit": 500}' ``` **Python** ```python import os, requests res = requests.post( "https://api.keirolabs.cloud/api/v2/sitemap", headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"}, json={ "url": "stripe.com", "path_prefix": "/blog", "limit": 500 }, ) print(res.json()) ``` **Node.js** ```js const res = await fetch("https://api.keirolabs.cloud/api/v2/sitemap", { method: "POST", headers: { Authorization: `Bearer ${process.env.KEIRO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "url": "stripe.com", "path_prefix": "/blog", "limit": 500 }), }); console.log(await res.json()); ``` ## Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | Domain or site URL, e.g. "stripe.com". | | `path_prefix` | string | no | Only return URLs whose path starts with this, e.g. "/blog". | | `since` | string | no | ISO date. Only URLs with a lastmod on or after it (URLs without lastmod are skipped and counted). | | `limit` | integer | no | Max URLs returned. Default and max: 50000. | ## Response ```json title="200 OK" { "site": "https://stripe.com", "source": "robots.txt", "sitemaps": [ "https://stripe.com/sitemap/sitemap.xml" ], "total_urls": 2, "truncated": false, "urls": [ { "url": "https://stripe.com/pricing", "lastmod": "2026-10-01" }, { "url": "https://stripe.com/blog", "lastmod": null } ], "creditsCharged": 1 } ``` | Name | Type | Required | Description | | --- | --- | --- | --- | | `urls` | array | no | Page URLs with lastmod (null when the sitemap has none). | | `sitemaps` | array | no | Sitemap files that were read. | | `source` | string | no | "robots.txt" if declared there, else "common paths". | | `truncated` | boolean | no | True when limit or the 60-file cap was hit. | | `skipped_without_lastmod` | integer | no | Only with since: URLs dropped because they carry no lastmod. | ## Errors | Status | Code | Cause | Fix | | --- | --- | --- | --- | | `400` | `VALIDATION_ERROR` | Missing url or bad path_prefix/since/limit | Send a domain; path_prefix must start with /. | | `402` | `INSUFFICIENT_CREDITS` | Not enough credits | Buy more credits or upgrade your plan at /dashboard/billing. | | `400` | `NO_SITEMAP` | No sitemap in robots.txt or common paths | Not charged. Use Search or Extract to discover pages. | ## Rate limits | Tier | Limit | | --- | --- | | Free | 5/min | | Starter | 20/min | | Pro | 40/min | | Startup | 80/min | | Enterprise | 120/min | Over the limit you get a `429`. See [Rate limits](/rate-limits) for headers and backoff. # Credits (/credits) Every call spends credits from your balance. Plans get lower rates on most endpoints. Signup and pack credits pay the pay-as-you-go rate, whatever the balance. | Endpoint | Plans | Pay as you go | | --- | --- | --- | | [Google SERP](/search/google-serp) | 2 | 2 | | [Search](/search/search) | 0.5 | 1 | | [Lite](/search/lite) | 0.0375 | 1 | | [Slow](/search/slow) | Free, unlimited | Not available | | [Deep Search](/search/deep-search) | 5 | 5 | | [Search + Content](/content/search-content) | 1.5 | 3 | | [Extract](/content/extract) | 2 | 2 | | [Sitemap](/content/sitemap) | 1 | 1 | | [Answer](/ai/answer) | 1 | 2 | | [Web Agent](/ai/web-agent) | 7 + 1 per 1,000 tokens | 7 + 1 per 1,000 tokens | | [Batch](/bulk/batch) | 0.5 per query | Not available | ## Where credits come from | Credits | How you get them | When they end | | --- | --- | --- | | Signup | 750 when you create an account, once | Never refilled | | Plan | Each billing cycle on Essential, Pro or Startup | Reset at the end of the cycle; unused ones don't roll over | | Pack | Top-ups from Billing ($10, $30, $100) | 12 months after your latest top-up. Each top-up moves the whole pack balance to a fresh 12 months | Plan and signup credits are spent first, then pack credits. ## How charging works - Each response includes `creditsCharged` and `creditsRemaining`. - Requests rejected before any work is done (`400`, `401`) cost nothing. - When an upstream fails (`502`), the credits are refunded automatically. - With too few credits you get `402 INSUFFICIENT_CREDITS`. Buy credits or change plan in [Billing](https://platform.keirolabs.cloud/dashboard/billing). # Errors (/errors) Errors return a JSON body with a machine-readable `code` and a human-readable message. | Status | Code | Cause | What to do | | --- | --- | --- | --- | | `400` | `VALIDATION_ERROR` | A parameter is missing or invalid | Fix the request body. Not charged. | | `401` | `UNAUTHORIZED` | Missing or invalid API key | Check the `Authorization: Bearer` header. Not charged. | | `402` | `INSUFFICIENT_CREDITS` | Balance too low for this call | Top up or upgrade in Billing. | | `429` | `RATE_LIMIT_EXCEEDED` | Too many requests | Wait `Retry-After` seconds, then back off. | | `502` | `SEARCH_UNAVAILABLE` | An upstream failed | Credits are refunded. Retry with backoff. | Each endpoint page lists the errors specific to it. # Introduction (/) Keiro gives your app and your agents the live web through one API key: search results, clean page content, cited answers and multi-step research, all as predictable JSON. - [Quickstart](/quickstart): Make your first call in under a minute. - [Connect your agent](/mcp): One prompt installs Keiro MCP in Claude Code, Cursor, Codex or Windsurf. - [Google SERP](/search/google-serp): Google-ranked results in about 300 ms, 2 credits a call. - [Coming from Tavily or Exa?](/migrate): Swap the base URL and keep your code. ## Pick an endpoint | You need | Use | Cost | | --- | --- | --- | | Google-ranked links and snippets | [Google SERP](/search/google-serp) | 2 credits | | Fast multi-engine results | [Search](/search/search) | 0.5 credits | | The cheapest results at volume | [Lite](/search/lite) | 0.0375 credits on a plan | | Search plus the full text of each page | [Search + Content](/content/search-content) | 1.5 credits | | Clean content from a URL you already have | [Extract](/content/extract) | 2 credits | | Every URL a site publishes | [Sitemap](/content/sitemap) | 1 credit | | A short cited answer | [Answer](/ai/answer) | 1 credit | | A researched report | [Deep Search](/search/deep-search), [Web Agent](/ai/web-agent) | 5 credits, 7 + tokens | | Many queries in one job | [Batch](/bulk/batch) | 0.5 credits per query | Costs are paid-plan rates. See [Credits](/credits) for free-tier rates. ## Base URL ```txt https://api.keirolabs.cloud ``` Every request is a `POST` with a JSON body and your key in the `Authorization` header. See [Authentication](/authentication). # Connect your agent (/mcp) ## The one-prompt way Paste this into your agent. It installs Keiro MCP, learns every endpoint, and can build Keiro into your project. ```txt Fetch https://platform.keirolabs.cloud/agent-onboarding.md and follow the instructions on it to install Keiro MCP ``` ## Manual setup The server speaks streamable HTTP: ```txt URL: https://api.keirolabs.cloud/mcp/api Header: Authorization: Bearer keiro_your_api_key ``` **Claude Code** ```bash claude mcp add --transport http keirolabs https://api.keirolabs.cloud/mcp/api \ --header "Authorization: Bearer $KEIRO_API_KEY" ``` **Cursor** ```json title="~/.cursor/mcp.json" { "mcpServers": { "keirolabs": { "url": "https://api.keirolabs.cloud/mcp/api", "headers": { "Authorization": "Bearer keiro_your_api_key" } } } } ``` **Codex** ```toml title="~/.codex/config.toml" [mcp_servers.keirolabs] url = "https://api.keirolabs.cloud/mcp/api" http_headers = { Authorization = "Bearer keiro_your_api_key" } ``` **Windsurf** ```json title="~/.codeium/windsurf/mcp_config.json" { "mcpServers": { "keirolabs": { "serverUrl": "https://api.keirolabs.cloud/mcp/api", "headers": { "Authorization": "Bearer keiro_your_api_key" } } } } ``` ## Tools | Tool | What it does | | --- | --- | | `ask_keiro` | Describe what you need; it routes to the right endpoint | | `google_serp` | Google SERP results | | `web_search` | Fast multi-engine search | | `lite_search` | Lowest-cost search | | `slow_search` | Free and unlimited on paid plans | | `search_and_extract` | Search plus full page content | | `extract_url` | Clean content from one URL | | `answer` | Short cited answer | | `web_research` | Multi-step research | | `list_endpoints`, `get_endpoint`, `generate_code`, `suggest_schema` | Docs tools, so your agent writes integrations against the real API | | `check_credits`, `get_rate_limits`, `get_auth` | Account info | | `create_support_ticket`, `get_support_ticket` | Talk to us from your agent | Tool calls cost the same credits as the matching endpoint. # Migrate from Tavily or Exa (/migrate) Keiro serves Tavily- and Exa-shaped endpoints. Change the base URL and the key, and your existing requests keep working. | You use | Base URL | | --- | --- | | Tavily | `https://api.keirolabs.cloud/api/compat/tavily/v1` | | Exa | `https://api.keirolabs.cloud/api/compat/exa/v1` | Supported routes: Tavily `/search` and `/extract`; Exa `/search`, `/contents` and `/answer`. **Tavily** ```python from tavily import TavilyClient tavily = TavilyClient( api_key="keiro_your_api_key", base_url="https://api.keirolabs.cloud/api/compat/tavily/v1", ) r = tavily.search("latest LLM benchmarks", search_depth="advanced", max_results=5) ``` **Exa** ```python from exa_py import Exa exa = Exa( api_key="keiro_your_api_key", base_url="https://api.keirolabs.cloud/api/compat/exa/v1", ) r = exa.search_and_contents("best vector databases 2026", num_results=5, text=True) ``` If your SDK has no base URL option, send the same JSON with plain HTTP to the URLs above. Check what is supported right now at `GET https://api.keirolabs.cloud/api/compat/status`. # Quickstart (/quickstart) ### Get an API key Sign in at [keirolabs.cloud](https://platform.keirolabs.cloud/dashboard), open **API Keys** and create a key. It starts with `keiro_` and is shown once, so copy it now. ```bash export KEIRO_API_KEY="keiro_..." ``` ### Make a request **cURL** ```bash curl -X POST https://api.keirolabs.cloud/api/v2/search/google \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "best vector databases 2026"}' ``` **Python** ```python import os, requests res = requests.post( "https://api.keirolabs.cloud/api/v2/search/google", headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"}, json={"query": "best vector databases 2026"}, ) print(res.json()) ``` **Node.js** ```js const res = await fetch("https://api.keirolabs.cloud/api/v2/search/google", { method: "POST", headers: { Authorization: `Bearer ${process.env.KEIRO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ query: "best vector databases 2026" }), }); console.log(await res.json()); ``` ### Read the response ```json { "query": "best vector databases 2026", "results": [ { "title": "...", "url": "https://...", "snippet": "..." } ], "count": 10, "latencyMs": 240, "creditsCharged": 2, "creditsRemaining": 11248 } ``` Every response tells you what it cost and what you have left. ## Next - [Get page content too](/content/search-content): Search and scrape the top results in one call. - [Let your agent do it](/mcp): Install Keiro MCP and your agent picks the right endpoint. # Rate limits (/rate-limits) Limits are per API key, per endpoint, per minute (Batch submissions are per hour). Each endpoint page lists its own limits. | Endpoint | Free | Starter | Pro | Startup | Enterprise | | --- | --- | --- | --- | --- | --- | | Google SERP | 3 | 20 | 30 | 40 | 60 | | Search | 10 | 30 | 60 | 120 | 200 | | Lite | 15 | 60 | 120 | 240 | 300 | | Slow | Not available | 30 | 60 | 120 | 240 | | Deep Search | 2 | 4 | 6 | 10 | 20 | | Search + Content | 10 | 20 | 40 | 80 | 120 | | Extract | 10 | 30 | 60 | 120 | 200 | | Sitemap | 5 | 20 | 40 | 80 | 120 | | Answer | 10 | 30 | 60 | 120 | 200 | | Web Agent | 3 | 10 | 20 | 40 | 80 | | Batch (submits per hour) | Not available | 10 | 25 | 50 | 200 | Your tier follows your credit balance: Starter from 1,000 credits, Pro from 15,000, Startup from 50,000. ## When you hit a limit You get `429 RATE_LIMIT_EXCEEDED`. Read these headers: | Header | Meaning | | --- | --- | | `X-RateLimit-Remaining` | Requests left in the current window | | `Retry-After` | Seconds to wait before retrying | Wait `Retry-After` seconds, then retry with exponential backoff. For large jobs, use [Batch](/bulk/batch) instead of a tight loop. # Deep Search (/search/deep-search) `POST https://api.keirolabs.cloud/api/v2/deep` Cost: 5 credits · Latency: 15-50 s Our flagship web API — 96% SimpleQA score, the most accurate answer layer we ship. Give it a question and it searches the web, crawls every top result in full, and orders them by how likely each page actually contains the answer, so results[0] is the page to read first rather than whatever the SERP put there. Every result carries an excerpt (the passage most likely to hold the answer) alongside the full page text, so one call returns the whole reading list already extracted. Set content: false to leave the page text out and get excerpts only. **When to use it.** Use Deep when you need the answer rather than a list of links: research questions, due diligence, fact-checking, or any pipeline where a person or a model will read the top page. Because it returns the full text of every result, one Deep call often replaces a search plus N separate scrape calls — and it is frequently cheaper than that combination. On an agent path, call it twice: once with content: false to read every excerpt and likelihood cheaply, then again for the one or two pages you actually need. The second call repeats the query, so it is served from cache. **When not to.** Don't use Deep on interactive paths. At 15-50 seconds it is wrong for autocomplete, live chat, or anything a user is waiting on — use Search or Lite there. Don't use it for a plain SERP (Search, 0.5 credits) or when you already know which URL to read (Extract, 2 credits). Set your client timeout to at least 120 seconds. Note there is no page-count setting: how many pages get crawled and read is chosen for you, so the depth is consistent rather than tuned per call. ## Request **cURL** ```bash curl -X POST https://api.keirolabs.cloud/api/v2/deep \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "how do EU AI Act obligations differ for general purpose models?"}' ``` **Python** ```python import os, requests res = requests.post( "https://api.keirolabs.cloud/api/v2/deep", headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"}, json={ "query": "how do EU AI Act obligations differ for general purpose models?" }, ) print(res.json()) ``` **Node.js** ```js const res = await fetch("https://api.keirolabs.cloud/api/v2/deep", { method: "POST", headers: { Authorization: `Bearer ${process.env.KEIRO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "query": "how do EU AI Act obligations differ for general purpose models?" }), }); console.log(await res.json()); ``` ## Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | yes | The question to answer (1-500 chars). Phrase it as a question — accuracy is tuned for question-shaped queries. | | `content` | boolean | no | Default true. Set false to leave the full page text out of the response and get only excerpt plus the likelihood scores — roughly 150 tokens per result instead of about 25,000. The one to use on an agent path, where you rarely need more than the passage. | ## Response ```json title="200 OK" { "query": "who invented the telephone", "results": [ { "position": 1, "serp_rank": 9, "title": "Alexander Graham Bell - Wikipedia", "url": "https://en.wikipedia.org/wiki/Alexander_Graham_Bell", "snippet": "Alexander Graham Bell was a Scottish-born inventor, scientist and engineer\u2026", "excerpt": "Bell is credited with patenting the first practical telephone in 1876, following months of work on the harmonic telegraph. The patent, US 174,465, was granted on March 7, 1876\u2026", "content": "Alexander Graham Bell (March 3, 1847 \u2013 August 2, 1922) was a Scottish-born inventor, scientist and engineer who is credited with patenting the first practical telephone\u2026", "content_status": "fetched", "answer_likelihood": 0.97 }, { "position": 2, "serp_rank": 3, "title": "Who really invented the telephone? - BBC Science Focus", "url": "https://www.sciencefocus.com/science/who-really-invented-the-telephone", "snippet": "The question of who invented the telephone has been argued over for more than a century\u2026", "excerpt": "Antonio Meucci, Alexander Graham Bell and Elisha Gray all played a part, and the US House of Representatives passed a resolution in 2002 recognising Meucci\u2026", "content": "Antonio Meucci, Alexander Graham Bell and Elisha Gray all played a part\u2026", "content_status": "fetched", "answer_likelihood": 0.93 } ], "meta": { "latency_ms": 45204, "fetched": 7, "ranked": true, "retry": false }, "count": 2, "creditsCharged": 5, "credits_used": 5, "creditsRemaining": 22495 } ``` | Name | Type | Required | Description | | --- | --- | --- | --- | | `results` | array | no | Pages ordered by answer likelihood, most likely first. | | `results[].position` | integer | no | Position in the returned reading list — 1 is the page most likely to answer. Always sequential from 1 with no gaps. | | `results[].title` | string | no | Page title. Can be empty on a DOI-resolver hop, where url is still valid. | | `results[].url` | string | no | Full URL of the page. | | `results[].snippet` | string | no | Search-result excerpt. May be empty. | | `results[].excerpt` | string | no | Around 700 characters pulled from the region of the page most likely to hold the answer. Read this before content: it answers most questions for a fraction of the tokens. Present whether or not content was requested. | | `results[].content` | string | no | Full extracted page text with images and boilerplate stripped. Uncapped — up to roughly 100,000 characters per page — so size responses for it. Empty in two cases: the page was blocked (content_status is "blocked"), or the request set content: false. Empty does not by itself mean the page failed. | | `results[].content_status` | string | no | "fetched" or "blocked". A blocked page still appears because its position is still the right one to read; only its text is missing. Stays "fetched" when content: false was used, since the page was read fine and the text was simply not returned. | | `results[].answer_likelihood` | number | no | 0-1 score for how likely this page contains the answer. null on DOI hops, which sort last. | | `results[].serp_rank` | integer | no | Where the page sat in the original search results, 0-based, before this endpoint ordered them. Compare it against position to see the reordering at work: position 1 with serp_rank 9 means the ninth search result was promoted because its content actually answers the question. | | `meta.latency_ms` | integer | no | Server-side time for the call. | | `meta.fetched` | integer | no | How many pages were crawled successfully. | | `meta.ranked` | boolean | no | false when the answer-ordering step was unavailable and raw SERP order was returned instead. Check this before trusting the order. | | `meta.retry` | boolean | no | true when the ordering pass was retried. | | `meta.cache` | boolean | no | Present and true only when the response was served from cache — a repeat of the same query and content setting within 15 minutes. Cached responses still cost 5 credits. | | `count` | integer | no | Number of results returned. | | `creditsCharged` | number | no | Always 5. Charged per call rather than per result, and identical on every plan including free. | | `creditsRemaining` | number | no | Your remaining credit balance. | ## Errors | Status | Code | Cause | Fix | | --- | --- | --- | --- | | `400` | `VALIDATION_ERROR` | Missing query, query longer than 500 characters, or a content value that is not a boolean | Send a non-empty query and, if you are setting it, a true or false content flag. | | `401` | `UNAUTHORIZED` | Missing or invalid API key | Check your Authorization: Bearer header. | | `402` | `INSUFFICIENT_CREDITS` | Fewer than 5 credits available | Top up or upgrade at /dashboard/billing. Nothing was charged. | | `502` | `SEARCH_UNAVAILABLE` | A page crawl timed out, or the backend was unreachable | Nothing was charged. Retry with exponential backoff. | | `502` | `SEARCH_UNAVAILABLE` | The answer backend returned an error | Nothing was charged. Retry; if it persists, try again later. | | `503` | `SERVICE_BUSY` | Deep Search is at capacity right now | Nothing was charged. Wait for Retry-After seconds, then retry. Concurrency is capped, so back off rather than retrying in parallel. | ## Rate limits | Tier | Limit | | --- | --- | | Free | 2/min | | Starter | 4/min | | Pro | 6/min | | Startup | 10/min | | Enterprise | 20/min | Over the limit you get a `429`. See [Rate limits](/rate-limits) for headers and backoff. # Google SERP (/search/google-serp) `POST https://api.keirolabs.cloud/api/v2/search/google` Cost: 2 credits · Latency: ~0.2–0.4 s Raw Google SERP results. Fast, clean web search powered by Google — returns title, URL, and snippet for each result, with optional country (bias), cr (restrict), and language filters. Results come back normalized, so the shape is always the same. **When to use it.** Use Google when you need Google-ranking SERP results with clean, predictable shape. Great for rank tracking, SERP analysis, and region/language-targeted queries via country/cr/lang. **When not to.** Don't use Google if you need the actual page content (use Search+Content) or the lowest-cost raw SERP (Search is cheaper at 0.5 credits). ## Request **cURL** ```bash curl -X POST https://api.keirolabs.cloud/api/v2/search/google \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "best vector databases 2026", "maxResults": 10, "country": "us"}' ``` **Python** ```python import os, requests res = requests.post( "https://api.keirolabs.cloud/api/v2/search/google", headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"}, json={ "query": "best vector databases 2026", "maxResults": 10, "country": "us" }, ) print(res.json()) ``` **Node.js** ```js const res = await fetch("https://api.keirolabs.cloud/api/v2/search/google", { method: "POST", headers: { Authorization: `Bearer ${process.env.KEIRO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "query": "best vector databases 2026", "maxResults": 10, "country": "us" }), }); console.log(await res.json()); ``` ## Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | yes | The search query (1-500 chars). | | `maxResults` | integer | no | Number of results to return. Default: 10. Max: 20 (server-clamped). | | `country` | string | no | ISO 3166-1 alpha-2 code (e.g. "us", "gb"). Geolocation bias — prefers results from that country. | | `cr` | string | no | ISO 3166-1 alpha-2 code. Hard-restricts results to sites from that country. | | `lang` | string | no | Language code (e.g. "en", "de", "fr"). Interface/host language. Defaults to "en". | ## Response ```json title="200 OK" { "query": "best js frameworks 2025", "results": [ { "title": "...", "url": "https://...", "snippet": "..." } ], "count": 10, "latencyMs": 240, "creditsCharged": 2, "creditsRemaining": 1136 } ``` | Name | Type | Required | Description | | --- | --- | --- | --- | | `results` | array | no | List of search results, in Google SERP rank order. | | `results[].title` | string | no | Page title from the search result. | | `results[].url` | string | no | Full URL of the result. | | `results[].snippet` | string | no | Short text excerpt from the page (Google snippet). | | `count` | integer | no | Number of results returned. | | `latencyMs` | integer | no | Server processing time in milliseconds. | | `query` | string | no | The sanitized query the results answer. | | `creditsCharged` | number | no | Credits deducted for this request (2 on every tier). | | `creditsRemaining` | number | no | Your remaining credit balance after this request. | ## Errors | Status | Code | Cause | Fix | | --- | --- | --- | --- | | `400` | `VALIDATION_ERROR` | Missing or invalid query parameter | Ensure query is a non-empty string (1-500 chars). | | `401` | `UNAUTHORIZED` | Missing or invalid API key | Check your Authorization: Bearer header. | | `402` | `INSUFFICIENT_CREDITS` | Not enough credits | Buy more credits or upgrade your plan at /dashboard/billing. | | `429` | `RATE_LIMIT_EXCEEDED` | Too many requests | Check X-RateLimit-Remaining header. Wait for Retry-After seconds. | | `502` | `SEARCH_UNAVAILABLE` | Google SERP backend failure | Credits auto-refunded. Retry with exponential backoff. | ## Rate limits | Tier | Limit | | --- | --- | | Free | 3/min | | Starter | 20/min | | Pro | 30/min | | Startup | 40/min | | Enterprise | 60/min | Over the limit you get a `429`. See [Rate limits](/rate-limits) for headers and backoff. # Lite (/search/lite) `POST https://api.keirolabs.cloud/api/v2/search/lite` Cost: 0.0375 credits on plans · 1 pay as you go · Latency: ~0.3-1 s Cost-efficient raw SERP (web, images, videos) — title, URL and snippet per result. Content mode (page text, prompt ranking, and region/date/domain filters) is temporarily unavailable; Lite is SERP-only right now. Use Search + Content for page text. **When to use it.** Use Lite for the cheapest raw SERP — link discovery, quick result lists, and image/video feeds. Content extraction, prompt ranking, and region/date/domain filters are temporarily unavailable on this endpoint. **When not to.** Don't use Lite when you need page text (use Search+Content), a synthesized cited answer (use Answer), or multi-iteration research (use Web Agent). ## Request **cURL** ```bash curl -X POST https://api.keirolabs.cloud/api/v2/search/lite \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "open source llm observability", "count": 10}' ``` **Python** ```python import os, requests res = requests.post( "https://api.keirolabs.cloud/api/v2/search/lite", headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"}, json={ "query": "open source llm observability", "count": 10 }, ) print(res.json()) ``` **Node.js** ```js const res = await fetch("https://api.keirolabs.cloud/api/v2/search/lite", { method: "POST", headers: { Authorization: `Bearer ${process.env.KEIRO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "query": "open source llm observability", "count": 10 }), }); console.log(await res.json()); ``` ## Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | yes | The search query (1-500 chars). | | `type` | enum | no | Search tab: web, images, videos. | | `count` | integer | no | Max results to return. Range: 1-30 (server-clamped). Alias: maxResults. | | `language` | string | no | Optional language tag, e.g. "de-DE". | | `content` | boolean | no | TEMPORARILY UNSUPPORTED — content:true returns 400 while Lite is SERP-only. Use /api/v2/search/content for page text. | | `prompt` | string | no | TEMPORARILY UNSUPPORTED — returns 400 while Lite is SERP-only. | ## Response ```json title="200 OK" { "query": "keirolabs", "type": "web", "content": false, "count": 2, "results": [ { "title": "KeiroLabs \u2014 pricing", "url": "https://keirolabs.cloud/pricing", "domain": "keirolabs.cloud", "snippet": "Plans start at $29/mo\u2026", "position": 1 }, { "title": "KeiroLabs Docs", "url": "https://platform.keirolabs.cloud/docs", "domain": "platform.keirolabs.cloud", "snippet": "Quick start, authentication, endpoints\u2026", "position": 2 } ], "creditsCharged": 0.0375, "creditsRemaining": 1141 } ``` | Name | Type | Required | Description | | --- | --- | --- | --- | | `results` | array | no | List of results in SERP order. | | `results[].title` | string | no | Result title. | | `results[].url` | string | no | Page URL. | | `results[].domain` | string | no | Result domain. | | `results[].snippet` | string | no | Short text excerpt from the search engine. | | `results[].position` | integer | no | 1-based rank in the SERP. | | `results[].score` | number | no | Relevance score (0-1) when the engine supplies one. | | `query` | string | no | The query that was searched. | | `type` | string | no | Search tab echoed back (web, images, videos). | | `count` | integer | no | Number of results returned. | | `content` | boolean | no | Always false while Lite is SERP-only. | | `creditsCharged` | number | no | Credits deducted (0.0375 on a plan, 1 on signup or pack credits). | | `creditsRemaining` | number | no | Remaining credit balance after this request. | ## Errors | Status | Code | Cause | Fix | | --- | --- | --- | --- | | `400` | `VALIDATION_ERROR` | Missing/invalid query, or query > 500 chars | Ensure query is a non-empty string (1-500 chars). | | `400` | `CONTENT_MODE_DISABLED` | content:true or prompt supplied while Lite is SERP-only | Omit content/prompt, or use /api/v2/search/content for page text. | | `401` | `UNAUTHORIZED` | Missing or invalid API key | Check your Authorization: Bearer header. | | `402` | `INSUFFICIENT_CREDITS` | Not enough credits | Buy more credits or upgrade at /dashboard/billing. | | `429` | `RATE_LIMIT_EXCEEDED` | Too many requests | Check X-RateLimit-Remaining. Wait for Retry-After seconds. | | `500` | `LITE_NOT_CONFIGURED` | Server has no SE_ROUTER_TOKEN | Server-side config issue — contact support. | | `502` | `SEARCH_UNAVAILABLE` | Search backend unreachable | Credits auto-refunded. Retry; if persistent, contact support. | ## Rate limits | Tier | Limit | | --- | --- | | Free | 15/min | | Starter | 60/min | | Pro | 120/min | | Startup | 240/min | | Enterprise | 300/min | Over the limit you get a `429`. See [Rate limits](/rate-limits) for headers and backoff. # Search (/search/search) `POST https://api.keirolabs.cloud/api/v2/search/fast` Cost: 0.5 credits paid · 1 free · Latency: ~1 s Always-fresh, multi-engine search backed by our own retrieval network. Engineered for high availability under heavy load, delivering sub-second results with a multi-engine race — first valid response wins. **When to use it.** Use Search when you need SERP-style results (URLs + titles + snippets) quickly. Lowest cost and lowest latency. Ideal for link discovery, SERP analysis, and simple search. **When not to.** Don't use Search if you need the actual page content (use Search+Content), an LLM answer (use Answer), or multi-step research (use Web Agent). ## Request **cURL** ```bash curl -X POST https://api.keirolabs.cloud/api/v2/search/fast \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "latest postgres release notes", "maxResults": 10}' ``` **Python** ```python import os, requests res = requests.post( "https://api.keirolabs.cloud/api/v2/search/fast", headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"}, json={ "query": "latest postgres release notes", "maxResults": 10 }, ) print(res.json()) ``` **Node.js** ```js const res = await fetch("https://api.keirolabs.cloud/api/v2/search/fast", { method: "POST", headers: { Authorization: `Bearer ${process.env.KEIRO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "query": "latest postgres release notes", "maxResults": 10 }), }); console.log(await res.json()); ``` ## Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | yes | The search query (1-500 chars). Sanitized server-side. | | `maxResults` | integer | no | Number of results to return. Default: 20. Max: 35 (server-clamped). | | `country` | string | no | ISO-3166-1 alpha-2 country code. Biases results to that geography. Major codes: us, gb, in, de, fr, ca, au, jp, br. | ## Response ```json title="200 OK" { "query": "best js frameworks 2025", "results": [ { "title": "...", "url": "https://...", "snippet": "...", "position": 1, "score": 1 } ], "count": 10, "latencyMs": 938, "partial": false, "creditsCharged": 0.5, "creditsRemaining": 1141 } ``` | Name | Type | Required | Description | | --- | --- | --- | --- | | `results` | array | no | List of search results, sorted by relevance. | | `results[].title` | string | no | Page title from the search result. | | `results[].url` | string | no | Full URL of the result. | | `results[].snippet` | string | no | Short text excerpt from the page (search engine snippet). | | `results[].position` | integer | no | Rank position (1 = top result). | | `results[].score` | number | no | Relevance score (0-1, where 1 = exact match). | | `count` | integer | no | Number of results returned. | | `latencyMs` | integer | no | Server-side processing time in milliseconds. | | `partial` | boolean | no | true if some engines timed out but results were still returned. | | `creditsCharged` | number | no | Credits deducted for this request (0.5 for paid tiers, 1 for free). | | `creditsRemaining` | number | no | Your remaining credit balance after this request. | ## Errors | Status | Code | Cause | Fix | | --- | --- | --- | --- | | `400` | `VALIDATION_ERROR` | Missing or invalid query parameter | Ensure query is a non-empty string (1-500 chars). | | `401` | `UNAUTHORIZED` | Missing or invalid API key | Check your Authorization: Bearer header. | | `402` | `INSUFFICIENT_CREDITS` | Not enough credits | Buy more credits or upgrade your plan at /dashboard/billing. | | `429` | `RATE_LIMIT_EXCEEDED` | Too many requests | Check X-RateLimit-Remaining header. Wait for Retry-After seconds. | | `502` | `SEARCH_UNAVAILABLE` | Search engine backend failure | Credits auto-refunded. Retry with exponential backoff. | ## Rate limits | Tier | Limit | | --- | --- | | Free | 10/min | | Starter | 30/min | | Pro | 60/min | | Startup | 120/min | | Enterprise | 200/min | Over the limit you get a `429`. See [Rate limits](/rate-limits) for headers and backoff. # Slow (/search/slow) `POST https://api.keirolabs.cloud/api/v2/search/slow` Cost: Free on paid plans · Latency: Varies with load Long-form search for agentic and bulk workloads where an immediate answer is not required. Free and unlimited for paying customers, subject to our fair use policy. **When to use it.** Use Slow for long-form, agentic, or bulk work where latency does not matter: enrichment passes, scheduled research jobs, background processing, dataset building. It is free and unlimited for paying customers (fair use policy applies), so it is the right default for anything that can wait rather than spending credits on Search. **When not to.** Don't use Slow on interactive or latency-sensitive paths, where response times vary with load. Use Search for a sub-second SERP, or Search + Content when you need page text. ## Request **cURL** ```bash curl -X POST https://api.keirolabs.cloud/api/v2/search/slow \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "companies hiring rust engineers in berlin"}' ``` **Python** ```python import os, requests res = requests.post( "https://api.keirolabs.cloud/api/v2/search/slow", headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"}, json={ "query": "companies hiring rust engineers in berlin" }, ) print(res.json()) ``` **Node.js** ```js const res = await fetch("https://api.keirolabs.cloud/api/v2/search/slow", { method: "POST", headers: { Authorization: `Bearer ${process.env.KEIRO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "query": "companies hiring rust engineers in berlin" }), }); console.log(await res.json()); ``` ## Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `query` | string | yes | The search query (1-500 chars). | ## Response ```json title="200 OK" { "query": "solid state battery progress 2026", "results": [ { "title": "Solid-state batteries: 2026 progress report", "url": "https://example.com/solid-state-2026", "snippet": "Manufacturers reported pilot-line yields above 80% for the first time this year\u2026", "position": 1 } ], "count": 1, "creditsCharged": 0, "creditsRemaining": 1141 } ``` | Name | Type | Required | Description | | --- | --- | --- | --- | | `results` | array | no | List of search results. | | `results[].title` | string | no | Page title. | | `results[].url` | string | no | Full URL of the result. | | `results[].snippet` | string | no | Short text excerpt. May be empty when none is available. | | `results[].position` | integer | no | Rank position (1 = top result). | | `count` | integer | no | Number of results returned. | | `creditsCharged` | number | no | Always 0 — Slow is free and unlimited for paying customers (fair use policy applies). | | `creditsRemaining` | number | no | Your remaining credit balance (unchanged by this request). | ## Errors | Status | Code | Cause | Fix | | --- | --- | --- | --- | | `400` | `VALIDATION_ERROR` | Missing query, or query longer than 500 characters | Send a non-empty query string of 1-500 chars. | | `401` | `UNAUTHORIZED` | Missing or invalid API key | Check your Authorization: Bearer header. | | `403` | `PLAN_UPGRADE_REQUIRED` | Slow is free and unlimited for paying customers — it is not available on the free plan | Upgrade to any paid plan at /dashboard/billing. | | `502` | `SEARCH_UNAVAILABLE` | Search backend unreachable | Nothing was charged. Retry with exponential backoff. | | `503` | `SERVICE_BUSY` | High demand | Nothing was charged. Wait for Retry-After seconds, then retry. | ## Rate limits | Tier | Limit | | --- | --- | | Free | Not available | | Starter | 30/min | | Pro | 60/min | | Startup | 120/min | | Enterprise | 240/min | Over the limit you get a `429`. See [Rate limits](/rate-limits) for headers and backoff.