adloop 0.19.0__tar.gz → 0.19.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. {adloop-0.19.0 → adloop-0.19.1}/PKG-INFO +5 -5
  2. {adloop-0.19.0 → adloop-0.19.1}/README.md +4 -4
  3. {adloop-0.19.0 → adloop-0.19.1}/pyproject.toml +1 -1
  4. {adloop-0.19.0 → adloop-0.19.1}/pyproject.toml.orig +1 -1
  5. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/custom_conversion_goals.py +0 -2
  6. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/read.py +6 -27
  7. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/write.py +0 -1
  8. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/server.py +22 -13
  9. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/__init__.py +0 -0
  10. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/__main__.py +0 -0
  11. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/_mcp_patches.py +0 -0
  12. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/__init__.py +0 -0
  13. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/ai_max.py +0 -0
  14. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/brands.py +0 -0
  15. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/client.py +0 -0
  16. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/conversion_actions.py +0 -0
  17. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/currency.py +0 -0
  18. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/enums.py +0 -0
  19. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/forecast.py +0 -0
  20. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/gaql.py +0 -0
  21. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/image_fetch.py +0 -0
  22. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/pmax.py +0 -0
  23. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ads/validate_only.py +0 -0
  24. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/auth.py +0 -0
  25. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/cli.py +0 -0
  26. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/config.py +0 -0
  27. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/crossref.py +0 -0
  28. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/diagnostics.py +0 -0
  29. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ga4/__init__.py +0 -0
  30. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ga4/client.py +0 -0
  31. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ga4/reports.py +0 -0
  32. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ga4/tracking.py +0 -0
  33. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/ga4/write.py +0 -0
  34. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/gsc/__init__.py +0 -0
  35. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/gsc/client.py +0 -0
  36. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/gsc/reports.py +0 -0
  37. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/gtm/__init__.py +0 -0
  38. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/gtm/client.py +0 -0
  39. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/gtm/read.py +0 -0
  40. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/merchant/__init__.py +0 -0
  41. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/merchant/client.py +0 -0
  42. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/merchant/read.py +0 -0
  43. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/pagespeed.py +0 -0
  44. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/reddit/__init__.py +0 -0
  45. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/reddit/auth.py +0 -0
  46. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/reddit/client.py +0 -0
  47. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/reddit/read.py +0 -0
  48. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/reddit/schedule.py +0 -0
  49. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/reddit/write.py +0 -0
  50. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/rules/__init__.py +0 -0
  51. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/rules/adloop.md +0 -0
  52. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/rules/commands/analyze-performance.md +0 -0
  53. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/rules/commands/budget-plan.md +0 -0
  54. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/rules/commands/create-ad.md +0 -0
  55. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/rules/commands/create-campaign.md +0 -0
  56. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
  57. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/rules/commands/optimize-campaign.md +0 -0
  58. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/rules_install.py +0 -0
  59. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/runtime.py +0 -0
  60. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/safety/__init__.py +0 -0
  61. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/safety/audit.py +0 -0
  62. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/safety/guards.py +0 -0
  63. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/safety/preview.py +0 -0
  64. {adloop-0.19.0 → adloop-0.19.1}/src/adloop/tracking.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: adloop
3
- Version: 0.19.0
3
+ Version: 0.19.1
4
4
  Summary: The AI command center for Google Ads, Reddit Ads, GA4, and tracking code.
5
5
  Keywords: mcp,google-ads,reddit-ads,google-analytics,ga4,cursor,marketing
6
6
  Author: Daniel Klose
@@ -255,7 +255,7 @@ All write operations follow a **draft → preview → confirm** workflow. Nothin
255
255
  | `draft_callouts` | Create campaign callout assets from 1-25 character text snippets. |
256
256
  | `draft_structured_snippets` | Create campaign structured snippet assets using official header values and 3-10 snippet values. |
257
257
  | `draft_image_assets` | Create campaign image assets from local files or public image URLs (PNG, JPEG, or GIF). |
258
- | `draft_keywords` | Propose keyword additions with match types. Proactively checks bidding strategy — blocks BROAD match on Manual CPC campaigns. |
258
+ | `draft_keywords` | Propose keyword additions with match types. Proactively checks bidding strategy — flags BROAD match on campaigns without Smart Bidding as dangerous in the preview. |
259
259
  | `add_negative_keywords` | Propose negative keywords directly on a campaign |
260
260
  | `add_negative_locations` | Propose negative geo exclusions on a campaign — exclude cities/regions while keeping broader positive targets |
261
261
  | `draft_key_event` | Mark a GA4 event as a key event (conversion) — the fix for "fires but isn't tracked as a conversion" |
@@ -314,8 +314,8 @@ AdLoop manages real ad spend, so safety is not optional.
314
314
  - **Budget caps.** Configurable maximum daily budget — the server rejects anything above the cap.
315
315
  - **Audit log.** Every operation (including dry runs) is logged to `~/.adloop/audit.log`.
316
316
  - **New campaigns and ads are PAUSED.** Nothing goes live without manual enablement.
317
- - **Destructive ops require double confirmation.** Removing entities or large budget increases trigger extra warnings.
318
- - **Broad Match + Manual CPC blocked.** The #1 cause of wasted ad spend is automatically prevented — `draft_keywords` refuses to add BROAD match keywords to campaigns without Smart Bidding.
317
+ - **Destructive ops are flagged for double confirmation.** Removing entities or large budget increases come back with extra warnings, and the AI is instructed to confirm twice.
318
+ - **Broad Match without Smart Bidding caught.** The #1 cause of wasted ad spend: `draft_campaign` refuses BROAD keywords unless the campaign uses Smart Bidding, and `draft_keywords` / `draft_ad_group` mark them as dangerous in the preview, so the AI has to raise it before anything is applied.
319
319
  - **Pre-write validation.** Before any write, the AI checks bidding strategy, conversion tracking status, and quality scores. If the campaign is fundamentally broken, AdLoop warns you instead of making things worse.
320
320
  - **Structured error handling.** All tools return actionable error messages with hints instead of raw exceptions. Auth errors include specific re-authorization steps.
321
321
  - **API version pinning.** The Google Ads API version is pinned to prevent silent breaking changes from library updates. `health_check` warns when a newer version is available.
@@ -578,7 +578,7 @@ What's been shipped and what's next:
578
578
  - ~~PyPI package~~ ✓ — `pip install adloop`
579
579
  - ~~[AdLoop Cloud](https://getadloop.com)~~ ✓ — the hosted version: no Google Cloud project, no API access application, connect Google in two clicks (EU-hosted, GDPR-first)
580
580
  - ~~Headless server support~~ ✓ — manual URL copy-paste flow for servers without a browser
581
- - ~~Behavioral eval suites~~ ✓ — 28 prompt-and-expectation tests covering read, write, tracking, and planning workflows
581
+ - ~~Behavioral eval suites~~ ✓ — prompt-and-expectation tests in `tests/evals/` covering read, write, tracking, and planning workflows
582
582
  - ~~Google Tag Manager integration~~ ✓ — read tools for tags, triggers, variables, workspaces, and version history, plus the `audit_event_coverage` three-way join across codebase events, GTM tags, and GA4 actual fires
583
583
  - **Community launch** — HN, Indie Hackers, r/cursor, Twitter
584
584
  - **Video walkthrough**
@@ -228,7 +228,7 @@ All write operations follow a **draft → preview → confirm** workflow. Nothin
228
228
  | `draft_callouts` | Create campaign callout assets from 1-25 character text snippets. |
229
229
  | `draft_structured_snippets` | Create campaign structured snippet assets using official header values and 3-10 snippet values. |
230
230
  | `draft_image_assets` | Create campaign image assets from local files or public image URLs (PNG, JPEG, or GIF). |
231
- | `draft_keywords` | Propose keyword additions with match types. Proactively checks bidding strategy — blocks BROAD match on Manual CPC campaigns. |
231
+ | `draft_keywords` | Propose keyword additions with match types. Proactively checks bidding strategy — flags BROAD match on campaigns without Smart Bidding as dangerous in the preview. |
232
232
  | `add_negative_keywords` | Propose negative keywords directly on a campaign |
233
233
  | `add_negative_locations` | Propose negative geo exclusions on a campaign — exclude cities/regions while keeping broader positive targets |
234
234
  | `draft_key_event` | Mark a GA4 event as a key event (conversion) — the fix for "fires but isn't tracked as a conversion" |
@@ -287,8 +287,8 @@ AdLoop manages real ad spend, so safety is not optional.
287
287
  - **Budget caps.** Configurable maximum daily budget — the server rejects anything above the cap.
288
288
  - **Audit log.** Every operation (including dry runs) is logged to `~/.adloop/audit.log`.
289
289
  - **New campaigns and ads are PAUSED.** Nothing goes live without manual enablement.
290
- - **Destructive ops require double confirmation.** Removing entities or large budget increases trigger extra warnings.
291
- - **Broad Match + Manual CPC blocked.** The #1 cause of wasted ad spend is automatically prevented — `draft_keywords` refuses to add BROAD match keywords to campaigns without Smart Bidding.
290
+ - **Destructive ops are flagged for double confirmation.** Removing entities or large budget increases come back with extra warnings, and the AI is instructed to confirm twice.
291
+ - **Broad Match without Smart Bidding caught.** The #1 cause of wasted ad spend: `draft_campaign` refuses BROAD keywords unless the campaign uses Smart Bidding, and `draft_keywords` / `draft_ad_group` mark them as dangerous in the preview, so the AI has to raise it before anything is applied.
292
292
  - **Pre-write validation.** Before any write, the AI checks bidding strategy, conversion tracking status, and quality scores. If the campaign is fundamentally broken, AdLoop warns you instead of making things worse.
293
293
  - **Structured error handling.** All tools return actionable error messages with hints instead of raw exceptions. Auth errors include specific re-authorization steps.
294
294
  - **API version pinning.** The Google Ads API version is pinned to prevent silent breaking changes from library updates. `health_check` warns when a newer version is available.
@@ -551,7 +551,7 @@ What's been shipped and what's next:
551
551
  - ~~PyPI package~~ ✓ — `pip install adloop`
552
552
  - ~~[AdLoop Cloud](https://getadloop.com)~~ ✓ — the hosted version: no Google Cloud project, no API access application, connect Google in two clicks (EU-hosted, GDPR-first)
553
553
  - ~~Headless server support~~ ✓ — manual URL copy-paste flow for servers without a browser
554
- - ~~Behavioral eval suites~~ ✓ — 28 prompt-and-expectation tests covering read, write, tracking, and planning workflows
554
+ - ~~Behavioral eval suites~~ ✓ — prompt-and-expectation tests in `tests/evals/` covering read, write, tracking, and planning workflows
555
555
  - ~~Google Tag Manager integration~~ ✓ — read tools for tags, triggers, variables, workspaces, and version history, plus the `audit_event_coverage` three-way join across codebase events, GTM tags, and GA4 actual fires
556
556
  - **Community launch** — HN, Indie Hackers, r/cursor, Twitter
557
557
  - **Video walkthrough**
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "adloop"
3
- version = "0.19.0"
3
+ version = "0.19.1"
4
4
  description = "The AI command center for Google Ads, Reddit Ads, GA4, and tracking code."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "adloop"
3
- version = "0.19.0"
3
+ version = "0.19.1"
4
4
  description = "The AI command center for Google Ads, Reddit Ads, GA4, and tracking code."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -537,7 +537,6 @@ def draft_clear_custom_conversion_goal(
537
537
 
538
538
  Call ``confirm_and_apply`` with the returned plan_id to execute.
539
539
  """
540
- from adloop.ads.client import normalize_customer_id
541
540
  from adloop.safety.guards import SafetyViolation, check_blocked_operation
542
541
  from adloop.safety.preview import ChangePlan, store_plan
543
542
 
@@ -553,7 +552,6 @@ def draft_clear_custom_conversion_goal(
553
552
  "details": ["campaign_id must be a numeric ID"],
554
553
  }
555
554
 
556
- cid = normalize_customer_id(customer_id or config.ads.customer_id)
557
555
  state = _custom_goal_state(config, customer_id, campaign_id=campaign_id)
558
556
  campaign = find_campaign(state, campaign_id)
559
557
  if campaign is None:
@@ -373,7 +373,11 @@ def get_search_terms(
373
373
  """Get search terms report — what users actually typed before clicking ads."""
374
374
  from adloop.ads.gaql import execute_query
375
375
 
376
- date_clause = _date_clause(date_range_start, date_range_end)
376
+ # search_term_view requires an explicit date segment, so the default is
377
+ # DURING LAST_30_DAYS and a given range replaces it. ``_date_clause``
378
+ # prefixes its fragment with AND (the other read tools append it to a
379
+ # WHERE); here it is the only predicate, so the AND comes off.
380
+ where = _date_clause(date_range_start, date_range_end).removeprefix("AND ")
377
381
 
378
382
  query = f"""
379
383
  SELECT search_term_view.search_term,
@@ -381,35 +385,10 @@ def get_search_terms(
381
385
  metrics.impressions, metrics.clicks,
382
386
  metrics.cost_micros, metrics.conversions
383
387
  FROM search_term_view
384
- WHERE segments.date DURING LAST_30_DAYS
385
- {f"AND segments.date BETWEEN '{date_range_start}' AND '{date_range_end}'" if date_range_start and date_range_end else ""}
388
+ WHERE {where}
386
389
  ORDER BY metrics.clicks DESC
387
390
  LIMIT 200
388
391
  """
389
- # search_term_view requires an explicit date segment, so we always
390
- # include DURING LAST_30_DAYS as baseline and override if dates given.
391
- if date_range_start and date_range_end:
392
- query = f"""
393
- SELECT search_term_view.search_term,
394
- campaign.name, ad_group.name,
395
- metrics.impressions, metrics.clicks,
396
- metrics.cost_micros, metrics.conversions
397
- FROM search_term_view
398
- WHERE segments.date BETWEEN '{date_range_start}' AND '{date_range_end}'
399
- ORDER BY metrics.clicks DESC
400
- LIMIT 200
401
- """
402
- else:
403
- query = """
404
- SELECT search_term_view.search_term,
405
- campaign.name, ad_group.name,
406
- metrics.impressions, metrics.clicks,
407
- metrics.cost_micros, metrics.conversions
408
- FROM search_term_view
409
- WHERE segments.date DURING LAST_30_DAYS
410
- ORDER BY metrics.clicks DESC
411
- LIMIT 200
412
- """
413
392
 
414
393
  rows = execute_query(config, customer_id, query)
415
394
  currency_code = get_currency_code(config, customer_id)
@@ -3867,7 +3867,6 @@ def _apply_create_campaign(client: object, cid: str, changes: dict) -> dict:
3867
3867
  results = {}
3868
3868
  num_keywords = len(kw_list)
3869
3869
  num_geo = len(changes.get("geo_target_ids") or [])
3870
- num_lang = len(changes.get("language_ids") or [])
3871
3870
  for i, resp in enumerate(response.mutate_operation_responses):
3872
3871
  rn = _extract_resource_name(resp)
3873
3872
  if rn:
@@ -13,9 +13,11 @@ from pydantic import BeforeValidator
13
13
  from adloop import diagnostics
14
14
  from adloop.runtime import current_config
15
15
 
16
- _READONLY = ToolAnnotations(readOnlyHint=True, destructiveHint=False)
17
- _WRITE = ToolAnnotations(readOnlyHint=False, destructiveHint=False)
18
- _DESTRUCTIVE = ToolAnnotations(readOnlyHint=False, destructiveHint=True)
16
+ # openWorldHint: every tool talks to an external service (Google, Reddit,
17
+ # a live web page), which directories such as ChatGPT's require to be said.
18
+ _READONLY = ToolAnnotations(readOnlyHint=True, destructiveHint=False, openWorldHint=True)
19
+ _WRITE = ToolAnnotations(readOnlyHint=False, destructiveHint=False, openWorldHint=True)
20
+ _DESTRUCTIVE = ToolAnnotations(readOnlyHint=False, destructiveHint=True, openWorldHint=True)
19
21
 
20
22
  # Toolset taxonomy: every tool carries exactly one of these tags (or "core",
21
23
  # which survives every ADLOOP_TOOLSETS selection — health_check and
@@ -552,6 +554,8 @@ def run_ga4_report(
552
554
 
553
555
  Date formats: "today", "yesterday", "7daysAgo", "28daysAgo", "90daysAgo", or "YYYY-MM-DD".
554
556
  If property_id is empty, uses the default from config.
557
+ Queries the GA4 Data API. Dimensions and metrics:
558
+ https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema
555
559
  """
556
560
  from adloop.ga4.reports import run_ga4_report as _impl
557
561
 
@@ -660,6 +664,8 @@ def run_gsc_report(
660
664
  [{"filters": [{"dimension": "query", "operator": "contains",
661
665
  "expression": "analytics"}]}]
662
666
  limit: maximum rows to return (default 100, max 25000).
667
+ Queries the Search Console API:
668
+ https://developers.google.com/webmaster-tools/v1/searchanalytics/query
663
669
  """
664
670
  from adloop.gsc.reports import run_gsc_report as _impl
665
671
 
@@ -708,8 +714,9 @@ def analyze_page_speed(url: str, strategy: str = "mobile") -> dict:
708
714
  def list_merchant_accounts() -> dict:
709
715
  """List Google Merchant Center accounts the connected user can access.
710
716
 
711
- Call first to discover merchant IDs for get_merchant_feed_health.
712
- Distinguishes standalone accounts from aggregator (MCA) accounts.
717
+ Returns each account's numeric ID, name and whether it is a test
718
+ account. Call it to find the account_id for get_merchant_feed_health;
719
+ not needed when merchant.account_id is set in the config.
713
720
  """
714
721
  from adloop.merchant.read import list_merchant_accounts as _impl
715
722
 
@@ -899,9 +906,9 @@ def get_negative_keyword_lists(
899
906
  ) -> dict:
900
907
  """List all shared negative keyword lists (SharedSets) in the account.
901
908
 
902
- Returns each list's ID, name, status, and keyword count. Always call
903
- this before propose_negative_keyword_list to avoid creating duplicates —
904
- a suitable list may already exist and just need attaching to a campaign.
909
+ Returns each list's ID, name, status, and keyword count. Useful before
910
+ propose_negative_keyword_list: a suitable list may already exist and
911
+ just need attaching to a campaign.
905
912
  """
906
913
  from adloop.ads.read import get_negative_keyword_lists as _impl
907
914
 
@@ -1543,7 +1550,8 @@ def run_gaql(
1543
1550
  """Execute an arbitrary GAQL (Google Ads Query Language) query.
1544
1551
 
1545
1552
  Use this for advanced queries not covered by the other tools.
1546
- See the GAQL reference in the AdLoop cursor rules for syntax help.
1553
+ Queries the Google Ads API. GAQL syntax and fields:
1554
+ https://developers.google.com/google-ads/api/docs/query/overview
1547
1555
 
1548
1556
  format: "table" (default, readable), "json" (structured), "csv" (exportable)
1549
1557
  """
@@ -2125,8 +2133,8 @@ def add_to_negative_keyword_list(
2125
2133
 
2126
2134
  Use this when a suitable list already exists and only needs more keywords
2127
2135
  (instead of propose_negative_keyword_list, which creates a new list).
2128
- Always call get_negative_keyword_lists first to find the right shared_set_id
2129
- and get_negative_keyword_list_keywords to avoid duplicating existing terms.
2136
+ The shared_set_id comes from get_negative_keyword_lists;
2137
+ get_negative_keyword_list_keywords shows the terms already in a list.
2130
2138
 
2131
2139
  shared_set_id: numeric ID from get_negative_keyword_lists (shared_set.id).
2132
2140
  keywords: list of keyword strings to append (duplicates in the input list
@@ -2815,6 +2823,7 @@ def run_reddit_report(
2815
2823
  (HOUR and DATE cannot be combined). Dates are account-local days.
2816
2824
  filter: Reddit filter expression (e.g. "campaign_id==abc123").
2817
2825
  Microcurrency fields are converted to currency amounts.
2826
+ Queries the Reddit Ads API: https://ads-api.reddit.com/docs/v3/
2818
2827
  """
2819
2828
  from adloop.reddit.read import run_reddit_report as _impl
2820
2829
 
@@ -3734,8 +3743,8 @@ def get_brand_lists(
3734
3743
  ) -> dict:
3735
3744
  """List all brand lists (SharedSets of type BRANDS) in the account.
3736
3745
 
3737
- Returns each list's ID, name, status, and member count. Call this before
3738
- propose_brand_list so an existing list can be reused instead of duplicated.
3746
+ Returns each list's ID, name, status, and member count. Useful before
3747
+ propose_brand_list, so an existing list can be reused instead of duplicated.
3739
3748
 
3740
3749
  Args:
3741
3750
  customer_id: Ads account ID. Defaults to the configured account.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes