Metadata-Version: 2.4
Name: anchorconnector
Version: 0.6.3
Summary: Anchor Connector for Podcast Data
Author: Open Podcast
License: MIT
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests
Requires-Dist: loguru
Requires-Dist: PyYAML
Requires-Dist: tenacity
Requires-Dist: myst_parser[docs]
Dynamic: author
Dynamic: description
Dynamic: description-content-type
Dynamic: license
Dynamic: license-file
Dynamic: requires-dist
Dynamic: summary

# Anchor Connector

[![Docs](https://readthedocs.org/projects/anchor-connector/badge?version=latest)](https://anchor-connector.readthedocs.io)

[![OpenPodcast Banner](https://raw.githubusercontent.com/openpodcast/banner/main/openpodcast-banner.png)](https://openpodcast.app/)

This is a simple library for connecting to the unofficial Spotify for Creators
(formerly Anchor) API. It can be used to export analytics data from your
podcast dashboard at https://podcasters.spotify.com.

## API Notes

The API is undocumented, unofficial, and subject to change at any time.
This library reverse-engineers the requests made by the Spotify for Creators
web dashboard.

### Base URL

As of early 2026, the active base URL is:

```
https://podcasters.spotify.com/pod/api/proxy/v3
```

Previous base URLs that are now **defunct**:

| URL | Status |
|-----|--------|
| `https://api-v5.anchor.fm/v3` | ❌ Dead (returns 401 for all requests) |
| `https://creators.spotify.com/pod/api/proxy/v3` | ✅ Still works as an alias |

### Station and Episode IDs

The API previously used string-prefixed identifiers in URL path segments:

- `webStationId:<hex-id>` for station-level endpoints
- `webEpisodeId:<alphanumeric-id>` for episode-level endpoints

These are now replaced by plain **numeric IDs** throughout all analytics
endpoints. The `webStationId` you have from your credentials is still useful
as a bootstrap — the `episodePage` endpoint still accepts it and returns the
numeric `stationId` in its response. This library handles the conversion
automatically: you only need to supply your `webStationId` and the numeric ID
is resolved and cached on the first API call.

Episode responses now also include `spotifyEpisodeUri` (e.g.
`spotify:episode:4pvCXSIxpZLPaLlJ4KWiBa`) and `spotifyShowUri` fields, but
the analytics endpoints use the plain numeric `episodeId`.

### GraphQL

The library previously used a GraphQL endpoint
(`https://creators-graph.spotify.com/v2/graph-pq`) to fetch the full episode
list. This endpoint is **no longer available** (returns 401). The same data is
now fetched via the REST `episodePage` endpoint, which is paginated and returns
all fields needed (including `episodeId`, `spotifyEpisodeUri`, `title`, etc.).
The `base_graphql_url` parameter and `ANCHOR_BASE_GRAPHQL_URL` environment
variable have been removed from the connector accordingly.

## Supported Endpoints

### Station-level

- `podcast_episode` — full episode list (wraps `episodes`)
- `episodes` — paginated episode iterator
- `total_plays`
- `total_plays_by_episode`
- `unique_listeners`
- `audience_size`
- `plays`
- `plays_by_age_range`
- `plays_by_app`
- `plays_by_device`
- `plays_by_episode`
- `plays_by_gender`
- `plays_by_geo`
- `plays_by_geo_city`
- `impressions`

### Episode-level

All episode-level endpoints take a numeric `episodeId` (available as
`episode["episodeId"]` from `episodes()`).

- `episode_plays`
- `episode_performance`
- `episode_aggregated_performance`
- `episode_metadata` (overview endpoint)

See `__main__.py` for a full working example of every endpoint.

## Credentials

Before you can use the library, you must extract your Anchor/Spotify for
Creators credentials from the dashboard; they are **not** exposed through your
account settings.

You can use our [web-extension](https://github.com/openpodcast/web-extension)
to capture them automatically, or
[take a look at the code](https://github.com/openpodcast/web-extension/blob/47fd44723caf6e8a4660f244814f316cdcf19c4c/src/openpodcast.js)
to see how to do it manually.

| Credential | Where to find it |
|-----------|-----------------|
| `ANCHOR_BASE_URL` | Use `https://podcasters.spotify.com/pod/api/proxy/v3` |
| `ANCHOR_WEBSTATION_ID` | The short hex ID in your podcast URL, e.g. `fb37d7a4` |
| `ANCHOR_PW_S` | Value of the `anchorpw_s` cookie on `podcasters.spotify.com` |

The `anchorpw_s` cookie expires periodically — if you start getting 401
errors, you need to grab a fresh value from your browser's DevTools
(**Application → Cookies → podcasters.spotify.com**).

## Installation

```sh
pip install anchorconnector
```

## Usage

```python
from anchorconnector import AnchorConnector
from datetime import datetime, timedelta

connector = AnchorConnector(
    base_url="https://podcasters.spotify.com/pod/api/proxy/v3",
    webstation_id=WEBSTATION_ID,
    anchorpw_s=ANCHOR_PW_S,
)

end = datetime.now()
start = end - timedelta(days=30)

# Station-level analytics
total_plays = connector.total_plays()
plays_by_geo = connector.plays_by_geo()
plays = connector.plays(start, end)
plays_by_age_range = connector.plays_by_age_range(start, end)
plays_by_app = connector.plays_by_app(start, end)
plays_by_device = connector.plays_by_device(start, end)
plays_by_episode = connector.plays_by_episode(start, end)
plays_by_gender = connector.plays_by_gender(start, end)

# Iterate over all episodes and fetch per-episode analytics
for episode in connector.episodes():
    episode_id = episode["episodeId"]  # numeric ID, required by analytics endpoints

    print(episode["title"])
    print(episode["spotifyEpisodeUri"])  # e.g. "spotify:episode:4pvCX..."

    plays        = connector.episode_plays(episode_id, start, end)
    performance  = connector.episode_performance(episode_id)
    aggregated   = connector.episode_aggregated_performance(episode_id)
    metadata     = connector.episode_metadata(episode_id)
```

## Development

We use [Pipenv] for virtualenv and dev dependency management. With Pipenv
installed:

1. Install your locally checked out code in [development mode], including its
   dependencies, and all dev dependencies into a virtual environment:

```sh
pipenv sync --dev
```

2. Create an environment file and fill in the required values:

```sh
cp .env.sample .env
```

3. Run the script in the virtual environment, which will [automatically load
   your `.env`][env]:

```sh
pipenv run anchorconnector
```

To add a new dependency for use during the development of this library:

```sh
pipenv install --dev $package
```

To add a new dependency necessary for the correct operation of this library, add
the package to the `install_requires` section of `./setup.py`, then:

```sh
pipenv install
```

To publish the package:

```sh
make publish
```

[pipenv]: https://pipenv.pypa.io/en/latest/index.html#install-pipenv-today
[development mode]: https://setuptools.pypa.io/en/latest/userguide/development_mode.html
[env]: https://pipenv.pypa.io/en/latest/advanced/#automatic-loading-of-env
