---
title: "Edit a Google campaign's device, location, or language targeting"
method: PUT
path: "/v1/ads/campaigns/{campaignId}/targeting"
tags: ["Ad Campaigns"]
---

# Edit a Google campaign's device, location, or language targeting

`PUT /v1/ads/campaigns/{campaignId}/targeting`

Google Ads compliance row M.10: geo and language targeting set at
creation must stay editable afterwards. Send at least one of `devices`,
`locations`, `languages`, `locationTargetingType`; each provided field REPLACES that field's
existing criteria on the campaign (a full set, not a delta). Fields left
out of the body are untouched. Google only; every other platform returns
501.

`devices` is the full set of device bid modifiers: a supported device you
leave out is switched off with a bid modifier of 0, since Google cannot
remove a device criterion. A device the campaign's channel does not carry,
and a set that switches every device off, both return 422.

`locations` accepts the same shapes as campaign creation: a bare array of
ISO country codes, or an object with `countries`/`regions`/`cities`/`zips`/`metros`
key lists (`key` from GET /v1/ads/targeting/search?dimension=geo). Negative
(excluded) locations are left untouched by this endpoint. An empty location list
returns 400 instead of removing every criterion: a Google campaign with no location
criteria targets every country, so omit `locations` to leave targeting alone.

The removes and the creates go out in ONE Google `googleAds:mutate`, so a failed
edit leaves the campaign's previous set intact rather than a half-applied one.

`languages` is an array of Google's language codes (ISO 639-1, plus variants
such as `zh_CN`); an unknown code returns 400.

`locationTargetingType` switches who the location targeting reaches:
`presence` (people in or regularly in the locations) or `presence_or_interest`
(also people searching for or interested in them). Example:
`{ "platform": "google", "targeting": { "locationTargetingType": "presence" } }`.

The response includes the refreshed `devices`/`locations`/`languages` state
read back from Google after the edit, and invalidates the cached copy
`GET` on this campaign would otherwise keep serving.

## Path parameters

- `campaignId` string, required

## Request body

- object
  - `platform` 'google', required
  - `targeting` object, required
    - `devices` union[] — Devices to include. Devices not listed become excluded (negative) criteria, same contract as the existing devices-only edit.
      - union
        - 'MOBILE' | 'DESKTOP' | 'TABLET' | 'CONNECTED_TV'
        - object
          - `device` 'MOBILE' | 'DESKTOP' | 'TABLET' | 'CONNECTED_TV', required
          - `bidModifier` number, required — Bid modifier for this device; devices without one use the campaign's base bid.
    - `locations` union — Bare country-code array, or the nested creation-time shape (countries/regions/cities/zips/metros).
      - string[]
      - object
        - `countries` string[]
        - `regions` union[]
          - union
            - string — The geo target constant id alone.
            - object
              - …
        - `cities` union[]
          - union
            - string — The geo target constant id alone.
            - object
              - …
        - `zips` union[]
          - union
            - string — The geo target constant id alone.
            - object
              - …
        - `metros` union[]
          - union
            - string — The geo target constant id alone.
            - object
              - …
    - `languages` string[] — Google's language codes (ISO 639-1, plus variants such as `zh_CN`), e.g. ["en", "de"].
    - `locationTargetingType` 'presence' | 'presence_or_interest' — Google only. Who a campaign's location targeting reaches (Google's `campaign.geo_target_type_setting.positive_geo_target_type`). `presence`: people in, or regularly in, the targeted locations. `presence_or_interest`: also people searching for or showing interest in them. Omitted leaves Google's default, `presence_or_interest`. Accepted on Search, Display and Performance Max campaigns. Excluded locations always use presence (Google refuses presence_or_interest for exclusions on these campaign types).

## Response `200`

Targeting updated

- object
  - `campaignId` string
  - `updated` string[] — Which targeting fields were applied.
  - `locationTargetingType` 'presence' | 'presence_or_interest' | 'null', nullable — The value read back from Google after the edit.
  - `devices` object[]
    - `device` 'MOBILE' | 'DESKTOP' | 'TABLET' | 'CONNECTED_TV'
    - `included` boolean
    - `bidModifier` number, nullable — Always null on this read; see GET's description.
  - `locations` object[]
    - `geoTargetId` string — Numeric id from Google's geoTargetConstants/{id}.
    - `negative` boolean — true = excluded location.
    - `name` string, nullable — Google's geo_target_constant.name; see GET's description.
    - `canonicalName` string, nullable — Google's geo_target_constant.canonical_name; see GET's description.
    - `type` string, nullable — Google's geo_target_constant.target_type; see GET's description.
    - `countryCode` string, nullable — Google's geo_target_constant.country_code; see GET's description.
  - `languages` object[]
    - `code` string
    - `id` string
    - `name` string

## Other responses

- `400` — Invalid request
- `401` — Unauthorized
- `403` — Returned with code `ads_allowance_exceeded` when the team has no payment method on file and has reached the 500 free live ads: add a card to resume.
- `404` — Campaign not found
- `501` — Only available on Google Ads campaigns

## Changes

- **2026-09-25** `2c04683ce694` — 4 breaking, 9 warning, 10 info
  - the `targeting/locations/oneOf[subschema #2]/cities/items/` request property type changed from `object` to no type
  - the `targeting/locations/oneOf[subschema #2]/metros/items/` request property type changed from `object` to no type
  - the `targeting/locations/oneOf[subschema #2]/regions/items/` request property type changed from `object` to no type
  - the `targeting/locations/oneOf[subschema #2]/zips/items/` request property type changed from `object` to no type
  - …19 more
- **2026-09-16** `3e6ddf2a99ea` — 1 info
  - added the optional property `details/budgetScope` to the response with the `400` status
- **2026-09-15** `0dba7d004d75` — 2 info
  - added the optional property `details/quotaExhausted` to the response with the `400` status
  - added the optional property `details/quotaScope` to the response with the `400` status
- **2026-09-09** `41eff0cffb2d` — 4 info
  - added the optional property `locations/items/canonicalName` to the response with the `200` status
  - added the optional property `locations/items/countryCode` to the response with the `200` status
  - added the optional property `locations/items/name` to the response with the `200` status
  - added the optional property `locations/items/type` to the response with the `200` status
- **2026-09-08** `d7a8fa118ef0` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/zernio/apis/zernio-api/changes/v1/ads/campaigns/:campaignId/targeting/put.md)

---

[API](https://skmtc.dev/zernio/apis/zernio-api.md) · [All operations](https://skmtc.dev/zernio/apis/zernio-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zernio/apis/zernio-api/revisions/e0122970214f?raw)
