**Describe the bug:**
When calling `AdGroupAdService.mutate_ad_group_ads()` to create a 
Demand Gen multi-asset ad in a child account under my MCC, the API always 
returns HTTP 403 Forbidden—even though:
- Developer token is **Basic** (not Test) and approved for production  
- OAuth `refresh_token` has `https://www.googleapis.com/auth/adwords` scope 
 
- The MCC → child link appears as `manager=FALSE` & `status=ENABLED` in 
`customer_client` GAQL  

**Steps to Reproduce:**
1. Fetch credentials (developer_token, client_id, client_secret, 
refresh_token, login_customer_id) from AWS SSM.  
2. Initialize client:  
   ```python
   googleads_client = GoogleAdsClient.load_from_dict({
     "developer_token":   "<REDACTED_DEV_TOKEN>",
     "client_id":         "<REDACTED_CLIENT_ID>",
     "client_secret":     "<REDACTED_CLIENT_SECRET>",
     "refresh_token":     "<REDACTED_REFRESH_TOKEN>",
     "login_customer_id": "<MY_MCC_ID>",
     "use_proto_plus":    False,
   })
3. Create assets under the child with 
AssetService.mutate_assets(customer_id="<CHILD_ID>", …) → succeeds.
4. Attempt to create the ad:
```
   ag_svc = googleads_client.get_service("AdGroupAdService")
   ad_op = googleads_client.get_type("AdGroupAdOperation")()
   # … build ad_op.create.demand_gen_multi_asset_ad …
   ag_svc.mutate_ad_group_ads(customer_id="<CHILD_ID>", operations=[ad_op])
```

**Expected behavior:**
- A 200 OK response with a new Demand Gen ad resource name, identical to 
how the library works for other ad types.

**Client library version and API version:**
Client library version: google-ads 21.0.0  
Google Ads API version: v18
<!-- Paste the list of dependencies you're using (i.e. `pip freeze`) -->

**Request/Response Logs:**
```
[INFO]  Request made: ClientCustomerId: <CHILD_ID>, Method: 
/google.ads.googleads.v18.services.AssetService/MutateAssets, … IsFault: 
False
[INFO]  Request made: ClientCustomerId: <CHILD_ID>, Method: 
/google.ads.googleads.v18.services.AssetService/MutateAssets, … IsFault: 
False
[ERROR] General exception: HTTP Error 403: Forbidden
RequestId (AssetService): c_XgB1RAD1XvX3zE4bEGiw
RequestId (AdGroupAdService): VqepG-MSt_US-YrrOE0xHQ
```
 
**Anything else we should know about your project / environment:**
- Running on AWS Lambda (Python 3.12), library installed via Lambda Layer
- Verified via GAQL:
```
SELECT
  customer_client.client_customer,
  customer_client.manager,
  customer_client.status
FROM customer_client
WHERE customer_client.manager = FALSE
```
shows <CHILD_ID> with status = ENABLED.
- list_accessible_customers() returns only the MCC, not the child (as 
expected).

### What I’ve tried so far
- Ensured login_customer_id remains the MCC and customer_id is the child.
- Confirmed developer token is Basic (not Test) in API Center.
- Verified OAuth scopes and user access to the child account.
- Queried customer_client to confirm the active link.
- Attempted rebuilding the client with both MCC and child as 
login_customer_id.

Could you advise:
1. Whether Demand Gen ad creation via AdGroupAdService is fully supported 
in v18 of this library?
2. If there is any missing configuration or permission that specifically 
blocks Demand Gen ad creation?
3. Any additional flags or header settings required for Demand Gen 
campaigns?


my full code: 
```
import json
import uuid
import urllib.request
import boto3

from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException
from google.ads.googleads.v18.enums.types.asset_type import AssetTypeEnum
from google.ads.googleads.v18.enums.types.ad_group_ad_status import 
AdGroupAdStatusEnum

# 1) Load your creds from Parameter Store
ssm   = boto3.client("ssm")
param = ssm.get_parameter(
    Name="GOOGLE_ADS_API_CREDENTIALS",
    WithDecryption=True
)
creds = json.loads(param["Parameter"]["Value"])

# 2) Initialize a single GoogleAdsClient as your MCC
googleads_client = GoogleAdsClient.load_from_dict({
    "developer_token":   creds["developer_token"],
    "client_id":         creds["client_id"],
    "client_secret":     creds["client_secret"],
    "refresh_token":     creds["refresh_token"],
    "login_customer_id": creds["login_customer_id"],
    "use_proto_plus":    False,
})

def _fetch_bytes(url: str) -> bytes:
    with urllib.request.urlopen(url) as resp:
        return resp.read()

def _create_text_asset(customer_id: str, text: str) -> str:
    svc   = googleads_client.get_service("AssetService")
    asset = {
        "name":       f"text-{uuid.uuid4()}",
        "type_":      AssetTypeEnum.AssetType.TEXT,
        "text_asset": {"text": text}
    }
    op = {"create": asset}
    resp = svc.mutate_assets(customer_id=customer_id, operations=[op])
    return resp.results[0].resource_name

def _create_image_asset(customer_id: str, url: str) -> str:
    svc        = googleads_client.get_service("AssetService")
    data       = _fetch_bytes(url)
    asset      = {
        "name":        f"img-{uuid.uuid4()}",
        "type_":       AssetTypeEnum.AssetType.IMAGE,
        "image_asset": {"data": data}
    }
    op = {"create": asset}
    resp = svc.mutate_assets(customer_id=customer_id, operations=[op])
    return resp.results[0].resource_name

def _create_video_asset(customer_id: str, vid: str) -> str:
    svc   = googleads_client.get_service("AssetService")
    asset = {
        "name":                   f"vid-{uuid.uuid4()}",
        "type_":                  AssetTypeEnum.AssetType.YOUTUBE_VIDEO,
        "youtube_video_asset":    {"youtube_video_id": vid}
    }
    op = {"create": asset}
    resp = svc.mutate_assets(customer_id=customer_id, operations=[op])
    return resp.results[0].resource_name

def lambda_handler(event, context):
    try:
        # — parse payload —
        payload = {
            "customer_id":   "12345679",
            "ad_group_id":   "9876543210123",
            "ad_type":       "Single image ad",
            "headline":      "見出しです",
            "description":   "これは説明文です。",
            "business_name": "Test Inc.",
            "marketing_image_urls": [
                # exactly 600×314 → aspect 1.91:1
                
"https://dummyimage.com/600x314/0077cc/ffffff.png&text=600x314";
            ],
            "logo_image_urls": [
                # exactly 128×128 → aspect 1:1
                
"https://dummyimage.com/128x128/cc3300/ffffff.png&text=128x128";
            ],
            "final_url":    "http://lemonmon.site/ab/hRtGSVaxcSbqPwDOA";,
        }
        child_cid     = payload["customer_id"].replace("-", "")
        ad_group_id   = payload["ad_group_id"]
        ad_type       = payload["ad_type"].lower()
        headline      = payload["headline"]
        description   = payload["description"]
        business_name = payload["business_name"]
        final_url     = payload["final_url"].replace("http://";, "https://";)
        m_urls        = payload.get("marketing_image_urls", [])
        l_urls        = payload.get("logo_image_urls", [])
        v_ids         = payload.get("video_urls", [])

        # — 1) create assets under the CHILD account —
        h_res = _create_text_asset(child_cid, headline)
        d_res = _create_text_asset(child_cid, description)
        m_res = [_create_image_asset(child_cid, u) for u in m_urls]
        l_res = [_create_image_asset(child_cid, u) for u in l_urls]
        v_res = [_create_video_asset(child_cid, v)   for v in v_ids]

        # — 2) build and send the Demand Gen ad —
        ag_svc = googleads_client.get_service("AdGroupAdService")
        ad_op  = googleads_client.get_type("AdGroupAdOperation")()
        ad_msg = ad_op.create
        ad_msg.ad_group = googleads_client.get_service("AdGroupService") \
                          .ad_group_path(child_cid, ad_group_id)
        ad_msg.status   = AdGroupAdStatusEnum.AdGroupAdStatus.PAUSED
        ad_msg.ad.final_urls.append(final_url)

        # choose the correct Demand Gen subtype:
        if "video" in ad_type and v_res:
            dg = ad_msg.ad.demand_gen_video_responsive_ad
            dg.business_name = business_name
            dg.headlines.append({"text": headline})
            dg.descriptions.append({"text": description})
            for logo in l_res:
                dg.logo_images.append({"asset": logo})
            for vid in v_res:
                dg.videos.append({"asset": vid})
        else:
            dg = ad_msg.ad.demand_gen_multi_asset_ad
            dg.business_name = business_name
            dg.headlines.append({"text": headline})
            dg.descriptions.append({"text": description})
            for img in m_res:
                dg.marketing_images.append({"asset": img})
            for logo in l_res:
                dg.logo_images.append({"asset": logo})

        resp = ag_svc.mutate_ad_group_ads(
            customer_id=child_cid,
            operations=[ad_op]
        )
        new_ad = resp.results[0].resource_name

        return {
            "statusCode": 200,
            "body": json.dumps({"status": "OK", "ad": new_ad}),
            "headers": {"Content-Type": "application/json"}
        }

    except GoogleAdsException as api_ex:
        # Surface the API errors
        errors = [e.message for e in api_ex.failure.errors]
        return {
            "statusCode": 500,
            "body": json.dumps({"status": "ERROR", "errors": errors})
        }

    except Exception as e:
        # Fallback
        return {
            "statusCode": 500,
            "body": json.dumps({"status": "ERROR", "message": str(e)})
        }

```

-- 
-- 
=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~
Also find us on our blog:
https://googleadsdeveloper.blogspot.com/
=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~

You received this message because you are subscribed to the Google
Groups "AdWords API and Google Ads API Forum" group.
To post to this group, send email to adwords-api@googlegroups.com
To unsubscribe from this group, send email to
adwords-api+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/adwords-api?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Google Ads API and AdWords API Forum" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to adwords-api+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/adwords-api/7f5467aa-52ad-452b-ada2-649458148385n%40googlegroups.com.
  • Ca... Ngoc Tien Tran
    • ... 'Google Ads API Forum Advisor' via Google Ads API and AdWords API Forum

Reply via email to