International keyword research: markets, languages and locations with a SERP API
International keyword research is the same research repeated per market, where a market is a country and a language together, and the differences between markets are the finding. The SERP for “accounting software” in the US, “Buchhaltungssoftware” in Germany and “logiciel comptable” in France, Canada and Switzerland each has its own questions, features, AI Overview behaviour and competitors. The steps below define markets, collect SERPs per market with this API’s country, hl and location parameters, and compare them. This API returns no search volume, so pair it with a volume source (why).
Definitions
- Market: a country plus a language, such as
CA/frorUS/es. Two languages in one country are two markets. - Seed keyword: a starting keyword for one market, written natively or translated and then validated.
- Transcreation: adapting a keyword to how people in a market express the same need, rather than translating the words.
- SERP overlap: the share of ranking domains two markets have in common for equivalent keywords.
The parameters
From the OpenAPI reference, POST /v1/monitor/google takes:
| Parameter | Format | Role |
|---|---|---|
country |
ISO 3166-1 alpha-2, such as DE |
Result geography. Required in the standard request shape. |
gl |
Same codes, either case | Google’s own name for the same setting. Send country or gl; different values in both is a 400. |
hl |
Google interface language code, such as de, fr, pt-BR |
Overrides the language derived from the country, so language and geography can differ. |
location |
Google canonical location name, City,Region,Country |
City- or region-level targeting within the country. Mutually exclusive with uule. |
uule |
Pre-encoded Google location string | Alternative to location. |
device |
desktop, mobile, ios, android |
Desktop or mobile results. |
url |
Full Google search URL | Alternative shape; the API keeps q, gl, hl, uule, num, start, tbs and safe from it and drops the rest. |
GET /v1/countries returns the supported country codes; pass ?model=google (or aioverview, aimode, chatgpt and so on) to filter by engine. Check it once when you define markets rather than discovering an unsupported code as a validation error mid-run.
AI engines take market parameters too. AI Mode accepts country, gl, hl, location and uule; ChatGPT, Perplexity, Gemini, Copilot and Grok take country and, for the US, state. Multilingual AI answers covers monitoring those per language.
Canonical location names
location expects names from Google’s geo targets list. The file published on 2026-08-12 (Google Ads API geotargets, checked 2026-09-17) has columns Criteria ID, Name, Canonical Name, Parent ID, Country Code, Target Type and Status. Canonical names do not always follow English spelling or an obvious region: in that file, Munich is Munich,Bavaria,Germany and Lyon is Lyon,Auvergne-Rhone-Alpes,France. Look names up in the file rather than typing them.
Step 1: define the market matrix
List markets explicitly, with the reason each exists:
MARKETS = [
{"id": "us-en", "country": "US", "hl": "en"},
{"id": "us-es", "country": "US", "hl": "es"}, # Spanish-language searchers in the US
{"id": "gb-en", "country": "GB", "hl": "en"},
{"id": "de-de", "country": "DE", "hl": "de"},
{"id": "ch-de", "country": "CH", "hl": "de"}, # same language as DE, different market
{"id": "ch-fr", "country": "CH", "hl": "fr"},
{"id": "ca-en", "country": "CA", "hl": "en"},
{"id": "ca-fr", "country": "CA", "hl": "fr"},
{"id": "br-pt", "country": "BR", "hl": "pt-BR"},
]
Three rules:
- Separate languages within a country. Canada in English and Canada in French are different research projects.
- Separate countries within a language. German in Germany, Austria and Switzerland shares words but not competitors, currencies or regulations.
- Add a market only if you would act on it. Every market multiplies cost.
Step 2: build native seeds
Translation gets you a first guess. The local SERP tells you whether locals search that way.
- Write or translate 10 to 30 seed keywords per market, ideally reviewed by a native speaker.
- Run each seed in its market.
- Read
relatedSearches[].queryandpeopleAlsoAsk[].questionfrom the response. These are phrased by the market. - Replace seeds that return thin or off-topic related searches with the phrasing the SERP suggests.
A seed whose related searches are about something else entirely is a mistranslation; drop it early before it spawns a hundred wrong expansions.
Step 3: collect SERPs as batches
One task per keyword per market. Submit them with POST /v1/async/task/batch (1 to 500 tasks per request), keyed by meaning so re-runs are safe:
import os
from datetime import date
import requests
API = "https://api.answerline.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['ANSWERLINE_API_KEY']}"}
WEBHOOK = "https://research.example.com/hooks/serp"
def tasks_for(keywords: dict[str, list[str]], run: str) -> list[dict]:
"""keywords maps market id to that market's keyword list."""
by_id = {m["id"]: m for m in MARKETS}
return [
{
"taskType": "GOOGLE",
"payload": {
"query": kw,
"country": by_id[market]["country"],
"hl": by_id[market]["hl"],
"include": {"aioverview": {}},
},
"idempotencyKey": f"intl:{run}:{market}:{kw}",
"webhook": {"url": WEBHOOK},
}
for market, kws in keywords.items()
for kw in kws
]
def submit(tasks: list[dict]) -> list[dict]:
created = []
for i in range(0, len(tasks), 500):
chunk = tasks[i:i + 500]
body = requests.post(f"{API}/v1/async/task/batch", headers=HEADERS, json=chunk, timeout=60).json()
for item in body["results"]:
if item["success"]:
created.append({"key": chunk[item["index"]]["idempotencyKey"], "task_id": item["task"]["id"]})
elif item["error"]["code"] != "RESOURCE_ALREADY_EXISTS":
print(chunk[item["index"]]["idempotencyKey"], item["error"]["code"])
return created
run = date.today().isoformat()
created = submit(tasks_for({"de-de": ["buchhaltungssoftware", "rechnungsprogramm"], "ca-fr": ["logiciel comptable"]}, run))
Idempotency keys are unique across your account, and long keyword text makes long keys; if that becomes unwieldy, use a keyword id from your own table instead of the text. Store task_id next to the key so a reconciliation pass can fetch anything whose webhook never arrived with GET /v1/async/task/{taskId}. Receiving and verifying webhooks is covered in webhook receiver design.
Step 4: reduce each SERP to a comparable record
Store one row per keyword, market and run:
from urllib.parse import urlparse
def host(url: str) -> str:
h = urlparse(url).netloc.lower()
return h[4:] if h.startswith("www.") else h
def record(market: str, keyword: str, result: dict) -> dict:
organic = [r for r in result.get("organicResults", []) if r.get("position", 99) <= 10]
aio = result.get("aioverview")
return {
"market": market,
"keyword": keyword,
"top10": [host(r["link"]) for r in organic],
"top10_urls": [r["link"] for r in organic],
"has_aio": aio is not None,
"aio_sources": [host(s["url"]) for s in (aio or {}).get("sources", [])],
"paa": [q["question"] for q in result.get("peopleAlsoAsk", [])],
"related": [r["query"] for r in result.get("relatedSearches", [])],
"ads": len(result.get("ads", [])),
"local": bool(result.get("localResults")),
}
Step 5: compare markets
Map equivalent keywords across markets with a concept id (for example accounting-software → buchhaltungssoftware in de-de, logiciel comptable in ca-fr). Then compare per concept.
Competitor overlap
def overlap(a: list[str], b: list[str]) -> float:
sa, sb = set(a), set(b)
return len(sa & sb) / len(sa | sb) if sa | sb else 0.0
Low overlap between two markets for the same concept means different competitors: global category leaders in one, local specialists in the other. That changes both content strategy and who you benchmark against.
Feature differences
For each concept, tabulate per market: AI Overview present, ads count, local results present, PAA count. Typical findings worth acting on:
- AI Overview in one market but not another. Plan for citation in the first; AI Overview tracking tools compares ways to monitor it.
- Ads only in some markets. Commercial competition differs; the SERP may be easier organically where advertisers are absent, or the market may be commercially unattractive.
- Local results appear. The concept is local there; research by city with
location(local rank tracking by city).
Question sets
Compare paa lists per concept. Questions that appear only in one market (regulation, tax, a local payment method, a local competitor) are the content that localisation by translation misses.
Step 6: check which of your pages rank where
International sites often rank the wrong locale: the US English page in Germany, the French-France page in Quebec. From the stored top10_urls:
EXPECTED = {"us-en": "/en-us/", "de-de": "/de-de/", "ca-fr": "/fr-ca/", "ch-fr": "/fr-ch/"}
def locale_mismatches(rows: list[dict], my_host: str) -> list[tuple]:
out = []
for row in rows:
expected = EXPECTED.get(row["market"])
for url in row["top10_urls"]:
if host(url) == my_host and expected and expected not in url:
out.append((row["market"], row["keyword"], url))
return out
Adapt the path check to your URL scheme (subdirectories, subdomains or country domains). Each mismatch is a candidate hreflang, canonical or content issue to investigate.
City-level research inside a market
Some markets are not uniform. For local-intent concepts, add location with a canonical name:
{ "query": "steuerberater", "country": "DE", "hl": "de", "location": "Munich,Bavaria,Germany" }
location cannot be combined with uule or with the url shape. Location does not change a request’s price, but every city is another request.
What it costs
Credit costs: a Google Search async task costs 3 credits for one page, include.aioverview adds 2, each extra results page adds 2, and a synchronous call adds 2.
| Run | Tasks | Credits |
|---|---|---|
| 200 keywords × 4 markets, one page | 800 | 2,400 |
| 500 keywords × 6 markets, with AI Overview | 3,000 | 15,000 |
| Seed validation: 20 seeds × 9 markets, with AI Overview | 180 | 900 |
| 50 local concepts × 10 cities in one market | 500 | 1,500 |
Markets multiply everything. Validate seeds on a small set first; the free tier’s 500 credits cover 100 keyword-market pairs with AI Overview detection. Plan prices are on /pricing.
Pitfalls
- One market per country. Multilingual countries need a market per language.
- Mismatched
countryandgl. Send one; different values in both is rejected. - Forgetting
hl. Without it the language is derived from the country, which is wrong for, say, Spanish in the US. - Guessed location names. Use canonical names from the geo targets file; the English exonym is often not the canonical name.
- Translated keyword lists taken as final. Validate every seed against its local SERP.
- Comparing runs from different days across markets. Collect all markets in the same run window so differences are geographic, not temporal.
- No volume source. SERP data tells you what a market looks like, not how big it is.
See the keyword research use case and the Google Search engine page to start.
Questions
Which parameters control country and language in this API?
On Google Search, country (or gl) sets the result geography as an ISO 3166-1 alpha-2 code, hl sets the interface language independently, and location or uule narrows to a city or region. GET /v1/countries lists the supported country codes, filterable by engine.
Can the interface language differ from the country?
Yes. hl overrides the language derived from the country, so Spanish in the US or French in Canada can be requested as separate markets.
Should I translate my keyword list for each market?
Use translation only to create seeds, then expand from the local results page: related searches and People Also Ask in the market's language show how people there phrase the need. Translated keywords often are not what locals search.
How do I check that the right language version of my site ranks in each market?
Collect organic results per market, find the URLs on your domain, and compare each URL's locale path or subdomain with the market's expected locale. A mismatch, such as the US page ranking in Germany, points to hreflang or localisation problems.
What does a multi-market keyword run cost?
Each keyword in each market is one request. A Google Search async task is 3 credits, plus 2 with the AI Overview. 500 keywords across 6 markets with AI Overview detection is 3,000 tasks and 15,000 credits.