Back to Blog
automationai-agentstutorial

How to Automate Ad Management with AI

A step-by-step guide to automating ad management with AI agents. Covers setup, common automation patterns, safety guardrails, and scaling from monitoring to full management.

Xylo Team|February 21, 2026|8 min read

The Case for AI Ad Automation

Manual ad management does not scale. An account with 20 campaigns, each with 5 ad sets and 3 ads, has 300 individual elements to monitor. Checking performance, adjusting budgets, pausing underperformers, testing new creative, and generating reports -- these tasks consume hours every day and follow patterns that AI can learn and execute.

This guide walks through a practical, incremental approach to automating ad management with AI. You will start with read-only monitoring and gradually add automated actions as confidence builds.

Step 1: Set Up the Infrastructure

Before automating anything, you need programmatic access to your ad accounts and a way for AI to interact with them.

Option A: REST API Automation

Best for scheduled scripts, data pipelines, and deterministic workflows.

// .env
XYLO_API_KEY=xylo_live_abc123
AD_ACCOUNT=act_123456789

// automation.ts
const XYLO_API = "https://api.xyloapi.dev/v1";

async function fetchCampaigns(datePreset: string) {
  const response = await fetch(
    `${XYLO_API}/campaigns?date_preset=${datePreset}&status=active`,
    {
      headers: {
        "x-api-key": process.env.XYLO_API_KEY!,
        "x-ad-account": process.env.AD_ACCOUNT!,
      },
    }
  );
  const { data } = await response.json();
  return data;
}

Option B: MCP Agent Automation

Best for conversational management, dynamic decision-making, and multi-step reasoning.

{
  "mcpServers": {
    "xylo-ads": {
      "command": "npx",
      "args": ["-y", "@xylo/mcp-server"],
      "env": {
        "XYLO_API_KEY": "xylo_live_abc123",
        "XYLO_AD_ACCOUNT": "act_123456789"
      }
    }
  }
}

You can use both simultaneously. REST for scheduled jobs, MCP for interactive sessions.

Step 2: Start with Monitoring (Read-Only)

The safest entry point is automated monitoring that reads data and sends alerts, without making changes.

Daily Performance Summary

async function dailySummary() {
  const campaigns = await fetchCampaigns("yesterday");

  const totalSpend = campaigns.reduce((s: number, c: any) => s + c.insights.spend, 0);
  const totalConversions = campaigns.reduce((s: number, c: any) => s + c.insights.conversions, 0);
  const blendedCPA = totalConversions > 0 ? totalSpend / totalConversions : 0;

  const summary = {
    date: new Date().toISOString().split("T")[0],
    totalSpend: totalSpend.toFixed(2),
    totalConversions,
    blendedCPA: blendedCPA.toFixed(2),
    activeCampaigns: campaigns.length,
    topPerformer: campaigns
      .filter((c: any) => c.insights.conversions > 0)
      .sort((a: any, b: any) => a.insights.cost_per_conversion - b.insights.cost_per_conversion)[0]?.name || "N/A",
  };

  // Send to Slack, email, or your notification system
  await sendNotification(summary);
}

Anomaly Alerts

Flag when metrics deviate significantly from baseline:

async function checkAnomalies() {
  const yesterday = await fetchCampaigns("yesterday");
  const baseline = await fetchCampaigns("last_7d");

  for (const campaign of yesterday) {
    const baselineCampaign = baseline.find((c: any) => c.id === campaign.id);
    if (!baselineCampaign) continue;

    const avgDailySpend = baselineCampaign.insights.spend / 7;
    const avgDailyConversions = baselineCampaign.insights.conversions / 7;

    // Spend spike: 2x normal
    if (campaign.insights.spend > avgDailySpend * 2 && avgDailySpend > 10) {
      await sendAlert({
        level: "warning",
        campaign: campaign.name,
        message: `Spend was $${campaign.insights.spend.toFixed(2)} yesterday vs $${avgDailySpend.toFixed(2)} daily average`,
      });
    }

    // Conversion drop: less than 50% of normal
    if (
      avgDailyConversions > 2 &&
      campaign.insights.conversions < avgDailyConversions * 0.5
    ) {
      await sendAlert({
        level: "warning",
        campaign: campaign.name,
        message: `Only ${campaign.insights.conversions} conversions vs ${avgDailyConversions.toFixed(1)} daily average`,
      });
    }
  }
}

Run these daily. After a few weeks of monitoring, you will understand your normal performance patterns and trust the data quality.

Step 3: Add Low-Risk Automations

Once monitoring is reliable, introduce automations that reduce waste without aggressive changes.

Pause Wasters

The lowest-risk automation: pause campaigns that have spent significant budget with zero conversions.

const WASTE_THRESHOLD = 75; // dollars
const LOOKBACK = "last_3d";

async function pauseWasters() {
  const campaigns = await fetchCampaigns(LOOKBACK);
  const paused: string[] = [];

  for (const campaign of campaigns) {
    if (campaign.insights.spend > WASTE_THRESHOLD && campaign.insights.conversions === 0) {
      await fetch(`${XYLO_API}/campaigns/${campaign.id}`, {
        method: "PATCH",
        headers: {
          "x-api-key": process.env.XYLO_API_KEY!,
          "x-ad-account": process.env.AD_ACCOUNT!,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ status: "paused" }),
      });
      paused.push(`${campaign.name} ($${campaign.insights.spend.toFixed(2)} spent, 0 conversions)`);
    }
  }

  if (paused.length > 0) {
    await sendNotification({
      action: "Paused wasters",
      campaigns: paused,
    });
  }
}

Creative Rotation

Pause ads that are significantly underperforming other ads in the same ad set:

async function rotateCreatives() {
  const ads = await fetch(`${XYLO_API}/ads?date_preset=last_7d&status=active`, {
    headers: {
      "x-api-key": process.env.XYLO_API_KEY!,
      "x-ad-account": process.env.AD_ACCOUNT!,
    },
  }).then((r) => r.json());

  // Group by ad set
  const byAdSet: Record<string, any[]> = {};
  for (const ad of ads.data) {
    const key = ad.ad_set_id;
    if (!byAdSet[key]) byAdSet[key] = [];
    byAdSet[key].push(ad);
  }

  for (const [setId, setAds] of Object.entries(byAdSet)) {
    const eligible = setAds.filter((a: any) => a.insights.impressions >= 1000);
    if (eligible.length < 2) continue;

    const avgCTR = eligible.reduce((s: number, a: any) => s + a.insights.ctr, 0) / eligible.length;

    for (const ad of eligible) {
      // Pause ads with CTR less than 50% of the ad set average
      if (ad.insights.ctr < avgCTR * 0.5 && ad.insights.spend > 25) {
        await fetch(`${XYLO_API}/ads/${ad.id}`, {
          method: "PATCH",
          headers: {
            "x-api-key": process.env.XYLO_API_KEY!,
            "x-ad-account": process.env.AD_ACCOUNT!,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ status: "paused" }),
        });
        console.log(`Paused ad "${ad.name}" (CTR: ${ad.insights.ctr}% vs avg ${avgCTR.toFixed(2)}%)`);
      }
    }
  }
}

Step 4: Budget Optimization

After low-risk automations are running smoothly (2-4 weeks), add budget adjustments.

Proportional Reallocation

Shift budget from underperformers to top performers:

const TOTAL_BUDGET = 500;
const MAX_CHANGE_PCT = 0.25; // 25% max change per run
const MIN_BUDGET = 15;

async function reallocateBudgets() {
  const campaigns = await fetchCampaigns("last_7d");

  const active = campaigns.filter(
    (c: any) => c.status === "active" && c.insights.spend > 0 && c.insights.conversions > 0
  );

  if (active.length === 0) return;

  // Score by conversion efficiency
  const totalEfficiency = active.reduce(
    (s: number, c: any) => s + c.insights.conversions / c.insights.spend, 0
  );

  for (const campaign of active) {
    const efficiency = campaign.insights.conversions / campaign.insights.spend;
    const idealBudget = (efficiency / totalEfficiency) * TOTAL_BUDGET;

    // Limit change rate
    const maxIncrease = campaign.daily_budget * (1 + MAX_CHANGE_PCT);
    const maxDecrease = campaign.daily_budget * (1 - MAX_CHANGE_PCT);
    const newBudget = Math.max(
      MIN_BUDGET,
      Math.min(maxIncrease, Math.max(maxDecrease, idealBudget))
    );

    const rounded = Math.round(newBudget * 100) / 100;

    if (Math.abs(rounded - campaign.daily_budget) > 1) {
      await fetch(`${XYLO_API}/campaigns/${campaign.id}`, {
        method: "PATCH",
        headers: {
          "x-api-key": process.env.XYLO_API_KEY!,
          "x-ad-account": process.env.AD_ACCOUNT!,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ daily_budget: rounded }),
      });
      console.log(`${campaign.name}: $${campaign.daily_budget} -> $${rounded}`);
    }
  }
}

Step 5: Implement Safety Guardrails

Automated systems need constraints. Here is a production-ready guardrail framework:

interface Guardrails {
  enabled: boolean;
  maxDailyBudgetChange: number;    // max % change per day
  maxTotalDailySpend: number;      // hard cap on total spend
  minDataDays: number;             // minimum days of data before acting
  minConversions: number;          // minimum conversions before scaling
  requireApproval: boolean;        // human approval for large changes
  approvalThreshold: number;       // dollar amount triggering approval
}

const guardrails: Guardrails = {
  enabled: process.env.AUTOMATION_ENABLED === "true",
  maxDailyBudgetChange: 0.25,
  maxTotalDailySpend: 1000,
  minDataDays: 3,
  minConversions: 5,
  requireApproval: true,
  approvalThreshold: 100,
};

function validateBudgetChange(
  current: number,
  proposed: number,
  guardrails: Guardrails
): { approved: boolean; reason?: string; adjusted?: number } {
  if (!guardrails.enabled) {
    return { approved: false, reason: "Automation disabled" };
  }

  const changePct = Math.abs(proposed - current) / current;

  if (changePct > guardrails.maxDailyBudgetChange) {
    const maxChange = current * guardrails.maxDailyBudgetChange;
    const adjusted = proposed > current
      ? current + maxChange
      : current - maxChange;
    return {
      approved: true,
      reason: `Change capped at ${guardrails.maxDailyBudgetChange * 100}%`,
      adjusted,
    };
  }

  if (proposed > guardrails.approvalThreshold && guardrails.requireApproval) {
    return { approved: false, reason: `Requires approval: budget exceeds $${guardrails.approvalThreshold}` };
  }

  return { approved: true };
}

Essential Safety Measures

Guardrail Implementation Why
Kill switch Environment variable AUTOMATION_ENABLED Instantly stop all automated changes
Max change rate 25% budget change per day Prevents wild swings that disrupt ML algorithms
Minimum data 3+ days of data before acting Avoids decisions based on noise
Spend cap Hard maximum on total daily spend Prevents runaway costs from bugs
Change logging Log every change with reason Enables auditing and debugging
Notifications Alert on every automated action Keeps humans informed

Step 6: Scheduling and Orchestration

Run your automations on a schedule:

// schedule.ts -- run with cron or a scheduled function

async function runAutomation() {
  console.log(`Running automation at ${new Date().toISOString()}`);

  try {
    // Step 1: Monitor
    await dailySummary();
    await checkAnomalies();

    // Step 2: Low-risk actions
    await pauseWasters();
    await rotateCreatives();

    // Step 3: Budget optimization (only on weekdays)
    const day = new Date().getDay();
    if (day >= 1 && day <= 5) {
      await reallocateBudgets();
    }

    console.log("Automation complete");
  } catch (error) {
    console.error("Automation failed:", error);
    await sendAlert({ level: "critical", message: `Automation failed: ${error}` });
  }
}

Recommended schedule:

Automation Frequency Best Time
Daily summary Daily 8:00 AM
Anomaly detection Daily 7:00 AM
Pause wasters Daily 9:00 AM
Creative rotation Every 3 days 10:00 AM
Budget reallocation Weekdays 9:30 AM
Weekly report Monday 8:00 AM

The Progression Path

Most teams follow this progression:

  1. Week 1-2: Monitoring only. Understand baseline performance.
  2. Week 3-4: Add pause-wasters automation. Low risk, high impact.
  3. Month 2: Add creative rotation. Moderate impact.
  4. Month 3: Add budget reallocation with conservative guardrails.
  5. Month 4+: Expand to audience management, cross-platform optimization, and AI-driven decisions.

The key is building confidence incrementally. Each automation layer proves itself before you add the next.

Getting Started

  1. Sign up for Xylo and connect your ad accounts.
  2. Generate an API key from the dashboard.
  3. Start with the daily summary script (Step 2).
  4. Add automations one at a time, following the progression path.

For AI agent integration via MCP, see building AI agents for ads. For specific optimization workflows, read 5 AI workflows for ad optimization. Check the API documentation for endpoint details.

Ready to simplify your ads API integration?

Get started with Xylo in minutes. One API key for every ad platform.