--- 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 this skill's folder and define the request wrapper: ```bash SKILL_DIR="" api() { "$SKILL_DIR/scripts/api-request.sh" monitor "$@"; } ``` Before the first Ads API v3 call, read and follow `$SKILL_DIR/references/live-openapi.md`. To retrieve settings values (TOKEN, AD_ACCOUNT_ID, AUTO_EXECUTE, BASE_URL, SDK_HEADER, SKILL_HEADER, PLUGIN_VERSION) for use outside API calls, run `api --env`. The output is eval-safe, so `eval $(api --env)` assigns them all. ## Parsing Arguments - No argument or `--all` → Health check all active campaigns - `` (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 ```bash api GET "ad_accounts/{ad_account_id}/campaigns?statuses=ACTIVE&limit=50&sort_direction=DESC" ``` #### 2. Active ad sets ```bash api GET "ad_accounts/{ad_account_id}/ad_sets?statuses=ACTIVE&limit=50&sort_direction=DESC" ``` #### 3. Today's campaign metrics ```bash 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) ```bash 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 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 ``` If no issues are found: ``` All 3 active campaigns are healthy. No issues detected. ``` --- ## Deep Campaign Health Check (``) ### API Calls #### 1. Campaign details ```bash api GET "ad_accounts/{ad_account_id}/campaigns/$CAMPAIGN_ID" ``` #### 2. All ad sets under the campaign ```bash api GET "ad_accounts/{ad_account_id}/ad_sets?campaign_ids=$CAMPAIGN_ID&limit=50" ``` #### 3. All ads under the campaign ```bash 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: ```bash AD_SET_IDS_QUERY="entity_ids=&entity_ids=" ``` 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 ```bash 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 ```bash 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 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%`).