Skip to main content
AdLoop’s safety model exists because AI touching ad budgets is a reasonable thing to be nervous about. It is enforced server-side: even a client that ignores every instruction cannot skip it.

Preview before everything

Every write tool (draft_campaign, add_negative_keywords, pause_entity, …) returns a preview with a plan_id instead of changing anything. Your AI shows you the preview; nothing happens until you approve it: with a click on the approval card in apps that show one, otherwise through confirm_and_apply with that plan ID.

Approval cards: only your click applies

In AI apps that support MCP Apps (Claude, ChatGPT, VS Code and others), the preview appears as an AdLoop approval card in the chat: what changes, any warnings, and Test run, Apply and Discard buttons. When you pause, enable or remove something in Google Ads, the card names it and the account next to the IDs, for example campaign “Brand DE” (1234567890) · Example GmbH (123-456-7890), so a wrong account stands out before you click. An ID that matches nothing in the account is refused before a card appears. Test run is the dry run below; Apply is enabled once it has passed. In those apps, only the click applies a change:
  • The buttons call a tool that the app keeps out of the AI’s tool list, and AdLoop offers it only to apps that show cards.
  • Every click carries a one-time code for that one preview. The code goes to the card, not into the AI’s context, and the AI cannot compute it.
  • If the AI calls confirm_and_apply with dry_run=false itself, AdLoop refuses (APPROVE_ON_CARD). A typed “yes, apply it” in the chat is not enough, and neither is “Always allow” in the app’s permission settings.
  • Changes that cannot be undone also need the “cannot be undone” box ticked on the card. The server checks it.

Confirmation dialogs in apps without cards

Apps that don’t show cards but support confirmation dialogs (MCP elicitation) get a dialog instead: when the AI calls confirm_and_apply with dry_run=false, AdLoop itself asks you in the app’s dialog what will change and whether to apply it. Changes that cannot be undone need an extra “cannot be undone” tick. The AI cannot answer the dialog for you; if you decline or close it, nothing changes and the preview stays pending. There is no dialog for dry runs, or for a plan that still needs its required dry run first. On AdLoop Cloud the dialog needs an app on the current MCP protocol (2026-07-28); apps on older protocol versions keep the chat flow there. Self-hosted, both work. Apps with neither cards nor dialogs keep the chat flow: the AI shows the preview and calls confirm_and_apply after your explicit confirmation. If the card or the dialog doesn’t work in your app, the workspace owner can allow approval in chat as well, which skips both (Cloud: Settings → Safety → Also allow approval in chat; self-hosted: safety.allow_chat_approval: true). Combined with “Always allow” for confirm_and_apply in your AI app, the AI could then apply changes without any click, so leave it off where the card works.

Dry-run by default

confirm_and_apply defaults to dry_run=true, which checks the change without applying it:
  • Google Ads: the exact change is sent to Google with validate_only. Google checks it the way it would check the real thing (field values, policy, whether the campaign or ad group exists) and executes nothing. If a change builds on something an earlier step would create, such as keywords for a new negative keyword list, the later step can’t be checked yet and is reported as skipped.
  • Reddit Ads: Reddit has no validate-only mode, so the dry run re-reads the target and re-checks the safety caps; nothing is sent.
A failed dry run (DRY_RUN_FAILED) carries the platform’s error and means the real apply would fail the same way. Applying for real requires an explicit dry_run=false, and two settings can restrict it further:
  • require_dry_run: all real applies are refused until you disable this (self-hosted: config.yaml; Cloud: dashboard safety settings)
  • Two-phase apply: a plan must pass a dry run before it can be applied for real (always on for Cloud tenants). A failed dry run doesn’t count, so a change the platform rejects can’t be applied.

Created paused

New campaigns and ads are always created PAUSED, on Google Ads and Reddit Ads alike, and so are new Reddit ad groups. A new Google Ads ad group is created enabled, but it can’t serve until one of its ads is enabled. You review everything in the platform’s UI (or by chat) and enable it explicitly, via enable_entity / enable_reddit_entity, which itself goes through preview and confirm.

Budget caps

max_daily_budget caps every budget AdLoop can set. Drafts above the cap are rejected at draft time, before anything reaches the ad platform. Cloud additionally enforces daily operation limits per workspace.

Bid caps

max_bid_increase_pct (default 100%) limits how far a single change can raise a bid: a Google Ads ad group’s manual CPC, a Maximize Clicks CPC ceiling, and Reddit ad group bids. Raising €2.00 to €5.00 is refused at draft time at the default. A first-time ceiling has nothing to compare against and isn’t capped.

Guardrails against classic money-wasters

  • Broad Match + Manual CPC is refused. Broad match keywords without smart bidding are the most common cause of wasted spend. Creating a campaign with that combination is refused; adding broad keywords to an existing manual-CPC ad group is flagged in the preview.
  • Campaigns require geo and language targeting. Untargeted campaigns serve globally and waste budget; drafts without targeting are rejected.
  • URLs are validated. Ads and sitelinks pointing at unreachable pages are rejected at draft time.

Read-only keys

A read-only API key (Cloud) refuses every write tool at the server. It’s the only way to get “analyze but never change”: Google’s OAuth scopes have no read-only mode for Ads. Ideal for reporting access and cautious starts.

Audit log

Every write operation, including dry runs, is logged: self-hosted to ~/.adloop/audit.log, Cloud to the change log (Protokoll) in your dashboard, attributable per client for agencies.

Destructive operations

remove_entity is irreversible and triggers double confirmation. Prefer pause_entity unless you explicitly want permanent removal.