| name | monitor |
| description | Check Spotify Ads API campaign health — pacing, delivery issues, budget burn rate, stalled campaigns, and underpacing alerts. Use for one-shot health checks or recurring monitoring when the host supports scheduled automations. |
| argument-hint | [campaign_id] | [--all] |
| allowed-tools | ["Read","Bash","AskUserQuestion"] |
Spotify Ads API — Campaign Health Monitor
Diagnose delivery problems across active campaigns. Goes beyond the dashboard by running diagnostic checks and recommending specific actions.
Setup
Set the plugin root and define the request wrapper:
PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-.}}"
api() { "$PLUGIN_ROOT/scripts/api-request.sh" monitor "$@"; }
Before the first Ads API v3 call, read and follow $PLUGIN_ROOT/skills/api-reference/references/live-openapi.md.
To retrieve settings values (TOKEN, AD_ACCOUNT_ID, AUTO_EXECUTE, BASE_URL) for use outside API calls, run api --env.
Parsing Arguments
- No argument or
--all → Health check all active campaigns
<campaign_id> (UUID) → Deep health check on a specific campaign (includes ad set and ad level diagnostics)
- If ambiguous, ask the user.
Account-Wide Health Check (default)
API Calls
Execute these calls to gather the data needed for diagnostics:
1. Active campaigns
api GET "ad_accounts/{ad_account_id}/campaigns?statuses=ACTIVE&limit=50&sort_direction=DESC"
2. Active ad sets
api GET "ad_accounts/{ad_account_id}/ad_sets?statuses=ACTIVE&limit=50&sort_direction=DESC"
3. Today's campaign metrics
api GET "ad_accounts/{ad_account_id}/aggregate_reports?\
entity_type=CAMPAIGN&\
fields=IMPRESSIONS&fields=SPEND&fields=REACH&fields=CLICKS&fields=CTR&fields=FREQUENCY&\
granularity=DAY&\
report_start=$(date -u +%Y-%m-%dT00:00:00Z)&\
report_end=$(date -u +%Y-%m-%dT00:00:00Z)&\
entity_status_type=CAMPAIGN&\
statuses=ACTIVE&\
limit=50"
4. Lifetime campaign metrics (for pacing)
api GET "ad_accounts/{ad_account_id}/aggregate_reports?\
entity_type=CAMPAIGN&\
fields=IMPRESSIONS&fields=SPEND&fields=REACH&fields=CLICKS&\
granularity=LIFETIME&\
entity_status_type=CAMPAIGN&\
statuses=ACTIVE&\
limit=50"
Health Checks
Run these diagnostics against the fetched data:
1. Pacing Check
For each active campaign, compare spend progress to flight progress:
-
DAILY budgets: Sum the daily budgets of all active ad sets under the campaign. Compare today's spend to the total daily budget.
- Display:
$142.50 / $200.00 daily (71%)
-
LIFETIME budgets: Compare total lifetime spend to total lifetime budget, and compare against elapsed flight percentage.
elapsed_pct = (today - start_date) / (end_date - start_date) * 100
spend_pct = total_spend / total_budget * 100
- Display:
$2,100 / $5,000 lifetime (42% spent, 35% of flight elapsed)
Alert levels:
- OK: Spend % and elapsed % within 20 points of each other
- WARNING: 20-40 point gap between spend % and elapsed %
- CRITICAL: >40 point gap, OR zero spend with >24 hours elapsed since campaign start
2. Stalled Delivery Check
- If an ACTIVE campaign has zero impressions today AND its start_time is more than 4 hours ago, flag as STALLED.
- Exclude campaigns that started within the last 4 hours (they may still be ramping up).
3. Budget Exhaustion Check
For LIFETIME budgets, project when the budget will run out at the current daily burn rate:
daily_burn = lifetime_spend / days_elapsed
days_remaining_at_pace = (budget - spend) / daily_burn
projected_end = today + days_remaining_at_pace
Flag if:
- Budget will exhaust before the scheduled
end_time → "May exhaust budget early"
- Budget will be >20% unspent at
end_time → "Likely to underspend"
Display Format
=== Campaign Health Check ===
Checked: 3 active campaigns, 7 active ad sets
Time: 2026-05-14 14:30 UTC
| Campaign | Status | Spend Today | Pacing | Issues |
|----------|--------|-------------|--------|--------|
| Summer Promo | OK | $142.50 / $200 daily (71%) | On track | — |
| Q2 Brand | WARNING | $28.00 / $100 daily (28%) | Underpacing | 1 stalled ad set |
| Podcast Launch | CRITICAL | $0.00 / $75 daily (0%) | Stalled | No delivery today |
Issues Found (2):
1. [WARNING] Q2 Brand: Spending at 28% of daily budget with 62% of day elapsed
→ Consider increasing bid or broadening targeting. Run: /spotify-ads-api:dashboard <campaign_id>
2. [CRITICAL] Podcast Launch: Zero impressions today (campaign started 2026-05-12)
→ Campaign may have delivery issues. Check ad approval status with: /spotify-ads-api:monitor <campaign_id>
If no issues are found:
All 3 active campaigns are healthy. No issues detected.
Deep Campaign Health Check (<campaign_id>)
API Calls
1. Campaign details
api GET "ad_accounts/{ad_account_id}/campaigns/$CAMPAIGN_ID"
2. All ad sets under the campaign
api GET "ad_accounts/{ad_account_id}/ad_sets?campaign_ids=$CAMPAIGN_ID&limit=50"
3. All ads under the campaign
api GET "ad_accounts/{ad_account_id}/ads?campaign_ids=$CAMPAIGN_ID&limit=50"
Extract active and paused ad set IDs from Step 2 and build repeated query parameters before fetching ad set metrics:
AD_SET_IDS_QUERY="entity_ids=<AD_SET_ID_1>&entity_ids=<AD_SET_ID_2>"
Use the ad set IDs directly because non-LIFETIME aggregate reports require entity_ids_type to match entity_type. If the campaign has more than 50 ad sets, chunk report requests in groups of 50.
4. Today's ad set metrics
api GET "ad_accounts/{ad_account_id}/aggregate_reports?\
entity_type=AD_SET&\
fields=IMPRESSIONS&fields=SPEND&fields=CLICKS&fields=REACH&fields=CTR&fields=FREQUENCY&fields=COMPLETES&\
granularity=DAY&\
report_start=$(date -u +%Y-%m-%dT00:00:00Z)&\
report_end=$(date -u +%Y-%m-%dT00:00:00Z)&\
${AD_SET_IDS_QUERY}&\
entity_ids_type=AD_SET&\
entity_status_type=AD_SET&\
limit=50"
5. Lifetime ad set metrics
api GET "ad_accounts/{ad_account_id}/aggregate_reports?\
entity_type=AD_SET&\
fields=IMPRESSIONS&fields=SPEND&fields=CLICKS&fields=REACH&fields=FREQUENCY&\
granularity=LIFETIME&\
${AD_SET_IDS_QUERY}&\
entity_ids_type=AD_SET&\
entity_status_type=AD_SET&\
include_parent_entity=true&\
limit=50"
Additional Health Checks (Deep Mode)
In addition to the pacing, stalled, and exhaustion checks from the account-wide mode, the deep check adds:
4. Ad Health Check
- Rejected ads: Ads with status
REJECTED — flag with recommendation to review and re-create.
- Pending ads: Ads with status
PENDING where created_at is more than 24 hours ago — flag as unusually slow approval.
- Delivery OFF: Ads with
delivery: OFF under an ACTIVE ad set — flag as potentially unintentional.
5. Audience Fatigue Check
- High frequency: If lifetime frequency > 3.0 on any ad set, warn about potential audience fatigue. Suggest expanding targeting or adding new creative.
- Low CTR: If CTR < 0.1% with > 10,000 impressions on any ad set, suggest reviewing creative or targeting.
Display Format
=== Summer Promo — Deep Health Check ===
Campaign: ACTIVE | Objective: REACH | Created: 2026-04-01
Ad Set Health:
| Ad Set | Format | Budget | Spend Today | Pacing | Freq | Issues |
|--------|--------|--------|-------------|--------|------|--------|
| US 18-34 Audio | AUDIO | $75/day | $68.50 (91%) | OK | 1.8 | — |
| US 25-54 Video | VIDEO | $50/day | $12.00 (24%) | WARNING | 0.4 | Underpacing |
Ad Health:
| Ad | Ad Set | Status | Delivery | Issues |
|----|--------|--------|----------|--------|
| 30s Spot A | US 18-34 Audio | APPROVED | ON | — |
| 30s Spot B | US 18-34 Audio | REJECTED | — | Rejected |
| 15s Video | US 25-54 Video | PENDING | ON | Pending >24h |
Recommendations:
1. "US 25-54 Video" is underpacing at 24% spend. Consider:
- Broadening targeting (current: US, ages 25-54, VIDEO)
- Increasing bid cap
- Run: /spotify-ads-api:ads ad-sets get <ad_set_id>
2. "30s Spot B" was rejected. Review and re-create via /spotify-ads-api:ads ads create
3. "15s Video" has been pending approval for >24h — check if creative meets format requirements.
Recurring Monitoring
After displaying results, suggest recurring checks only when the host environment supports scheduled tasks or automations:
To monitor automatically, schedule a recurring run of:
/spotify-ads-api:monitor --all
Execution Behavior
- If
auto_execute is true, execute all API calls directly and display the health check.
- If
auto_execute is false, present the curl commands and ask for confirmation before executing.
- Always check the
HTTP_STATUS: line from curl output to determine success or failure before interpreting the response body.
- On error, show the error message from the response body and continue with available data.
Formatting Rules
- Spend from
aggregate_reports: Already in the ad account's billing currency — display directly as $X.XX.
- Budget
micro_amount from entity details: In micro-units — divide by 1,000,000 to get the billing currency amount.
- Impressions/Reach/Clicks: Format with thousands separators (e.g.,
156,234).
- CTR: Display as percentage with 2 decimal places (e.g.,
0.79%).
- Frequency: Display with 1 decimal place.
- Percentages: Round to nearest integer for pacing (e.g.,
71%).