Bing Search API Alternative: Web Results as JSON
Microsoft retired its Bing Search APIs on August 11, 2025, so any code still calling them has to move. One replacement is GET /v1/bing/search, which returns Bing web results as JSON for 1 credit per successful call. If you'd rather change engines, /v1/brave/search and /v1/duckduckgo/search use the same key and the same cost. This guide compares the three by parameters and response shape.
Why did the official Bing Search API go away?
Microsoft announced the retirement in May 2025 and set August 11, 2025 as the date existing instances were decommissioned, with the product closed to new sign-ups (Microsoft Lifecycle announcement, 2025). Microsoft pointed customers to Bing grounding in its Azure AI Agents offering instead (The Register, 2025).
That route fits if your search call already lives inside an Azure agent. It does not help if you want a plain HTTP call that returns a list of links and snippets to your own code, which is what most pipelines using the old API did.
The endpoints below are Perceptlayer's own, not Microsoft's. The path, parameters and response shape are different, so expect to change your client code and not only the URL.
How do Microsoft's old names map to the new endpoint?
Microsoft's Bing Web Search API v7 used q, count, offset, safeSearch and setLang as query parameters and returned results under webPages.value[] with name, url and snippet (Microsoft query parameters and response objects, 2025). The table pairs each with its closest counterpart on GET /v1/bing/search.
| Microsoft v7 (old) | /v1/bing/search (new) |
What changes |
|---|---|---|
q |
query |
Renamed. Microsoft allowed the site: operator inside q; here site is its own parameter |
count |
num |
Renamed. Microsoft documented a default of 10 and a maximum of 50; this endpoint's num limits aren't documented, so test before relying on them |
offset (zero-based count of results to skip) |
page (integer) |
Skip-count paging becomes page-number paging; recompute your loop |
safeSearch: Off, Moderate, Strict |
safe: off, moderate, strict |
Renamed and lower-cased |
setLang, mkt |
hl |
One language parameter; its accepted values aren't documented here |
Ocp-Apim-Subscription-Key header |
X-API-Key header |
Different key, see authentication |
webPages.value[] |
data[] |
The list moves to the top level |
name |
title |
Renamed |
snippet |
description |
Renamed |
url |
url |
Same name |
displayUrl |
domain |
Not the same thing: domain is the host name |
webPages.totalEstimatedMatches |
results_on_page |
Different meaning: a count for this page, not an estimate for the query |
Microsoft's other answer types (images, news, relatedSearches and the rest) don't appear in the recorded Bing response below, which holds only data, results_on_page and source. If your code read those answers, check whether you still need them.
What does the Bing search endpoint take?
GET /v1/bing/search has one required parameter, query (up to 500 characters), and six optional ones for paging, site and file-type filtering, safe search, language and raw HTML. A successful call costs 1 credit. The documented parameters are in the table, and the Bing API page has a playground where you can run it with your key.
| Parameter | Type | Required | Notes |
|---|---|---|---|
query |
string | yes | Up to 500 characters |
page |
integer | no | |
num |
integer | no | |
site |
string | no | Up to 253 characters |
filetype |
string | no | Up to 20 characters |
safe |
string | no | One of off, moderate, strict |
hl |
string | no | Up to 20 characters |
include_html |
boolean | no |
Only the types and limits above are documented, so this guide doesn't state defaults or page sizes. Check them in the playground before you rely on them.
Request and response
The request is one GET with your key in the X-API-Key header (see authentication):
curl "https://api.perceptlayer.com/v1/bing/search?query=best+web+scraping+api" \
-H "X-API-Key: $API_KEY"
This is an excerpt of the recorded response for that exact request:
{
"data": [
{
"position": 1,
"title": "Best",
"description": "Sep 14, 2026 · Надійний інтернет-провайдер Best ⭐️ Головна ⚡️Швидкість від 200 Мбіт/с до 1 Гбіт/с ⭐️ Працює при …",
"url": "https://best.net.ua/",
"domain": "best.net.ua"
},
…
],
"results_on_page": 10,
"source": "bing"
}
Each result has position, title, description, url and domain. The list sits under data, and results_on_page says how many results this page held. The snippet text is description, and the link is url.
Which parameters change when you switch engines?
The three endpoints cover the same job but name their inputs differently: Bing uses query, Brave uses query or q, and DuckDuckGo uses q only. Paging also differs, with page on Bing and Brave and start on DuckDuckGo. Here are the documented parameters side by side, all at 1 credit per call.
| Bing | Brave | DuckDuckGo | |
|---|---|---|---|
| Path | /v1/bing/search |
/v1/brave/search |
/v1/duckduckgo/search |
| Required | query |
query |
q |
| Paging | page, num |
page |
start |
| Language or region | hl |
language |
kl |
| Safe search | safe: off, moderate, strict |
not listed | safe: boolean |
| Site filter | site |
not listed | not listed |
| File type | filetype |
not listed | not listed |
| Date filter | not listed | not listed | df: d, w, m, y |
| Raw HTML | include_html |
include_html |
not listed |
| Credits | 1 | 1 | 1 |
"Not listed" means the parameter isn't in the endpoint's documented list. It doesn't prove the engine can't do it, but you can't pass it through this endpoint. If your old code relied on site or filetype, Bing is the closest match.
Brave's language accepts words (english, deutsch, french and others), two-letter codes (en, de, fr) or all. The recorded DuckDuckGo request uses kl=us-en; kl is a string of up to 10 characters.
How does each response look?
Every engine returns JSON with a ranked list, but the field names and the extras around it differ. Bing returns the leanest body. Brave adds related searches and related questions. DuckDuckGo wraps everything in success, data and meta, and adds pagination and a knowledge_graph block.
| Bing | Brave | DuckDuckGo | |
|---|---|---|---|
| List | data[] |
organic_results[] |
data.organic_results[] |
| URL field | url |
link |
link |
| Text field | description |
snippet |
snippet |
| Extra per-result fields | domain |
redirect_link, displayed_link, source |
display_url, favicon |
| Beyond the list | results_on_page |
related_searches, related_questions, results_count |
pagination, knowledge_graph, meta |
Here is an excerpt of the recorded Brave response for the same query, best web scraping api:
{
"search_information": {
"query_displayed": "best web scraping api"
},
"organic_results": [
{
"position": 1,
"title": "…The Best Web Scraping API",
…
}
],
"related_questions": [
{
"question": "What is the best web scraping API in 2026?",
…
}
],
"results_count": 20,
"source": "brave"
}
And the recorded DuckDuckGo response for q=laravel+taylor+otwell&kl=us-en&start=0, trimmed to the pagination block:
{
"success": true,
"data": {
"search_parameters": {
"q": "laravel taylor otwell",
"kl": "us-en",
"safe": true,
"start": 0
},
…
"pagination": {
"current": 1,
"has_more": true,
"next_start": 10,
"next": 2
}
},
"meta": {
"duration_ms": 2169,
"endpoint_family": "web_search"
}
}
DuckDuckGo gives you has_more and next_start, so a page loop can follow the response and doesn't need a guessed offset. One detail to handle: the recorded snippet contains the HTML entity &, so decode snippets before you show them or feed them to a model.
How much should you expect results to change?
Expect the result sets to change in both shape and ranking, and measure it on your own queries. The shape differs by the tables above: Bing's recorded body reports results_on_page: 10, Brave's reports results_count: 20, and DuckDuckGo reports paging through pagination. Rankings differ too, but this guide has one recorded query per engine, which isn't enough to say how much.
Three habits keep the migration honest:
- Replay a sample of your real production queries against the old results you saved, and compare the top URLs, not only the count.
- Keep
positionin your stored rows so you can compare rankings later. - Use the page-size fields in each body and don't hard-code a page size.
Here is the one example we have. For best web scraping api, the recorded Bing top two were an internet provider's homepage and a dictionary entry for "best", while the recorded Brave top two were pages about scraping APIs. That is one short, ambiguous query on one day, not a benchmark. For ambiguous queries, add words that pin the topic, or use site on the Bing endpoint to restrict results to a domain. Data is returned as found at the time of the call, so rankings can move between runs.
How do you call all three from one script?
One small normaliser lets the rest of your code ignore which engine answered. This Python script sends the same query to each endpoint, maps each body to position, title, url and snippet, and prints the credits the call used from the X-Credits-Used header.
import html
import os
import requests
BASE = "https://api.perceptlayer.com"
HEADERS = {"X-API-Key": os.environ["API_KEY"]}
def bing(q):
r = requests.get(f"{BASE}/v1/bing/search", params={"query": q}, headers=HEADERS, timeout=30)
r.raise_for_status()
rows = [(x["position"], x["title"], x["url"], x["description"]) for x in r.json()["data"]]
return r, rows
def brave(q):
r = requests.get(f"{BASE}/v1/brave/search", params={"query": q}, headers=HEADERS, timeout=30)
r.raise_for_status()
rows = [(x["position"], x["title"], x["link"], x["snippet"]) for x in r.json()["organic_results"]]
return r, rows
def duckduckgo(q):
r = requests.get(f"{BASE}/v1/duckduckgo/search", params={"q": q}, headers=HEADERS, timeout=30)
r.raise_for_status()
rows = [
(x["position"], x["title"], x["link"], html.unescape(x["snippet"]))
for x in r.json()["data"]["organic_results"]
]
return r, rows
for name, fn in [("bing", bing), ("brave", brave), ("duckduckgo", duckduckgo)]:
resp, rows = fn("best web scraping api")
print(name, "credits used:", resp.headers.get("X-Credits-Used"))
for position, title, url, snippet in rows[:3]:
print(" ", position, title, url)
The script handles errors only with raise_for_status(). For retries, rate-limit waits and the error codes, see errors and the related guide on handling web data API errors in Python. To fetch more than one page, paginate safely.
What does a search migration cost in credits?
Each successful call costs 1 credit on all three endpoints, and each page you request is its own call. A job that sends 5,000 queries and reads 3 pages of each makes 15,000 calls, which is 15,000 credits. Failed calls cost nothing and return X-Credits-Used: 0, so retries on errors don't add to the bill.
| Workload | Calls | Credits |
|---|---|---|
| Trial: 1,000 queries, 1 page each | 1,000 | 1,000 (the monthly free tier) |
| 20,000 queries, 1 page each | 20,000 | 20,000 |
| 5,000 queries, 3 pages each | 15,000 | 15,000 |
Credits come in prepaid packs. The Starter pack is 10,000 credits, so 20,000 credits needs the Build pack (50,000) or two Starter packs. Purchased credits last 12 months, with no subscription. The pricing page has what credits cost; this guide doesn't quote prices because they change.
If you compare against another provider's rate card, count calls the same way: one call per page, not one per query. The credits docs explain how X-Credits-Used and X-Credits-Remaining report each call, and rate limits cover requests per second if you run the migration as a batch.
How do you start?
Sign up, send the curl call from the Bing section with your key, and diff its top URLs against a saved result from your old integration. Free accounts get 1,000 credits a month after email verification, with no credit card, so a first comparison across all three engines costs nothing. Get a free API key, then run the same query on the Bing, Brave and DuckDuckGo search endpoints in the playground.
If you're feeding results to an agent, the same endpoints are available as tools through the MCP server; see AI agents (MCP). For Google results instead, read the Google Search API guide. The search engines hub lists every search source in the catalogue.
APIs in this post
Frequently asked questions
Is the Bing Search API still available?
No. Microsoft retired the Bing Search APIs on August 11, 2025, and existing instances were decommissioned (Microsoft Lifecycle announcement, 2025). Microsoft suggested Bing grounding in Azure AI Agents as its replacement. GET /v1/bing/search is a separate endpoint with its own parameters and JSON shape.
What does one search call cost?
One credit for each successful call on the Bing, Brave and DuckDuckGo search endpoints. Failed calls cost nothing and return X-Credits-Used: 0. Every account gets 1,000 free credits a month after verifying an email address, so a first comparison costs nothing. See pricing for what credits cost.
Which endpoint should I use if I need site or file-type filters?
Use the Bing endpoint. It documents site (up to 253 characters) and filetype (up to 20 characters), and the Brave and DuckDuckGo endpoints list neither.
Will I get the same results as the old Bing API?
Not guaranteed. Data is returned as found at the time of the call, and result sets can differ by engine. In the one recorded query, Bing and Brave returned different top results, which is an example and not a benchmark. Replay a sample of your real queries and compare URLs before you cut over.
How do I get more than one page of results?
Bing and Brave take page; DuckDuckGo takes start, and its response includes pagination.has_more and next_start so you can follow it. Each page is a separate call at 1 credit. Stop the loop when has_more is false or when your credit budget for the job is spent.