Bing Search API Is Gone. What Should Pass Before You Switch?

By , Founder of Serpent API·

Microsoft retired Bing Search APIs, but a replacement request returning 200 does not make your migration safe. Write down the fields your application needs, run representative query fixtures, and hold the cutover if useful parsed rows or downstream behavior fail.

Microsoft retired its Bing Search APIs in August 2025. I founded Serpent API; this affiliated guide compares migration requirements with our documented web contract. Its code and sample outcomes are illustrative, with no authenticated parsed production result for these exact queries.

Write the acceptance rules before migration

Define the output your application consumes: organic URL, title and position; country and query echo; requested versus returned rows; plus any rich blocks needed by a Deep workflow. Write a pass condition for each before changing production traffic.

Build a fixture set with branded, generic, long-tail and no-result-prone queries in your important countries. Include at least one query with a known owned URL and one where the expected answer may be empty. Store the date, endpoint, parameters, raw response and parsed count. Compare semantics, not just field names: a missing organic array is a contract failure; an empty valid array is an observation that needs investigation.

Map the fields your application consumes

Microsoft says its Bing Search APIs retired on August 11, 2025. That lifecycle notice explains the migration pressure, but it does not make any third-party endpoint a drop-in equivalent. Serpent’s Quick endpoint is organic-only; rich SERP features belong to Deep and may be absent even there.

Field or controlUse in this workflowCheck before trusting it
q, engine, countryPreserve the exact request identity.Compare only samples with intended matching settings.
results.organic[]Read url, title and position from each usable row.Validate array and row types; a position is an observed list location.
num and returned countKeep requested depth separate from usable rows.Requested results are best-effort; never fill missing rows with invented values.
delivery, meta.partialResultsMark short or incomplete observations for review.Inspect these before interpreting a missing URL as a change.

Run a parsed-result fixture set

Install requests and set SERPENT_API_KEY in your local environment. This complete Python example shows the request and parsed-field handling for the workflow. It has been syntax-checked locally, but the sample query output has not been validated against an authenticated production response. Run it against your account before adopting it.

import os
from datetime import datetime, timezone
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode
import requests

BASE = "https://apiserpent.com"
KEY = os.environ["SERPENT_API_KEY"]

def snapshot(engine, query, country="us", num=10, deep=False):
    endpoint = "/api/search" if deep else "/api/search/quick"
    response = requests.get(
        BASE + endpoint,
        params={"engine": engine, "q": query, "country": country,
                "num": num, "format": "full"},
        headers={"X-API-Key": KEY}, timeout=55 if deep else 30)
    response.raise_for_status()
    data = response.json()
    if data.get("success") is not True or not isinstance(data.get("results"), dict):
        raise ValueError("No usable results object")
    rows = data["results"].get("organic")
    if not isinstance(rows, list):
        raise ValueError("Organic rows are missing")
    return {"observed_at": datetime.now(timezone.utc).isoformat(),
            "query": query, "country": country, "engine": engine,
            "requested": num, "organic": rows,
            "delivery": data.get("delivery"),
            "partial": (data.get("meta") or {}).get("partialResults")}

def normalized_url(raw):
    if not isinstance(raw, str) or not raw.startswith(("https://", "http://")):
        return None
    parts = urlsplit(raw)
    host = (parts.hostname or "").lower().removeprefix("www.")
    path = parts.path.rstrip("/") or "/"
    query = urlencode(sorted((k, v) for k, v in parse_qsl(parts.query)
                             if not k.lower().startswith("utm_")))
    return urlunsplit((parts.scheme.lower(), host, path, query, ""))

FIXTURES = [
    {"q": "example brand", "country": "us", "known_domain": "example.com"},
    {"q": "best running shoes", "country": "gb", "known_domain": None},
]
for case in FIXTURES:
    sample = snapshot("bing", case["q"], country=case["country"], num=20)
    rows = sample["organic"]
    parsed = []
    for row in rows:
        if not isinstance(row, dict):
            continue
        url = normalized_url(row.get("url"))
        if url is None or not isinstance(row.get("title"), str):
            continue
        parsed.append({"position": row.get("position"), "title": row["title"],
                       "url": url})
    domains = {urlsplit(item["url"]).hostname for item in parsed}
    print({"fixture": case, "returned": len(rows), "usable": len(parsed),
           "known_domain_seen": case["known_domain"] in domains
                                if case["known_domain"] else None,
           "delivery": sample["delivery"], "partial": sample["partial"]})

How to read the output: Set thresholds from your own business need before seeing the result. For example, require every response to have a valid organic array and every record you store to have a usable URL. A known-domain miss is a review trigger, not automatic proof the API is faulty. Repeat it on a different date and compare the live result.

Budget the acceptance run

Price the exact path you plan to cut over. At the documented Serpent Default Web rate, 100 Quick fixtures cost 100 × $0.0006 = $0.06 in metered usage; 100 Deep requests for three pages each use 300 Web units, or $0.18. These are planning calculations from requested units, not delivery benchmarks. The account deposit and any optional features are separate.

Serpent’s published pricing gives Web-category rates by account tier. The figures above use the documented Default rate as a dated planning example, not a measured invoice. Confirm your tier, optional features and actual charges in your account before scaling. A response with fewer rows does not by itself change the requested-page unit for Deep.

Compare replacement routes by output

OptionUseful forPublished access or billing unitDecision
Microsoft Bing Search APIThe legacy official API named in the retirement notice.Retired August 11, 2025.Do not design a new cutover around unavailable legacy access.
Bing Webmaster ToolsOwned-site performance and submission workflows.Official access for verified properties.Useful to corroborate your site, not a replacement public SERP.
Serpent Bing web searchQuick organic snapshots or Deep richer result blocks.Quick one Web unit; Deep per requested page.Map fields explicitly and test parsed quality before routing users.

What can make a migration look safer than it is?

A replacement can meet the JSON shape and still fail your use case through short lists, changed ranking, different country interpretation or missing optional blocks. Keep a rollback plan and compare business output over a representative period. Do not convert an empty array into a “pass” just because the request returned 200.

Make the cutover decision from useful delivery

Make the acceptance sheet from your application’s actual dependencies, not a generic promise that both APIs return “Bing data.” An app that displays ten links needs valid URL, title and position on enough rows to render its screen; an app that ranks competitors may need a particular country setting and a stable URL key. Keep required fields separate from optional rich blocks so one missing feature does not silently change the verdict for the whole product.

Fixture resultExample pass rule to adaptFailure action
Parsed shapeEvery fixture has an organic array; every displayed row has a usable URL, title and integer position.Block cutover and fix the mapping or choose another response path.
Useful deliveryEnough usable rows arrive for the screen or job you named before testing; record short and partial responses separately.Keep the existing path and investigate the specific query-country pair.
Downstream decisionA saved application fixture produces the intended UI or alert with the new payload, including empty and partial cases.Repair client behavior before any traffic shift.

For a worked hypothetical case, suppose your screen needs five links. A 200 response with two valid rows fails that fixture even though the JSON parses; a valid empty list may pass a deliberately empty-result fixture. Record the expected minimum before running the provider sample, then test a small production-account canary and a rollback to the old path. The sample code prints evidence for this sheet; it does not decide whether your own thresholds passed.

The final acceptance checklist

  1. Choose real navigational, informational and low-volume Bing queries from the system being replaced; record required result fields for each.
  2. Run each query through the candidate endpoint and validate parsed organic URL, title and position, plus returned count and delivery metadata.
  3. Compare the output with a same-day manual Bing sample and your application’s existing acceptance thresholds; record material gaps by field.
  4. Cut over only after client error handling, billing units and a rollback procedure have passed a small production-account test.

Run a small acceptance fixture set

Start with the few queries that drive a real decision. Save the returned rows and their limits before increasing the schedule.

Get an API key

Try the playground · Read the web API contract

FAQ

Is Serpent a drop-in replacement for Microsoft Bing Search API?

No. The endpoint and response contract differ; map the fields your application uses and run an acceptance fixture set.

What should be checked before switching traffic?

Required parsed fields, meaningful result quality, country behavior, short delivery, billing units and your application’s downstream decisions.

Does Quick include featured snippets?

No. Quick is organic-only. Use Deep if your application needs richer SERP blocks and handle their absence.

Can I trust a successful HTTP response as a passed fixture?

No. Parse the results object, count usable rows and inspect delivery information against each fixture’s expected behavior.

Related Posts