Mobile SERP tracking: desktop vs mobile Google results
Google’s results page differs between a phone and a desktop. Google also ranks pages from their mobile version: “Google uses the mobile version of a site’s content, crawled with the smartphone agent, for indexing and ranking” (Google Search Central, checked 2026-11-11). A rank report built only from desktop results can miss what most searchers see. This post shows how to request both layouts and compare them.
The device parameter
Google Search takes device on every request:
| Value | Results page |
|---|---|
desktop (default) |
Desktop |
ios |
Mobile, as Safari on an iPhone |
android |
Mobile, as Chrome on an Android phone |
mobile |
Same as android |
Google AI Mode takes desktop (default) or mobile.
Google News does not serve device yet. A request that sets it is refused with a 400 and not charged. The chat engines (ChatGPT, Gemini, Perplexity, Copilot, Grok) have no device field. The full list of targeting fields per engine is in coverage and engine features.
The device does not change the price. A one-page Google Search request costs $0.0015 at the Starter tier and $1.00–$1.50 per 1,000 across tiers, on desktop or mobile (pricing).
What to combine with device
countrysets the Google market.location(Google’sCity,Region,Countryform, for exampleChicago,Illinois,United States) oruulesets the city. Without either, results are localized to the country.hlsets the interface language.pagesfrom 1 to 10 adds results pages. Each page after the first adds $0.00071.
Keep every field except device the same between the two runs. Otherwise you are measuring two variables at once. The request fields are listed in the API reference, and Google Search parameters explains each one.
Compare desktop and mobile positions in Python
This script asks for the same query on desktop and on an iPhone, then prints where a domain ranks in each.
import os
from urllib.parse import urlparse
import requests
API = "https://api.answerline.dev/v1/monitor/google"
HEADERS = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
def position(query, domain, device, location):
r = requests.post(
API,
headers=HEADERS,
json={
"query": query,
"country": "US",
"location": location,
"device": device,
},
timeout=630,
)
r.raise_for_status()
for item in r.json()["result"].get("organicResults", []):
host = urlparse(item["link"]).hostname or ""
if host == domain or host.endswith("." + domain):
return item["position"]
return None
query = "running shoes for flat feet"
for device in ("desktop", "ios"):
pos = position(query, "example.com", device, "Chicago,Illinois,United States")
print(device, pos if pos is not None else "not in top 10")
Each organicResults item has position, title, link, displayedLink, snippet and page. The two calls cost the same.
Run both devices as a batch
For a keyword list, send async tasks instead of synchronous calls. POST /v1/async/task/batch takes up to 500 tasks per call. Put the device and the date in each idempotencyKey, so a retried batch does not create a second task for the same keyword, device and day:
[
{
"taskType": "GOOGLE",
"idempotencyKey": "2026-11-11:desktop:running-shoes-flat-feet",
"webhook": {"url": "https://your-app.com/webhook-handler"},
"payload": {"query": "running shoes for flat feet", "country": "US", "device": "desktop"}
},
{
"taskType": "GOOGLE",
"idempotencyKey": "2026-11-11:ios:running-shoes-flat-feet",
"webhook": {"url": "https://your-app.com/webhook-handler"},
"payload": {"query": "running shoes for flat feet", "country": "US", "device": "ios"}
}
]
This is an example body. The fields follow the batch schema in the async docs. The webhook gets each finished task with its result.
Cost: 200 keywords on 2 devices once a day is 400 requests a day and 12,000 over 30 days, $18.00 at the Starter tier. Failed requests are not charged.
Storing the results
Store the device you asked for in every row, next to the query, country, location and date. Then a rank chart is one line per device, and a gap between the lines is a finding:
| Keyword | Date | Device | Position |
|---|---|---|---|
| running shoes for flat feet | 2026-11-11 | desktop | 4 |
| running shoes for flat feet | 2026-11-11 | ios | 7 |
The rows above are an illustration, not a measurement.
Pitfalls
localResults(the local pack) is returned for the desktop layout only. Track local pack presence ondesktop.mobileandandroidare the same request. Tracking both doubles the cost for one list.- Positions shift between runs on both devices. Compare trends over several days, not one pair of requests. AI answer volatility covers how to read run-to-run change.
- Google News refuses
devicewith a 400. Leave it out of News payloads when you reuse a Google Search template. stateon Google Search answers for the whole US. For a city, uselocationoruule.
Next step
A new account gets 100 free requests. Run your top keywords on desktop and ios and see how far the two lists differ before you set a schedule. Field reference: Google Search engine page.
Questions
Which device values does Google Search accept?
desktop (the default), mobile, ios and android. desktop returns the desktop results page. ios is Safari on an iPhone, android is Chrome on an Android phone, and mobile is the same as android.
Does a mobile request cost more than a desktop one?
No. The device does not change the price. A one-page Google Search request costs $0.0015 at the Starter tier on any device.
Which engines accept device?
Google Search (desktop, mobile, ios, android) and Google AI Mode (desktop, mobile). Google News refuses a request that sets device with a 400. ChatGPT, Gemini, Perplexity, Copilot and Grok have no device field.
Should I track iOS and Android separately?
Only if your traffic splits that way and the two lists differ for your keywords. Run both for a sample of keywords for a week. If the positions match, track one of them.
Why does my local pack disappear on mobile?
The localResults field is returned for the desktop layout only. A mobile request does not return the local pack. Track local pack presence with device set to desktop.