// Use case · SERP API

Search results as data, from three engines in one call

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

POST /api/search/v1 200 OK
{  "results": [    {      "rank": 1,      "absolute_rank": 2,      "type": "organic",      "ad": false,      "url": "https://github.com/example/…",      "engine": "google"    }  ],  "pagination": {    "page": 1,    "has_more": true  }}
credits per provider page
One row of the envelope: the organic rank, the rank on the page as rendered, and the engine. Search bills 2 credits per provider result page.

// Why

Rank data without a parser per engine

The problem

Search pages are built for people, not parsers

  • Each engine has its own markup, its own operators and its own way of mixing ads into results.
  • A blocked engine often looks like an empty result list, so failures pass silently.
  • Three engines means three integrations and three ways of counting rank.

What Wayfern does

One envelope for three engines

  • Engines you name run concurrently. A URL found by more than one appears once, tagged with the engine that produced it.
  • rank counts organic results only; absolute_rank counts every row, ads included, in page order.
  • A failed engine is named in meta.engines_failed. If every engine fails, the reason becomes the HTTP status.

// How it works

One query in, ranked rows out

POST or GET /api/search/v1. Every result carries the engine that produced it, so one envelope serves all three.

  1. Step 1 of 4

    Send one query

    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

  2. Step 2 of 4

    Engines run in parallel

    Google, Bing and DuckDuckGo are fetched at the same time, and duplicate URLs are merged.

    "engines": ["google", "bing", "duckduckgo"]

  3. Step 3 of 4

    Read ranked rows

    rank, absolute_rank, ad, type and engine on every row, plus people-also-search blocks in serp_features.

    results[]

  4. Step 4 of 4

    Page, or hand off

    Loop on pagination.next_start until has_more is false, or send the URLs you chose to scrape or extract.

    pagination.next_start

// Code

Send a search, read the envelope

The last tab is an abbreviated response; meta also carries request_id, requested_at and took_ms.

POST /api/search/v1
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

Typed fields, not a query string you concatenate

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.

  • text

    Type
    string
    Default
    —
    What it does
    The phrase, up to 512 characters. Optional when site or filetype is set, but one of the three is required.
  • engines

    Type
    string[]
    Default
    ["google"]
    What it does
    Any of google, bing, duckduckgo; CSV or repeated values on GET. Duplicates are collapsed before anything runs or is billed.
  • site

    Type
    string
    Default
    —
    What it does
    Restrict to one domain, e.g. github.com. No operator string to assemble.
  • filetype

    Type
    string
    Default
    —
    What it does
    Restrict to one file type, e.g. pdf.
  • date

    Type
    string
    Default
    —
    What it does
    Range as YYYYMMDD..YYYYMMDD. Anything else is a 400 before a browser opens.
  • lang

    Type
    string
    Default
    —
    What it does
    Language hint, e.g. en or en-US.
  • region

    Type
    string
    Default
    —
    What it does
    Region or market hint, e.g. US.
  • filter

    Type
    boolean
    Default
    true
    What it does
    Hide near-duplicate results.
  • features

    Type
    boolean
    Default
    true
    What it does
    Return people-also-search blocks in serp_features, for the engines that expose them.
  • limit

    Type
    integer
    Default
    50
    What it does
    Results per engine, 1 to 200. Wayfern fetches as many provider pages as that takes.
  • start

    Type
    integer
    Default
    0
    What it does
    Offset. Carried into rank and absolute_rank, so page two keeps counting where page one stopped.

Offsets in, a stop signal out

The envelope returns the next offset and whether another page exists, so the loop condition is a field rather than a guess.

  • pagination.page

    Meaning
    Which page you are on: start ÷ limit, rounded down, plus one.
  • pagination.has_more

    Meaning
    True when any requested engine returned a full page: the signal to fetch again.
  • pagination.next_start

    Meaning
    The start for the next call (start + limit). Loop until has_more is false.

// Failures

A blocked engine is a status, not an empty array

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.

  • captcha_detected

    Status
    429
    When
    A captcha/soft-block page was served by the engine.
  • rate_limited

    Status
    429
    When
    The upstream engine rate-limited the request.
  • blocked

    Status
    403
    When
    A hard block was detected (e.g. a /sorry/ page).
  • search_timeout

    Status
    504
    When
    The search did not complete in time.
  • internal_error

    Status
    500
    When
    An unexpected error occurred.
Errors are a flat { 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

Two calls, on purpose

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.

Search, then extract the URLs you picked
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

2 credits per provider result page

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.

One research query, top 50 on all three

Page sizes differ: 50 results is one Google page but five on Bing and on DuckDuckGo.

Credits for: One research query, top 50 on all three
ItemCredits
Google, 1 page2
Bing, 5 pages10
DuckDuckGo, 5 pages10

Total

22credits

about $0.28
at $0.0125 / credit

About 69 runs a month on Developer ($19 for 1,520 credits).

Track 100 keywords, top 10, three engines

A daily rank check: one provider page per engine per keyword.

Credits for: Track 100 keywords, top 10, three engines
ItemCredits
Google, 100 pages200
Bing, 100 pages200
DuckDuckGo, 100 pages200

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

Questions about search

How is a search billed?

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.

What is the difference between rank and absolute_rank?

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.

What happens when one engine is blocked?

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.

Does search open the result pages?

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.

Can an AI agent use the same 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.

How do I know which engines are available?

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.

// Next step

Run your first query against three engines

Mint a token with the search scope, send one request, and read ranked results from Google, Bing and DuckDuckGo in one envelope.