// Use case · SERP API
For rank trackers, research tools and agents that need search results as rows: Google, Bing and DuckDuckGo with absolute ranks, ad flags and typed operators.
engines per request
3
credits per result page
2
results per engine, max
200
{ "results": [ { "rank": 1, "absolute_rank": 2, "type": "organic", "ad": false, "url": "https://github.com/example/…", "engine": "google" } ], "pagination": { "page": 1, "has_more": true }} // Why
The problem
What Wayfern does
engine that produced it.rank counts organic results only; absolute_rank counts every row, ads included, in page order.meta.engines_failed. If every engine fails, the reason becomes the HTTP status.// How it works
POST or GET /api/search/v1. Every result carries the engine that produced it, so one envelope serves all three.
Step 1 of 4
text and engines, plus typed fields such as site, filetype or a date range, with a token that has the search scope.
POST /api/search/v1
Step 2 of 4
Google, Bing and DuckDuckGo are fetched at the same time, and duplicate URLs are merged.
"engines": ["google", "bing", "duckduckgo"]
Step 3 of 4
rank, absolute_rank, ad, type and engine on every row, plus people-also-search blocks in serp_features.
results[]
Step 4 of 4
Loop on pagination.next_start until has_more is false, or send the URLs you chose to scrape or extract.
pagination.next_start
// Code
The last tab is an abbreviated response; meta also carries request_id, requested_at and took_ms.
curl -X POST https://api.wayfern.com/api/search/v1 \
-H "Authorization: Bearer wf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"text": "anti detect browser",
"engines": ["google", "bing", "duckduckgo"],
"site": "github.com",
"limit": 50
}'// Request fields
Site, file type, date range, language and region are fields, validated before the call runs. A malformed date is a 400, not a silently unfiltered result set, and each engine's own syntax is built for you.
| Field | Type | Default | What it does |
|---|---|---|---|
| text | string | — | The phrase, up to 512 characters. Optional when site or filetype is set, but one of the three is required. |
| engines | string[] | ["google"] | Any of google, bing, duckduckgo; CSV or repeated values on GET. Duplicates are collapsed before anything runs or is billed. |
| site | string | — | Restrict to one domain, e.g. github.com. No operator string to assemble. |
| filetype | string | — | Restrict to one file type, e.g. pdf. |
| date | string | — | Range as YYYYMMDD..YYYYMMDD. Anything else is a 400 before a browser opens. |
| lang | string | — | Language hint, e.g. en or en-US. |
| region | string | — | Region or market hint, e.g. US. |
| filter | boolean | true | Hide near-duplicate results. |
| features | boolean | true | Return people-also-search blocks in serp_features, for the engines that expose them. |
| limit | integer | 50 | Results per engine, 1 to 200. Wayfern fetches as many provider pages as that takes. |
| start | integer | 0 | Offset. Carried into rank and absolute_rank, so page two keeps counting where page one stopped. |
text
site or filetype is set, but one of the three is required.engines
site
filetype
date
YYYYMMDD..YYYYMMDD. Anything else is a 400 before a browser opens.lang
region
filter
features
serp_features, for the engines that expose them.limit
start
rank and absolute_rank, so page two keeps counting where page one stopped.The envelope returns the next offset and whether another page exists, so the loop condition is a field rather than a guess.
| Field | Meaning |
|---|---|
| pagination.page | Which page you are on: start ÷ limit, rounded down, plus one. |
| pagination.has_more | True when any requested engine returned a full page: the signal to fetch again. |
| pagination.next_start | The start for the next call (start + limit). Loop until has_more is false. |
pagination.page
start ÷ limit, rounded down, plus one.pagination.has_more
pagination.next_start
start for the next call (start + limit). Loop until has_more is false.// Failures
If some engines answered, you get their results with the failures listed in meta.engines_failed. If every requested engine failed and nothing usable came back, the reason becomes the HTTP status.
| Reason | Status | When |
|---|---|---|
| captcha_detected | 429 | A captcha/soft-block page was served by the engine. |
| rate_limited | 429 | The upstream engine rate-limited the request. |
| blocked | 403 | A hard block was detected (e.g. a /sorry/ page). |
| search_timeout | 504 | The search did not complete in time. |
| internal_error | 500 | An unexpected error occurred. |
{ error, code, request_id, message, reason } body, where reason names the engine that failed; the id is repeated in the X-Request-ID header.captcha_detected
rate_limited
blocked
search_timeout
internal_error
{ error, code, request_id, message, reason } body, where reason names the engine that failed; the id is repeated in the X-Request-ID header.// Next call
Search does not open the result pages or bill you for pages you did not ask for. Filter the results however you like, then send the URLs you chose to extract (or scrape). maxPages caps that second call.
curl -X POST https://api.wayfern.com/api/v1/extract \
-H "Authorization: Bearer wf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://github.com/example/anti-detect-browser",
"https://github.com/example/fingerprint-suite"
],
"prompt": "project name, stated licence, and the primary language",
"maxPages": 25
}'// What it costs
Google returns up to 100 results per page; Bing and DuckDuckGo return 10, so the same limit costs different amounts per engine. Plans start at $19 a month, and non-expiring credit packs are the small way in.
Page sizes differ: 50 results is one Google page but five on Bing and on DuckDuckGo.
| Item | Credits |
|---|---|
| Google, 1 page | 2 |
| Bing, 5 pages | 10 |
| DuckDuckGo, 5 pages | 10 |
Total
22credits
about $0.28
at $0.0125 / credit
About 69 runs a month on Developer ($19 for 1,520 credits).
A daily rank check: one provider page per engine per keyword.
| Item | Credits |
|---|---|
| Google, 100 pages | 200 |
| Bing, 100 pages | 200 |
| DuckDuckGo, 100 pages | 200 |
Total
600credits
about $7.50
at $0.0125 / credit
About 2 runs a month on Developer ($19 for 1,520 credits).
Search draws on the same balance as browser sessions and the Web Data API. Every rate is on the pricing page.
// FAQ
2 credits for every provider result page a request fetches, not per call. Ask one engine for 10 results and that is one page; ask three engines and each bills its own pages. Pages served from the search cache are counted the same way. Listing the engines is free.
rank is the position among organic results; ads count on a separate counter, so a sponsored row never pushes an organic result down. absolute_rank is the position among every row, ads included, in the order the engine rendered them. Pair it with ad and type to rebuild the page as a person saw it.
You get the results from the engines that answered, with the failure named in meta.engines_failed. If every requested engine fails and nothing usable comes back, the sentinel becomes the HTTP status. Running out of credits is a 402 billing_blocked before any browser opens.
No. Search returns titles, descriptions and URLs, and nothing is fetched that you did not name. For content, make a second call: /api/v1/scrape for one page as markdown, or /api/v1/extract for structured data from a list. The token then needs the web scope next to search.
Yes. Over MCP, search_web runs the same search code with the same three engines, the same 1 to 200 limit and the same 2-credit rate per provider page. Its inputs are renamed for agents (query, language, filter_duplicates, include_related), and query is required.
GET /api/search/engines returns the live list from the same registry the search path uses, at no cost. Over MCP, list_search_engines is free as well.
// Keep reading
// Next step
Mint a token with the search scope, send one request, and read ranked results from Google, Bing and DuckDuckGo in one envelope.