adloop 0.13.3__tar.gz → 0.15.0__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 (59) hide show
  1. adloop-0.13.3/README.md → adloop-0.15.0/PKG-INFO +82 -25
  2. adloop-0.13.3/PKG-INFO → adloop-0.15.0/README.md +55 -51
  3. {adloop-0.13.3 → adloop-0.15.0}/pyproject.toml +5 -3
  4. {adloop-0.13.3 → adloop-0.15.0}/pyproject.toml.orig +7 -4
  5. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ads/client.py +7 -2
  6. adloop-0.15.0/src/adloop/ads/conversion_actions.py +558 -0
  7. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ads/enums.py +2 -4
  8. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ads/forecast.py +4 -5
  9. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ads/gaql.py +15 -6
  10. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ads/write.py +339 -20
  11. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/auth.py +23 -0
  12. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/cli.py +136 -13
  13. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/config.py +27 -0
  14. adloop-0.15.0/src/adloop/reddit/__init__.py +9 -0
  15. adloop-0.15.0/src/adloop/reddit/auth.py +504 -0
  16. adloop-0.15.0/src/adloop/reddit/client.py +270 -0
  17. adloop-0.15.0/src/adloop/reddit/read.py +1344 -0
  18. adloop-0.15.0/src/adloop/reddit/schedule.py +200 -0
  19. adloop-0.15.0/src/adloop/reddit/write.py +1684 -0
  20. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/rules/adloop.md +75 -7
  21. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/safety/preview.py +4 -0
  22. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/server.py +1026 -15
  23. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/__init__.py +0 -0
  24. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/__main__.py +0 -0
  25. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/_mcp_patches.py +0 -0
  26. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ads/__init__.py +0 -0
  27. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ads/currency.py +0 -0
  28. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ads/pmax.py +0 -0
  29. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ads/read.py +0 -0
  30. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/crossref.py +0 -0
  31. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/diagnostics.py +0 -0
  32. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ga4/__init__.py +0 -0
  33. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ga4/client.py +0 -0
  34. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ga4/reports.py +0 -0
  35. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ga4/tracking.py +0 -0
  36. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/ga4/write.py +0 -0
  37. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/gsc/__init__.py +0 -0
  38. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/gsc/client.py +0 -0
  39. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/gsc/reports.py +0 -0
  40. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/gtm/__init__.py +0 -0
  41. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/gtm/client.py +0 -0
  42. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/gtm/read.py +0 -0
  43. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/merchant/__init__.py +0 -0
  44. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/merchant/client.py +0 -0
  45. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/merchant/read.py +0 -0
  46. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/pagespeed.py +0 -0
  47. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/rules/__init__.py +0 -0
  48. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/rules/commands/analyze-performance.md +0 -0
  49. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/rules/commands/budget-plan.md +0 -0
  50. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/rules/commands/create-ad.md +0 -0
  51. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/rules/commands/create-campaign.md +0 -0
  52. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
  53. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/rules/commands/optimize-campaign.md +0 -0
  54. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/rules_install.py +0 -0
  55. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/runtime.py +0 -0
  56. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/safety/__init__.py +0 -0
  57. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/safety/audit.py +0 -0
  58. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/safety/guards.py +0 -0
  59. {adloop-0.13.3 → adloop-0.15.0}/src/adloop/tracking.py +0 -0
@@ -1,27 +1,54 @@
1
+ Metadata-Version: 2.3
2
+ Name: adloop
3
+ Version: 0.15.0
4
+ Summary: The AI command center for Google Ads, Reddit Ads, GA4, and tracking code.
5
+ Keywords: mcp,google-ads,reddit-ads,google-analytics,ga4,cursor,marketing
6
+ Author: Daniel Klose
7
+ Author-email: Daniel Klose <info@daniel-klose.com>
8
+ License: MIT
9
+ Requires-Dist: fastmcp>=3.0.0
10
+ Requires-Dist: google-ads>=32.0.0
11
+ Requires-Dist: google-analytics-data>=0.20.0
12
+ Requires-Dist: google-analytics-admin>=0.27.0
13
+ Requires-Dist: google-api-python-client>=2.100.0
14
+ Requires-Dist: google-auth-oauthlib>=1.0.0
15
+ Requires-Dist: google-api-python-client>=2.0.0
16
+ Requires-Dist: pyyaml>=6.0
17
+ Requires-Dist: requests>=2.31
18
+ Requires-Dist: pytest>=8.0 ; extra == 'dev'
19
+ Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
20
+ Requires-Python: >=3.11
21
+ Project-URL: Homepage, https://github.com/kLOsk/adloop
22
+ Project-URL: Repository, https://github.com/kLOsk/adloop
23
+ Project-URL: Issues, https://github.com/kLOsk/adloop/issues
24
+ Project-URL: Changelog, https://github.com/kLOsk/adloop/releases
25
+ Provides-Extra: dev
26
+ Description-Content-Type: text/markdown
27
+
1
28
  <!-- mcp-name: com.getadloop/adloop -->
2
29
 
3
30
  <div align="center">
4
31
 
5
32
  # AdLoop
6
33
 
7
- **The AI command center for Google Ads, GA4, and tracking code.**
34
+ **The AI command center for Google Ads, Reddit Ads, GA4, and tracking code.**
8
35
 
9
36
  [![PyPI](https://img.shields.io/pypi/v/adloop.svg)](https://pypi.org/project/adloop/)
10
37
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
11
38
  [![Python 3.11+](https://img.shields.io/badge/Python-3.11+-3776AB.svg?logo=python&logoColor=white)](https://www.python.org/downloads/)
12
39
  [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-8A2BE2.svg)](https://modelcontextprotocol.io)
13
- [![Google Ads API](https://img.shields.io/badge/Google%20Ads-API%20v24-4285F4.svg?logo=google-ads&logoColor=white)](https://developers.google.com/google-ads/api/docs/start)
40
+ [![Google Ads API](https://img.shields.io/badge/Google%20Ads-API%20v25-4285F4.svg?logo=google-ads&logoColor=white)](https://developers.google.com/google-ads/api/docs/start)
14
41
  [![GA4 Data API](https://img.shields.io/badge/GA4-Data%20API-E37400.svg?logo=google-analytics&logoColor=white)](https://developers.google.com/analytics/devguides/reporting/data/v1)
15
42
  [![GitHub stars](https://img.shields.io/github/stars/kLOsk/adloop?style=social)](https://github.com/kLOsk/adloop)
16
43
 
17
- An MCP server that gives your AI assistant read + write access to Google Ads and GA4 — with safety guardrails that prevent accidental spend.
44
+ An MCP server that gives your AI assistant read + write access to Google Ads, Reddit Ads and GA4 — with safety guardrails that prevent accidental spend.
18
45
 
19
46
  **[☁️ Skip the setup — use AdLoop Cloud (free plan, no card)](https://getadloop.com)** &nbsp;·&nbsp; or self-host: `pip install adloop`
20
47
 
21
48
  </div>
22
49
 
23
50
  > [!TIP]
24
- > **[AdLoop Cloud](https://getadloop.com) is the hosted version of this project, with a free plan that needs no credit card.** Connect Google in two clicks and use the full toolset from claude.ai, ChatGPT, Claude Code, Cursor, or Gemini. No Google Cloud project, no developer token, no OAuth verification wait. EU-hosted, GDPR-first, DPA included.
51
+ > **[AdLoop Cloud](https://getadloop.com) is the hosted version of this project, with a free plan that needs no credit card.** Connect Google in two clicks and use the full toolset from claude.ai, ChatGPT, Claude Code, Cursor, or Gemini. No Google Cloud project, no API access application, no OAuth verification wait. EU-hosted, GDPR-first, DPA included.
25
52
 
26
53
  > 📚 **Documentation: [docs.getadloop.com](https://docs.getadloop.com)** — setup guides per AI client, toolsets, the safety model, and troubleshooting for both editions.
27
54
 
@@ -35,7 +62,7 @@ Both versions run the same tools with the same safety model. The difference is w
35
62
  |---|---|---|
36
63
  | **Setup** | Connect Google in two clicks | ~5 min: own Google Cloud project + `adloop init` |
37
64
  | **Google Cloud project** | Not needed | Required (free) |
38
- | **Ads developer token** | Not needed | Required (from your MCC) |
65
+ | **Ads API access** | Not needed | Granted to your Cloud project (no developer token, no MCC since Sept 2026) |
39
66
  | **Works with** | claude.ai, ChatGPT, Claude Code, Cursor, Gemini | Claude Code, Cursor, Claude Desktop, any local MCP client |
40
67
  | **Where your data flows** | EU servers (Germany), GDPR-first, DPA included | 100% your machine — nothing leaves it |
41
68
  | **Updates** | Automatic | `pip install -U adloop` |
@@ -171,6 +198,27 @@ These tools read the live GTM container and join it with the codebase + GA4 to f
171
198
 
172
199
  > **Setup for GSC tools** — Enable the **Search Console API** in your GCP project. Upgrading OAuth users must re-authorize once for the new scope (delete `~/.adloop/token.json`, run any tool). The killer combo: cross-reference organic queries with `get_keyword_performance` to find paid/organic cannibalization and untapped keyword opportunities.
173
200
 
201
+ ### Reddit Ads Tools
202
+
203
+ A second ad platform, same safety model. Reddit is a separate connection: its own developer app, its own OAuth, no developer token and no approval process. Every tool takes `ad_account_id` (defaults to `reddit.ad_account_id`).
204
+
205
+ | Tool | What It Does |
206
+ |------|-------------|
207
+ | `list_reddit_accounts` | Discover businesses and ad accounts (currency, time zone, approval state) |
208
+ | `list_reddit_funding_instruments` | Billing instruments and posting profiles — prerequisites for creating campaigns and ads |
209
+ | `get_reddit_campaigns` / `get_reddit_ad_groups` / `get_reddit_ads` | Structure with configured vs effective status, budgets, bids, pixel, targeting, weekly schedule, rejection reasons; `include_copy` returns each ad's post (headline, body, destination, media) |
210
+ | `get_reddit_performance` | Spend, clicks, CTR, CPC, conversions, CPA, ROAS per account/campaign/ad group/ad, optional breakdown (date, country, community, keyword, placement, …), compact mode with insights |
211
+ | `run_reddit_report` | Raw reports endpoint for any metric (REACH, video, per-event conversions) |
212
+ | `get_reddit_pixels` | Pixels and when each event last fired — flags ad groups optimizing for events the pixel never sent |
213
+ | `search_reddit_targeting` | Communities, interests, geolocations, languages, keyword suggestions, and Reddit's related-community suggestions (by seed communities or website) |
214
+ | `get_reddit_account_history` | Who changed what and when: field, before/after, member |
215
+ | `estimate_reddit_ad_group` | Audience size, delivery estimate and suggested bid range for a planned ad group (Reddit's counterpart of `estimate_budget`) |
216
+ | `pause_reddit_entity` / `enable_reddit_entity` / `remove_reddit_entity` | Status changes through the preview gate (remove = ARCHIVE, irreversible, double-confirmed) |
217
+ | `update_reddit_campaign` / `update_reddit_ad_group` / `update_reddit_ad` | Budget, bid, run dates, weekly schedule (day names, viewer-local hours), targeting and placements (validated with Reddit at draft time), landing URL and comment changes with old → new previews, budget cap and bid-increase guards |
218
+ | `draft_reddit_campaign` / `draft_reddit_ad_group` / `draft_reddit_ad` | Create campaign → ad group (pixel + targeting required) → post + ad, or promote an existing post with `post_id`. Everything is created **PAUSED**. |
219
+
220
+ > **Setup for Reddit Ads tools** — In Reddit Ads Manager open **Business Manager → Developer Application → Create app** (business admins only; no approval wait). Register the redirect URL exactly as `http://localhost:8765/callback`, then run `adloop init` and complete the Reddit Ads step: it opens Reddit's consent page (scopes `adsread` + `adsedit`, permanent grant), stores the refresh token at `~/.adloop/reddit_token.json`, and lets you pick the default ad account. Reddit rate-limits per user (reporting: 60 requests/min) and requires a descriptive User-Agent, which AdLoop builds from your app id and Reddit username. Reddit has no validate-only mode, so `confirm_and_apply(dry_run=true)` re-reads the target and re-checks the safety caps instead.
221
+
174
222
  ### Planning Tools
175
223
 
176
224
  | Tool | What It Does |
@@ -251,7 +299,7 @@ AdLoop manages real ad spend, so safety is not optional.
251
299
 
252
300
  > **AdLoop uses your own (free) Google Cloud project for OAuth.** The `adloop init` wizard walks you through it — a one-time setup of about 5 minutes, with no shared user caps and no waiting on anyone's verification review. AdLoop does not ship built-in OAuth credentials.
253
301
  >
254
- > **Prefer zero setup?** [**AdLoop Cloud**](https://getadloop.com) is the hosted version: connect Google in two clicks — no Cloud project, no developer token, EU-hosted.
302
+ > **Prefer zero setup?** [**AdLoop Cloud**](https://getadloop.com) is the hosted version: connect Google in two clicks — no Cloud project, no API access application, EU-hosted.
255
303
  >
256
304
  > *(Upgrading from ≤0.9 with built-in credentials? Those sign-ins were retired in 0.10 — run `adloop init` once to switch to your own project.)*
257
305
 
@@ -281,8 +329,8 @@ uv run adloop init
281
329
  The wizard walks you through:
282
330
 
283
331
  1. **Google Cloud setup** — creates a project, enables the three APIs, generates an OAuth client (see [Custom Google Cloud Project Setup](#custom-google-cloud-project-setup) below for the exact steps the wizard refers you to)
284
- 2. **Developer token** — from your Google Ads MCC ([API Center](https://ads.google.com/aw/apicenter))
285
- 3. **MCC Account ID** — your Manager Account ID (top bar in the MCC UI)
332
+ 2. **Google Ads API access** — applied for on your Cloud project's [Google Ads API Overview page](https://console.cloud.google.com/google/ads-apis/overview); the legacy developer-token prompt can be left empty
333
+ 3. **MCC Account ID** — optional, only if you reach several accounts through a Manager Account
286
334
  4. **OAuth sign-in** — opens a browser to sign in with Google (or prints a URL for headless servers)
287
335
  5. **Auto-discovers your accounts** — finds your GA4 properties and Ads accounts automatically
288
336
  6. **Optional services** — pin a GTM container, a Search Console property (both auto-discovered too), and a PageSpeed API key; skip any of them with Enter
@@ -293,26 +341,25 @@ The wizard walks you through:
293
341
  ### Requirements
294
342
 
295
343
  - Python 3.11+
296
- - A Google Ads account with an MCC (Manager Account)
297
- - A Google Ads Developer Token (see below)
344
+ - A Google Ads account (a Manager Account is optional; you only need one to reach several accounts through one login)
345
+ - Google Ads API access on your Google Cloud project (see below)
298
346
 
299
- ### Google Ads Developer Token
347
+ ### Google Ads API access
300
348
 
301
- A developer token is **always required**. Your OAuth client handles Google sign-in; the developer token is a separate key that grants API access to your Google Ads data.
349
+ Since 9 September 2026, Google Ads API access belongs to the **Google Cloud project that owns your OAuth client**. Developer tokens are sunset: the API ignores the header, the wizard's token prompt is optional, and you no longer need a Manager Account to get access.
302
350
 
303
- 1. **Create an MCC** (free) at [ads.google.com/home/tools/manager-accounts](https://ads.google.com/home/tools/manager-accounts/) if you don't have one. Link your regular Google Ads account to it.
304
- 2. In the MCC, go to **Tools & Settings → API Center**
305
- 3. Your **developer token** is shown there. Copy it — the wizard will ask for it.
351
+ Apply on your project's [Google Ads API Overview page](https://console.cloud.google.com/google/ads-apis/overview) in the Cloud Console:
306
352
 
307
- **Access levels** — your token's access level determines what it can do:
353
+ | Level | How to get it | What it allows |
354
+ |-------|---------------|----------------|
355
+ | **Test** | Automatic when you enable the Google Ads API | Test accounts only, **not production accounts**. `CLOUD_PROJECT_NOT_APPROVED_FOR_PRODUCTION` (or `DEVELOPER_TOKEN_NOT_APPROVED` on older API versions) means you are here. |
356
+ | **Explorer** | "Upgrade access level" → apply; usually granted automatically | 2,880 operations/day on production accounts. Enough to get started. |
357
+ | **Basic** | Apply; reviewed automatically within minutes once the project is brand-verified (OAuth consent screen set to External and published "In production") | 15,000 operations/day. |
358
+ | **Standard** | Apply; manual review, about ten business days | Higher limits; Google's RMF policy applies |
308
359
 
309
- | Level | How to Get | What It Allows |
310
- |-------|-----------|----------------|
311
- | **Test Account** | Default for new tokens | Can only access test accounts — **not production accounts**. If you see `DEVELOPER_TOKEN_NOT_APPROVED`, this is why. |
312
- | **Explorer** | Automatic after first API call with a production account | 2,880 operations/day on production accounts. Enough to get started. |
313
- | **Basic** | [Apply through API Center](https://ads.google.com/aw/apicenter) | 15,000 operations/day. Apply if you need more. |
360
+ Projects on a Cloud free trial or with billing disabled are refused Explorer and Basic; use a project with billing set up.
314
361
 
315
- > **Getting `DEVELOPER_TOKEN_NOT_APPROVED`?** Your token is at "Test Account" level. Go to [API Center](https://ads.google.com/aw/apicenter) in your MCC and check your access level. If it shows "Test Account", you need to apply for Basic access or wait for Explorer access to be granted after your first production API call.
362
+ > **Getting `CLOUD_PROJECT_NOT_APPROVED_FOR_PRODUCTION`?** Your project is at Test level. Open the Overview page above and apply for Explorer access.
316
363
 
317
364
  ### Headless Servers
318
365
 
@@ -417,9 +464,13 @@ All configuration lives in `~/.adloop/config.yaml`. See [`config.yaml.example`](
417
464
  | `google` | `credentials_path` | *(empty)* | Path to OAuth client JSON or service account key. Empty = `~/.adloop/credentials.json`, else Application Default Credentials. |
418
465
  | `google` | `token_path` | `~/.adloop/token.json` | Where to store the OAuth token (auto-created) |
419
466
  | `ga4` | `property_id` | — | Your GA4 property ID (auto-discovered by `adloop init`) |
420
- | `ads` | `developer_token` | — | Your Google Ads API developer token |
467
+ | `ads` | `developer_token` | — | Legacy, optional: API access belongs to your Cloud project since Sept 2026 |
421
468
  | `ads` | `customer_id` | — | Default Google Ads customer ID (auto-discovered by `adloop init`) |
422
469
  | `ads` | `login_customer_id` | — | Your MCC account ID |
470
+ | `reddit` | `client_id` / `client_secret` | *(empty)* | Your Reddit developer app (Business Manager → Developer Application) |
471
+ | `reddit` | `ad_account_id` | *(empty)* | Default Reddit ad account for every Reddit tool (picked by `adloop init`) |
472
+ | `reddit` | `username` | *(empty)* | Your Reddit username, used only in the User-Agent Reddit requires |
473
+ | `reddit` | `token_path` | `~/.adloop/reddit_token.json` | Where the Reddit refresh token is stored |
423
474
  | `safety` | `max_daily_budget` | `50.00` | Maximum allowed daily budget per campaign |
424
475
  | `safety` | `require_dry_run` | `true` | Force all writes to dry-run mode |
425
476
  | `safety` | `two_phase_apply` | `false` | Refuse real applies until the plan had a dry-run pass |
@@ -442,6 +493,7 @@ Most MCP clients (claude.ai, ChatGPT, Cursor, …) load **every tool schema into
442
493
  | `gsc` | Search Console reads |
443
494
  | `web` | PageSpeed / Core Web Vitals |
444
495
  | `merchant` | Merchant Center feed health |
496
+ | `reddit` | Reddit Ads reads, writes, and planning |
445
497
 
446
498
  `health_check` and `confirm_and_apply` are always included, whatever you select. Unset = the full catalog; unknown names fail at startup with the valid list. The effect is real: `ads,ga4` drops the session cost to ~13k tokens, and a `ga4`-only client pays ~2k — nearly 90% less. Toolsets are per *client*, not per install: one AdLoop config can serve a trimmed Cursor and a full-catalog Claude Code side by side.
447
499
 
@@ -452,7 +504,7 @@ On [AdLoop Cloud](https://getadloop.com), the same feature is per API key: pick
452
504
  ```
453
505
  src/adloop/
454
506
  ├── __init__.py # Entry point — routes 'adloop init' to wizard, otherwise starts MCP server
455
- ├── server.py # FastMCP server — 67 tool registrations with safety annotations
507
+ ├── server.py # FastMCP server — every tool registration with safety annotations and toolset tags
456
508
  ├── config.py # Config loader (~/.adloop/config.yaml)
457
509
  ├── auth.py # OAuth 2.0 flow (user-supplied credentials, headless fallback) + service accounts; GA4 / Ads / GTM scopes
458
510
  ├── cli.py # Interactive 'adloop init' setup wizard
@@ -472,6 +524,11 @@ src/adloop/
472
524
  ├── gtm/
473
525
  │ ├── client.py # Google Tag Manager API v2 client
474
526
  │ └── read.py # Live container fetching, tag/trigger/variable parsing, workspace diff, version history
527
+ ├── reddit/
528
+ │ ├── auth.py # Reddit OAuth2 (own app, permanent refresh token, loopback flow for adloop init)
529
+ │ ├── client.py # Reddit Ads API v3 REST client — bearer refresh, per-user rate limits, pagination
530
+ │ ├── read.py # Accounts, campaigns, ad groups, ads, performance reports, pixels, targeting lookups
531
+ │ └── write.py # Draft/preflight/apply for status, budget, bid, targeting, and PAUSED creation
475
532
  └── safety/
476
533
  ├── guards.py # Budget caps, bid limits, blocked operations, Broad Match safety
477
534
  ├── preview.py # Change plans and previews
@@ -493,7 +550,7 @@ What's been shipped and what's next:
493
550
  - ~~Claude Code support~~ ✓ — `CLAUDE.md`, `.mcp.json`, `.claude/rules/`, `.claude/commands/`, CLI wizard snippets
494
551
  - **Claude Desktop one-click install** — `adloop install claude-desktop` (and/or a `.dxt` extension bundle) that writes the AdLoop MCP entry into `claude_desktop_config.json` automatically, so Claude Desktop + Cowork users don't have to hand-edit JSON
495
552
  - ~~PyPI package~~ ✓ — `pip install adloop`
496
- - ~~[AdLoop Cloud](https://getadloop.com)~~ ✓ — the hosted version: no Google Cloud project, no developer token, connect Google in two clicks (EU-hosted, GDPR-first)
553
+ - ~~[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)
497
554
  - ~~Headless server support~~ ✓ — manual URL copy-paste flow for servers without a browser
498
555
  - ~~Behavioral eval suites~~ ✓ — 28 prompt-and-expectation tests covering read, write, tracking, and planning workflows
499
556
  - ~~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
@@ -1,53 +1,27 @@
1
- Metadata-Version: 2.3
2
- Name: adloop
3
- Version: 0.13.3
4
- Summary: The AI command center for Google Ads, GA4, and tracking code.
5
- Keywords: mcp,google-ads,google-analytics,ga4,cursor,marketing
6
- Author: Daniel Klose
7
- Author-email: Daniel Klose <info@daniel-klose.com>
8
- License: MIT
9
- Requires-Dist: fastmcp>=3.0.0
10
- Requires-Dist: google-ads>=31.1.0
11
- Requires-Dist: google-analytics-data>=0.20.0
12
- Requires-Dist: google-analytics-admin>=0.27.0
13
- Requires-Dist: google-api-python-client>=2.100.0
14
- Requires-Dist: google-auth-oauthlib>=1.0.0
15
- Requires-Dist: google-api-python-client>=2.0.0
16
- Requires-Dist: pyyaml>=6.0
17
- Requires-Dist: pytest>=8.0 ; extra == 'dev'
18
- Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
19
- Requires-Python: >=3.11
20
- Project-URL: Homepage, https://github.com/kLOsk/adloop
21
- Project-URL: Repository, https://github.com/kLOsk/adloop
22
- Project-URL: Issues, https://github.com/kLOsk/adloop/issues
23
- Project-URL: Changelog, https://github.com/kLOsk/adloop/releases
24
- Provides-Extra: dev
25
- Description-Content-Type: text/markdown
26
-
27
1
  <!-- mcp-name: com.getadloop/adloop -->
28
2
 
29
3
  <div align="center">
30
4
 
31
5
  # AdLoop
32
6
 
33
- **The AI command center for Google Ads, GA4, and tracking code.**
7
+ **The AI command center for Google Ads, Reddit Ads, GA4, and tracking code.**
34
8
 
35
9
  [![PyPI](https://img.shields.io/pypi/v/adloop.svg)](https://pypi.org/project/adloop/)
36
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
37
11
  [![Python 3.11+](https://img.shields.io/badge/Python-3.11+-3776AB.svg?logo=python&logoColor=white)](https://www.python.org/downloads/)
38
12
  [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-8A2BE2.svg)](https://modelcontextprotocol.io)
39
- [![Google Ads API](https://img.shields.io/badge/Google%20Ads-API%20v24-4285F4.svg?logo=google-ads&logoColor=white)](https://developers.google.com/google-ads/api/docs/start)
13
+ [![Google Ads API](https://img.shields.io/badge/Google%20Ads-API%20v25-4285F4.svg?logo=google-ads&logoColor=white)](https://developers.google.com/google-ads/api/docs/start)
40
14
  [![GA4 Data API](https://img.shields.io/badge/GA4-Data%20API-E37400.svg?logo=google-analytics&logoColor=white)](https://developers.google.com/analytics/devguides/reporting/data/v1)
41
15
  [![GitHub stars](https://img.shields.io/github/stars/kLOsk/adloop?style=social)](https://github.com/kLOsk/adloop)
42
16
 
43
- An MCP server that gives your AI assistant read + write access to Google Ads and GA4 — with safety guardrails that prevent accidental spend.
17
+ An MCP server that gives your AI assistant read + write access to Google Ads, Reddit Ads and GA4 — with safety guardrails that prevent accidental spend.
44
18
 
45
19
  **[☁️ Skip the setup — use AdLoop Cloud (free plan, no card)](https://getadloop.com)** &nbsp;·&nbsp; or self-host: `pip install adloop`
46
20
 
47
21
  </div>
48
22
 
49
23
  > [!TIP]
50
- > **[AdLoop Cloud](https://getadloop.com) is the hosted version of this project, with a free plan that needs no credit card.** Connect Google in two clicks and use the full toolset from claude.ai, ChatGPT, Claude Code, Cursor, or Gemini. No Google Cloud project, no developer token, no OAuth verification wait. EU-hosted, GDPR-first, DPA included.
24
+ > **[AdLoop Cloud](https://getadloop.com) is the hosted version of this project, with a free plan that needs no credit card.** Connect Google in two clicks and use the full toolset from claude.ai, ChatGPT, Claude Code, Cursor, or Gemini. No Google Cloud project, no API access application, no OAuth verification wait. EU-hosted, GDPR-first, DPA included.
51
25
 
52
26
  > 📚 **Documentation: [docs.getadloop.com](https://docs.getadloop.com)** — setup guides per AI client, toolsets, the safety model, and troubleshooting for both editions.
53
27
 
@@ -61,7 +35,7 @@ Both versions run the same tools with the same safety model. The difference is w
61
35
  |---|---|---|
62
36
  | **Setup** | Connect Google in two clicks | ~5 min: own Google Cloud project + `adloop init` |
63
37
  | **Google Cloud project** | Not needed | Required (free) |
64
- | **Ads developer token** | Not needed | Required (from your MCC) |
38
+ | **Ads API access** | Not needed | Granted to your Cloud project (no developer token, no MCC since Sept 2026) |
65
39
  | **Works with** | claude.ai, ChatGPT, Claude Code, Cursor, Gemini | Claude Code, Cursor, Claude Desktop, any local MCP client |
66
40
  | **Where your data flows** | EU servers (Germany), GDPR-first, DPA included | 100% your machine — nothing leaves it |
67
41
  | **Updates** | Automatic | `pip install -U adloop` |
@@ -197,6 +171,27 @@ These tools read the live GTM container and join it with the codebase + GA4 to f
197
171
 
198
172
  > **Setup for GSC tools** — Enable the **Search Console API** in your GCP project. Upgrading OAuth users must re-authorize once for the new scope (delete `~/.adloop/token.json`, run any tool). The killer combo: cross-reference organic queries with `get_keyword_performance` to find paid/organic cannibalization and untapped keyword opportunities.
199
173
 
174
+ ### Reddit Ads Tools
175
+
176
+ A second ad platform, same safety model. Reddit is a separate connection: its own developer app, its own OAuth, no developer token and no approval process. Every tool takes `ad_account_id` (defaults to `reddit.ad_account_id`).
177
+
178
+ | Tool | What It Does |
179
+ |------|-------------|
180
+ | `list_reddit_accounts` | Discover businesses and ad accounts (currency, time zone, approval state) |
181
+ | `list_reddit_funding_instruments` | Billing instruments and posting profiles — prerequisites for creating campaigns and ads |
182
+ | `get_reddit_campaigns` / `get_reddit_ad_groups` / `get_reddit_ads` | Structure with configured vs effective status, budgets, bids, pixel, targeting, weekly schedule, rejection reasons; `include_copy` returns each ad's post (headline, body, destination, media) |
183
+ | `get_reddit_performance` | Spend, clicks, CTR, CPC, conversions, CPA, ROAS per account/campaign/ad group/ad, optional breakdown (date, country, community, keyword, placement, …), compact mode with insights |
184
+ | `run_reddit_report` | Raw reports endpoint for any metric (REACH, video, per-event conversions) |
185
+ | `get_reddit_pixels` | Pixels and when each event last fired — flags ad groups optimizing for events the pixel never sent |
186
+ | `search_reddit_targeting` | Communities, interests, geolocations, languages, keyword suggestions, and Reddit's related-community suggestions (by seed communities or website) |
187
+ | `get_reddit_account_history` | Who changed what and when: field, before/after, member |
188
+ | `estimate_reddit_ad_group` | Audience size, delivery estimate and suggested bid range for a planned ad group (Reddit's counterpart of `estimate_budget`) |
189
+ | `pause_reddit_entity` / `enable_reddit_entity` / `remove_reddit_entity` | Status changes through the preview gate (remove = ARCHIVE, irreversible, double-confirmed) |
190
+ | `update_reddit_campaign` / `update_reddit_ad_group` / `update_reddit_ad` | Budget, bid, run dates, weekly schedule (day names, viewer-local hours), targeting and placements (validated with Reddit at draft time), landing URL and comment changes with old → new previews, budget cap and bid-increase guards |
191
+ | `draft_reddit_campaign` / `draft_reddit_ad_group` / `draft_reddit_ad` | Create campaign → ad group (pixel + targeting required) → post + ad, or promote an existing post with `post_id`. Everything is created **PAUSED**. |
192
+
193
+ > **Setup for Reddit Ads tools** — In Reddit Ads Manager open **Business Manager → Developer Application → Create app** (business admins only; no approval wait). Register the redirect URL exactly as `http://localhost:8765/callback`, then run `adloop init` and complete the Reddit Ads step: it opens Reddit's consent page (scopes `adsread` + `adsedit`, permanent grant), stores the refresh token at `~/.adloop/reddit_token.json`, and lets you pick the default ad account. Reddit rate-limits per user (reporting: 60 requests/min) and requires a descriptive User-Agent, which AdLoop builds from your app id and Reddit username. Reddit has no validate-only mode, so `confirm_and_apply(dry_run=true)` re-reads the target and re-checks the safety caps instead.
194
+
200
195
  ### Planning Tools
201
196
 
202
197
  | Tool | What It Does |
@@ -277,7 +272,7 @@ AdLoop manages real ad spend, so safety is not optional.
277
272
 
278
273
  > **AdLoop uses your own (free) Google Cloud project for OAuth.** The `adloop init` wizard walks you through it — a one-time setup of about 5 minutes, with no shared user caps and no waiting on anyone's verification review. AdLoop does not ship built-in OAuth credentials.
279
274
  >
280
- > **Prefer zero setup?** [**AdLoop Cloud**](https://getadloop.com) is the hosted version: connect Google in two clicks — no Cloud project, no developer token, EU-hosted.
275
+ > **Prefer zero setup?** [**AdLoop Cloud**](https://getadloop.com) is the hosted version: connect Google in two clicks — no Cloud project, no API access application, EU-hosted.
281
276
  >
282
277
  > *(Upgrading from ≤0.9 with built-in credentials? Those sign-ins were retired in 0.10 — run `adloop init` once to switch to your own project.)*
283
278
 
@@ -307,8 +302,8 @@ uv run adloop init
307
302
  The wizard walks you through:
308
303
 
309
304
  1. **Google Cloud setup** — creates a project, enables the three APIs, generates an OAuth client (see [Custom Google Cloud Project Setup](#custom-google-cloud-project-setup) below for the exact steps the wizard refers you to)
310
- 2. **Developer token** — from your Google Ads MCC ([API Center](https://ads.google.com/aw/apicenter))
311
- 3. **MCC Account ID** — your Manager Account ID (top bar in the MCC UI)
305
+ 2. **Google Ads API access** — applied for on your Cloud project's [Google Ads API Overview page](https://console.cloud.google.com/google/ads-apis/overview); the legacy developer-token prompt can be left empty
306
+ 3. **MCC Account ID** — optional, only if you reach several accounts through a Manager Account
312
307
  4. **OAuth sign-in** — opens a browser to sign in with Google (or prints a URL for headless servers)
313
308
  5. **Auto-discovers your accounts** — finds your GA4 properties and Ads accounts automatically
314
309
  6. **Optional services** — pin a GTM container, a Search Console property (both auto-discovered too), and a PageSpeed API key; skip any of them with Enter
@@ -319,26 +314,25 @@ The wizard walks you through:
319
314
  ### Requirements
320
315
 
321
316
  - Python 3.11+
322
- - A Google Ads account with an MCC (Manager Account)
323
- - A Google Ads Developer Token (see below)
317
+ - A Google Ads account (a Manager Account is optional; you only need one to reach several accounts through one login)
318
+ - Google Ads API access on your Google Cloud project (see below)
324
319
 
325
- ### Google Ads Developer Token
320
+ ### Google Ads API access
326
321
 
327
- A developer token is **always required**. Your OAuth client handles Google sign-in; the developer token is a separate key that grants API access to your Google Ads data.
322
+ Since 9 September 2026, Google Ads API access belongs to the **Google Cloud project that owns your OAuth client**. Developer tokens are sunset: the API ignores the header, the wizard's token prompt is optional, and you no longer need a Manager Account to get access.
328
323
 
329
- 1. **Create an MCC** (free) at [ads.google.com/home/tools/manager-accounts](https://ads.google.com/home/tools/manager-accounts/) if you don't have one. Link your regular Google Ads account to it.
330
- 2. In the MCC, go to **Tools & Settings → API Center**
331
- 3. Your **developer token** is shown there. Copy it — the wizard will ask for it.
324
+ Apply on your project's [Google Ads API Overview page](https://console.cloud.google.com/google/ads-apis/overview) in the Cloud Console:
332
325
 
333
- **Access levels** — your token's access level determines what it can do:
326
+ | Level | How to get it | What it allows |
327
+ |-------|---------------|----------------|
328
+ | **Test** | Automatic when you enable the Google Ads API | Test accounts only, **not production accounts**. `CLOUD_PROJECT_NOT_APPROVED_FOR_PRODUCTION` (or `DEVELOPER_TOKEN_NOT_APPROVED` on older API versions) means you are here. |
329
+ | **Explorer** | "Upgrade access level" → apply; usually granted automatically | 2,880 operations/day on production accounts. Enough to get started. |
330
+ | **Basic** | Apply; reviewed automatically within minutes once the project is brand-verified (OAuth consent screen set to External and published "In production") | 15,000 operations/day. |
331
+ | **Standard** | Apply; manual review, about ten business days | Higher limits; Google's RMF policy applies |
334
332
 
335
- | Level | How to Get | What It Allows |
336
- |-------|-----------|----------------|
337
- | **Test Account** | Default for new tokens | Can only access test accounts — **not production accounts**. If you see `DEVELOPER_TOKEN_NOT_APPROVED`, this is why. |
338
- | **Explorer** | Automatic after first API call with a production account | 2,880 operations/day on production accounts. Enough to get started. |
339
- | **Basic** | [Apply through API Center](https://ads.google.com/aw/apicenter) | 15,000 operations/day. Apply if you need more. |
333
+ Projects on a Cloud free trial or with billing disabled are refused Explorer and Basic; use a project with billing set up.
340
334
 
341
- > **Getting `DEVELOPER_TOKEN_NOT_APPROVED`?** Your token is at "Test Account" level. Go to [API Center](https://ads.google.com/aw/apicenter) in your MCC and check your access level. If it shows "Test Account", you need to apply for Basic access or wait for Explorer access to be granted after your first production API call.
335
+ > **Getting `CLOUD_PROJECT_NOT_APPROVED_FOR_PRODUCTION`?** Your project is at Test level. Open the Overview page above and apply for Explorer access.
342
336
 
343
337
  ### Headless Servers
344
338
 
@@ -443,9 +437,13 @@ All configuration lives in `~/.adloop/config.yaml`. See [`config.yaml.example`](
443
437
  | `google` | `credentials_path` | *(empty)* | Path to OAuth client JSON or service account key. Empty = `~/.adloop/credentials.json`, else Application Default Credentials. |
444
438
  | `google` | `token_path` | `~/.adloop/token.json` | Where to store the OAuth token (auto-created) |
445
439
  | `ga4` | `property_id` | — | Your GA4 property ID (auto-discovered by `adloop init`) |
446
- | `ads` | `developer_token` | — | Your Google Ads API developer token |
440
+ | `ads` | `developer_token` | — | Legacy, optional: API access belongs to your Cloud project since Sept 2026 |
447
441
  | `ads` | `customer_id` | — | Default Google Ads customer ID (auto-discovered by `adloop init`) |
448
442
  | `ads` | `login_customer_id` | — | Your MCC account ID |
443
+ | `reddit` | `client_id` / `client_secret` | *(empty)* | Your Reddit developer app (Business Manager → Developer Application) |
444
+ | `reddit` | `ad_account_id` | *(empty)* | Default Reddit ad account for every Reddit tool (picked by `adloop init`) |
445
+ | `reddit` | `username` | *(empty)* | Your Reddit username, used only in the User-Agent Reddit requires |
446
+ | `reddit` | `token_path` | `~/.adloop/reddit_token.json` | Where the Reddit refresh token is stored |
449
447
  | `safety` | `max_daily_budget` | `50.00` | Maximum allowed daily budget per campaign |
450
448
  | `safety` | `require_dry_run` | `true` | Force all writes to dry-run mode |
451
449
  | `safety` | `two_phase_apply` | `false` | Refuse real applies until the plan had a dry-run pass |
@@ -468,6 +466,7 @@ Most MCP clients (claude.ai, ChatGPT, Cursor, …) load **every tool schema into
468
466
  | `gsc` | Search Console reads |
469
467
  | `web` | PageSpeed / Core Web Vitals |
470
468
  | `merchant` | Merchant Center feed health |
469
+ | `reddit` | Reddit Ads reads, writes, and planning |
471
470
 
472
471
  `health_check` and `confirm_and_apply` are always included, whatever you select. Unset = the full catalog; unknown names fail at startup with the valid list. The effect is real: `ads,ga4` drops the session cost to ~13k tokens, and a `ga4`-only client pays ~2k — nearly 90% less. Toolsets are per *client*, not per install: one AdLoop config can serve a trimmed Cursor and a full-catalog Claude Code side by side.
473
472
 
@@ -478,7 +477,7 @@ On [AdLoop Cloud](https://getadloop.com), the same feature is per API key: pick
478
477
  ```
479
478
  src/adloop/
480
479
  ├── __init__.py # Entry point — routes 'adloop init' to wizard, otherwise starts MCP server
481
- ├── server.py # FastMCP server — 67 tool registrations with safety annotations
480
+ ├── server.py # FastMCP server — every tool registration with safety annotations and toolset tags
482
481
  ├── config.py # Config loader (~/.adloop/config.yaml)
483
482
  ├── auth.py # OAuth 2.0 flow (user-supplied credentials, headless fallback) + service accounts; GA4 / Ads / GTM scopes
484
483
  ├── cli.py # Interactive 'adloop init' setup wizard
@@ -498,6 +497,11 @@ src/adloop/
498
497
  ├── gtm/
499
498
  │ ├── client.py # Google Tag Manager API v2 client
500
499
  │ └── read.py # Live container fetching, tag/trigger/variable parsing, workspace diff, version history
500
+ ├── reddit/
501
+ │ ├── auth.py # Reddit OAuth2 (own app, permanent refresh token, loopback flow for adloop init)
502
+ │ ├── client.py # Reddit Ads API v3 REST client — bearer refresh, per-user rate limits, pagination
503
+ │ ├── read.py # Accounts, campaigns, ad groups, ads, performance reports, pixels, targeting lookups
504
+ │ └── write.py # Draft/preflight/apply for status, budget, bid, targeting, and PAUSED creation
501
505
  └── safety/
502
506
  ├── guards.py # Budget caps, bid limits, blocked operations, Broad Match safety
503
507
  ├── preview.py # Change plans and previews
@@ -519,7 +523,7 @@ What's been shipped and what's next:
519
523
  - ~~Claude Code support~~ ✓ — `CLAUDE.md`, `.mcp.json`, `.claude/rules/`, `.claude/commands/`, CLI wizard snippets
520
524
  - **Claude Desktop one-click install** — `adloop install claude-desktop` (and/or a `.dxt` extension bundle) that writes the AdLoop MCP entry into `claude_desktop_config.json` automatically, so Claude Desktop + Cowork users don't have to hand-edit JSON
521
525
  - ~~PyPI package~~ ✓ — `pip install adloop`
522
- - ~~[AdLoop Cloud](https://getadloop.com)~~ ✓ — the hosted version: no Google Cloud project, no developer token, connect Google in two clicks (EU-hosted, GDPR-first)
526
+ - ~~[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)
523
527
  - ~~Headless server support~~ ✓ — manual URL copy-paste flow for servers without a browser
524
528
  - ~~Behavioral eval suites~~ ✓ — 28 prompt-and-expectation tests covering read, write, tracking, and planning workflows
525
529
  - ~~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
@@ -1,12 +1,13 @@
1
1
  [project]
2
2
  name = "adloop"
3
- version = "0.13.3"
4
- description = "The AI command center for Google Ads, GA4, and tracking code."
3
+ version = "0.15.0"
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"
7
7
  keywords = [
8
8
  "mcp",
9
9
  "google-ads",
10
+ "reddit-ads",
10
11
  "google-analytics",
11
12
  "ga4",
12
13
  "cursor",
@@ -14,13 +15,14 @@ keywords = [
14
15
  ]
15
16
  dependencies = [
16
17
  "fastmcp>=3.0.0",
17
- "google-ads>=31.1.0",
18
+ "google-ads>=32.0.0",
18
19
  "google-analytics-data>=0.20.0",
19
20
  "google-analytics-admin>=0.27.0",
20
21
  "google-api-python-client>=2.100.0",
21
22
  "google-auth-oauthlib>=1.0.0",
22
23
  "google-api-python-client>=2.0.0",
23
24
  "pyyaml>=6.0",
25
+ "requests>=2.31",
24
26
  ]
25
27
 
26
28
  [[project.authors]]
@@ -1,23 +1,26 @@
1
1
  [project]
2
2
  name = "adloop"
3
- version = "0.13.3"
4
- description = "The AI command center for Google Ads, GA4, and tracking code."
3
+ version = "0.15.0"
4
+ description = "The AI command center for Google Ads, Reddit Ads, GA4, and tracking code."
5
5
  readme = "README.md"
6
6
  authors = [
7
7
  { name = "Daniel Klose", email = "info@daniel-klose.com" }
8
8
  ]
9
9
  requires-python = ">=3.11"
10
10
  license = { text = "MIT" }
11
- keywords = ["mcp", "google-ads", "google-analytics", "ga4", "cursor", "marketing"]
11
+ keywords = ["mcp", "google-ads", "reddit-ads", "google-analytics", "ga4", "cursor", "marketing"]
12
12
  dependencies = [
13
13
  "fastmcp>=3.0.0",
14
- "google-ads>=31.1.0",
14
+ "google-ads>=32.0.0",
15
15
  "google-analytics-data>=0.20.0",
16
16
  "google-analytics-admin>=0.27.0",
17
17
  "google-api-python-client>=2.100.0",
18
18
  "google-auth-oauthlib>=1.0.0",
19
19
  "google-api-python-client>=2.0.0",
20
20
  "pyyaml>=6.0",
21
+ # Reddit Ads has no SDK; the REST client (adloop.reddit.client) and the
22
+ # PageSpeed helper call requests directly.
23
+ "requests>=2.31",
21
24
  ]
22
25
 
23
26
  [project.urls]
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
16
16
  # Pin the API version so library upgrades don't silently break field names,
17
17
  # enum values, or mutate operation structures. Bump this deliberately when
18
18
  # migrating to a new API version — never let it float to the library default.
19
- GOOGLE_ADS_API_VERSION = "v24"
19
+ GOOGLE_ADS_API_VERSION = "v25"
20
20
 
21
21
 
22
22
  def get_ads_client(config: AdLoopConfig) -> GoogleAdsClient:
@@ -28,11 +28,16 @@ def get_ads_client(config: AdLoopConfig) -> GoogleAdsClient:
28
28
  credentials = get_ads_credentials(config)
29
29
 
30
30
  client_config = {
31
- "developer_token": config.ads.developer_token,
32
31
  "use_proto_plus": True,
33
32
  "version": GOOGLE_ADS_API_VERSION,
34
33
  }
35
34
 
35
+ # Access levels live on the Google Cloud project that owns the OAuth
36
+ # client since 2026-09-09; the developer token header is optional and
37
+ # ignored by the API. Send it only when someone still has one configured.
38
+ if config.ads.developer_token:
39
+ client_config["developer_token"] = config.ads.developer_token
40
+
36
41
  if config.ads.login_customer_id:
37
42
  client_config["login_customer_id"] = config.ads.login_customer_id.replace("-", "")
38
43