spotify/ads-agentic-tools · Archived

build-campaign

Create a full campaign (campaign + ad sets + ads) from a plain-text description. Parses natural language into structured API calls. Prefers the draft workflow for safer creation with batch validation.

First seen Jun 22, 2026

Installation

$ npx skills add spotify/ads-agentic-tools --skill build-campaign

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from spotify/ads-agentic-tools.

npx skills add spotify/ads-agentic-tools

Browse all from spotify/ads-agentic-tools

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 11
License LICENSE
Default branch main
Open issues 0
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Allowed toolsRead, Bash, AskUserQuestion

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 12,992 B
  • docs SUMMARY.md 222 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Spotify Ads API — Full Campaign Builder

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.

Default Flow: Draft → Validate → Publish

By default, use the draft workflow for all campaign hierarchy creation. This creates draft entities first, validates the entire hierarchy, and only publishes after confirmation. Route to the /spotify-ads-api:drafts build <description> skill to execute the draft flow.

The draft flow is preferred because:

  • Batch validation catches all errors across the hierarchy before anything goes live
  • Safe iteration — the user can review and edit drafts before publishing
  • Easy undo — delete the draft if something looks wrong; no live entities to clean up

Publishing a draft always requires explicit user confirmation immediately before the PUBLISH request, even when auto_execute is enabled.

Only use the direct creation flow below if the user explicitly asks to skip drafts or create live entities immediately. If a direct write is denied, do not infer that the credentials are read-only; offer the draft workflow instead.

Direct Creation Flow (Legacy)

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" build-campaign "$@"; }

Before the first Ads API v3 call, read and follow $PLUGIN_ROOT/skills/api-reference/references/live-openapi.md.

To retrieve settings values (TOKEN, ADACCOUNTID, AUTOEXECUTE, BASEURL, SDKHEADER, SKILLHEADER, PLUGIN_VERSION) for use outside API calls, run api --env. The output is eval-safe, so eval $(api --env) assigns them all.

Step 1: Parse the Campaign Description

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.

Campaign-level fields

Field Required Default
name yes —
objective yes REACH
ad_product no UNSET (resolves to AUCTION)

Valid objectives: REACH, CLICKS, VIDEOVIEWS, CONVERSIONS, LEADGEN, EVENIMPRESSIONDELIVERY, PODCASTSTREAMS, APPINSTALLS, WEBSITE_VISITS

Ad set-level fields (one or more)

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 — Amount (in ad account's billing currency) x 1,000,000
budget.type yes DAILY DAILY or LIFETIME
asset_format yes AUDIO AUDIO, VIDEO, IMAGE, or CATALOG
category yes — Valid ADVXY code (fetch from GET /ad_categories if needed)
bid_strategy yes MAX_BID Plain string: MAXBID, COSTPER_RESULT, AUTOBID, or UNSET
bidmicroamount yes with MAXBID/COSTPER_RESULT 15000000 Bid cap in micro-units. Not required with AUTOBID.
pacing no PACING_EVEN PACINGEVEN or PACINGASAP
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:

  • Reject or ask to correct zero/negative budgets and zero bids. budget.microamount and bidmicro_amount must be positive when present.
  • Do not include currency in ad set budget; currency is only required in /estimates/audience budget payloads.
  • Do not send costmodel, skippable, isskippable, or ad_platforms in ad set create payloads.
  • Only use ANDROID, DESKTOP, and IOS in targets.platforms; never use WEB, MOBILE, or CONNECTED_DEVICE.
  • Use min >= 18 for age ranges unless the user explicitly confirms a market/category that allows minors.
  • When geo refinements are present (cityids, postalcodeids, regionids), include countrycode in the same geotargets object.
  • If bidstrategy=UNSET, omit bidmicro_amount unless the API response or user-provided source explicitly requires it.

Ad-level fields (one or more per ad set)

Field Required Notes
name yes 2-200 chars
tagline yes (optional for drafts) 2-40 chars
advertiser_name yes 2-25 chars
assets.asset_id yes (optional for drafts) UUID — prompt user to select
assets.logoassetid yes UUID — prompt user to select
assets.companionassetid yes (audio) UUID — required for AUDIO format ads
calltoaction.key yes e.g. SHOPNOW, LEARNMORE, LISTENNOW, SIGNUP
calltoaction.clickthrough_url yes (optional for drafts) Landing page URL
delivery no ON (default) or OFF

Step 1.5: Load Ad Product Rules

Read and follow $PLUGIN_ROOT/skills/api-reference/references/ad-product-validation.md. Fetch the live catalog once for this workflow, resolve the planned campaign's product, and use the applicable rules while constructing the plan. Do not display a per-field checklist.

Step 2: Confirm the Parsed Plan

Before making any mutating 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.

Step 2.5: Validate Audience Size

After the user confirms the plan but before executing API calls, run an audience estimate for each ad set's targeting:

api POST "estimates/audience" \
  '{
    "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> }
  }'

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 the ad account's billing currency for display.

If the audience is too small (very low projecteduniqueusers or the API returns a 400 error indicating audience too small), warn the user and suggest:

  • Broadening the age range
  • Adding more platforms
  • Removing restrictive targeting (artist/genre/interest)
  • Switching from VIDEO to AUDIO format (lower thresholds)
  • Expanding geo targeting

Use AskUserQuestion to ask whether to:

  1. Proceed anyway with current targeting
  2. Adjust targeting (then re-estimate)
  3. Cancel this ad set

Run the estimate for each ad set in the plan before proceeding to Step 3.

Step 3: Prompt for Assets

For each ad, fetch available assets from the account:

api GET "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:

  • assetid — the creative (must match the ad set's assetformat: audio for AUDIO, video for VIDEO, etc.)
  • logoassetid — a logo image
  • companionassetid — a companion image (required for AUDIO format ads)

Step 3.5: Validate the Final Hierarchy

Using the catalog loaded in Step 1.5, validate the complete campaign, ad set, and ad request bodies now that assets and all dependent fields are known. Apply the canonical procedure's static and runtime checks, including asset lookups and the audience estimate above. Never send a known-invalid request.

Do not add another confirmation or print per-field successes. If the existing plan summary is still visible, one compact validation status line is sufficient. Surface a failure only when an explicit user choice must change or no safe compliant value can be inferred.

Step 4: Execute API Calls Sequentially

Execute each step in order, passing IDs forward from each response.

4a. Create Campaign

Include ad_product when the resolved destination product is CONTENT or FPMNG. Omit it for the default AUCTION flow.

api POST "ad_accounts/{ad_account_id}/campaigns" \
  '{"name":"...","objective":"..."}'

Extract the campaign id from the response.

4b. Create Ad Sets (using campaign_id from 4a)

api POST "ad_accounts/{ad_account_id}/ad_sets" \
  '{
    "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"
  }'

Extract each ad set id for use in ad creation.

4c. Create Ads (using adsetid from 4b)

api POST "ad_accounts/{ad_account_id}/ads" \
  '{
    "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"
  }'

Step 5: Summary

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 ... ...

Execution Behavior

  • If auto_execute is true, execute each API call directly after presenting the plan.
  • If 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.

  • 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 and stop. Do not continue creating dependent entities if a parent fails. Never automatically retry a POST — if a campaign/ad set/ad creation fails with a 5xx, check if the entity was actually created (e.g., list campaigns) before suggesting a retry.

Critical Schema Notes

These are non-obvious API requirements that MUST be followed:

  1. bidstrategy is a plain STRING enum, NOT an object. Valid: MAXBID, COSTPERRESULT, AUTOBID, UNSET
  2. geotargets is a flat object {"countrycode": "US"}, NOT an array of objects
  3. platforms valid values are ANDROID, DESKTOP, IOS — NOT "MOBILE" or "CONNECTED_DEVICE"
  4. category is required on ad sets — must be a valid ADVXY code from GET /ad_categories
  5. end_time is required when budget type is LIFETIME
  6. companionassetid is required when creating ads for AUDIO ad sets
  7. calltoaction uses field name key (not type) and clickthrough_url (not url)
  8. Budget amounts must be in micro-units (multiply amount by 1,000,000)
  9. Min audience thresholds apply — VIDEO format may require broader targeting than AUDIO. If you get a "Min audience threshold was not met" error, suggest expanding the age range or switching format.