状況やること
GA4 インポートCV と既存CV が同じ行動を二重に数えているお金に近い 1 つだけ集計オン、他は集計オフ(削除しない)
自動入札の成果が急に「良く見える」のに売上が増えない二重計上を疑い、集計対象を 1 行動 1 つに整理
一度オフにしたが履歴を見たいオフでも記録は残る。レポートで参照可
google.ads.googleads.errors.GoogleAdsException:
  status = StatusCode.INVALID_ARGUMENT
  details = "Request contains an invalid argument."
  message: "The error code is not in this version."
  trigger { int64_value: 4 }
  field_path_elements { field_name: "operations" index: 0 }
                       { field_name: "update" }
                       { field_name: "status" }

「The error code is not in this version」── このエラーメッセージは、 API v24 の仕様の隙間を突いた瞬間にだけ顔を出します。本記事では、google-ads Python SDK で Conversion Action を mutate しようとして踏んだ 5 つの罠 と、それぞれの突破コードを記録します。

Google Ads API 実装でハマっていませんか? EXBANK の API 実装代行で 1 日で動かします。
Claude Code 研修
研修の内容を見る

背景 — なぜ HIDDEN に戻したかったか

クライアントの Google Ads アカウントで、 からインポートした Conversion Action 3 件を一度 ENABLED に戻しました。GA4 側で発火しているリードシグナルを Google Ads でも見えるようにするためです。 しかし管理画面で確認したところ、既存の WEBPAGE 系 Conversion Action(モデルハウス見学予約・電話クリック等)と 同じユーザー行動で重複計上される可能性 に気づきました。 | Conversion Action | type | category | 既存 incl_metric | 私が ENABLED 化 | |---|---|---|:-:|:-:| | モデルハウス見学予約_TESLA | WEBPAGE | SUBMIT_LEAD_FORM | True | — | | 電話クリック_LP | WEBPAGE | PHONE_CALL_LEAD | True | — | | ランディ申し込み完了 (GA4 import) | GA4_CUSTOM | DEFAULT | False | ENABLED 化 | | phone_click (GA4 import) | GA4_CUSTOM | PHONE_CALL_LEAD | False | ENABLED 化 | | click_to_call (GA4 import) | GA4_CUSTOM | PHONE_CALL_LEAD | False | ENABLED 化 | 電話タップ 1 回で 電話クリック_LP + phone_click + click_to_call の 3 倍計上 になる懸念があり、慌てて HIDDEN に戻すスクリプトを書きました。それが冒頭のエラーに繋がります。

罠 1: ENABLED → HIDDEN は API から mutate できない

最初に書いた mutation コードはこうでした。
op = client.get_type("ConversionActionOperation")
op.update.resource_name = ca_service.conversion_action_path(cid, conversion_action_id)
op.update.status = client.enums.ConversionActionStatusEnum.HIDDEN
op.update_mask.paths.append("status")

resp = ca_service.mutate_conversion_actions(
    customer_id=cid, operations=[op]
)

シンプルです。何が問題か分かりません。

エラーログをよく読むと、ヒントが隠れていました。

trigger { int64_value: 4 }
field_path_elements { field_name: "status" }

int64_value: 4 は status enum の 4 番目の値 を意味します。ConversionActionStatusEnum のソースを引くと、4 番目は HIDDEN です。

ConversionActionStatusEnum:
  0: UNSPECIFIED
  1: UNKNOWN
  2: ENABLED
  3: REMOVED
  4: HIDDEN

つまり API は 「status フィールドに HIDDEN という値を入れる mutation は、このバージョンでは許可されていない」 と返してきていたわけです。

💡 KEY TAKEAWAYS
Google Ads API の ConversionActionStatusEnum.HIDDEN は UI からのみ設定可能な内部状態 として設計されており、API 経由の mutation では拒否されます。同様の制約は他の enum でも存在するので、The error code is not in this version を見たら、まず trigger.int64_value から enum 値を逆引きします。

突破方法: include_in_conversions_metric=False で集計から外す



集計から外すという目的だけなら、include_in_conversions_metric フィールドを False にすれば達成できます。こちらは API で mutate 可能です。

op = client.get_type("ConversionActionOperation")
op.update.resource_name = ca_service.conversion_action_path(cid, conversion_action_id)
op.update.include_in_conversions_metric = False
op.update_mask.paths.append("include_in_conversions_metric")

resp = ca_service.mutate_conversion_actions(
    customer_id=cid, operations=[op]
)
incl_metricprimary_for_goal自動入札への影響集計列
TrueTrue影響大、★主要 CVConversions / All Conv
TrueFalse影響あり(集計に入る)Conversions / All Conv
FalseTrue無し(マイクロ CV 扱い)All Conv のみ
FalseFalse無し(参考値)All Conv のみ
client.copy_from(
    op.update_mask,
    client.get_type("FieldMask")(paths=["status"])
)

これは v22 まで動いていたコードです。v24 では:

ValueError: Specified type 'FieldMask' does not exist in Google Ads API v24

FieldMask という型自体が SDK の type レジストリから削除されました。

突破方法: paths を直接 append する



正しい書き方:

op.update_mask.paths.append("status")
# 複数フィールドの場合
op.update_mask.paths.extend(["status", "name", "value_settings.default_value"])

update_mask は protobuf の FieldMask メッセージで、paths は repeated string です。新しい SDK では type を経由せず、operation の field に直接書きます。

💡 KEY TAKEAWAYS
google-ads SDK のメジャーバージョン更新では、こうした 型レジストリの整理 が必ず混じります。エラー文言で does not exist in Google Ads API vXX を見たら、リリースノートで該当型を検索します。

罠 3: USER_PERMISSION_DENIED は「別 MCC 配下」のサイン



別の場面で MCC 配下のクライアント一覧を取得しようとしました。

customer_service = client.get_service("CustomerService")
ga_service = client.get_service("GoogleAdsService")

accessible = customer_service.list_accessible_customers()
for resource in accessible.resource_names:
    cid = resource.split("/")[-1]
    query = "SELECT customer_client.id, customer_client.descriptive_name FROM customer_client"
    try:
        for batch in ga_service.search_stream(customer_id=cid, query=query):
            for row in batch.results:
                print(row.customer_client.descriptive_name, row.customer_client.id)
    except GoogleAdsException as e:
        print(f"  {cid}: {extract_error_code(e)}")

list_accessible_customers() で 18 件の top-level customer が返ってきましたが、そのうち 5 件が USER_PERMISSION_DENIED を返しました。

9066450572: authorization_error: USER_PERMISSION_DENIED
8538982978: authorization_error: USER_PERMISSION_DENIED
9482254684: authorization_error: USER_PERMISSION_DENIED
4815331144: authorization_error: USER_PERMISSION_DENIED
9585924613: authorization_error: USER_PERMISSION_DENIED

最初は 「権限がない、招待を受けていない」 と解釈しました。実際は違いました。

突破方法: login_customer_id を変えて再試行



USER_PERMISSION_DENIED は その customer が別 MCC の配下にある サインです。私が現在使っている login_customer_id(自分の MCC)からは見えないだけで、別の MCC を login_customer_id に指定すれば取れます。

# 別 MCC 配下のアカウントを取りに行く
client_for_other_mcc = GoogleAdsClient.load_from_dict({
    "developer_token": cfg["developer_token"],
    "refresh_token": token_data["refresh_token"],
    "client_id": installed["client_id"],
    "client_secret": installed["client_secret"],
    "login_customer_id": "OTHER_MCC_ID_HERE",  # 別 MCC の 10 桁 ID
    "use_proto_plus": True,
})
エラーコード意味対処
USER_PERMISSION_DENIED別 MCC 配下にあるlogin_customer_id を変えて再試行
CUSTOMER_NOT_ENABLEDアカウントが停止・解約済みスキップ可、運用停止アカウント
DEVELOPER_TOKEN_NOT_APPROVEDTest access のままBasic access を申請
AUTHENTICATION_ERROR token 失効refresh または再認証
# A. search
resp = ga.search(customer_id=cid, query=q)
for row in resp:  # ListPager
    print(row.campaign.name)

# B. search_stream
stream = ga.search_stream(customer_id=cid, query=q)
for batch in stream:  # generator of batches
    for row in batch.results:
        print(row.campaign.name)

使い分け

| 観点 | search | search_stream | |---|---|---| | 戻り値 | ListPager(自動ページング) | generator(batch 単位) | | total_results | 取れる | 取れない | | メモリ効率 | △(全件保持) | ○(batch 単位で開放) | | 大量データ向き | × | ○ | | 並列処理 | × | ○ | 大量データ取得は search_stream、件数や ListPager 機能が欲しいときは search と覚えるとシンプルです。 私が今回踏んだ罠は、search_stream の戻り値を「list だ」と誤解して len(stream) した瞬間です。search_stream の戻り値は generator なので len() は使えません。for batch in stream で逐次処理してください。

罠 5: REMOVED は不可逆、UI でも復元不可

罠 1 で「HIDDEN がダメなら REMOVED にすればよい」と一瞬考えました。
op.update.status = client.enums.ConversionActionStatusEnum.REMOVED

REMOVED は API で許可されています。ところが REMOVED は不可逆 です。一度 REMOVED にした Conversion Action は、UI でも API でも ENABLED に戻せません。

REMOVED したい本当の理由が「もう使わない」なら問題ありません。「一時的に集計除外」が目的なら、include_in_conversions_metric=False で運用するのが正解です。

完成形 — 共有の一括追加



罠を全部回避した実用コードを置きます。Tesla キャンペーンに EXACT match の共有除外キーワードを 33 件追加した実例です。

from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException

CAMPAIGN_ID = 23806896702
NEGATIVE_KEYWORDS_EXACT = [
    "太陽 光",
    "太陽 光 発電",
    "蓄電池 奈良",
    # ... 33 件
]


def add_exact_negatives(client, cid: str, campaign_id: int, keywords: list[str]):
    cs = client.get_service("CampaignService")
    cc_service = client.get_service("CampaignCriterionService")
    operations = []

    for kw in keywords:
        op = client.get_type("CampaignCriterionOperation")
        cc = op.create
        cc.campaign = cs.campaign_path(cid, campaign_id)
        cc.negative = True
        cc.keyword.text = kw
        cc.keyword.match_type = client.enums.KeywordMatchTypeEnum.EXACT
        operations.append(op)

    try:
        resp = cc_service.mutate_campaign_criteria(
            customer_id=cid, operations=operations
        )
        return [r.resource_name for r in resp.results]
    except GoogleAdsException as e:
        for err in e.failure.errors:
            print(f"  ❌ {err.message}")
        raise
  1. エラーログの trigger.int64_value を必ず読む — enum 値の特定にこれが効く
  2. field_path_elements で問題のフィールド位置を特定する — どの operation のどの field が問題か即座に分かる
  3. SDK のメジャーバージョン更新時はリリースノートを必ず確認 — 型レジストリの整理が混じる
  4. status 系の mutation は不可逆を疑う — REMOVED や HIDDEN は元に戻せないと考える