Product Feature · 11 min read

AI Ad Operations with Maxxer: What's Automated and Where You Stay in Charge

Maxxer is Admaxxer's Claude-powered AI agent. It can read every metric in your account (revenue, MER, CAPI match rate, cohort LTV, MMM contribution, forecast) and can pause + scale Meta and Google campaigns — but only with your explicit confirmation. This playbook explains exactly what Maxxer can do, what it requires confirmation for, and the safety rails that prevent an AI agent from ever spending your money without permission.

4
Read tools
list, insights, account, query
2
Action tools
Both confirmation-gated
Real-time
Confirmation latency
Inline chat approval

A Claude-powered AI agent embedded in your ad operations surface

Maxxer is Admaxxer's in-product AI agent. It sits inside the workspace, reads from the same Tinybird and ClickHouse pipes the dashboard renders from, and dispatches Meta, Google, Amazon, and Pinterest API calls through the same service layer the point-and-click UI uses. There is no separate AI console, no model gateway you have to set up - when you connect an ad platform, Maxxer can already see it and operate on it.

The model is claude-sonnet-4-6. Sonnet was picked for production chat because the failure mode of an ad-ops agent is not "the model made a brittle inference" - most of the load is multi-turn tool use and spreadsheet-style data summarization, both of which Sonnet does faster and cheaper than Opus. Opus stays available for offline batch jobs like the weekly performance review email, where the extra reasoning depth is worth the latency tax.

Maxxer streams via Server-Sent Events to the /chat surface. You can see partial tokens land in real time, which matters because tool calls are often the longest leg of a turn - without streaming, a single "show me last week" could stall for five seconds. Every chat message is persisted to chat_sessions and chat_messages with the full Anthropic content-block array (text, tool_use, tool_result), so the transcript is replayable and auditable.

Four read-only tools, called freely

These four tools are non-destructive - they fetch data and return it. Maxxer invokes them automatically to ground every answer in current numbers. They cost nothing on the ad platform side (read calls only) and change nothing in your account.

Tool nameWhat it does
list_campaignsReturns id, name, status, objective, and daily_budget for every campaign on a connected ad account. Used as the discovery step before any other tool.
get_campaign_insightsFetches spend, impressions, clicks, CTR, CPA, ROAS, and conversions for a single campaign over an arbitrary date range. Defaults to the trailing 7 days.
get_account_insightsAccount-level performance across every campaign, optionally broken down by age, gender, country, device, or any platform-supported dimension.
query_metricsThe bridge between the ad platforms and Admaxxer's first-party analytics. Lets Maxxer read revenue, MER, cohort LTV, CAPI match rate, MMM contribution, attribution breakdown, and incrementality without leaving chat.

These tools are read-only by design. Maxxer can call them every turn without asking - there is no rate-limit risk to the ad platforms (Meta's documented limit is ~200 calls/hour/user-token, and Maxxer's tool-call cadence is well below that), no risk of an accidental write, and no audit-log noise from approval prompts you would have to dismiss.

Two tools that can change your ad accounts — both confirmation-gated

Pausing campaigns and changing daily budgets are the only destructive surfaces Maxxer is wired into. Every other write - ad-set edits, audience changes, creative uploads, payment method, account-level budget caps - is intentionally out of scope. The small surface is the point: the blast radius of an LLM mistake is bounded by what we let the LLM call.

Tool nameBehaviour
update_campaignUpdate a single campaign's name, status (PAUSED / ACTIVE), or daily_budget. The narrow surface keeps the blast radius small - Maxxer cannot edit ad sets, creatives, audiences, or any account-level setting. First call returns a {confirmRequired: true, summary, args, nonce} envelope; no API side effect. Only after the UI POSTs the nonce to /api/agent/confirm does executeTool dispatch the platform call.
pause_all_low_roasSweep tool: pauses every campaign whose ROAS is below a threshold AND whose spend is at least min_spend. Pre-counts how many campaigns will be paused before asking for confirmation; if the count exceeds LARGE_SWEEP_THRESHOLD, requires a second 'large sweep' confirmation.

Why create_campaign was removed in R1

The first iteration of Maxxer shipped with a create_campaign tool. Customers tested it for two weeks. The usability review pulled it before the R1 release for three reasons.

  1. Account targeting is fragile. A chat-driven flow that creates a campaign on the wrong ad account (e.g. the client's test account vs production) is a much louder failure than a chat-driven flow that pauses the wrong campaign. The blast radius is asymmetric.
  2. Creative payload is fiddly. Meta and Google both want specific creative slots, link previews, attribution windows, and audience definitions. Typing all of that into chat is slower than the native campaign builder; the AI lever isn't the bottleneck for campaign launch.
  3. Maxxer can still help. You can ask Maxxer to draft a campaign brief - objective, target CPA, daily budget, ad-set structure, suggested creative angles - and copy it to your clipboard. You launch in Meta Ads Manager or Google Ads, where the platform's own UI catches misconfiguration the AI loop never would.

The decision sits inside the Admaxxer engineering rule "ad account safety > speed." Removing a tool is uncomfortable; pausing the wrong campaign by accident is worse.

Three layers between an AI suggestion and your ad spend

The hard architectural claim: an AI agent should never be able to move money without an out-of-band human confirmation. Maxxer enforces this in three independent layers - failure of any one layer still keeps the other two intact.

  • Layer 1 — Every recommendation cites the data it's based on. Maxxer's system prompt mandates 'never invent campaign IDs, account IDs, numbers, or dates - if you don't have the data, call a tool.' Every actionable claim it makes is preceded by a tool result you can re-read in the chat transcript. There is no 'trust me' tier; the data is always shown.
  • Layer 2 — Destructive actions require an out-of-band confirmation nonce. The server issues a one-time nonce scoped to (sessionId, tool, args_hash) when a destructive tool is first attempted. The UI must POST that nonce to /api/agent/confirm to mark it approved. The LLM cannot mint or replay the nonce - a prompt-injected 'yes' in user content does not bind the user's intent to the LLM's tool-call arguments.
  • Layer 3 — Every Maxxer-initiated action is written to an audit log. ad_sync_logs records the chat session ID, the tool call payload, the confirmation timestamp, and the platform's response. If a campaign was paused incorrectly, the audit trail tells you whether you or Maxxer initiated the pause, which arguments were used, and exactly when. This is admin-readable for the AD_PLATFORM tier.
// First call — Maxxer attempts pause_all_low_roas without a confirmation
// nonce. The server short-circuits and returns this envelope. No platform
// API was called. The UI renders this as a "Confirm action" surface.

{
  "confirmRequired": true,
  "tool": "pause_all_low_roas",
  "action": "Pause all low-ROAS campaigns",
  "args": {
    "platform": "meta",
    "connection_id": "conn_abc",
    "roas_threshold": 1.0,
    "min_spend": 50
  },
  "summary": "Pause every meta campaign with ROAS < 1 AND spend >= 50. This is a sweep — multiple campaigns may be paused.",
  "nonce": "01HXY3K9P2QZTAB8V5..."
}

// Second call — only after the UI POSTs the nonce to /api/agent/confirm,
// Maxxer re-invokes with the SAME args + confirmed: true. The server's
// consumeApprovedConfirm(sessionId, tool, args_hash) finds the approved
// nonce, deletes it (single-use), and dispatches the Meta API call.

Prompt caching on the system block and tools array

The Anthropic Messages API supports cache_control: ephemeral on the system block and tools array. Once a turn has been served, subsequent turns within the cache window pay the cache-hit rate on the cached prefix - roughly 10% of the full input-token cost - leaving only the new user message and new assistant turn at full rate.

The system prompt (~2.4k tokens) and the tools array (~1.8k tokens) together account for the bulk of every turn's input. Caching them keeps a typical chat session's cost under 2x the cost of a single short prompt, even with five or ten tool-call iterations.

// server/ads/ClaudeAgentService.ts — cache_control on the system block
// + tools array means turns N+1, N+2, ... pay the cache-hit rate (90% off)
// for the prefix, not the full input-token rate.

const response = await anthropic.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 4096,
  system: [
    {
      type: "text",
      text: SYSTEM_PROMPT,        // ~2.4k tokens — cached
      cache_control: { type: "ephemeral" },
    },
  ],
  tools: TOOLS,                   // ~1.8k tokens — cached alongside system
  messages,                       // varies per turn — NOT cached
  stream: true,                   // streams via SSE to /chat
});

// Turn 1 input cost: full rate on SYSTEM_PROMPT + TOOLS + first user message.
// Turn 2 input cost: cache-hit rate on SYSTEM_PROMPT + TOOLS, full rate
//                    only on the new user message + prior assistant turn.
//
// IMPORTANT: append-only TOOLS array (every new tool registers AT THE END).
// Reordering or inserting earlier busts the cache for every workspace.

The tools array is hashed alongside the cached system block. Reordering tools or inserting a new tool at any position other than the end of the array invalidates the cache for every workspace on the next turn. The codebase enforces an append-only rule on the TOOLS array; the comment block above it is the canonical reminder.

Three real Maxxer transcripts

The shape and voice of these transcripts matches what you'll see in the live /chat surface. Sonnet's tone is terse and factual; Maxxer's system prompt enforces the 'lead with the number, then the why' convention.

Read-only insight. User asks for the underperforming Meta campaigns this week. Maxxer calls list_campaigns + get_account_insights, ranks the bottom 5 by ROAS, and proactively flags the campaign that's 71% above its trailing CPA average. No confirmation needed - all read-only.

you  Which Meta campaigns are underperforming this week?

maxxer  Calling list_campaigns(platform="meta", connection_id=...,
        status_filter="ACTIVE") and get_account_insights for the last 7 days...

        Bottom 5 campaigns by ROAS (spend >= $50, last 7 days):

        | Campaign                        | Spend     | CPA     | ROAS  |
        |---------------------------------|-----------|---------|-------|
        | Retargeting · Cart Abandon      | $1,840.22 | $87.16  | 0.62  |
        | Prospecting · Lookalike 5%      | $2,103.55 | $112.41 | 0.74  |
        | Brand Search · Generic          |   $612.08 |  $58.94 | 0.81  |
        | Static Image · Winter Holiday   |   $904.66 |  $71.20 | 0.88  |
        | DPA · All Catalog               | $1,257.30 |  $46.85 | 0.94  |

        Heads up: Retargeting · Cart Abandon is 71% above its trailing 7-day
        CPA average. Want me to pull a campaign-level breakdown or pause it?

Destructive action with confirmation. User asks to pause the bottom 3. Maxxer stages 3 update_campaign calls, surfaces the daily spend that will stop, and waits. The destructive tools return a confirmRequired envelope on first call; only after the UI POSTs the nonce do the actual Meta API calls fire.

you  Pause the bottom 3 ROAS campaigns from that list.

maxxer  I'll stage that as 3 update_campaign calls. Here's what I'm proposing:

        | Campaign                     | Current Status | Proposed | ROAS  |
        |------------------------------|----------------|----------|-------|
        | Retargeting · Cart Abandon   | ACTIVE         | PAUSED   | 0.62  |
        | Prospecting · Lookalike 5%   | ACTIVE         | PAUSED   | 0.74  |
        | Brand Search · Generic       | ACTIVE         | PAUSED   | 0.81  |

        Combined daily spend that will stop: $651.41/day.
        Click "Confirm 3 actions" to proceed, or "Cancel" to keep them running.

        [Server returns: {confirmRequired: true, nonce: "...", args: {...}}]

you  *clicks Confirm 3 actions* -> UI POSTs nonce to /api/agent/confirm

maxxer  Calling update_campaign(...) with confirmed: true for each of the 3
        campaigns... done. Logged 3 entries to ad_sync_logs at 14:22:08 UTC.
        Status now reads PAUSED in Meta Ads Manager.

Combined: insight + reallocation. User asks for a $5k/week budget reallocation. Maxxer queries insights, cross-references blended MER from query_metrics, drafts a 6-action reallocation table, and asks for confirmation. The reallocation only applies after the explicit confirm - Maxxer never moves spend without it.

you  Help me reallocate $5,000/wk from low-ROAS campaigns into the
      top-converting ones. Same total spend, just shifted.

maxxer  Sequence:

        1. Calling get_account_insights for the last 30 days, grouped by
           campaign... 23 active campaigns, $42,180 spent.
        2. Calling query_metrics for blended MER and per-campaign CAPI match
           rate... blended MER is 3.2x, CAPI match rate 76%.

        Proposed reallocation (only your confirmation can apply this):

        | Action                  | Campaign                  | $/day change |
        |-------------------------|---------------------------|--------------|
        | Reduce by $200/day      | Retargeting · Cart Abandon | -$1,400/wk   |
        | Reduce by $150/day      | Static Image · Winter      | -$1,050/wk   |
        | Reduce by $230/day      | Prospecting · Lookalike 5% | -$1,610/wk   |
        | Increase by $200/day    | DPA · Top 20 SKUs          | +$1,400/wk   |
        | Increase by $150/day    | Video · UGC Hooks          | +$1,050/wk   |
        | Increase by $230/day    | Brand Search · Branded     | +$1,610/wk   |

        Net change: $0/wk. Expected MER lift based on current 30-day per-
        campaign ROAS deltas: 3.2x -> 3.6x. The numbers driving this are
        in the previous tool result; re-read if you want to verify.

        Click "Confirm 6 actions" to apply, or edit the list inline.

BYOK Anthropic — bring your own key, or use ours

On the lower tiers (Starter, Growth, Pro, Scale, Enterprise), Maxxer runs on Admaxxer's Anthropic credentials - token usage is included in your plan and you never see an API bill from Anthropic. On the AD_PLATFORM tier, the BYOK pathway lets you paste your own Anthropic API key; Maxxer then dispatches every turn against your account, so the usage shows up on your Anthropic invoice and you can apply your own model-tier discounts or volume agreements.

The BYOK pathway is also the right call for any agency or multi-brand operator who wants per-client billing separation. Each connected workspace can carry its own Anthropic key (and optionally an OpenAI / Gemini / xAI / DeepSeek key - Maxxer supports the OpenAI-compat family via the same provider registry, though Claude Sonnet remains the default).

Model selection is intentional and conservative. The production chat surface uses claude-sonnet-4-6 because it's fast, cheap, and capable enough for every Maxxer task; Opus is reserved for offline batch jobs where latency is not user-visible (the weekly performance review email, the monthly forecast refresh). The model identifier is a single constant in the codebase - when Anthropic ships the next Sonnet release and we've burn-tested it on representative traffic, the swap is one line.

What Maxxer cannot do — and won't in v1

The deliberate non-goals matter as much as the goals. Maxxer is scoped to the operations a daily ad ops manager performs in chat, not the configuration changes a head of growth makes once a quarter.

  • No campaign creation. create_campaign is intentionally absent. Maxxer can draft a brief; you launch in the native UI.
  • No account-level changes. Maxxer cannot edit the ad account's payment method, daily budget cap, billing currency, or business manager permissions. Those changes live in the platform's own settings, where a 2-factor prompt or admin review is the canonical guard.
  • No creative deletion or modification. Maxxer cannot remove an ad, swap a creative, change a landing URL, or edit an ad-set audience. The campaign-level pause/budget surface is the only write.
  • No cross-workspace access. A Maxxer session is scoped to one workspace. The connection check in loadConnection rejects any tool call whose connection_id doesn't belong to the chat session's workspace.
  • No autonomous mode. Maxxer does not run on a schedule and does not act without a user-initiated chat turn. The weekly review email Opus generates is a one-way summary - it includes no action-taking surface.

Every Maxxer action lands in ad_sync_logs

Every successful tool call writes a row to ad_sync_logs with the chat session ID, the tool name, the tool arguments (as JSON), the platform API response, and the timestamp. Errors land in the same table with status = error and the error message in errorMessage.

This is the artefact you reach for when a campaign was paused and you're trying to figure out whether someone on the team paused it manually in Meta Ads Manager or Maxxer paused it via chat. The session ID joins back to chat_sessions so you can replay the full transcript and see exactly which user confirmed the action and which prompt led to the suggestion.

On the AD_PLATFORM tier, admin users can query ad_sync_logs via the admin console; lower tiers see actions surfaced inline in the chat transcript itself (each tool result is persisted as a message block, so the chat history is its own audit trail).

The five questions Maxxer answers best

After a few weeks of usage, most teams settle on a recurring rotation of questions Maxxer is uniquely good at. Each pulls from a different tool - the breadth of the read-tool surface is what makes the rotation work.

  • 'What changed yesterday vs the trailing 7-day average?' get_account_insights with proactive flagging - Maxxer surfaces CPA spikes >25% and impression drops >40% automatically.
  • 'What's our blended MER, and how does it compare to Meta's in-platform ROAS?' query_metrics for blended MER + get_account_insights for platform-reported ROAS - Maxxer cross-checks them in one answer.
  • 'Which campaigns should I pause this week?' list_campaigns + get_campaign_insights ranked by ROAS - Maxxer stages update_campaign calls and waits for confirmation.
  • 'Pull a cohort LTV breakdown for new customers acquired last month.' query_metrics into Admaxxer's cohort pipes - Maxxer renders a markdown table and flags any acquisition source with sub-1.5x 90-day LTV.
  • 'Reallocate $5k/wk from low-ROAS to high-ROAS campaigns, keeping total spend flat.' The combined session above - insights + draft + confirmation. This is the highest-leverage use case Maxxer was designed for.

Frequently asked questions

What can Maxxer actually do without my approval?
Read-only operations only: list your campaigns, fetch insights (spend / impressions / clicks / conversions / cost-per-result), pull account-level summaries, run queries against Admaxxer's analytics pipes (revenue, MER, cohort LTV, CAPI match rate, MMM, forecast, incrementality). These four tools — list_campaigns, get_campaign_insights, get_account_insights, query_metrics — execute immediately. They cost nothing and change nothing.
What requires my confirmation?
Any destructive action: update_campaign (pause, resume, change budget) and pause_all_low_roas (a guardrail tool that pauses every campaign below a ROAS threshold). Both require the message to contain confirmed: true before Maxxer can execute. The chat UI surfaces a 'Confirm action' button when one of these tools is staged — without the click, the action never reaches Meta or Google. This is a hard safety rail: it's enforced in server/ads/ClaudeAgentService.ts, not in the prompt, so a prompt-injection attack cannot disable it.
Can Maxxer create new campaigns?
Not in v1. The create_campaign tool was removed in the R1 release after a usability review — too easy to misfire (wrong account, wrong creative, wrong budget). Maxxer can draft a campaign for you in chat (objective, budget, ad set, creative slots) and copy it to your clipboard, but you launch the actual campaign manually in Meta Ads Manager or Google Ads. This is intentional; ad account safety > speed.
Which Claude model does Maxxer use?
claude-sonnet-4-6 (with prompt caching on the system prompt + tools array, so per-turn cost stays predictable). Maxxer streams responses via SSE so the chat UI feels responsive. The model selection is intentional — Sonnet for production chat (fast, cheap, capable); Opus for offline batch jobs (e.g., the weekly performance review email). BYOK is supported for the AD_PLATFORM tier — paste your own Anthropic API key in /settings/byok and Maxxer uses your credentials.
What happens if Maxxer makes a wrong recommendation?
Three layers of containment. (1) All recommendations come with the data they're based on — you can verify the input numbers before approving. (2) All destructive actions require confirmed: true; nothing happens automatically. (3) Every Maxxer-initiated action writes to /audit-log with the chat session ID, the tool call payload, your confirmation timestamp, and the platform's response — so if a campaign was paused incorrectly, you can resume it and audit who (you or Maxxer) caused the pause. This audit trail is admin-readable for AD_PLATFORM tier customers.

Run this playbook in your own dashboard

Admaxxer ships the pixel + Meta CAPI + Google Enhanced Conversions + Maxxer AI agent + cohort analytics out of the box. The playbook above becomes a live surface in your account after a 5-minute setup.

Start a 7-day trial See pricing