Bing Rankings Look Different by Country. Here’s How to Compare Them Fairly.
A Bing URL can appear at different positions in US and UK result samples. To compare those positions fairly, save a dated list for each query and country, hold the settings and sample depth steady, and report a missing URL as unobserved rather than lost.
I founded Serpent API, so this is an affiliated guide to its web search contract. The country examples and Python output are illustrative; these exact queries have no authenticated parsed production result for this article. The Bing API overview lists the current product scope.
Decide what a country ranking means
Track a fixed list of owned URLs across a small set of country and query pairs. Keep the search phrase, country, UTC observation time, returned count and delivery note beside each ranking.
Start with exact query-country pairs such as “running shoes” in us and gb. Hold the phrase, language and safe-search choice steady between runs. Use one canonical URL rule before comparing: remove tracking parameters, normalize host casing and trailing slashes, but retain meaningful paths and query parameters. Do not collapse two distinct product pages into one domain rank.
Keep every query and country setting with its rows
A country code selects country-level search targeting in the Serpent web contract. There is no city-targeting parameter on these endpoints. If you need owned-site clicks and impressions, use Bing Webmaster Tools rather than interpreting a SERP sample as analytics.
| Field or control | Use in this workflow | Check before trusting it |
|---|---|---|
q, engine, country | Preserve 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 count | Keep requested depth separate from usable rows. | Requested results are best-effort; never fill missing rows with invented values. |
delivery, meta.partialResults | Mark short or incomplete observations for review. | Inspect these before interpreting a missing URL as a change. |
Collect a dated Bing sample
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, ""))
TARGET = "https://example.com/shoes"
for country in ("us", "gb"):
sample = snapshot("bing", "running shoes", country=country, num=20)
target = normalized_url(TARGET)
hits = [{"position": row.get("position"), "url": row.get("url")}
for row in sample["organic"] if isinstance(row, dict)
and normalized_url(row.get("url")) == target]
print({"country": country, "observed_at": sample["observed_at"],
"requested": sample["requested"], "returned": len(sample["organic"]),
"target_hits": hits, "delivery": sample["delivery"],
"partial": sample["partial"]})
How to read the output: A missing target means it was not among the returned rows in that sample. If you requested 20 and received fewer, report the shortfall with the observation. Even a full sample does not prove the URL is absent from Bing overall. Repeat with the same setup before declaring a trend.
Budget the repeat schedule
For 20 queries in two countries once daily for 30 days, the worksheet is 20 × 2 × 30 = 1,200 Quick calls. At Serpent’s documented Default Web rate of $0.60 per 1,000 calls, that is $0.72 in metered usage, apart from account deposit and storage. Quick is one charged call even when more results are requested; returned row count is not the billing unit.
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.
Choose between result samples and owned-site data
| Option | Useful for | Published access or billing unit | Decision |
|---|---|---|---|
| Bing Webmaster Tools | Official owned-site Rank & Traffic Stats; useful for site performance, not a public competitor SERP. | Official account access; review its current terms. | Choose it for clicks, impressions and your verified property. |
| SerpApi Bing Search API | Structured Bing organic results with a country control and an explicit result offset. | Its Starter plan lists $25/month for 1,000 successful searches; one successful request counts as a monthly search, regardless of returned rows. | Consider it when explicit offsets or its location controls fit the audit. Compare useful parsed rows for your own queries. |
| Serpent Bing Quick | Country-level sampled organic rows with URL and position. | One Web-category call per Quick request. | Choose it for repeatable public-result observations; validate delivery in your market. |
| Manual Bing Search | Direct visual check of a few queries. | Human time, not an API billing unit. | Choose it to review a surprising change, not for a durable monitor. |
Handle short and missing results
Record device and personalization limits in the report: the API documents country input, but a country code does not reproduce each user’s location, history or exact screen. Search results move over time. A 20-row sample cannot establish a sitewide rank. Do not average a missing observation into position 21; store it as “not observed in returned rows.”
Compare ranks only across like-for-like samples
Keep one record for every planned query-country-target combination, even when the target is not returned. For example, an illustrative US sample may show a target at position 6 on Monday and position 9 on Tuesday, while the UK sample returns only 12 of 20 requested rows and does not show it. The US observation is a three-position movement in those two samples; the UK status is unobserved in 12 returned rows, not “ranked below 20.” Store both raw result lists so someone can check that URL normalization did not merge two pages.
| Situation | Record | Next action |
|---|---|---|
| Same normalized target URL appears twice | Both original URLs and positions; choose a declared reporting rule such as best observed position. | Check whether they are actually duplicate pages before merging. |
| Target missing from a full returned sample | “Not observed within the returned top N,” with N and the date. | Repeat the same query and country before escalating. |
| Target missing from a short or partial sample | Unknown rank; save requested, returned and delivery fields. | Fix sample quality or review manually before comparing positions. |
Compare like with like: identical query text, country, endpoint, requested depth and normalization policy. If one of those changes, start a new series rather than plotting a rank delta. A weekly report can show each target’s observed positions, unknown samples and the raw evidence link; it should never turn an unknown into a numeric rank.
Before you report a rank change
- Pick two countries and three queries that matter to the same site. Write down the target URL and what a useful rank observation would change.
- Run the same query settings in each country. Save parsed organic URLs, positions, requested and returned counts, and the observation time.
- Normalize only tracking noise, then inspect a few surprising positions in the live Bing interface. Keep an unseen URL as unobserved, not absent.
- Repeat the exact pairs on another date before treating a one-day position movement as a trend.
Try a fixed two-country query panel
Start with the few queries that drive a real decision. Save the returned rows and their limits before increasing the schedule.
Get an API keyFAQ
Does country=gb mean London?
No. The documented country input is country-level targeting; it is not a city or postcode selector.
Can a zero-row response prove my page is gone?
No. Preserve the returned count and delivery information, check the query and repeat before making an absence claim.
Should I compare a country sample with Webmaster clicks?
Use the two as different measures. A sampled position describes a public result list; Webmaster Tools reports owned-site search performance.
How many credits does a two-country daily audit use?
The example uses 1,200 Quick calls in 30 days. Check the current Web rate and your account ledger; output row count does not set the Quick call count.






