# Local rank tracking & geo targeting

> Three independent layers of geo targeting — the country exit, the state/city residential exit, and Google geotarget localities via uule — and how to combine them for local rank tracking.

AI answers are local. "best dentist near me", "coffee shop open now", or "plumber
prices" return a different answer in Buffalo than in Brooklyn — because the
surface localizes to where the request appears to come from. This guide covers
the three **independent** layers you can stack to control that geography, and how
to use them for local rank tracking.

## Three layers, from broad to precise

Each layer targets a different mechanism, and they compose. You can use one, two,
or all three on the same region object.

<Steps>
  <Step title="Country exit — the residential IP's country">
    `country` places the capture behind a residential exit IP in that country.
    This is the only required field in a region object and the coarsest layer.

    ```json
    { "country": "US" }
    ```

  </Step>
  <Step title="State / city exit — a city-level residential IP">
    `state` and `city` narrow the residential exit to a sub-national region and a
    city, so the request egresses from an IP that actually resolves to that
    locality. This applies to every **browser-captured** surface.

    ```json
    { "country": "US", "state": "New York", "city": "Buffalo" }
    ```

  </Step>
  <Step title="Locality — a Google geotarget via uule">
    `location` is a **Google Ads geotarget canonical name** applied as a `uule`
    parameter on the four Google surfaces (`google_search`, `google_news`,
    `google_ai_overview`, `google_ai_mode`). It pins the answer to one of
    ~100,000 named localities — cities, ZIPs, boroughs — **independent of the
    exit IP**. Names come from [Google's public geotargets
    list](https://developers.google.com/google-ads/api/data/geotargets).

    ```json
    { "country": "US", "location": "Buffalo,New York,United States" }
    ```

  </Step>
</Steps>

<Note>
  These layers are independent. The residential exit (`country`/`state`/`city`)
  works on every browser-captured surface; the `location` geotarget only affects
  the Google surfaces, where it stacks **on top of** the exit IP. On a
  non-Google surface, `location` is simply ignored.
</Note>

## A local rank-tracking request

Combine all three to capture what a searcher in a specific locality sees across
the Google surfaces. Because every surface × region pair is a separate child, one
request captures the same query across as many localities as you list (up to 10
regions per request).

<CodeGroup>
```bash cURL
curl -X POST https://api.aisearchapi.dev/v1/search \
  -H "Authorization: Bearer $AISEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "best coffee shop near me",
    "surfaces": ["google_ai_overview", "google_search"],
    "regions": [
      {
        "country": "US",
        "state": "New York",
        "city": "Buffalo",
        "location": "Buffalo,New York,United States"
      },
      {
        "country": "US",
        "state": "New York",
        "city": "New York",
        "location": "Brooklyn,New York,United States"
      }
    ]
  }'
```

```python Python
import os, requests

resp = requests.post(
    "https://api.aisearchapi.dev/v1/search",
    headers={"Authorization": f"Bearer {os.environ['AISEARCH_API_KEY']}"},
    json={
        "query": "best coffee shop near me",
        "surfaces": ["google_ai_overview", "google_search"],
        "regions": [
            {
                "country": "US",
                "state": "New York",
                "city": "Buffalo",
                "location": "Buffalo,New York,United States",
            },
            {
                "country": "US",
                "state": "New York",
                "city": "New York",
                "location": "Brooklyn,New York,United States",
            },
        ],
    },
)
print(resp.json())
```

```javascript JavaScript
const resp = await fetch('https://api.aisearchapi.dev/v1/search', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AISEARCH_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: 'best coffee shop near me',
    surfaces: ['google_ai_overview', 'google_search'],
    regions: [
      {
        country: 'US',
        state: 'New York',
        city: 'Buffalo',
        location: 'Buffalo,New York,United States',
      },
      {
        country: 'US',
        state: 'New York',
        city: 'New York',
        location: 'Brooklyn,New York,United States',
      },
    ],
  }),
});
console.log(await resp.json());
```

</CodeGroup>

<Warning>
  Each `surface × region` pair is billed separately. Two surfaces across two
  localities is **four** captures. See [Credits &amp; billing](/guides/credits)
  before fanning out across many localities.
</Warning>

## Verifying what was localized

Every Envelope reports what geo was actually applied under `provenance.region`,
so you can trust the result rather than assume it. Alongside the residential
`requested`/`effective` region keys, two fields cover the `location` geotarget:

<ResponseField name="provenance.region.requestedLocation" type="string | null">
  The Google geotarget canonical name you asked for (or `null` when you sent no
  `location`).
</ResponseField>

<ResponseField name="provenance.region.effectiveLocation" type="string | null">
  The name a `uule` was **actually** applied for. It is `null` — paired with a
  [`location_not_applied`](/guides/output-formats#job) warning — whenever the
  name could not be encoded, so a wrong locality is never claimed.
</ResponseField>

```json
{
  "provenance": {
    "region": {
      "requested": "us:buffalo",
      "effective": "us:buffalo",
      "requestedLocation": "Buffalo,New York,United States",
      "effectiveLocation": "Buffalo,New York,United States"
    }
  }
}
```

<Tip>
  Always read `effectiveLocation`, not your request, when you attribute a result
  to a locality. If it differs from `requestedLocation` (or is `null`), the
  precise locality was not applied — check for a `location_not_applied` warning.
</Tip>

## Choosing the right layer

<AccordionGroup>
  <Accordion title="National or regional market coverage" icon="earth-americas">
    Use `country` (optionally `state`/`city`) alone. The residential exit is
    enough when you care about country- or region-level differences, and it
    works across every browser-captured surface, not just Google.
  </Accordion>
  <Accordion title="Precise local rank tracking on Google" icon="location-dot">
    Add `location` with the Google geotarget canonical name for the exact
    locality. This is the layer that gives you ZIP- and borough-level precision
    on the Google surfaces, independent of which residential IP the capture
    egressed from.
  </Accordion>
  <Accordion title="Both, for maximum fidelity" icon="layer-group">
    Set the residential `state`/`city` **and** the `location` geotarget
    together. The IP resolves to the city while the `uule` pins the Google
    answer to the named locality — the closest match to what a local searcher
    actually sees.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Regional availability" icon="globe" href="/guides/regions">
    The full `regions` array reference — every field, country coverage, and
    requested-vs-effective semantics.
  </Card>
  <Card
    title="Output formats"
    icon="brackets-curly"
    href="/guides/output-formats"
  >
    Where `provenance.region` and the `location_not_applied` warning live in the
    Envelope.
  </Card>
</CardGroup>
