AnswerLineStart free

, Keyword Research · Local SEO · Google

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

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:

  1. Separate languages within a country. Canada in English and Canada in French are different research projects.
  2. Separate countries within a language. German in Germany, Austria and Switzerland shares words but not competitors, currencies or regulations.
  3. 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.

  1. Write or translate 10 to 30 seed keywords per market, ideally reviewed by a native speaker.
  2. Run each seed in its market.
  3. Read relatedSearches[].query and peopleAlsoAsk[].question from the response. These are phrased by the market.
  4. 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-softwarebuchhaltungssoftware 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:

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

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.

Try it on your own prompts

500 free credits a month, no card. One POST returns the answer, sources and citations as JSON.

Keep reading