Google search parameters: gl, hl, uule, num, start, tbs and safe, mapped to API fields
Google’s results change with a handful of URL parameters: gl (country), hl (interface language), uule (location), num and start (depth and offset), tbs (time and other filters) and safe (SafeSearch). Device is not a URL parameter but changes the page just as much.
Two request shapes
POST /v1/monitor/google (and the GOOGLE async task type) accepts one of two shapes. The request must contain either query with country, query with gl, or url.
Standard shape. You describe the search with fields:
{ "query": "running shoes", "country": "US", "hl": "en", "location": "New York,New York,United States", "device": "mobile", "pages": 2 }
URL shape. You pass a complete Google web search URL, and the API applies a fixed set of its parameters:
{ "url": "https://www.google.com/search?q=running+shoes&gl=us&hl=en&num=20&tbs=qdr:m" }
The URL must be an absolute http(s) URL on a Google web search host (such as google.com, google.co.uk or www.google.de), with the path /search and a non-empty q. Only q, gl, hl, uule, num, start, tbs and safe are applied; everything else is dropped. Use the URL shape when you need start, tbs or safe, which have no field in the standard shape, or when you already store Google URLs.
Mapping table
| Google parameter | Meaning | Standard shape | URL shape |
|---|---|---|---|
q |
Query, operators included | query (1 to 10,000 characters) |
q in the URL |
gl |
Result country | country or gl (ISO 3166-1 alpha-2) |
gl in the URL, or country to override |
hl |
Interface language | hl |
hl in the URL, or the hl field |
uule |
Encoded location | uule, or location by name |
uule in the URL |
num |
Results per page | pages (1 to 10) |
num, read as depth in whole pages of ten, max 10 pages |
start |
Result offset | none | start in the URL |
tbs |
Time range and other filters | none | tbs in the URL |
safe |
SafeSearch | none | safe in the URL |
tbm |
Vertical (news, images, …) | none | rejected; use POST /v1/monitor/google/news for news |
| (not a URL parameter) | Device | device |
device |
lr, cr, filter, others |
Various | none | dropped |
gl and country
gl sets the country Google serves results for. In this API, country and gl take the same ISO 3166-1 alpha-2 codes, gl is accepted in either case (us or US), and the country must be one the endpoint supports:
curl https://api.answerline.dev/v1/countries?model=google \
-H "Authorization: Bearer $ANSWERLINE_API_KEY"
Send country or gl. Sending both with different values is a 400. In the URL shape, country is derived from the URL’s gl; pass country explicitly to override it or when the URL has no gl.
hl
hl is the interface language, as Google’s own codes: en, de, pt-BR. It is free-form and case-insensitive, and it overrides the language the API would derive from the country. That lets you capture, for example, English-language results pages in Germany (country: "DE", hl: "en"). For multilingual AI answer monitoring, see multilingual AI answers.
Google’s Custom Search API documented a separate lr parameter to restrict documents to a language; for Google web search URLs, this API drops lr.
uule and location
Country-level targeting is not enough when the results you care about depend on where the searcher is, such as the local pack. Google encodes a location in the uule parameter. This API gives you two ways to set it:
location: a Google canonical location name inCity,Region,Countryform, such as"New York,New York,United States". The valid names are those in Google Ads’ geotargets list, about 100,000 locations.uule: a pre-encoded UULE string, if you already build them.
location and uule are mutually exclusive. Neither can be combined with url; in the URL shape, put uule in the URL. Send country alongside for the matching country. For the local use case, see local rank tracking and state-level AI answers.
num, start and pages
Google’s num once set results per page, up to 100. Search Engine Roundtable reported in September 2025 that &num=100 had stopped working and that this broke most Google rank trackers. Results now come in pages of about ten.
This API models depth as pages:
- Standard shape:
pagesfrom 1 to 10 (default 1). Every organic result carriespage(1-indexed) andposition(1-indexed). - URL shape:
numis read as result depth, rounded up to whole pages of ten and capped at ten pages.num=20returns 2 pages;num=25returns 3;num=100returns 10.
start is the offset of the first result, and only exists in the URL shape. According to Bright Data’s Google URL parameter reference (dated February 26, 2026; Google does not document it), start=0 is page 1, start=10 page 2 and start=20 page 3. Pages continue from start, so start=20&num=20 asks for two pages beginning at the third.
For most tracking, skip start and ask for pages: one request returns the top N pages with page numbers already attached. See rank tracking with batches and Google rank tracking API.
device
Google serves separate desktop and mobile results pages, so capture the one your audience sees. device accepts:
| Value | Page returned |
|---|---|
desktop (default) |
Desktop SERP |
ios |
Mobile SERP, as Safari on iPhone |
android |
Mobile SERP, as Chrome on Android |
mobile |
Alias for android |
device works in both shapes.
tbs
tbs carries Google’s search tools filters, most often time ranges. Google does not document its values. Bright Data’s reference lists:
tbs value |
Filter |
|---|---|
qdr:h |
Past hour |
qdr:d |
Past 24 hours |
qdr:w |
Past week |
qdr:m |
Past month |
qdr:y |
Past year |
cdr:1,cd_min:01/01/2025,cd_max:12/31/2025 |
Custom date range |
URL-encode the value when you build the URL. If a documented alternative works for you, prefer it: Google’s Refine Google searches help page documents before: and after: operators, which go in query. See Google search operators.
safe
safe controls SafeSearch filtering. Google’s Custom Search reference defined active (filter) and off (default) for its API, and Bright Data’s reference lists the same values for web search URLs. In this API, safe is only available in the URL shape.
Validation rules
The request schema allows no unknown fields. These cases are rejected:
| Request | Why |
|---|---|
Neither query + country/gl nor url |
One of the three shapes is required |
query and url together |
Mutually exclusive |
url with location, uule or pages |
The URL owns those values |
location and uule together |
Mutually exclusive |
country and gl with different values |
Conflicting geography |
A country not listed by GET /v1/countries?model=google |
Unsupported |
A url that is not a Google web search URL with path /search and a non-empty q |
Not a search URL |
A url containing tbm |
Verticals have their own endpoint |
pages outside 1 to 10, query longer than 10,000 characters, a device outside the enum |
Schema limits |
Any field not in the schema (for example num or start at the top level) |
additionalProperties is false |
A synchronous call returns HTTP 400 with the validation error shape from the OpenAPI document:
{
"success": false,
"error": "Request validation failed",
"details": [{ "field": "uule", "message": "..." }]
}
For async tasks, validation failures return 422. Retry logic should never retry a 400 or 422 unchanged; see API errors and retries.
Cost in credits
A Google Search async task costs 3 credits, each page after the first adds 2, include.aioverview or include.paaAioverview adds 2 once, and a synchronous call adds 2. An accepted request reserves the maximum cost and is charged for the pages returned; a failed request is not charged.
| Request | Async task | Synchronous call |
|---|---|---|
query + country, 1 page |
3 | 5 |
pages: 3 |
7 | 9 |
pages: 10 |
21 | 23 |
1 page + include.aioverview |
5 | 7 |
pages: 3 + include.aioverview |
9 | 11 |
url with num=30 (3 pages) |
7 | 9 |
device, hl, location, uule, tbs and safe do not change the price. See /docs/credits and cost planning.
Examples
Python: one keyword across markets and devices
This uses requests and async tasks, which cost less than synchronous calls for volume.
import itertools
import os
import requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['ANSWERLINE_API_KEY']}"}
markets = [
{"country": "US", "hl": "en", "location": "New York,New York,United States"},
{"country": "DE", "hl": "de"},
{"country": "DE", "hl": "en"},
]
devices = ["desktop", "ios"]
tasks = [
{
"taskType": "GOOGLE",
"payload": {"query": "project management software", **market, "device": device, "pages": 2},
}
for market, device in itertools.product(markets, devices)
]
r = requests.post(f"{API}/v1/async/task/batch", json=tasks, headers=HEADERS, timeout=60)
r.raise_for_status()
for task, item in zip(tasks, r.json()["results"]):
print(task["payload"]["country"], task["payload"]["hl"], task["payload"]["device"], item["success"])
Six tasks at 5 credits each (3 + 2 for the second page) cost 30 credits. Collect results with GET /v1/async/task/{taskId} or a webhook; see async tasks.
TypeScript: the URL shape with a time filter
const params = new URLSearchParams({ q: "\"acme analytics\"", gl: "gb", hl: "en", num: "20", tbs: "qdr:w" });
const res = await fetch("https://api.answerline.dev/v1/monitor/google", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.ANSWERLINE_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ url: `https://www.google.co.uk/search?${params}`, device: "android" }),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
const { result } = await res.json();
for (const o of result.organicResults ?? []) console.log(o.page, o.position, o.date, o.link);
This is two pages as a synchronous call: 3 + 2 + 2 = 7 credits.
Validate before sending
Catching shape errors client-side saves a round trip:
def check(body: dict) -> None:
if "url" in body:
clash = {"query", "location", "uule", "pages"} & body.keys()
if clash:
raise ValueError(f"url cannot be combined with {sorted(clash)}")
elif "query" not in body or not ({"country", "gl"} & body.keys()):
raise ValueError("send query with country or gl, or send url")
if {"location", "uule"} <= body.keys():
raise ValueError("location and uule are mutually exclusive")
if "country" in body and "gl" in body and body["country"].upper() != body["gl"].upper():
raise ValueError("country and gl differ")
if not 1 <= body.get("pages", 1) <= 10:
raise ValueError("pages must be 1 to 10")
Pitfalls
- Sending
numorstartas top-level fields. They only exist insideurl; at the top level they are unknown fields and fail validation. - Assuming
hlsets the country. It sets the interface language; results followcountry/gland location. - City targeting without a canonical name.
locationmust match a Google geotarget name; free text like “NYC” is not one. - Comparing desktop history with mobile results. Store
devicewith every result. - Undocumented parameters as a long-term dependency.
tbs,startandnumare not documented by Google, andnum=100already changed once. Preferpagesand thebefore:/after:operators where they fit. - Paying synchronous prices for batch work. Async tasks cost 2 credits less each; see sync, async and webhooks.
Related: Google Custom Search JSON API alternatives, Bing Search API alternatives and what is a SERP.
Full request and response fields are on the Google Search engine page.
Questions
What is the difference between gl and hl in Google search?
gl sets the country the results are for, and hl sets the interface language. In this API, country or gl sets the result geography with an ISO 3166-1 alpha-2 code, and hl overrides the language that would otherwise be derived from the country, so the two can differ.
Does Google's num=100 parameter still work?
Search Engine Roundtable reported in September 2025 that num=100 stopped working. This API reads num in a search URL as result depth, rounded up to whole pages of ten and capped at ten pages, and returns those pages; in the standard request shape you set pages from 1 to 10.
How do I target a city instead of a country?
Send location with a Google canonical name such as New York,New York,United States, or uule with a pre-encoded UULE string, together with country. location and uule are mutually exclusive, and neither can be sent with url; put uule inside the URL instead.
Which parameter combinations return a 400 error?
query together with url; url together with location, uule or pages; location together with uule; country and gl with different values; an unsupported country; a url that is not a Google web search URL with a non-empty q, or that carries tbm; and any field not in the schema.
How many credits does a Google Search request cost?
3 credits as an async task, plus 2 for each page after the first and 2 once for include.aioverview or include.paaAioverview. Synchronous calls add 2. A three-page async task with the AI Overview costs 9 credits.