Multi-Engine SERP API for Agents - Brave, DuckDuckGo, Mojeek
Pricing
from $0.00005 / actor start
Multi-Engine SERP API for Agents - Brave, DuckDuckGo, Mojeek
One normalized search API across Brave, DuckDuckGo and Mojeek. Uniform JSON schema, BYO API keys, per-query pay-per-event pricing, and low-latency Standby HTTP mode for AI agents.
Pricing
from $0.00005 / actor start
Rating
0.0
(0)
Developer
Eonix Pvt Ltd
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
3 days ago
Last modified
Categories
Share
Multi-Engine SERP API for Agents — Brave, DuckDuckGo, Mojeek
One search call, three engines, one schema. serp-multi gives AI agents (and the humans building them) a normalized web-search API across Brave, DuckDuckGo, and Mojeek — with predictable per-query pricing, bring-your-own API keys, cross-engine deduplication, optional LLM re-ranking, and a low-latency Standby HTTP endpoint so your agent gets an answer in one round-trip instead of waiting for a batch run.
Built for agent consumption first:
- Uniform JSON schema — every engine's results come back as the same record shape. Your agent parses one format, forever.
- Predictable cost — pay-per-event: a flat price per query × engine executed. No token surprises, no per-GB ambiguity. Failed engine calls are never charged.
- Real-time via Standby mode —
GET /search?q=…returns JSON in the response body with cold-start-free latency. Perfect as an agent tool or an MCP tool. - BYO keys where engines have official APIs — Brave and Mojeek run through their official APIs with your keys (both have free tiers), so you control quotas and stay in each engine's terms. DuckDuckGo needs no key.
- Cross-engine consensus — results pointing at the same canonical URL are merged, keeping each engine's rank in
enginesRanks. Agreement across independent indexes is a strong relevance signal.
Supported engines
| Engine | How | Key needed? | Notes |
|---|---|---|---|
brave | Official Brave Search API | Yes — braveApiKey (free tier: 2,000 queries/mo) | Skipped with a logged warning if no key |
duckduckgo | HTML endpoint via got-scraping + Cheerio | No | Graceful degradation on blocks: one proxy retry, then partial results with a warning |
mojeek | Official Mojeek Search API | Yes — mojeekApiKey | Independent index — great diversity signal. Skipped if no key |
Quick start (batch mode)
Run the Actor with:
{"queries": ["apify actor pricing", "best vector database"],"engines": ["brave", "duckduckgo", "mojeek"],"resultsPerEngine": 10,"mergeResults": true,"braveApiKey": "YOUR_BRAVE_KEY","mojeekApiKey": "YOUR_MOJEEK_KEY"}
Every query is searched on every enabled engine (concurrency-limited, retried with exponential backoff, 429 Retry-After respected) and normalized records land in the default dataset.
Standby mode — the real-time agent endpoint
This Actor supports Apify Actor Standby: the platform keeps a warm instance running an HTTP server, so requests answer in real time.
GET https://<your-username>--serp-multi.apify.actor/search?q=best+vector+database&engines=duckduckgo,braveAuthorization: Bearer <YOUR_APIFY_TOKEN>
Query parameters (all optional except q):
| Param | Meaning | Default |
|---|---|---|
q | The search query (required) | — |
engines | Comma-separated subset of brave,duckduckgo,mojeek | run input / all |
resultsPerEngine (alias count) | 1–50 results per engine | run input / 10 |
country, language | Two-letter localization codes | us, en |
mergeResults (alias merge) | true/false cross-engine merge | true |
rerank | true to LLM-rerank (needs openaiApiKey in the standby run input) | false |
Secrets never travel in the URL. API keys (braveApiKey, mojeekApiKey, openaiApiKey) are configured once in the standby run's input; per-request params only tune non-secret options.
The endpoint returns the same normalized records as batch mode, in a JSON envelope:
{ "query": "…", "count": 12, "results": [ … ], "engines": { "executed": ["brave"], "skipped": [], "failed": [] }, "reranked": false, "warnings": [], "latencyMs": 913 }
Standby requests charge the same pay-per-event prices as batch runs — one serp-query event per engine executed.
Use as an MCP tool
Apify exposes every Actor as an MCP (Model Context Protocol) tool — no extra code in this Actor needed. Point your MCP client (Claude, or any MCP-capable agent framework) at the Apify MCP server, allow serp-multi, and your agent can call multi-engine search natively as a tool. Combined with Standby mode, that gives your agent sub-second, fixed-price web search.
Input reference
| Field | Type | Default | Description |
|---|---|---|---|
queries | string[] | — | Search queries. Required in batch mode; ignored in Standby (use ?q=). Max 500 per run. |
engines | string[] | all three | Any of brave, duckduckgo, mojeek. Keyless engines requiring a key are skipped with a warning. |
braveApiKey | secret string | — | Brave Search API subscription token. |
mojeekApiKey | secret string | — | Mojeek Search API key. |
country | string | us | Two-letter country code (Brave country, DDG region). |
language | string | en | Two-letter language code (Brave search_lang, DDG region). |
resultsPerEngine | integer | 10 | 1–50 organic results per engine per query. |
mergeResults | boolean | true | Merge duplicates across engines (see below). false → one record per engine per result. |
rerank | boolean | false | LLM re-rank merged results per query (needs openaiApiKey). |
openaiApiKey | secret string | — | Only used when rerank is true. |
proxyConfiguration | proxy | none | DuckDuckGo only — regular egress if enabled, and always the retry path when DDG serves a CAPTCHA. |
Declared outputs
The Actor ships a machine-readable .actor/output_schema.json, so Console, the API, and MCP clients know exactly what a run produces:
| Output | Where it lives | What it is |
|---|---|---|
results | Default dataset (/items) | Every normalized SERP record — the main output |
runSummary | Key-value store, key OUTPUT | One JSON object: queries/records processed, per-engine execution counts, engines skipped for missing keys, failures with reasons, warnings, and the exact PPE events charged |
standbySearchEndpoint | The run's container URL | …/search — the live real-time endpoint while running in Standby |
The dataset also ships a .actor/dataset_schema.json with typed, documented fields (so agents can interpret each column) and two views: Overview and Cross-engine consensus, which flattens enginesRanks into one column per engine to show which URLs several independent engines agree on.
Real OUTPUT summary from the sample run below:
{"mode": "batch","startedAt": "2026-08-15T15:33:20.353Z","finishedAt": "2026-08-15T15:33:23.005Z","queries": 1,"records": 10,"settings": { "engines": ["duckduckgo"], "resultsPerEngine": 10, "mergeResults": true, "rerank": false, "country": "us", "language": "en" },"engines": { "executedByEngine": { "duckduckgo": 1 }, "skipped": [] },"charged": { "serp-query": 1, "rerank": 0 },"failures": [],"warnings": []}
Note that the summary lives in the key-value store, not the dataset: every dataset item keeps one uniform shape, which is the point of this Actor for agent consumers.
Output schema & real sample
Each dataset record (and each element of the Standby results array):
| Field | Type | Notes |
|---|---|---|
query | string | The query that produced this record |
engine | string | Producing engine; for merged records, the best-ranked contributor |
rank | number | Rank within its engine; for merged records, the best rank across engines |
title, url, snippet | string | Normalized result fields |
fetchedAt | ISO-8601 string | When the result was fetched |
enginesRanks | object | Merged records only: { engine: rank } for every engine that listed this URL |
rerankScore | number 0–1 | Only when rerank ran: higher = more relevant |
Real output from a local run with { "queries": ["apify actor pricing"], "engines": ["duckduckgo"] }:
[{"query": "apify actor pricing","engine": "duckduckgo","rank": 1,"title": "Apify pricing - plans for data collection at any scale · Apify","url": "https://apify.com/pricing","snippet": "The Apify platform has a number of services that are charged based on usages, such as Actors, proxies, data transfer, and storage. See pricing for the full list of platform services. Each subscription plan comes with a certain amount of prepaid platform usage that is used to pay for services. If your platform usage in a given billing cycle exceeds this prepaid amount, the excess usage will be ...","fetchedAt": "2026-08-14T15:02:28.111Z","enginesRanks": { "duckduckgo": 1 }},{"query": "apify actor pricing","engine": "duckduckgo","rank": 2,"title": "Apify Actor Cost Estimator","url": "https://apify.com/agentictools/actor-cost-estimator","snippet": "Estimate what an Apify Actor will cost before you run it, across its pricing model and your workload.","fetchedAt": "2026-08-14T15:02:28.111Z","enginesRanks": { "duckduckgo": 2 }},{"query": "apify actor pricing","engine": "duckduckgo","rank": 3,"title": "How to Monetize Apify Actors: Pricing, Payouts & Publishing | Use Apify","url": "https://use-apify.com/docs/apify-for-developers/monetize-actors","snippet": "Monetize Apify Actors with pay-per-event, pay-per-result, or rental pricing. Commission split, PayPal and bank payouts, and steps to publish a paid Store listing.","fetchedAt": "2026-08-14T15:02:28.111Z","enginesRanks": { "duckduckgo": 3 }}]
How merging works
With mergeResults: true (default), URLs are canonicalized — tracking params (utm_*, gclid, fbclid, …) stripped, trailing slash removed, host lowercased, http/https folded — and results from different engines pointing at the same canonical URL become one record:
enginesRankskeeps every engine's rank:{ "brave": 2, "duckduckgo": 1 }means both engines found it — a strong consensus signal for agents.- Title/snippet/
rankcome from the best-ranked contributor. - Output is ordered by best rank; ties go to results more engines agree on.
With mergeResults: false you get the raw per-engine view: one record per engine per result.
LLM re-ranking (optional)
With rerank: true and an openaiApiKey, one structured gpt-4o-mini call per query re-orders the merged results by relevance to the query intent and attaches rerankScore (0–1). If the call fails, the run continues with engine order — and the rerank event is not charged.
Pricing (pay-per-event)
| Event | Description | Charged | Suggested price |
|---|---|---|---|
serp-query | One executed search of one query on one engine, including normalization, dedupe, and record delivery | Per query × engine actually executed — only after its records are stored | $0.003 |
rerank | One AI relevance re-ranking pass over a query's merged results, adding rerankScore | Per successfully re-ranked query — only after records are stored | $0.005 |
Cost intuition: a 3-engine merged search is ~$0.009; a 100-query × 2-engine batch is ~$0.60. Skipped engines (missing key) and failed engine calls cost nothing.
Run it at 512 MB — measured peak memory is 76 MB, so the platform default of 4 GB just multiplies your compute bill. See MONETIZATION.md for the full Console setup and measured margins.
Three concrete use cases
- Agent web-search tool with a fixed unit cost. Wire the Standby endpoint (or MCP tool) into your agent framework as its
web_searchfunction. Finance knows exactly what 10,000 agent searches cost before the month starts. - RAG freshness pipeline. Nightly batch run over your product's top 500 questions across all three engines; merged, deduped records feed your retrieval index with a cross-engine consensus signal (
enginesRanks) for source weighting. - Brand & SEO monitoring across independent indexes. Track where your domain ranks on Brave vs DuckDuckGo vs Mojeek for target keywords — three genuinely different indexes, one normalized dataset you can diff over time.
FAQ
Why bring my own Brave/Mojeek keys? Both offer official APIs with free tiers. BYO keys keep you inside each engine's terms, give you your own quota, and keep this Actor's per-query price flat regardless of your plan.
What happens if I only provide some keys? Engines without keys are skipped with a logged warning; everything else runs. DuckDuckGo always works keyless.
Is Google or Bing supported? Not in this Actor — it focuses on engines that are agent-friendly via official APIs or stable endpoints. For Google, use a dedicated Google SERP actor from the Store.
What if DuckDuckGo blocks the request? The engine retries once through your proxyConfiguration (fresh session), then returns whatever it parsed with a warning instead of failing the whole request. For heavy DDG use, enable residential proxy.
Do standby requests also write to the dataset? Yes — records are pushed to the run's default dataset and returned in the response, and charging happens only after the push succeeds.
How fresh are results? Every record carries fetchedAt. Nothing is cached by the Actor — every query hits the engines live.
Rate limits? Global concurrency is capped at 5 in-flight engine requests (DuckDuckGo held to 2), with exponential backoff honoring Retry-After on 429s.
Local development
npm installnpm run build # tsc — zero errors expectednpm test # unit tests: URL canonicalizer + standby server (engine calls mocked)apify run # batch run against storage/key_value_stores/default/INPUT.json# Local standby server on :3123 — APIFY_META_ORIGIN is what selects the mode,# exactly as the platform does it (the port var alone is set on every run).APIFY_META_ORIGIN=STANDBY ACTOR_STANDBY_PORT=3123 npm start
Changelog
0.1.0
- Initial release: Brave / DuckDuckGo / Mojeek engines, cross-engine merge with
enginesRanks, batch + Standby modes, optional LLM re-rank, pay-per-event charging (serp-query,rerank).