Building a Natural Language Interface to the Spotify Ads API with Claude Code Plugins

We built a suite of skills, agents and tools bundled as a plugin to make the Spotify Ads Platform easily accessible to AI agentic tools. The spotify-ads-api plugin is available on GitHub and through Anthropic's Claude Plugins marketplace.
Advertising APIs are powerful — but demanding. The Spotify Ads API v3 has over 30 resource types, nested targeting structures, and multi-step entity hierarchies where campaigns contain ad sets that contain ads. For someone who just wants to launch an audio campaign targeting listeners in Connecticut, the cognitive distance between intent and execution is significant.
We wanted to close that gap by letting people describe what they want in plain English and having the system figure out the rest. A single natural language request becomes a full, correct campaign structure.
The result is a Claude Code plugin that translates natural language into Spotify Ads API calls built from Markdown files, a bash script, and two small Python helpers. It’s open source and designed for developers and advertisers who want full control over how LLMs interact with the Ads API.
This post covers the architecture, the opinionated technology choices we made, and what we learned about building developer tools on top of LLM-powered agents.
What it does
The plugin installs into Claude Code and provides a set of slash commands for managing Spotify ad campaigns:
The more interesting interaction mode is conversational. You can say things like:
"Create an audio campaign called Back to School Promo targeting 25-44 year olds in the US with $100/day budget"
Instead of issuing a sequence of commands or writing orchestration code, users describe the outcome and the agent handles the workflow by routing to the right skills and API calls. The system bridges the gap between intent (“launch a campaign”) and execution (multiple dependent API calls with correct parameters, IDs, and validations), removing the need to manually orchestrate the campaign → ad set → ad lifecycle.
The plugin's agent decomposes this into the correct sequence of API calls: looking up geo-targeting IDs, converting dollar amounts to micro-units, validating audience size, creating the campaign entity, creating the ad set with targeting and budget, and creating the ad with creative assets. It understands required fields and prompts the user for any missing information along the way.
Why these technology choices
This plugin is opinionated. To build fast, we focused on Claude Code, macOS, used a CLI-first approach backed by an OpenAPI spec, and deliberately avoided MCP for now.
Why Claude Code
Claude Code's plugin system has a property that turned out to be critical for this project: plugins are written in Markdown. Skills, agents, and the reference documentation that grounds the LLM's API knowledge are all .md files. There is no compiled code in this plugin. There is no build step, no package manager, no bundler, no transpiler.
The plugin architecture has four component types that map well to different concerns:
Skills define the slash commands. Each is a self-contained Markdown file describing the command's behavior, API endpoints, request formats, and output handling.
Agents handle freeform natural language. Our request-builder agent triggers automatically when a user describes an advertising task conversationally, and decomposes it into API calls.
Hooks intercept tool calls before execution. We use a PreToolUse hook to transparently refresh OAuth tokens and inject HTTP headers.
Settings store per-user configuration (credentials, ad account, environment preference) in a gitignored local file.
The result is a plugin where every component is human-readable, diffable, and version-controllable. When a new API quirk surfaces during testing, the fix is usually adding a sentence to a Markdown file. This design makes the system easy to customize, extend, and adapt without the overhead of maintaining a traditional SDK or compiled integration.
Why CLI + OpenAPI Spec (and not MCP)
Model Context Protocol (MCP) is becoming the default standard for connecting LLMs to external tools. It would seem like a natural fit here where we define a set of tools for campaign management, expose them via an MCP server, and let any MCP-compatible client use them. We considered this approach and chose against it for several reasons.
The API surface is too large for static tool definitions. The Spotify Ads API has dozens of endpoints with nested request schemas. Defining each as an MCP tool with full parameter schemas would produce a massive tool registry that would consume significant context window space in every interaction, whether or not the user needs those endpoints. With the Claude Code plugin approach, the agent loads only the reference documentation it needs for a given request on demand.
Curl commands are transparent and debuggable. Every API call the plugin makes is a curl command that it shows to the user. The user can see exactly what's being sent, copy it, modify it, run it independently. This transparency provides auditability and user control in an advertising system where every request can impact real budgets. Users can verify, reproduce, and adjust execution rather than relying on opaque tool behavior.
The OpenAPI spec is the source of truth. The Spotify Ads API publishes a comprehensive OpenAPI v3 spec (~8,600 lines). Rather than translating this into MCP tool schemas (and maintaining that translation as the API evolves), we ship the spec directly in the plugin. The agent reads it when it needs to understand an endpoint's parameters or response format. When the API changes, we update one file.
The agent: domain-expert translation
The natural language agent (agents/spotify-ads-request-builder.md) is a Markdown file that turns the LLM into a Spotify Ads API specialist. Its system prompt covers:
Value conversions: dollars to micro-amounts, date descriptions to ISO 8601, platform names to API enum values
Multi-step orchestration: decomposing "create a full campaign" into the three sequential API calls (campaign, ad set, ad), passing IDs between steps
Geo-targeting lookups: when a user says "target Connecticut," the agent calls the geo-targeting search endpoint, finds the region ID, and constructs the flat geo_targets object with the correct structure
Pre-flight validation: before creating an ad set, the agent calls the audience estimate endpoint to check that the targeting is broad enough to meet minimum thresholds
Execution control: respecting the user's auto_execute preference, either showing the curl command and asking for confirmation, or executing directly
OpenAPI links as a navigation graph
One existing feature of our OpenAPI spec that proved valuable is our use of OpenAPI Links. Links are a relatively underused part of the OpenAPI 3.x specification that define relationships between operations - specifically, how the response from one operation can be used as input to another.
The Spotify Ads API spec encodes the entire entity hierarchy through links. The 201 response for campaign creation declares:
The ad set creation response, in turn, links forward to ad creation and backward to its parent campaign:
And the ad response links laterally to the assets it references: the primary creative, the companion image, the logo:
For a human developer reading API docs, these relationships are usually implicit; you piece together the entity hierarchy from endpoint naming conventions and documentation prose. For an LLM agent, having them declared explicitly in a machine-readable format is a significant advantage. The runtime expressions ($response.body#/id, $request.path.ad_account_id) tell the agent exactly which fields to extract from one response and inject into the next request. The operationId values let the agent look up the target endpoint's full schema.
This is one reason we chose to ship the raw OpenAPI spec alongside our curated documentation. The curated docs describe what each endpoint does. The links in the spec describe the graph of operations that makes multi-step workflows possible, effectively encoding the same workflow graph that the agent needs to follow at runtime. When the agent decomposes "create a full campaign" into three sequential API calls, it's following the same traversal path that the OpenAPI links define: campaign → ad set → ad, with response IDs flowing forward through $response.body#/id at each step.
The Ad Set response also links to targeting lookup endpoints which is how the agent knows to call /targets/geos before constructing a geo-targeting payload. The spec doesn't just document individual endpoints; it documents the workflows between them.
The role of the OpenAPI Spec
The plugin ships with the full Spotify Ads API v3 OpenAPI spec (external-v3.yaml, approximately 8,600 lines). The agent consults this spec through the api-reference skill, which provides curated endpoint documentation, schema definitions, and enum values distilled from the spec.
The spec serves as both documentation and validation. When the agent isn't sure about a field name or type, it reads the relevant schema definition. When a user asks about targeting options, the agent can look up the valid enum values. The decision to ship the raw spec alongside curated documentation reflects a pragmatic tradeoff. The curated docs in api-reference/references/ cover the most common operations with clear examples. The raw spec covers everything, including less-used endpoints, edge-case parameters and error responses. The agent uses the curated docs first and falls back to the spec when needed.
What’s next?
Observing and optimizing user flows. The current agent logic is based on understanding of relatively simple advertising flows. We plan to observe common patterns and "de-composition" bottlenecks to further refine our Markdown-based skills. By identifying where the agent requires the most clarification, we can optimize the system prompts to handle those edge cases preemptively.
Support for idempotency keys. To prevent the accidental creation of duplicate campaigns or ads during network retries or agent re-runs, we plan to introduce support for idempotency keys. By generating a unique client-side key for each intent, we can ensure that the Spotify Ads API treats a retried request as a single operation, providing a safety net for automated execution
Supporting other platforms. We started with Claude because of its strong support for tool calls, but are exploring other platforms including Codex and Gemini CLI. We are particularly excited about the image generation capabilities with GPT5.5 and Images 2.0 for creative generation.
Wrapping up
This plugin is a bet on a specific thesis: that the best interface to a complex API is natural language, grounded by detailed documentation, with transparent execution. The user says what they want. The agent figures out the API calls. The hook layer handles authentication silently. And every curl command is visible, copyable, and debuggable.
There's no framework, no runtime, no dependencies beyond curl, jq, and python3. The source of truth is the OpenAPI spec. The business logic is prose.
Whether this approach scales to the most complex API integrations is an open question. But for the Spotify Ads API, where the distance between "I want to run an audio campaign in the US" and the correct sequence of POST requests is substantial, it works impressively well.
The plugin is released as open source under the Apache 2.0 license. If you're building Claude Code plugins or working with the Spotify Ads API, we'd welcome contributions and feedback. The spotify-ads-api plugin is available on GitHub and through Anthropic's Claude Plugins marketplace.



