Search.
One endpoint for every mode and engine. Authenticated with a bearer key, metered per call.
POST https://metasearch.sh/api/v1/search Authorization: Bearer ms_live_… Content-Type: application/json Idempotency-Key: 0b7e… (optional)
Create a key in the dashboard; the key search login stores for search-cli is the same kind of key. An unknown or revoked key returns 401 invalid_api_key.
Request body#
The body is a JSON object. A field not listed here is rejected with 400 invalid_request, never ignored.
| field | type | rule |
|---|---|---|
| query | string, required | Trimmed. In text modes, at most 2,000 characters. In extract, scrape and similar, an absolute http(s) URL of at most 2,048 characters whose host resolves to a public address; private, loopback and link-local hosts are refused with 400 invalid_url. |
| mode | string | Default auto: the server picks one of the modes below and says why in routing.auto (see Auto routing). Otherwise one of the twelve modes below, or scrape, an alias of extract; an explicit mode is always obeyed. The response echoes the mode as you sent it. |
| providers | string[] | 1 to 5 distinct engine names. Omitted: the first available engine for the mode, with fallback. One name: pinned, no fallback. Several: queried together and merged by rank fusion. stealth is accepted as the old name of direct. |
| provider | string | Legacy form of providers: [name]. Sending both is a 400 invalid_request. |
| count | integer | 1 to 20, default 10. The most results returned; an engine may return fewer. Out of range is a 400 invalid_request. |
| filters | object | Optional. Fields listed under Filters. Not allowed in URL modes. |
| rerank | boolean | Default true. Reorders results by relevance to the query in general, news, academic, scholar, people and deep when there are at least three and the request is not pinned to one engine. Free. Send falseto keep the engines' order. |
| fresh | boolean | Default false. A request repeated within a short time is answered from the stored result, at the same price; send true to always ask the engines. |
Filters#
| filter | type | rule |
|---|---|---|
| freshness | string | day, week, month or year. |
| include_domains | string[] | At most 20 bare hostnames, lowercased, e.g. arxiv.org. In social mode these are X handles without the @, limiting the authors searched. |
| exclude_domains | string[] | At most 20, same form as include_domains. |
| country | string | ISO 3166-1 alpha-2, e.g. gb. |
| language | string | ISO 639-1, e.g. de. |
A filter is never dropped silently. Without providers, the router skips engines that cannot honour every filter you sent. A pinned engine that cannot honour one returns 400 filter_unsupported with the engines that can in supported_by. The manifest lists the filters each engine accepts per mode. An unknown filter name is a 400 invalid_request.
{
"query": "retrieval augmented generation evaluation",
"mode": "academic",
"providers": ["exa", "serper"],
"count": 10,
"filters": { "freshness": "year", "exclude_domains": ["medium.com"] }
}Modes#
| mode | query | use it for |
|---|---|---|
| general | text | Any web lookup not covered by a more specific mode. Not for: Papers (academic), current events (news), a named person (people). |
| news | text | Current events and recent coverage. |
| academic | text | Papers and studies on a topic, found semantically. Not for: Citation counts and PDF links: use scholar. |
| scholar | text | Google Scholar records: citation counts, versions, PDF links. |
| people | text | A named person: their role, employer, LinkedIn profile. Served by Exa alone; if Exa is unavailable, search general for the name and employer. Not for: What people are saying about a topic: use social. |
| social | text | Posts and videos on X, TikTok, Instagram and YouTube, with views, likes, comments and authors. Name the platform in the query ("on tiktok", "instagram reels", "youtube videos", "on X") to search only there; otherwise X and the one platform likeliest to hold the answer are searched together. Put X handles, without @, in include_domains to limit X authors. Extract a TikTok, Instagram, YouTube, Facebook or Threads post URL for its caption, stats, transcript where there is one, and top comments. Not for: Discussion threads (community), reporting (news). |
| community | text | What real people say about a topic: Reddit posts and Hacker News stories, with scores or points, comment counts and authors. Put r/<subreddit> in the query to search one subreddit (cheapest); without one, all of Reddit is searched. Extract a Reddit thread or news.ycombinator.com/item URL for the whole thread, every comment in order with its author, score and depth. Not for: X, TikTok, Instagram and YouTube posts (social), reporting (news). |
| images | text | Pictures. Returns imageUrl (the image) and url (the page it sits on). Served by Serper, with SerpApi as the fallback on the hosted API. |
| places | text | Businesses and physical locations, with addresses. |
| patents | text | Patent filings. |
| deep | text | Wider coverage on a hard question. Slower and dearer than general. Not for: Routine lookups: general costs a fraction of this. |
| extract | url | One page as markdown. Tries our own fetch first, then rendering services. A Reddit or Hacker News thread, or a TikTok, Instagram, YouTube, Facebook or Threads post URL, is read by that source: the post, its stats and its comments or transcript. Not for: Finding pages: search first, then extract the URL you want. |
| scrape | url | Alias of extract. Same behaviour and price. |
| similar | url | Pages resembling a given page: competitors, related coverage. |
The engines behind each mode are on Providers & modes.
Routing#
With no mode, auto routing picks one first; it never picks deep, and picks social only when the query names a platform: X, Twitter, TikTok, Instagram or YouTube. Send a mode to skip it.
Without providers, engines are tried in the mode's priority order, healthy engines first, until one returns at least one result: at most 3 attempts, 4 in URL modes. If no engine can serve the mode you get 503 no_provider_for_mode.
A named engine is checked strictly: an unknown name is 400 unknown_provider (with valid_providers), one that does not serve the mode is 400 provider_mode_mismatch (with supported_modes), and one that is switched off or failing is 503 provider_not_available.
Idempotency#
Send an Idempotency-Key header (1 to 255 characters of A-Z a-z 0-9 _ . : -) to make a retry safe. Keys are scoped to your organisation and kept for 24 hours.
- Same key and same body after a
200: the stored response, withreplayed: trueand the headerIdempotent-Replayed: true. A replay is not charged. - Same key while the first request is still running:
409 idempotency_in_progress; retry after theRetry-Afterseconds. - Same key with a different body:
422 idempotency_mismatch. - A request that ended in an error releases its key.
Response#
Every response, errors included, carries an X-Request-Id header with the same value as request_id.
| field | description |
|---|---|
| request_id | UUID of this call. Quote it to support. |
| mode | The mode as you sent it. |
| provider | The first engine that contributed results, or null. Kept for older clients; prefer routing. |
| results[] | At most count items: title, url, snippet, and when known published and imageUrl (images mode: the image itself; url stays the page). extra holds engine-specific fields; in URL modes it has content_origin: "untrusted_web_content", since the page text comes from the open web. After a rerank, extra.relevance is the judged relevance, 0 to 1. |
| results[].sources | Fused requests only: each engine that returned this URL and its rank there. |
| answers[] | Present only when an engine returned a written answer: text, provider, citations. Answers never take a result slot. |
| routing.merge | chain (no providers), single (one) or fused (several). |
| routing.served_by | The engine whose results you got, or null. |
| routing.providers | Every engine that contributed at least one result. |
| routing.fell_back | True when the first engine tried did not serve the request. |
| routing.auto | Present when the mode was auto: the mode that ran, its source (rule, model or fallback), a one-sentence reason, and when they apply probabilities, filters_applied(a freshness taken from words such as "today", in news and social only) and latency_ms. |
| routing.reranked | True when results were reordered by relevance; each then carries extra.relevance. |
| routing.cached | True when the answer is the stored result of the same request made recently (15 minutes for news and social, hours for web searches, days for papers, patents and profiles); routing.cached_at says when it was fetched. Send fresh: true to skip it. |
| routing.hints[] | Advisory, for an explicit mode that looks wrong: { type: "mode", suggested, p, message }. It never changes what ran. |
| routing.attempts[] | One entry per engine tried: provider, outcome (ok, empty, rejected, failed, timeout or skipped), category and http_status on failure, latency_ms and charged_micros. escalated: truemarks a list that was set aside for the next engine's; it is not charged. |
| billing.reserved_micros | Held before dispatch. The charge is never higher. |
| billing.charged_micros | What this call cost, in micro-USD (1 µ$ = $0.000001).charged_usd is the same in dollars. |
| billing.balance_micros | Your balance after the charge. balance_usd is the same in dollars. |
| message | Set when there are no results. |
| replayed | Present, and true, only on an idempotent replay. |
No results#
If every engine tried returns nothing, the response is still a 200, with results: [], charged_micros: 0, the message "No provider returned results; nothing was charged." and each attempt's outcome in routing.attempts.
Time limits#
Each mode has one deadline shared by all its attempts. The manifest gives it as deadline_ms. Set your client timeout above it; 90 seconds covers every mode.
| deadline | modes |
|---|---|
| 20 s | general, news, people, similar, scholar, patents, images, places, community |
| 25 s | academic |
| 45 s | deep |
| 60 s | extract, social |