From spotify-ads-api
Parses plain-text campaign descriptions into structured Spotify Ads API calls, creating campaigns, ad sets, and ads.
How this skill is triggered — by the user, by Claude, or both
Slash command
/spotify-ads-api:build-campaign <plain-text campaign description><plain-text campaign description>This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Given a plain-text description of an advertising campaign, parse it into structured API
Given a plain-text description of an advertising campaign, parse it into structured API calls and create the full campaign hierarchy: Campaign → Ad Sets → Ads.
access_token, ad_account_id, and auto_execute from the active platform settings file:
.codex/spotify-ads-api.local.md, then fall back to .claude/spotify-ads-api.local.md, then .gemini/spotify-ads-api.local.md..claude/spotify-ads-api.local.md, then fall back to .codex/spotify-ads-api.local.md, then .gemini/spotify-ads-api.local.md..gemini/spotify-ads-api.local.md, then fall back to .claude/spotify-ads-api.local.md, then .codex/spotify-ads-api.local.md.https://api-partner.spotify.com/ads/v3/spotify-ads-api:configure on Claude/Codex, /configure on Gemini).version: .codex-plugin/plugin.json on Codex, .claude-plugin/plugin.json on Claude, or gemini-extension.json (extension root) on Gemini.SDK_PRODUCT to codex-plugin on Codex, claude-code-plugin on Claude, or gemini-cli-extension on Gemini. Set SDK_HEADER="X-Spotify-Ads-Sdk: $SDK_PRODUCT/$PLUGIN_VERSION" and include -H "$SDK_HEADER" on all API requests.Extract the following from the user's plain-text input. If a field is missing or ambiguous, use the defaults noted below. If a required field cannot be inferred, ask the user.
| Field | Required | Default |
|---|---|---|
| name | yes | — |
| objective | yes | REACH |
Valid objectives: REACH, CLICKS, VIDEO_VIEWS, CONVERSIONS, LEAD_GEN, EVEN_IMPRESSION_DELIVERY, PODCAST_STREAMS, APP_INSTALLS, WEBSITE_VISITS
| Field | Required | Default | Notes |
|---|---|---|---|
| name | yes | — | 2-200 chars |
| start_time | yes | — | ISO 8601 UTC |
| end_time | required if LIFETIME | — | ISO 8601 UTC |
| budget.micro_amount | yes | — | Dollar amount x 1,000,000 |
| budget.type | yes | DAILY | DAILY or LIFETIME |
| asset_format | yes | AUDIO | AUDIO, VIDEO, IMAGE, or CATALOG |
| category | yes | — | Valid ADV_X_Y code (fetch from GET /ad_categories if needed) |
| bid_strategy | yes | MAX_BID | Plain string: MAX_BID, COST_PER_RESULT, AUTOBID, or UNSET |
| bid_micro_amount | yes with MAX_BID/COST_PER_RESULT | 15000000 | Bid cap in micro-units. Not required with AUTOBID. |
| pacing | no | PACING_EVEN | PACING_EVEN or PACING_ASAP |
| delivery | no | ON | ON or OFF |
| targets.age_ranges | yes | [{"min":18,"max":54}] | Array of {min, max} objects |
| targets.geo_targets | yes | {"country_code":"US"} | Flat object with country_code string |
| targets.platforms | no | ["ANDROID","DESKTOP","IOS"] | Valid: ANDROID, DESKTOP, IOS |
| targets.placements | yes | ["MUSIC"] | MUSIC or PODCAST |
| targets.genders | no | [] | MALE, FEMALE, NON_BINARY |
Ad set validation guardrails:
budget.micro_amount and bid_micro_amount must be positive when present.currency in ad set budget; currency is only required in /estimates/audience budget payloads.cost_model, skippable, is_skippable, or ad_platforms in ad set create payloads.ANDROID, DESKTOP, and IOS in targets.platforms; never use WEB, MOBILE, or CONNECTED_DEVICE.min >= 18 for age ranges unless the user explicitly confirms a market/category that allows minors.city_ids, dma_ids, postal_code_ids, region_ids), include country_code in the same geo_targets object.bid_strategy=UNSET, omit bid_micro_amount unless the API response or user-provided source explicitly requires it.| Field | Required | Notes |
|---|---|---|
| name | yes | 2-200 chars |
| tagline | yes | 2-40 chars |
| advertiser_name | yes | 2-25 chars |
| assets.asset_id | yes | UUID — prompt user to select |
| assets.logo_asset_id | yes | UUID — prompt user to select |
| assets.companion_asset_id | yes (audio) | UUID — required for AUDIO format ads |
| call_to_action.key | yes | e.g. SHOP_NOW, LEARN_MORE, LISTEN_NOW, SIGN_UP |
| call_to_action.clickthrough_url | yes | Landing page URL |
| delivery | no | ON (default) or OFF |
Before making any API calls, present the full parsed plan as a visual tree:
Campaign: "My Campaign" (objective: REACH)
├── Ad Set 1: "Ad Set A" (AUDIO, $75/day, US, ages 25-54, Mar 1 start)
│ └── Ad 1: "My Ad" → SHOP_NOW → example.com
└── Ad Set 2: "Ad Set B" (VIDEO, $500 lifetime, US, ages 18-54, Mar 4–Apr 4)
└── Ad 2: "My Video Ad" → LEARN_MORE → example.com
Also show a table with all field values for each entity. Ask the user to confirm or adjust.
If the ad category was not specified, ask the user to select one using AskUserQuestion.
You can fetch valid categories from GET /ad_categories to present options.
After the user confirms the plan but before executing API calls, run an audience estimate for each ad set's targeting:
curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST -H "Authorization: Bearer $TOKEN" \
-H "$SDK_HEADER" \
-H "Content-Type: application/json" \
-d '{
"ad_account_id": "<AD_ACCOUNT_ID>",
"start_date": "<start_time>",
"asset_format": "<AUDIO|VIDEO|IMAGE|CATALOG>",
"objective": "<campaign_objective>",
"bid_strategy": "<MAX_BID|COST_PER_RESULT|AUTOBID|UNSET>",
"bid_micro_amount": <bid>,
"budget": {"micro_amount": <budget>, "type": "<DAILY|LIFETIME>", "currency": "USD"},
"targets": { <same targets object as the ad set> }
}' \
"https://api-partner.spotify.com/ads/v3/estimates/audience"
Important: This endpoint is NOT scoped under /ad_accounts/{id}/ — it's at the top level: POST /estimates/audience. Use the base URL directly followed by /estimates/audience.
Display the estimate results in a summary:
Audience Estimate for "Ad Set A":
Projected unique users: ~142,000
Estimated daily reach: 8,500 – 12,000
Estimated daily impressions: 15,000 – 22,000
Estimated CPM: $12.50 – $18.00
Likely to deliver budget: Yes
Convert any CPM micro-amounts to dollars for display.
If the audience is too small (very low projected_unique_users or the API returns a 400 error indicating audience too small), warn the user and suggest:
Use AskUserQuestion to ask whether to:
Run the estimate for each ad set in the plan before proceeding to Step 3.
For each ad, fetch available assets from the account:
curl -s -w "\nHTTP_STATUS:%{http_code}" -H "Authorization: Bearer $TOKEN" \
-H "$SDK_HEADER" \
"$BASE_URL/ad_accounts/$AD_ACCOUNT_ID/assets?limit=50&sort_direction=DESC"
Present audio/video assets and image assets separately in tables, and ask the user to pick:
asset_format: audio for AUDIO, video for VIDEO, etc.)Execute each step in order, passing IDs forward from each response.
curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST -H "Authorization: Bearer $TOKEN" \
-H "$SDK_HEADER" \
-H "Content-Type: application/json" \
-d '{"name":"...","objective":"..."}' \
"$BASE_URL/ad_accounts/$AD_ACCOUNT_ID/campaigns"
Extract the campaign id from the response.
For each ad set:
curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST -H "Authorization: Bearer $TOKEN" \
-H "$SDK_HEADER" \
-H "Content-Type: application/json" \
-d '{
"name": "...",
"campaign_id": "<from step 4a>",
"start_time": "...",
"end_time": "...",
"budget": {"micro_amount": ..., "type": "..."},
"asset_format": "...",
"category": "ADV_X_Y",
"targets": {
"age_ranges": [{"min": ..., "max": ...}],
"geo_targets": {"country_code": "..."},
"platforms": ["ANDROID", "DESKTOP", "IOS"],
"placements": ["MUSIC"]
},
"bid_strategy": "MAX_BID",
"bid_micro_amount": ...,
"pacing": "PACING_EVEN",
"delivery": "ON"
}' \
"$BASE_URL/ad_accounts/$AD_ACCOUNT_ID/ad_sets"
Extract each ad set id for use in ad creation.
For each ad:
curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST -H "Authorization: Bearer $TOKEN" \
-H "$SDK_HEADER" \
-H "Content-Type: application/json" \
-d '{
"name": "...",
"ad_set_id": "<from step 4b>",
"tagline": "...",
"advertiser_name": "...",
"assets": {
"asset_id": "...",
"logo_asset_id": "...",
"companion_asset_id": "..."
},
"call_to_action": {
"key": "SHOP_NOW",
"clickthrough_url": "https://..."
},
"delivery": "ON"
}' \
"$BASE_URL/ad_accounts/$AD_ACCOUNT_ID/ads"
After all entities are created, display a final summary table:
| Entity | ID | Name | Status |
|---|---|---|---|
| Campaign | uuid | ... | ... |
| Ad Set 1 | uuid | ... | ... |
| ↳ Ad 1 | uuid | ... | ... |
| Ad Set 2 | uuid | ... | ... |
| ↳ Ad 2 | uuid | ... | ... |
auto_execute is true, execute each API call directly after presenting the plan.auto_execute is false, present the full plan and ask for confirmation before
executing. Then execute all calls in sequence without additional confirmation per call.HTTP_STATUS: line from curl output to determine success or failure before interpreting the response body.These are non-obvious API requirements that MUST be followed:
bid_strategy is a plain STRING enum, NOT an object. Valid: MAX_BID, COST_PER_RESULT, AUTOBID, UNSETgeo_targets is a flat object {"country_code": "US"}, NOT an array of objectsplatforms valid values are ANDROID, DESKTOP, IOS — NOT "MOBILE" or "CONNECTED_DEVICE"category is required on ad sets — must be a valid ADV_X_Y code from GET /ad_categoriesend_time is required when budget type is LIFETIMEcompanion_asset_id is required when creating ads for AUDIO ad setscall_to_action uses field name key (not type) and clickthrough_url (not url)npx claudepluginhub spotify/ads-agentic-tools --plugin spotify-ads-apiGenerates Spotify Ads campaign strategy from landing pages, brand briefs, or creative assets. Outputs API-ready campaign plans with targeting, budget, and creative rotation.
Creates and launches paid ad campaigns on Google, Meta, LinkedIn, or TikTok with targeting, bid strategy, budget safeguards, and post-launch monitoring.
Manages ad campaigns across Google Ads, Meta Ads, LinkedIn Ads, and TikTok Ads via Adspirer MCP server. Analyzes performance, researches keywords, creates campaigns, optimizes budgets.