Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spotify-ads-api",
"version": "1.4.0",
"version": "1.5.0",
"description": "Manage Spotify ad campaigns with natural language. Create campaigns, ad sets, ads, pull reports, and handle OAuth — all through conversation.",
"author": {
"name": "Alex Murphy"
Expand All @@ -9,5 +9,6 @@
"repository": "https://github.com/spotify/ads-agentic-tools",
"license": "Apache-2.0",
"keywords": ["spotify", "ads", "api", "campaigns", "advertising", "oauth", "reporting"],
"skills": "./skills/"
"skills": "./skills/",
"hooks": "./hooks/claude-hooks.json"
}
4 changes: 2 additions & 2 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spotify-ads-api",
"version": "1.4.0",
"version": "1.5.0",
"description": "Manage Spotify ad campaigns with natural language. Create campaigns, ad sets, ads, pull reports, and handle OAuth — all through conversation.",
"author": {
"name": "Alex Murphy"
Expand All @@ -10,7 +10,7 @@
"license": "Apache-2.0",
"keywords": ["spotify", "ads", "api", "campaigns", "advertising", "oauth", "reporting"],
"skills": "./skills/",
"hooks": "./hooks/hooks.json",
"hooks": "./hooks/claude-hooks.json",
"interface": {
"displayName": "Spotify Ads API",
"shortDescription": "Manage Spotify ad campaigns with natural language",
Expand Down
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#### Checklist
<!--- Put an `x` in all the boxes that apply: -->
- [ ] Tested against the Spotify Ads API with Codex or Claude Code
- [ ] Tested against the Spotify Ads API with Codex, Claude Code, or Gemini CLI
- [ ] Existing skills still work as expected
- [ ] SKILL.md frontmatter is valid (name, description, allowed-tools)
- [ ] README or CHANGELOG updated (if user-facing change)
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.claude/*.local.md
.codex/*.local.md
.gemini/*.local.md
.DS_Store
*.log
internal-v3.yaml
13 changes: 8 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This is the canonical instruction file for agents working in this repository. Co

## What This Is

A Codex and Claude Code plugin package for the Spotify Ads API v3. All source files are markdown — there is no compiled code, no package manager, no build step, no tests. The plugin translates natural language into REST API calls for managing campaigns, ad sets, ads, assets, audiences, and reporting.
A Codex, Claude Code, and Gemini CLI plugin package for the Spotify Ads API v3. All source files are markdown — there is no compiled code, no package manager, no build step, no tests. The plugin translates natural language into REST API calls for managing campaigns, ad sets, ads, assets, audiences, and reporting.

## Architecture

Expand All @@ -24,8 +24,9 @@ The plugin follows the agent plugin structure with four component types:
- `skills/clone/` — Clone campaigns or ad sets by reading the source hierarchy and recreating entities with modifications
- `skills/api-reference/` — Comprehensive API v3 reference documentation with `references/` (endpoints, schemas, enums) and `examples/` (full flows). Activates automatically when the Spotify Ads API is mentioned.
- **Agent** (`agents/spotify-ads-request-builder.md`) — A natural language agent that triggers automatically when users describe advertising tasks conversationally. Handles multi-step operations (campaign -> ad set -> ad) by chaining API calls and passing IDs between steps.
- **Hooks** (`hooks/hooks.json`) — A `PreToolUse` hook that automatically refreshes expired OAuth tokens before Spotify API calls.
- **Settings** (`.codex/spotify-ads-api.local.md`, with fallback to `.claude/spotify-ads-api.local.md`) — Per-user local config with YAML frontmatter storing OAuth credentials (access_token, refresh_token, client_id, token_expires_at), ad_account_id, and auto_execute. The client_secret is stored in the macOS Keychain (service: `spotify-ads-api-client-secret`, account: `spotify-ads-api`), not in this file. Template lives in `templates/settings-template.md`. These files are gitignored.
- **Hooks** (`hooks/`) — Two per-platform hook configs invoking the same `hooks/check-token.sh` to automatically refresh expired OAuth tokens before Spotify API calls: `hooks/hooks.json` is Gemini-only (`BeforeTool` — Gemini auto-discovers this fixed path and warns on unknown event names, so it must not contain `PreToolUse`), and `hooks/claude-hooks.json` is for Claude/Codex (`PreToolUse`), declared explicitly via the `hooks` field in both `.claude-plugin/plugin.json` and `.codex-plugin/plugin.json`. The script detects the platform from the `hook_event_name` in the hook payload. Note the timeout units differ: seconds for `PreToolUse`, milliseconds for `BeforeTool`.
- **Commands** (`commands/configure.toml`) — A Gemini CLI custom command exposing `/configure` as an explicit entry point to the configure skill. Other skills auto-activate on Gemini via its native Agent Skills support.
- **Settings** (`.codex/spotify-ads-api.local.md`, `.claude/spotify-ads-api.local.md`, or `.gemini/spotify-ads-api.local.md`, with each platform preferring its own file and falling back to the other two) — Per-user local config with YAML frontmatter storing OAuth credentials (access_token, refresh_token, client_id, token_expires_at), ad_account_id, and auto_execute. The client_secret is stored in the macOS Keychain (service: `spotify-ads-api-client-secret`, account: `spotify-ads-api`), not in this file. Template lives in `templates/settings-template.md`. These files are gitignored.

## Marketplace Compatibility

Expand All @@ -34,7 +35,9 @@ Keep both marketplace files intentional and in sync:
- `.agents/plugins/marketplace.json` is the Codex-facing catalog used by `codex plugin marketplace add spotify/ads-agentic-tools`. It must include Codex's `interface.displayName`, `policy.installation`, `policy.authentication`, and `category` metadata. Because this plugin lives at the repository root, use a Git-backed root plugin source (`"source": "url"` with the repository URL) instead of a local `source.path` of `"./"`; Codex treats that local root path as empty and skips the plugin.
- `.claude-plugin/marketplace.json` is the Claude Code-compatible catalog. Claude Code requires marketplace metadata at that path and requires a top-level `owner`. To keep the file portable, use the stricter Claude-valid schema: local plugin sources should use the relative string form (`"source": "./"` for this repo-root plugin), and Codex-only marketplace fields such as top-level `interface` or plugin `policy` should not be added to this file. The Claude plugin manifest `.claude-plugin/plugin.json` must also avoid Codex-only manifest fields such as `interface`; keep display metadata in `.codex-plugin/plugin.json` and the Codex marketplace.

When updating marketplace metadata, keep the plugin name, source path, category, description, and user-facing display name aligned wherever each schema supports those fields.
- Gemini CLI has no marketplace file. The root `gemini-extension.json` is the Gemini manifest; `gemini extensions install <repo-url>` reads it directly, and Gemini auto-discovers the `skills/`, `commands/`, and `hooks/` directories at the extension root. Keep its `name`, `version`, and `description` in sync with the other two manifests, and do not add Gemini-only fields (such as `contextFileName`) to the Claude or Codex manifests.

When updating marketplace metadata, keep the plugin name, source path, category, description, and user-facing display name aligned wherever each schema supports those fields. The plugin `version` must be bumped in all three manifests together: `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, and `gemini-extension.json`.

## API Conventions to Know

Expand All @@ -51,7 +54,7 @@ These non-obvious API quirks were discovered through real testing and are critic
- **Report field name** is `fields`, not `report_fields`.
- **No DELETE** on campaigns/ad sets/ads — use status changes (ARCHIVED, PAUSED).
- **Base URL**: `https://api-partner.spotify.com/ads/v3`.
- **Tracking header**: Every API request must include the SDK tracking header. On Codex, read the `version` from `.codex-plugin/plugin.json` and set `SDK_HEADER="X-Spotify-Ads-Sdk: codex-plugin/$PLUGIN_VERSION"`. On Claude, read `.claude-plugin/plugin.json` and use `claude-code-plugin/$PLUGIN_VERSION`. Include `-H "$SDK_HEADER"` on all curl commands.
- **Tracking header**: Every API request must include the SDK tracking header. On Codex, read the `version` from `.codex-plugin/plugin.json` and set `SDK_HEADER="X-Spotify-Ads-Sdk: codex-plugin/$PLUGIN_VERSION"`. On Claude, read `.claude-plugin/plugin.json` and use `claude-code-plugin/$PLUGIN_VERSION`. On Gemini, read `gemini-extension.json` (extension root) and use `gemini-cli-extension/$PLUGIN_VERSION`. Include `-H "$SDK_HEADER"` on all curl commands.
- **`entity_status_type` must match `entity_type`** in `aggregate_reports` queries. For example, use `entity_status_type=AD_SET` when `entity_type=AD_SET` — using `entity_status_type=CAMPAIGN` with `entity_type=AD_SET` causes a filter validation error.
- **Audience estimates**: The build-campaign and ads skills run `POST /estimates/audience` before creating ad sets to validate targeting. This catches "min audience threshold" errors before they happen.

Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## [1.5.0] - 2026-06-10

### Added
- Gemini CLI extension support: root `gemini-extension.json` manifest, `GEMINI.md` context file, and a `/configure` custom command; skills load through Gemini's native Agent Skills support with no content duplication
- OAuth token auto-refresh on Gemini CLI via a `BeforeTool` hook; hook configs are now split per platform (`hooks/hooks.json` for Gemini, `hooks/claude-hooks.json` for Claude/Codex) since each platform rejects the other's event names
- `.gemini/spotify-ads-api.local.md` settings path (gitignored)

### Changed
- Settings lookup is now a three-way fallback across `.codex/`, `.claude/`, and `.gemini/` in all skills, the agent, and the token-refresh hook
- `check-token.sh` detects the platform from the hook payload's `hook_event_name` and emits Gemini's `tool_input` output schema when rewriting commands
- SDK tracking header gains a third product: `gemini-cli-extension/$PLUGIN_VERSION` on Gemini
- Plugin version is now synced across three manifests (`.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, `gemini-extension.json`), all bumped to 1.5.0

## [1.4.0] - 2026-05-20

### Added
Expand Down
11 changes: 9 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Look for issues labeled [`good first issue`](https://github.com/spotify/ads-agen

## Building the Project

This is a Codex and Claude Code plugin package made mostly of markdown files. There is no build step, no package manager, and no compiled code.
This is a Codex, Claude Code, and Gemini CLI plugin package made mostly of markdown files. There is no build step, no package manager, and no compiled code.

To run the plugin locally:

Expand All @@ -29,10 +29,16 @@ To run the plugin locally:
claude --plugin-dir /path/to/ads-agentic-tools
```

For Gemini CLI:
```bash
gemini extensions link /path/to/ads-agentic-tools
```

3. Configure credentials:
```
/spotify-ads-api:configure
```
(`/configure` on Gemini CLI)

## Workflow

Expand All @@ -51,9 +57,10 @@ We follow the [GitHub Flow Workflow](https://guides.github.com/introduction/flow

There is no automated test suite. Before submitting a pull request:

- Verify your changes work against the Spotify Ads API
- Verify your changes work against the Spotify Ads API on at least one supported platform (Codex, Claude Code, or Gemini CLI)
- Confirm that existing skills (`/spotify-ads-api:campaigns`, `/spotify-ads-api:ads`, etc.) still function correctly
- If adding a new skill, include a `SKILL.md` following the patterns in existing skill directories
- If touching `hooks/`, note there are two per-platform configs: `hooks/hooks.json` (Gemini, `BeforeTool`) and `hooks/claude-hooks.json` (Claude/Codex, `PreToolUse`), both calling `check-token.sh` — test the token-refresh hook on all three platforms

## Style

Expand Down
31 changes: 31 additions & 0 deletions GEMINI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Spotify Ads API Extension

Manage Spotify ad campaigns with natural language. Capabilities are packaged as Agent Skills (campaigns, ads, assets, reporting, dashboards, monitoring, bulk operations, cloning, exports, and a full campaign builder). Describe what you want — e.g. "show my campaign dashboard" or "create a campaign for my podcast" — or run `/skills list` to see everything available.

## Settings

Read and write per-user configuration in `.gemini/spotify-ads-api.local.md` (YAML frontmatter: `access_token`, `refresh_token`, `token_expires_at`, `client_id`, `ad_account_id`, `environment`, `auto_execute`). If that file does not exist, fall back to `.claude/spotify-ads-api.local.md`, then `.codex/spotify-ads-api.local.md`.

Never commit these files. The `client_secret` is stored in the macOS Keychain (service: `spotify-ads-api-client-secret`, account: `spotify-ads-api`), not in the settings file.

## First-Time Setup

Run `/configure` (or ask to "configure Spotify Ads API credentials") to set up OAuth 2.0 authentication and select an ad account. Documentation elsewhere may reference `/spotify-ads-api:configure` — that is the Claude Code/Codex name for the same configure skill.

## SDK Tracking Header

Every Spotify Ads API request must include the SDK tracking header. Read the `version` from this extension's `gemini-extension.json` and set:

```bash
SDK_HEADER="X-Spotify-Ads-Sdk: gemini-cli-extension/$PLUGIN_VERSION"
```

Include `-H "$SDK_HEADER"` on all curl commands to `api-partner.spotify.com`.

## Token Expiry

A BeforeTool hook refreshes expired OAuth tokens automatically before API calls. If a request still returns 401, refresh the token per the configure skill's instructions or re-run `/configure`.

## Contributors

If you are editing this repository itself (not using the extension), `AGENTS.md` is the canonical instruction file — read it before making changes.
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Spotify Ads Agentic Tools

A Codex and Claude Code plugin package that lets you manage Spotify advertising campaigns through natural language. Create campaigns, target audiences, launch ads, and pull performance reports — all by describing what you want in plain English.
A Codex, Claude Code, and Gemini CLI plugin package that lets you manage Spotify advertising campaigns through natural language. Create campaigns, target audiences, launch ads, and pull performance reports — all by describing what you want in plain English.

Check out our post on the [Spotify Engineering Blog](https://engineering.atspotify.com/2026/5/spotify-ads-api-claude-plugins).

## Prerequisites

- Codex or [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
- Codex, [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code), or [Gemini CLI](https://geminicli.com/)
- A [Spotify Developer](https://developer.spotify.com/) account with an ads-enabled app
- A Spotify Ads ad account ID
- Python 3.8+ (for automated OAuth flow; optional — manual flow available as fallback)
Expand All @@ -31,6 +31,14 @@ Restart Codex after adding the marketplace. Then open the plugin directory in th

Use `codex plugin marketplace upgrade` later to refresh installed marketplace sources.

### Gemini CLI

```bash
gemini extensions install https://github.com/spotify/ads-agentic-tools
```

Restart Gemini CLI, then verify with `/extensions`. On Gemini, skills activate automatically from natural language (or browse them with `/skills list`); run `/configure` for first-time setup instead of `/spotify-ads-api:configure`. Note: automatic OAuth token refresh uses the macOS Keychain, so auto-refresh is macOS-only.

## Install from source

Use a source checkout for local development or testing unreleased changes.
Expand Down Expand Up @@ -58,7 +66,14 @@ Use a source checkout for local development or testing unreleased changes.
alias claude-ads='claude --plugin-dir /path/to/ads-agentic-tools'
```

The repository includes platform-specific marketplace metadata: `.agents/plugins/marketplace.json` for Codex and `.claude-plugin/marketplace.json` for Claude Code. Both point at the repo-root plugin, so keep them in sync when changing plugin metadata.
4. For Gemini CLI, link the checkout as a local extension:
```bash
gemini extensions link "$(pwd)"
```

The link is a symlink, so source changes are picked up on the next Gemini CLI restart.

The repository includes platform-specific marketplace metadata: `.agents/plugins/marketplace.json` for Codex and `.claude-plugin/marketplace.json` for Claude Code. Gemini CLI has no marketplace file — it installs directly from the repository using the root `gemini-extension.json` manifest. Keep all three manifests in sync when changing plugin metadata.

## Configure

Expand All @@ -72,6 +87,7 @@ The repository includes platform-specific marketplace metadata: `.agents/plugins
```
/spotify-ads-api:configure
```
(On Gemini CLI, run `/configure` instead — the skill names below all apply, but the slash-command prefix is Claude Code/Codex syntax.)
This opens your browser for Spotify authorization, then saves your tokens locally with automatic refresh.

3. Create your first campaign:
Expand All @@ -94,6 +110,8 @@ Run `/spotify-ads-api:configure token <your-token>`. Accepts a pre-obtained acce

## Available Skills

Skill names below use Claude Code/Codex slash-command syntax. On Gemini CLI, the same skills activate automatically from natural language (browse them with `/skills list`), and setup is `/configure`.

| Skill | Description |
|-------|-------------|
| `/spotify-ads-api:configure` | Set up OAuth credentials, ad account, and preferences |
Expand Down Expand Up @@ -130,7 +148,7 @@ The plugin includes an agent that interprets natural language requests automatic

## Configuration Reference

Settings are stored in `.codex/spotify-ads-api.local.md` on Codex and `.claude/spotify-ads-api.local.md` on Claude. Each platform falls back to the other settings file if its preferred file does not exist. Both paths are gitignored.
Settings are stored in `.codex/spotify-ads-api.local.md` on Codex, `.claude/spotify-ads-api.local.md` on Claude, and `.gemini/spotify-ads-api.local.md` on Gemini. Each platform falls back to the other settings files if its preferred file does not exist. All three paths are gitignored.

| Field | Description | Default |
|-------|-------------|---------|
Expand Down Expand Up @@ -160,6 +178,9 @@ Your targeting is too narrow for the selected ad format. Try broadening the age
**"Asset stuck in PROCESSING"**
Large files may take longer to transcode. Check status with `/spotify-ads-api:assets get <id>`. If status is REJECTED, the file may not meet format requirements.

**Skill not activating on Gemini CLI**
Run `/skills list` to confirm the extension's skills loaded, and `/extensions` to confirm the extension is enabled. Restart Gemini CLI after installing or linking.

## License

Copyright 2026 Spotify, Inc.
Expand Down
Loading