Automated ad management: a technical guide to the approval layer
Every automated ad management tool eventually faces the same question: who decides when a change goes live? The answer is either a rule, an AI, or a human — and only one of those has business context, client relationships, and consequence awareness.
The cost of skip-approval automation
An automated system that pushes changes directly to live has a very short feedback loop for getting things right and a very expensive one for getting things wrong. A rule that accidentally doubles a $20K/day budget runs for however long it takes you to notice. That is usually not minutes.
The ad industry has a long history of runaway scripts. Google Ads Scripts, if misconfigured, can exhaust a monthly budget in an afternoon. The problem is not automation — it is automation without a human checkpoint.
How the draft layer works
SpendSignoff stages every AI-proposed change as a Draft object before any API call goes to Google or Meta. A draft contains: the action type, the affected entity ID, the current state, the proposed state, the AI's reasoning, and a confidence signal.
You see this as a before→after diff in the approval queue. The budget line item goes from $500/day to $650/day with a one-sentence explanation of why. You approve or reject. If you approve, the FastAPI policy core calls the platform API and appends a signed log entry. If you reject, the draft is discarded and logged as rejected.
Draft object structure (simplified)
{
"draft_id": "drft_01HX...",
"action": "update_campaign_budget",
"entity_id": "campaign::1234567",
"before": { "daily_budget_usd": 500 },
"after": { "daily_budget_usd": 650 },
"reasoning": "Campaign pacing at 89% of budget over 7 days. Headroom available.",
"status": "pending_approval"
}Server-side enforcement, not client-side trust
The draft layer is enforced in the policy core, not in the AI client. The model has mcp.read and mcp.draft scopes — those are the only tools it can call. There is no mcp.approve tool in the model's menu. A model that tried to approve its own draft would receive a 403 with the error SCOPE_INSUFFICIENT.
This means the safety contract holds regardless of which AI client you use. Claude, ChatGPT, Cursor — none of them have approve authority, because the server never issued that scope.
The model cannot approve its own drafts
mcp.approve authority. No model token carries that scope. The policy core validates scope on every request before any platform API call is made.Audit log and rollback
Every approved change writes a KMS-signed log entry to the append-only audit table. The entry includes the draft ID, the approver's user ID, the timestamp, the before/after states, and the platform API response code.
If a change produces unexpected results, you have the exact state you can revert to — and the log entry to show what was approved, by whom, and when. For enterprise accounts, this log exports to Datadog or your SIEM on demand.
FAQ
- What happens if I approve a draft and the platform API call fails?
- The draft is marked failed with the API error attached. The log records the failure. The original state is unchanged. You can retry or discard the draft.
- Can I set an auto-approve rule for small changes?
- V1 is propose-only — every draft requires manual approval. Auto-approve thresholds (e.g. budget changes under 10%) are on the V2 roadmap after the audit trail pattern is established.
Connect an account read-only and watch the operator work.
Reads are free on every plan. Nothing spends without your two-step approval.
Related reading