From a491c79164754118077c7b1d0fc9845397d6fdc8 Mon Sep 17 00:00:00 2001 From: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Date: Tue, 2 Jun 2026 23:33:58 -0400 Subject: [PATCH 1/8] Update research docs for provider-neutral IDs --- api-reference/openapi/research.json | 547 +++++++++++++++++++++------- workflows/create-artist.mdx | 24 +- 2 files changed, 427 insertions(+), 144 deletions(-) diff --git a/api-reference/openapi/research.json b/api-reference/openapi/research.json index 92135d2f..b28893a2 100644 --- a/api-reference/openapi/research.json +++ b/api-reference/openapi/research.json @@ -892,7 +892,7 @@ }, "/api/research": { "get": { - "description": "Unified Chartmetric search. This is the discovery primitive for the detail-lookup endpoints — use `type=artists` to find an `id` for [GET /api/research/albums](/api-reference/research/albums), `type=tracks` for [GET /api/research/track](/api-reference/research/track), `type=playlists` for [GET /api/research/playlist](/api-reference/research/playlist), etc. Pick the right `id` from the returned results and pass it to the appropriate detail endpoint.\n\nThin proxy over Chartmetric's `/search`. Chartmetric's default search engine is narrow — for ambiguous queries like `Hotline Bling` or `Flowers` it often returns a single low-quality match. **Pass `beta=true` for higher relevance and accuracy**, which switches Chartmetric to its improved ranking engine. Beta results include a numeric `match_strength` score and have a slightly different shape than the default engine (see the response schema).", + "description": "Unified music research search. This is the discovery primitive for the detail-lookup endpoints — use `type=artists` to find an `id` for [GET /api/research/albums](/api-reference/research/albums), `type=tracks` for [GET /api/research/track](/api-reference/research/track), `type=playlists` for [GET /api/research/playlist](/api-reference/research/playlist), etc. Pick the right `id` from the returned results and pass it to the appropriate detail endpoint.\n\nSearches the configured research data source. Use the `type` and `limit` parameters to narrow results before passing a returned `id` to a detail endpoint.", "parameters": [ { "name": "q", @@ -935,7 +935,7 @@ "name": "beta", "in": "query", "required": false, - "description": "When `true`, switches Chartmetric to its improved beta search engine — recommended for ambiguous queries. Results come back sorted by `match_strength` (higher is better) and include `target`/`platform` fields instead of the default-mode shape.", + "description": "Legacy search option. Accepted for compatibility; ignored by providers that do not support it.", "schema": { "type": "string", "enum": [ @@ -948,10 +948,10 @@ "name": "platforms", "in": "query", "required": false, - "description": "Comma-separated platforms to include in beta results (e.g. `cm,spotify`). Only valid with `beta=true`; ignored otherwise.", + "description": "Comma-separated platforms to include in legacy search results. Accepted for compatibility; ignored by providers that do not support it.", "schema": { "type": "string", - "example": "cm,spotify" + "example": "spotify" } }, { @@ -1001,17 +1001,17 @@ }, "/api/research/albums": { "get": { - "description": "Get the album discography — albums, EPs, and singles with release dates — for a Chartmetric `artist_id`.\n\nThin proxy over Chartmetric's `/artist/:id/albums`. By default `is_primary=true`, so only albums where the artist is a main artist are returned — DJ compilations, soundtracks, and pure feature appearances are excluded. Pass `is_primary=false` to include them. Discover a Chartmetric `artist_id` via [GET /api/research](/api-reference/research/search) with `type=artists&beta=true`.", + "description": "Get the album discography — albums, EPs, and singles with release dates — for a provider `artist_id`.\n\nFetches provider data for `/artist/:id/albums`. By default `is_primary=true`, so only albums where the artist is a main artist are returned — DJ compilations, soundtracks, and pure feature appearances are excluded. Pass `is_primary=false` to include them. Discover a provider `artist_id` via [GET /api/research](/api-reference/research/search) with `type=artists`.", "parameters": [ { "name": "artist_id", "in": "query", "required": true, - "description": "Chartmetric artist ID (positive integer). Obtain via [GET /api/research](/api-reference/research/search) with `type=artists&beta=true` — the `id` field in each result is the Chartmetric ID to pass here.", + "description": "Provider artist ID. Obtain it from search or lookup results.", "schema": { "type": "string", - "pattern": "^[1-9][0-9]*$", - "example": "3380" + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" } }, { @@ -1021,7 +1021,10 @@ "description": "When `true` (default), returns only albums where the artist is a main artist. Set to `false` to include feature appearances, DJ compilations, and soundtracks.", "schema": { "type": "string", - "enum": ["true", "false"], + "enum": [ + "true", + "false" + ], "default": "true" } }, @@ -1029,7 +1032,7 @@ "name": "limit", "in": "query", "required": false, - "description": "Number of albums to return. Defaults to Chartmetric's default (100) when omitted.", + "description": "Number of albums to return. Defaults to the provider's default when omitted.", "schema": { "type": "string", "pattern": "^[1-9][0-9]*$" @@ -1058,7 +1061,7 @@ } }, "400": { - "description": "Validation error — `artist_id` missing or not a positive integer", + "description": "Validation error — `artist_id` missing or invalid", "content": { "application/json": { "schema": { @@ -1087,8 +1090,8 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID (UUID).", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } @@ -1107,6 +1110,17 @@ ], "default": "instagram" } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -1150,11 +1164,22 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID (UUID).", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -1298,11 +1323,22 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID (UUID).", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -1341,7 +1377,7 @@ }, "/api/research/curator": { "get": { - "description": "Get curator profile — name, image, follower counts across platforms, tag-based genre info, and Chartmetric playlist reach statistics. Requires a numeric Chartmetric curator ID (e.g. `2` for Spotify's own account). Discover a Chartmetric curator ID via [GET /api/research](/api-reference/research/search) with `type=curators&beta=true`. You can also find curators indirectly by looking up their playlists via [GET /api/research/playlists](/api-reference/research/playlists).", + "description": "Get curator profile — name, image, follower counts across platforms, tag-based genre info, and playlist reach statistics. Requires a numeric curator ID (e.g. `2` for Spotify's own account). Discover a curator ID via [GET /api/research](/api-reference/research/search) with `type=curators`. You can also find curators indirectly by looking up their playlists via [GET /api/research/playlists](/api-reference/research/playlists).", "parameters": [ { "name": "platform", @@ -1355,13 +1391,13 @@ "deezer" ] }, - "description": "Streaming platform. Only Spotify, Apple Music, and Deezer have curator metadata on Chartmetric. (YouTube and Amazon are not supported by this endpoint.)" + "description": "Streaming platform. Only Spotify, Apple Music, and Deezer have curator metadata from the configured provider. (YouTube and Amazon are not supported by this endpoint.)" }, { "name": "id", "in": "query", "required": true, - "description": "Numeric Chartmetric curator ID (e.g. `2` for Spotify's own account). Non-numeric values will be rejected. Obtain via [GET /api/research](/api-reference/research/search) with `type=curators&beta=true`.", + "description": "Numeric curator ID (e.g. `2` for Spotify's own account). Non-numeric values will be rejected. Obtain via [GET /api/research](/api-reference/research/search) with `type=curators`.", "schema": { "type": "string", "pattern": "^[0-9]+$", @@ -1730,11 +1766,22 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID (UUID).", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -1778,11 +1825,22 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID (UUID).", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -1826,11 +1884,21 @@ { "name": "url", "in": "query", - "required": true, - "description": "Platform URL or ID (e.g., Spotify artist URL, Spotify ID).", + "required": false, + "description": "Spotify artist URL. Required unless `spotifyId` is provided.", "schema": { "type": "string" } + }, + { + "name": "spotifyId", + "in": "query", + "required": false, + "description": "Spotify artist ID. Required unless `url` is provided.", + "schema": { + "type": "string", + "example": "3TVXtAsR1Inumwj472S9r4" + } } ], "responses": { @@ -1874,8 +1942,8 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID (UUID).", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } @@ -1904,6 +1972,17 @@ "bandsintown" ] } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -1947,11 +2026,22 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID.", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -2037,7 +2127,7 @@ }, "/api/research/playlist": { "get": { - "description": "Get playlist metadata — name, description, follower count, track count, and curator info.\n\nThin proxy over Chartmetric's `/playlist/:platform/:id` detail lookup. **`id` is Chartmetric's own numeric playlist ID, not the streaming-platform's native ID.** Discover the Chartmetric playlist ID via [GET /api/research](/api-reference/research/search) with `type=playlists&beta=true` — the `id` field in each result is the Chartmetric ID to pass here.", + "description": "Get playlist metadata — name, description, follower count, track count, and curator info.\n\nFetches provider data for `/playlist/:platform/:id` detail lookup. **`id` is the provider's playlist ID, not the streaming-platform's native ID.** Discover the provider playlist ID via [GET /api/research](/api-reference/research/search) with `type=playlists` — the `id` field in each result is the provider ID to pass here.", "parameters": [ { "name": "platform", @@ -2053,13 +2143,13 @@ "youtube" ] }, - "description": "Streaming platform the Chartmetric playlist is sourced from." + "description": "Streaming platform the the configured provider playlist is sourced from." }, { "name": "id", "in": "query", "required": true, - "description": "Chartmetric playlist ID (positive integer). This is **not** the streaming platform's native ID — e.g. Spotify's base62 ID `37i9dQZF1DXcBWIGoYBM5M` for RapCaviar maps to Chartmetric ID `848051`. Obtain via [GET /api/research](/api-reference/research/search) with `type=playlists&beta=true`.", + "description": "provider playlist ID (positive integer). This is **not** the streaming platform's native ID — e.g. Spotify's base62 ID `37i9dQZF1DXcBWIGoYBM5M` for RapCaviar maps to provider ID `848051`. Obtain via [GET /api/research](/api-reference/research/search) with `type=playlists`.", "schema": { "type": "string", "pattern": "^[1-9][0-9]*$", @@ -2108,8 +2198,8 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID (UUID).", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } @@ -2219,6 +2309,17 @@ "schema": { "type": "boolean" } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -2262,11 +2363,22 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID (UUID).", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -2305,7 +2417,7 @@ }, "/api/research/radio": { "get": { - "description": "List radio stations tracked by Chartmetric. NOT artist-scoped. Returns station names, formats, and markets.", + "description": "List radio stations tracked by the configured provider. NOT artist-scoped. Returns station names, formats, and markets.", "responses": { "200": { "description": "Radio station list", @@ -2342,16 +2454,27 @@ }, "/api/research/rank": { "get": { - "description": "Get an artist's global Chartmetric ranking as a single integer value.", + "description": "Get an artist's global the configured provider ranking as a single integer value.", "parameters": [ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID.", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -2395,8 +2518,8 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID (UUID).", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } @@ -2469,6 +2592,17 @@ "type": "integer", "default": 10 } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -2507,17 +2641,17 @@ }, "/api/research/track": { "get": { - "description": "Get full Chartmetric track metadata by numeric track `id` — title, artists, albums, release date, genres, popularity, and platform IDs.\n\nThis endpoint is a thin proxy over Chartmetric's `/track/:id` detail lookup. Discover a Chartmetric track ID via [GET /api/research](/api-reference/research/search) with `type=tracks&beta=true` — pick a result's `id`, then call this endpoint. Keeping the two concerns separate means predictable credit charging and no silent wrong-match behavior for ambiguous queries.", + "description": "Get full provider track metadata by track `id` — title, artists, albums, release date, genres, popularity, and platform IDs.\n\nThis endpoint is a thin proxy over the provider `/track/:id` detail lookup. Discover a provider track ID via [GET /api/research](/api-reference/research/search) with `type=tracks` — pick a result's `id`, then call this endpoint. Keeping the two concerns separate means predictable credit charging and no silent wrong-match behavior for ambiguous queries.", "parameters": [ { "name": "id", "in": "query", "required": true, - "description": "Chartmetric track ID (positive integer). Obtain via [GET /api/research](/api-reference/research/search) with `type=tracks&beta=true` — beta-ranked search results include the `id` field directly.", + "description": "Provider track ID. Obtain it from search results or a track lookup.", "schema": { "type": "string", - "pattern": "^[1-9][0-9]*$", - "example": "15194376" + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "track_123" } } ], @@ -2553,7 +2687,7 @@ } }, "404": { - "description": "No Chartmetric track exists with the supplied `id`", + "description": "No provider track exists with the supplied `id`", "content": { "application/json": { "schema": { @@ -2572,11 +2706,22 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID (UUID).", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -2620,11 +2765,22 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID (UUID).", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -2668,11 +2824,22 @@ { "name": "artist", "in": "query", - "required": true, - "description": "Artist name or Recoup artist ID.", + "required": false, + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" } + }, + { + "name": "id", + "in": "query", + "required": false, + "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", + "schema": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" + } } ], "responses": { @@ -2758,14 +2925,16 @@ }, "/api/research/track/playlists": { "get": { - "description": "Returns playlists featuring a specific track. Accepts a Chartmetric track ID (`id`) or a track name + optional artist (`q`, `artist`) for a Spotify-powered lookup. When no filter flag is passed, defaults to editorial + indie + majorCurator + popularIndie = `true`. Costs 5 credits per request.\n\nDiscover a Chartmetric track `id` via [GET /api/research](/api-reference/research/search) with `type=tracks&beta=true` — then pass the resulting `id` here for the most reliable match (avoids the ambiguity of name-based lookup).", + "description": "Returns playlists featuring a specific track. Accepts a provider track ID (`id`) or a track name + optional artist (`q`, `artist`) for a Spotify-powered lookup. When no filter flag is passed, defaults to editorial + indie + majorCurator + popularIndie = `true`. Costs 5 credits per request.\n\nDiscover a provider track `id` via [GET /api/research](/api-reference/research/search) with `type=tracks` — then pass the resulting `id` here for the most reliable match (avoids the ambiguity of name-based lookup).", "parameters": [ { "name": "id", "in": "query", - "description": "Chartmetric track ID. Required if `q` is not provided. Obtain via [GET /api/research](/api-reference/research/search) with `type=tracks&beta=true`.", + "description": "Provider track ID. Required if `q` is not provided.", "schema": { - "type": "string" + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "track_123" } }, { @@ -2779,10 +2948,11 @@ { "name": "artist", "in": "query", - "description": "Artist name to disambiguate the track lookup when using `q`.", + "description": "Artist name. Required unless `id` is provided.", "schema": { "type": "string" - } + }, + "required": false }, { "name": "platform", @@ -2956,7 +3126,7 @@ }, "placements": { "type": "array", - "description": "Playlist placement objects returned by Chartmetric (shape varies per platform).", + "description": "Playlist placement objects returned by the configured provider (shape varies per platform).", "items": { "type": "object" } @@ -3895,7 +4065,7 @@ }, "ResearchAudienceResponse": { "type": "object", - "description": "Audience demographics and engagement metrics from Chartmetric. Contains two parallel families of fields: **followers** (`audience_*` and `top_*`) describe the artist's follower base, while **likers** (`audience_likers_*`, `likers_top_*`) describe the people who engage (like/comment) with the artist's posts. Engagement aggregates (`followers`, `avg_likes_per_post`, `engagement_rate`) and `notable_followers` round out the payload. All array shapes are upstream Chartmetric objects that pass through via `additionalProperties`.", + "description": "Audience demographics and engagement metrics from the configured provider. Contains two parallel families of fields: **followers** (`audience_*` and `top_*`) describe the artist's follower base, while **likers** (`audience_likers_*`, `likers_top_*`) describe the people who engage (like/comment) with the artist's posts. Engagement aggregates (`followers`, `avg_likes_per_post`, `engagement_rate`) and `notable_followers` round out the payload. All array shapes are upstream provider objects that pass through via `additionalProperties`.", "properties": { "status": { "type": "string", @@ -3922,71 +4092,116 @@ "top_countries": { "type": "array", "description": "Top countries by audience share.", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "top_cities": { "type": "array", "description": "Top cities by audience concentration.", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "likers_top_countries": { "type": "array", "description": "Top countries for the likers (engagement) cohort.", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "likers_top_cities": { "type": "array", "description": "Top cities for the likers cohort.", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "audience_genders": { "type": "array", "description": "Gender breakdown of the follower base.", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "audience_genders_per_age": { "type": "array", "description": "Follower gender split per age bracket.", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "audience_ethnicities": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "audience_interests": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "audience_brand_affinities": { "type": "array", "description": "Brand affinity scores for the follower cohort.", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "audience_likers_genders": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "audience_likers_genders_per_age": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "audience_likers_ethnicities": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "audience_likers_interests": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "audience_likers_brand_affinities": { "type": "array", "description": "Brand affinity scores for the likers cohort.", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "notable_followers": { "type": "array", "description": "Notable public/influential accounts in the follower base.", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } } }, "additionalProperties": true @@ -4085,7 +4300,7 @@ }, "id": { "type": "integer", - "description": "Chartmetric curator ID." + "description": "curator ID." }, "user_id": { "type": "string", @@ -4105,32 +4320,42 @@ }, "last_updated": { "type": "string", - "description": "ISO timestamp of the last Chartmetric sync." + "description": "ISO timestamp of the last provider sync." }, "suspicion_score": { "type": "integer", - "description": "Chartmetric's internal suspicion score (lower is better)." + "description": "Provider-internal suspicion score (lower is better)." }, "tags": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "tag_ids": { "type": "array", - "items": { "type": "integer" } + "items": { + "type": "integer" + } }, "tag_names": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + } }, "spotifySocialUrls": { "type": "array", "description": "Spotify-linked social URLs for the curator.", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "cm_statistics": { "type": "object", - "description": "Aggregated Chartmetric statistics — playlist reach, editorial counts, velocity deltas, etc.", + "description": "Aggregated the configured provider statistics — playlist reach, editorial counts, velocity deltas, etc.", "additionalProperties": true }, "instagram_followers": { @@ -4532,7 +4757,7 @@ }, "ResearchInstagramPostsResponse": { "type": "object", - "description": "Top Instagram posts and reels sorted by engagement. Fields are returned directly from Chartmetric's DeepSocial integration.", + "description": "Top Instagram posts and reels sorted by engagement. Fields are returned directly from the configured provider's the configured research provider.", "properties": { "status": { "type": "string", @@ -4595,11 +4820,11 @@ }, "data": { "type": "object", - "description": "Cross-platform artist identifiers returned by Chartmetric.", + "description": "Cross-platform artist identifiers returned by the configured provider.", "properties": { "id": { "type": "integer", - "description": "Chartmetric artist ID" + "description": "provider artist ID" }, "spotify_id": { "type": "string" @@ -4632,8 +4857,12 @@ "items": { "type": "object", "properties": { - "timestp": { "type": "string" }, - "value": { "type": "number" } + "timestp": { + "type": "string" + }, + "value": { + "type": "number" + } }, "additionalProperties": true } @@ -4644,8 +4873,12 @@ "items": { "type": "object", "properties": { - "timestp": { "type": "string" }, - "value": { "type": "number" } + "timestp": { + "type": "string" + }, + "value": { + "type": "number" + } }, "additionalProperties": true } @@ -4656,8 +4889,12 @@ "items": { "type": "object", "properties": { - "timestp": { "type": "string" }, - "value": { "type": "number" } + "timestp": { + "type": "string" + }, + "value": { + "type": "number" + } }, "additionalProperties": true } @@ -4668,8 +4905,12 @@ "items": { "type": "object", "properties": { - "timestp": { "type": "string" }, - "value": { "type": "number" } + "timestp": { + "type": "string" + }, + "value": { + "type": "number" + } }, "additionalProperties": true } @@ -4841,14 +5082,14 @@ }, "ResearchPlaylistResponse": { "type": "object", - "description": "Playlist metadata — name, description, follower count, track count, curator info, and Chartmetric-derived signals (editorial flag, suspicion score, active-ratio, week/month delta, mood/activity/genre tags).", + "description": "Playlist metadata — name, description, follower count, track count, curator info, and Provider-derived signals (editorial flag, suspicion score, active-ratio, week/month delta, mood/activity/genre tags).", "properties": { "status": { "type": "string" }, "id": { "type": "integer", - "description": "Chartmetric playlist ID (same as the `id` query param)." + "description": "provider playlist ID (same as the `id` query param)." }, "name": { "type": "string" @@ -4904,11 +5145,11 @@ }, "active_ratio": { "type": "integer", - "description": "Fraction of days recently that the playlist was updated (Chartmetric-internal)." + "description": "Fraction of days recently that the playlist was updated (Provider-internal)." }, "suspicion_score": { "type": "integer", - "description": "Chartmetric's internal playlist-suspicion score (lower is better)." + "description": "Provider-internal playlist-suspicion score (lower is better)." }, "fdiff_week": { "type": "integer", @@ -4924,31 +5165,49 @@ }, "sys_last_updated": { "type": "string", - "description": "ISO timestamp Chartmetric last synced the playlist." + "description": "ISO timestamp the provider last synced the playlist." }, "genres": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "moods": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "activities": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "mood_smart_ordered": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "activity_smart_ordered": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "tags": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } } }, "additionalProperties": true @@ -4974,14 +5233,14 @@ }, "ResearchProfileResponse": { "type": "object", - "description": "Full artist profile — identity, biography, genres, geography, label/management, and Chartmetric-derived ranks/statistics. For longitudinal platform metrics (Spotify followers/listeners over time, social stats, etc.) call `GET /api/research/metrics` instead.", + "description": "Full artist profile — identity, biography, genres, geography, label/management, and Provider-derived ranks/statistics. For longitudinal platform metrics (Spotify followers/listeners over time, social stats, etc.) call `GET /api/research/metrics` instead.", "properties": { "status": { "type": "string" }, "id": { "type": "integer", - "description": "Chartmetric artist ID." + "description": "provider artist ID." }, "name": { "type": "string" @@ -5029,7 +5288,7 @@ }, "hometown_city": { "nullable": true, - "description": "Chartmetric hometown-city object (null when unknown)." + "description": "Provider hometown-city object (null when unknown)." }, "current_city": { "type": "string", @@ -5041,11 +5300,11 @@ }, "cm_artist_rank": { "type": "integer", - "description": "Overall Chartmetric artist rank (lower is better)." + "description": "Overall Provider artist rank (lower is better)." }, "cm_artist_score": { "type": "number", - "description": "Chartmetric artist-score aggregate (higher is better)." + "description": "Provider artist-score aggregate (higher is better)." }, "cm_statistics": { "type": "object", @@ -5064,7 +5323,10 @@ }, "genre_smart_ordered": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "genreRank": { "type": "object", @@ -5080,11 +5342,17 @@ }, "moods": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "activities": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "record_label": { "type": "string", @@ -5104,7 +5372,10 @@ }, "topSongwriterCollaborators": { "type": "array", - "items": { "type": "object", "additionalProperties": true } + "items": { + "type": "object", + "additionalProperties": true + } }, "created_at": { "type": "string" @@ -5125,7 +5396,7 @@ }, "stations": { "type": "array", - "description": "Radio stations tracked by Chartmetric.", + "description": "Radio stations tracked by the configured provider.", "items": { "type": "object", "additionalProperties": true @@ -5147,7 +5418,7 @@ "rank": { "type": "integer", "nullable": true, - "description": "Global Chartmetric artist rank." + "description": "Global Provider artist rank." } } }, @@ -5179,10 +5450,14 @@ "type": "string" }, "id": { - "description": "Chartmetric internal ID for use with the detail-lookup endpoints (`/api/research/track`, `/api/research/playlist`, `/api/research/albums`, etc.). Usually an integer; some playlist sources return a string.", + "description": "Provider ID for use with the detail-lookup endpoints (`/api/research/track`, `/api/research/playlist`, `/api/research/albums`, etc.). Usually an integer; some playlist sources return a string.", "oneOf": [ - { "type": "integer" }, - { "type": "string" } + { + "type": "integer" + }, + { + "type": "string" + } ] }, "image_url": { @@ -5214,7 +5489,9 @@ "artist_names": { "type": "array", "nullable": true, - "items": { "type": "string" }, + "items": { + "type": "string" + }, "description": "Default-engine tracks only." } }, @@ -5295,14 +5572,14 @@ }, "ResearchTrackResponse": { "type": "object", - "description": "Track metadata — title, artists, albums, release date, genres, popularity, and platform IDs. Only the most commonly-used fields are enumerated here; the upstream Chartmetric response includes additional fields (e.g. `cm_statistics`, `activities`, `moods`) that pass through under `additionalProperties`.", + "description": "Track metadata — title, artists, albums, release date, genres, popularity, and platform IDs. Only the most commonly-used fields are enumerated here; the upstream provider response includes additional fields (e.g. `cm_statistics`, `activities`, `moods`) that pass through under `additionalProperties`.", "properties": { "status": { "type": "string" }, "id": { "type": "integer", - "description": "Chartmetric track ID" + "description": "provider track ID" }, "name": { "type": "string", @@ -5331,7 +5608,7 @@ }, "score": { "type": "integer", - "description": "Chartmetric track score", + "description": "Provider track score", "nullable": true }, "explicit": { @@ -5340,13 +5617,13 @@ }, "artists": { "type": "array", - "description": "Credited artists. Each entry is a Chartmetric artist object; at minimum `id` and `name` are populated.", + "description": "Credited artists. Each entry is a provider artist object; at minimum `id` and `name` are populated.", "items": { "type": "object", "properties": { "id": { "type": "integer", - "description": "Chartmetric artist ID" + "description": "provider artist ID" }, "name": { "type": "string" @@ -5367,7 +5644,7 @@ "properties": { "id": { "type": "integer", - "description": "Chartmetric album ID" + "description": "provider album ID" }, "name": { "type": "string" @@ -5413,7 +5690,7 @@ }, "cm_statistics": { "type": "object", - "description": "Chartmetric's aggregated cross-platform statistics for the track — Spotify popularity/streams, TikTok video counts, YouTube views, Shazam counts, Apple/Deezer/Amazon playlist reach, weekly deltas, etc. Fields are often null for less-popular tracks. Opaque object; inspect an actual response for the full field set.", + "description": "Provider aggregated cross-platform statistics for the track — Spotify popularity/streams, TikTok video counts, YouTube views, Shazam counts, Apple/Deezer/Amazon playlist reach, weekly deltas, etc. Fields are often null for less-popular tracks. Opaque object; inspect an actual response for the full field set.", "additionalProperties": true }, "tempo": { @@ -5424,14 +5701,20 @@ "moods": { "type": "array", "nullable": true, - "description": "Chartmetric-inferred mood tags (e.g. `Happy`, `Melancholic`).", - "items": { "type": "object", "additionalProperties": true } + "description": "Provider-inferred mood tags (e.g. `Happy`, `Melancholic`).", + "items": { + "type": "object", + "additionalProperties": true + } }, "activities": { "type": "array", "nullable": true, - "description": "Chartmetric-inferred activity tags (e.g. `Workout`, `Studying`).", - "items": { "type": "object", "additionalProperties": true } + "description": "Provider-inferred activity tags (e.g. `Workout`, `Studying`).", + "items": { + "type": "object", + "additionalProperties": true + } } }, "additionalProperties": true diff --git a/workflows/create-artist.mdx b/workflows/create-artist.mdx index 99d70b1d..a172c6f7 100644 --- a/workflows/create-artist.mdx +++ b/workflows/create-artist.mdx @@ -16,7 +16,7 @@ The chain is 8 sequential API calls. Long deterministic chains executed from pro The flow has three phases, run from a single checklist file: 1. **Create + identify** — `POST /api/artists`, then find the canonical Spotify match -2. **Enrich** — `PATCH` the artist with image/label/socials, then run structured research (Chartmetric profile/career/playlists) plus a web search for narrative context and additional socials +2. **Enrich** — `PATCH` the artist with image/label/socials, then run structured research (profile/career/playlists) plus a web search for narrative context and additional socials 3. **Synthesize + persist** — generate a knowledge-base report, save it (RECOUP.md tree or hosted URL), then optionally `PATCH` the `knowledges` array ## Step 0: Scaffold the workspace BEFORE any API call @@ -36,7 +36,7 @@ artistId: spotifyArtistId: spotifyProfileUrl: imageUrl: -cmArtistId: +researchArtistId: --- # $ARTIST_NAME @@ -46,7 +46,7 @@ cmArtistId: - [ ] 1. Create the artist (\`POST /api/artists\`) — capture \`account_id\` → \`artistId\` - [ ] 2. Find canonical Spotify match (\`GET /api/spotify/search\`) — capture \`id\`, \`external_urls.spotify\`, \`images[0].url\` - [ ] 3. PATCH artist with image + Spotify profile URL -- [ ] 4. Structured research — \`/research/lookup\` (capture \`cmArtistId\`) → \`/research/profile\` + \`/research/career\` + \`/research/playlists\` + \`/research/web\`. Save responses under \`## Research\` in this file. +- [ ] 4. Structured research — \`/research/lookup\` (capture \`researchArtistId\`) → \`/research/profile\` + \`/research/career\` + \`/research/playlists\` + \`/research/web\`. Save responses under \`## Research\` in this file. - [ ] 5. Pull Spotify catalog → write \`releases/{album-slug}/RELEASE.md\` per album + \`releases/top-tracks.md\` - [ ] 6. Web search for additional socials (instagram / tiktok / twitter / youtube) - [ ] 7. PATCH artist with discovered socials @@ -141,21 +141,21 @@ This single endpoint replaces what the chat tool chain runs as four separate MCP Don't use `POST /api/research/deep` here — it tends to hang in sandboxes and returns paraphrased prose. Instead, fan out across four bounded structured endpoints (one prerequisite lookup + three structured pulls + one web search). The outputs are typed JSON the agent can use directly without paraphrasing. -### 4a: Look up the Chartmetric `artist_id` +### 4a: Look up the research artist ID -Most of the structured research endpoints take a Chartmetric `artist_id`, not the Spotify ID. Resolve it once and reuse it for the rest of step 4. +Most structured research endpoints can take the provider artist ID, not the Spotify ID. Resolve it once and reuse it for the rest of step 4. ```bash LOOKUP=$(curl -sS -G "https://api.recoupable.com/api/research/lookup" \ -H "Authorization: Bearer $RECOUP_ACCESS_TOKEN" \ --data-urlencode "spotifyId=$SPOTIFY_ARTIST_ID") -CM_ARTIST_ID=$(echo "$LOOKUP" | jq -r '.artist.id // empty') +RESEARCH_ARTIST_ID=$(echo "$LOOKUP" | jq -r '.id // .artist.id // .data.id // empty') -[ -n "$CM_ARTIST_ID" ] || { echo "No Chartmetric match for Spotify ID $SPOTIFY_ARTIST_ID — skipping structured research"; } +[ -n "$RESEARCH_ARTIST_ID" ] || { echo "No structured research match for Spotify ID $SPOTIFY_ARTIST_ID — skipping structured research"; } ``` -If the lookup fails (rare — most Spotify-discoverable artists have a Chartmetric profile), skip 4b–4d and just run 4e (web search). See [Artist Lookup](/api-reference/research/lookup) for the request/response schema. +If the lookup fails, skip 4b–4d and just run 4e (web search). See [Artist Lookup](/api-reference/research/lookup) for the request/response schema. ### 4b: Pull the artist profile @@ -164,7 +164,7 @@ Returns bio, genres, social URLs, label, career stage, and basic metrics — mos ```bash PROFILE=$(curl -sS -G "https://api.recoupable.com/api/research/profile" \ -H "Authorization: Bearer $RECOUP_ACCESS_TOKEN" \ - --data-urlencode "id=$CM_ARTIST_ID") + --data-urlencode "id=$RESEARCH_ARTIST_ID") ``` See [Artist Profile](/api-reference/research/profile) for the response schema. @@ -176,7 +176,7 @@ Career milestones, trajectory, and career-stage classification — covers the "n ```bash CAREER=$(curl -sS -G "https://api.recoupable.com/api/research/career" \ -H "Authorization: Bearer $RECOUP_ACCESS_TOKEN" \ - --data-urlencode "id=$CM_ARTIST_ID") + --data-urlencode "id=$RESEARCH_ARTIST_ID") ``` See [Career Timeline](/api-reference/research/career) for the response schema. @@ -188,7 +188,7 @@ Replaces the "playlists / radio rotations / editorial features" portion of the d ```bash PLAYLISTS=$(curl -sS -G "https://api.recoupable.com/api/research/playlists" \ -H "Authorization: Bearer $RECOUP_ACCESS_TOKEN" \ - --data-urlencode "id=$CM_ARTIST_ID") + --data-urlencode "id=$RESEARCH_ARTIST_ID") ``` See [Artist Playlists](/api-reference/research/playlists) for the response schema. @@ -207,7 +207,7 @@ RESEARCH_WEB=$(curl -sS -X POST "https://api.recoupable.com/api/research/web" \ See [Web Search](/api-reference/research/web) for the request/response schema. -**After this step:** persist `cmArtistId: $CM_ARTIST_ID` into the frontmatter, save the four response payloads (profile, career, playlists, web) into a new `## Research` section of `RECOUP.md` — one subsection per endpoint, each with the raw JSON or a tight markdown summary so step 8 can compose from it. Tick `- [ ] 4.` → `- [x] 4.`. +**After this step:** persist `researchArtistId: $RESEARCH_ARTIST_ID` into the frontmatter, save the four response payloads (profile, career, playlists, web) into a new `## Research` section of `RECOUP.md` — one subsection per endpoint, each with the raw JSON or a tight markdown summary so step 8 can compose from it. Tick `- [ ] 4.` → `- [x] 4.`. ## Step 5: Pull the Spotify catalog From 2ef5fb70f6a24a676e9f1f88de74e92b9fc0f87e Mon Sep 17 00:00:00 2001 From: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Date: Tue, 2 Jun 2026 23:39:55 -0400 Subject: [PATCH 2/8] Document unsupported research data responses --- api-reference/openapi/research.json | 108 ++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/api-reference/openapi/research.json b/api-reference/openapi/research.json index b28893a2..3b5e3d6a 100644 --- a/api-reference/openapi/research.json +++ b/api-reference/openapi/research.json @@ -995,6 +995,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1079,6 +1082,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1153,6 +1159,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1212,6 +1221,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1312,6 +1324,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1371,6 +1386,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1445,6 +1463,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1492,6 +1513,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1587,6 +1611,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1634,6 +1661,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1681,6 +1711,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1718,6 +1751,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1755,6 +1791,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1814,6 +1853,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1873,6 +1915,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -1931,6 +1976,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2015,6 +2063,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2074,6 +2125,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2121,6 +2175,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2187,6 +2244,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2352,6 +2412,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2411,6 +2474,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2448,6 +2514,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2507,6 +2576,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2635,6 +2707,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2695,6 +2770,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2754,6 +2832,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2813,6 +2894,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2872,6 +2956,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -2919,6 +3006,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -3165,6 +3255,9 @@ } } } + }, + "501": { + "$ref": "#/components/responses/ResearchDataSourceUnsupported" } } } @@ -5994,6 +6087,21 @@ } } } + }, + "responses": { + "ResearchDataSourceUnsupported": { + "description": "The configured research data source does not support this endpoint or data shape.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "example": { + "error": "Request failed with status 501" + } + } + } + } } } } From dd794ff2ac55cb7702f60ec4ffb4a3dc53f807c6 Mon Sep 17 00:00:00 2001 From: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Date: Wed, 3 Jun 2026 00:16:48 -0400 Subject: [PATCH 3/8] fix: align research OpenAPI review feedback --- api-reference/openapi/research.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api-reference/openapi/research.json b/api-reference/openapi/research.json index 3b5e3d6a..f439a915 100644 --- a/api-reference/openapi/research.json +++ b/api-reference/openapi/research.json @@ -2200,7 +2200,7 @@ "youtube" ] }, - "description": "Streaming platform the the configured provider playlist is sourced from." + "description": "Streaming platform the configured provider playlist is sourced from." }, { "name": "id", @@ -6094,9 +6094,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/ResearchErrorResponse" }, "example": { + "status": "error", "error": "Request failed with status 501" } } From ee29c138a42202c069f1c49ee2c3a6379fbc3896 Mon Sep 17 00:00:00 2001 From: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Date: Wed, 3 Jun 2026 01:36:56 -0400 Subject: [PATCH 4/8] docs: include SongStats radio metric sources --- api-reference/openapi/research.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api-reference/openapi/research.json b/api-reference/openapi/research.json index f439a915..ebf3e7f2 100644 --- a/api-reference/openapi/research.json +++ b/api-reference/openapi/research.json @@ -1985,7 +1985,7 @@ }, "/api/research/metrics": { "get": { - "description": "Get platform-specific metrics for an artist over time — followers, listeners, views, engagement. Supports 14 platforms.", + "description": "Get platform-specific metrics for an artist over time — followers, listeners, views, engagement, and radio airplay. Supports 16 sources.", "parameters": [ { "name": "artist", @@ -2017,7 +2017,9 @@ "line", "melon", "wikipedia", - "bandsintown" + "bandsintown", + "radio", + "sxm" ] } }, From 5e356ce8fea60549dd32a24899755135b2116355 Mon Sep 17 00:00:00 2001 From: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Date: Wed, 3 Jun 2026 01:49:30 -0400 Subject: [PATCH 5/8] docs: clarify related artist research --- api-reference/openapi/research.json | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/api-reference/openapi/research.json b/api-reference/openapi/research.json index ebf3e7f2..f9abce1b 100644 --- a/api-reference/openapi/research.json +++ b/api-reference/openapi/research.json @@ -2525,7 +2525,7 @@ }, "/api/research/rank": { "get": { - "description": "Get an artist's global the configured provider ranking as a single integer value.", + "description": "Get an artist's global ranking as a single integer value when the configured research source supports that ranking.", "parameters": [ { "name": "artist", @@ -2587,7 +2587,7 @@ }, "/api/research/similar": { "get": { - "description": "Find similar artists based on audience overlap, genre, mood, and musicality. Use for competitive analysis and collaboration discovery.", + "description": "Find similar or related artists for competitive analysis and collaboration discovery. Weight parameters are applied when the configured research source supports weighted matching; otherwise the endpoint returns the closest available related-artist set.", "parameters": [ { "name": "artist", @@ -5513,7 +5513,7 @@ "rank": { "type": "integer", "nullable": true, - "description": "Global Provider artist rank." + "description": "Global artist rank from the configured research source." } } }, @@ -5599,7 +5599,15 @@ "type": "string" }, "id": { - "type": "integer" + "oneOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Provider artist ID." }, "similarity": { "type": "number", From 65855af54442d7776318feb1cd97a43b18ae2728 Mon Sep 17 00:00:00 2001 From: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Date: Wed, 3 Jun 2026 02:47:49 -0400 Subject: [PATCH 6/8] docs: document research metrics refresh pending --- api-reference/openapi/research.json | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/api-reference/openapi/research.json b/api-reference/openapi/research.json index f9abce1b..7b727a78 100644 --- a/api-reference/openapi/research.json +++ b/api-reference/openapi/research.json @@ -2046,6 +2046,16 @@ } } }, + "202": { + "description": "Metrics refresh is pending. Retry this endpoint shortly.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResearchRefreshPendingResponse" + } + } + } + }, "400": { "description": "Validation error", "content": { @@ -5013,6 +5023,35 @@ }, "additionalProperties": true }, + "ResearchRefreshPendingResponse": { + "type": "object", + "required": [ + "status", + "state", + "message" + ], + "description": "Returned when metrics are being refreshed and no cached result is available yet.", + "properties": { + "status": { + "type": "string", + "enum": [ + "pending" + ], + "example": "pending" + }, + "state": { + "type": "string", + "enum": [ + "refresh_pending" + ], + "example": "refresh_pending" + }, + "message": { + "type": "string", + "example": "Research metrics refresh is pending. Retry this endpoint shortly." + } + } + }, "ResearchMilestone": { "type": "object", "properties": { From 985be19e17335e54b36892b12674a4503bea57d0 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 3 Jun 2026 19:54:20 -0500 Subject: [PATCH 7/8] docs: remove Chartmetric-only research endpoints (SongStats-only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the decision to fully abandon Chartmetric (YAGNI), drop the 10 research endpoints with no SongStats equivalent — they would only ever return 501: charts, cities, curator, discover, festivals, genres, instagram-posts, playlist (singular), radio, venues. - research.json: remove the 10 paths + 15 now-orphaned response schemas - delete the 10 endpoint reference pages - docs.json: drop their nav entries; collapse the now-empty "Charts & radio" and "Touring & live" groups; rename "Playlists & curators" -> "Playlists" Kept: 16 SongStats-backed + 5 provider-independent (web/deep/people via Perplexity/Exa, enrich/extract via Parallel). mintlify validate passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- api-reference/openapi/research.json | 1217 +------------------- api-reference/research/charts.mdx | 4 - api-reference/research/cities.mdx | 4 - api-reference/research/curator.mdx | 4 - api-reference/research/discover.mdx | 4 - api-reference/research/festivals.mdx | 4 - api-reference/research/genres.mdx | 4 - api-reference/research/instagram-posts.mdx | 4 - api-reference/research/playlist.mdx | 4 - api-reference/research/radio.mdx | 4 - api-reference/research/venues.mdx | 4 - docs.json | 24 +- 12 files changed, 8 insertions(+), 1273 deletions(-) delete mode 100644 api-reference/research/charts.mdx delete mode 100644 api-reference/research/cities.mdx delete mode 100644 api-reference/research/curator.mdx delete mode 100644 api-reference/research/discover.mdx delete mode 100644 api-reference/research/festivals.mdx delete mode 100644 api-reference/research/genres.mdx delete mode 100644 api-reference/research/instagram-posts.mdx delete mode 100644 api-reference/research/playlist.mdx delete mode 100644 api-reference/research/radio.mdx delete mode 100644 api-reference/research/venues.mdx diff --git a/api-reference/openapi/research.json b/api-reference/openapi/research.json index 7b727a78..7ffc8da7 100644 --- a/api-reference/openapi/research.json +++ b/api-reference/openapi/research.json @@ -1228,248 +1228,6 @@ } } }, - "/api/research/charts": { - "get": { - "description": "Get global chart positions for a platform — Spotify, Apple Music, TikTok, YouTube, iTunes, Shazam, etc. NOT artist-scoped. Returns ranked entries with track and artist info.", - "parameters": [ - { - "name": "platform", - "in": "query", - "required": true, - "description": "Chart platform: spotify, applemusic, tiktok, youtube, itunes, shazam, etc.", - "schema": { - "type": "string", - "pattern": "^[a-z]+$" - } - }, - { - "name": "country", - "in": "query", - "required": false, - "description": "Two-letter ISO country code (e.g. US, GB, DE). Defaults to `US` if omitted.", - "schema": { - "type": "string", - "default": "US", - "minLength": 2, - "maxLength": 2, - "pattern": "^[A-Z]{2}$" - } - }, - { - "name": "interval", - "in": "query", - "required": false, - "description": "Time interval: `daily` or `weekly`. Defaults to `daily`.", - "schema": { - "type": "string", - "default": "daily", - "enum": [ - "daily", - "weekly" - ] - } - }, - { - "name": "type", - "in": "query", - "required": false, - "description": "Chart type (e.g. `regional`, `viral`). Defaults to `regional`.", - "schema": { - "type": "string", - "default": "regional", - "enum": [ - "regional", - "viral" - ] - } - }, - { - "name": "latest", - "in": "query", - "required": false, - "description": "Return only the latest chart. Defaults to `true`.", - "schema": { - "type": "boolean", - "default": true - } - } - ], - "responses": { - "200": { - "description": "Chart data", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchChartsResponse" - } - } - } - }, - "400": { - "description": "Validation error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "401": { - "description": "Authentication failed — invalid or missing API key", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "501": { - "$ref": "#/components/responses/ResearchDataSourceUnsupported" - } - } - } - }, - "/api/research/cities": { - "get": { - "description": "Get the top cities where an artist's fans listen, ranked by listener concentration.", - "parameters": [ - { - "name": "artist", - "in": "query", - "required": false, - "description": "Artist name. Required unless `id` is provided.", - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "query", - "required": false, - "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", - "schema": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", - "example": "artist_123" - } - } - ], - "responses": { - "200": { - "description": "Top listener cities", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchCitiesResponse" - } - } - } - }, - "400": { - "description": "Validation error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "401": { - "description": "Authentication failed — invalid or missing API key", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "501": { - "$ref": "#/components/responses/ResearchDataSourceUnsupported" - } - } - } - }, - "/api/research/curator": { - "get": { - "description": "Get curator profile — name, image, follower counts across platforms, tag-based genre info, and playlist reach statistics. Requires a numeric curator ID (e.g. `2` for Spotify's own account). Discover a curator ID via [GET /api/research](/api-reference/research/search) with `type=curators`. You can also find curators indirectly by looking up their playlists via [GET /api/research/playlists](/api-reference/research/playlists).", - "parameters": [ - { - "name": "platform", - "in": "query", - "required": true, - "schema": { - "type": "string", - "enum": [ - "spotify", - "applemusic", - "deezer" - ] - }, - "description": "Streaming platform. Only Spotify, Apple Music, and Deezer have curator metadata from the configured provider. (YouTube and Amazon are not supported by this endpoint.)" - }, - { - "name": "id", - "in": "query", - "required": true, - "description": "Numeric curator ID (e.g. `2` for Spotify's own account). Non-numeric values will be rejected. Obtain via [GET /api/research](/api-reference/research/search) with `type=curators`.", - "schema": { - "type": "string", - "pattern": "^[0-9]+$", - "example": "2" - } - } - ], - "responses": { - "200": { - "description": "Curator profile", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchCuratorResponse" - } - } - } - }, - "400": { - "description": "Validation error — missing/invalid `platform` or `id`.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "401": { - "description": "Authentication failed — invalid or missing API key", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "404": { - "description": "Curator not found for the given platform/id.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "501": { - "$ref": "#/components/responses/ResearchDataSourceUnsupported" - } - } - } - }, "/api/research/deep": { "post": { "description": "Perform deep, comprehensive research on a topic. Browses multiple sources extensively and returns a cited report. Use for full artist deep dives, competitive analysis, and any research requiring synthesis across many sources.", @@ -1520,104 +1278,6 @@ } } }, - "/api/research/discover": { - "get": { - "description": "Discover artists by criteria — filter by country, genre, Spotify monthly listener range, and sort by growth metrics.", - "parameters": [ - { - "name": "country", - "in": "query", - "required": false, - "description": "ISO country code (e.g., US, BR, GB).", - "schema": { - "type": "string", - "minLength": 2, - "maxLength": 2 - } - }, - { - "name": "genre", - "in": "query", - "required": false, - "description": "Genre ID (use GET /api/research/genres to list).", - "schema": { - "type": "integer" - } - }, - { - "name": "sp_monthly_listeners_min", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "sp_monthly_listeners_max", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "sort", - "in": "query", - "required": false, - "description": "Sort field (e.g., weekly_diff.sp_monthly_listeners).", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "default": 20, - "minimum": 1, - "maximum": 100 - } - } - ], - "responses": { - "200": { - "description": "Filtered artist list", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchDiscoverResponse" - } - } - } - }, - "400": { - "description": "Validation error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "401": { - "description": "Authentication failed — invalid or missing API key", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "501": { - "$ref": "#/components/responses/ResearchDataSourceUnsupported" - } - } - } - }, "/api/research/enrich": { "post": { "description": "Enrich an entity with structured data from web research. Provide a description of who or what to research and a JSON schema defining the fields to extract. Returns typed data with citations. **Important:** The `schema` object must include `\"type\": \"object\"` at the top level — requests without an explicit type will be rejected.", @@ -1718,86 +1378,6 @@ } } }, - "/api/research/festivals": { - "get": { - "description": "List music festivals.", - "responses": { - "200": { - "description": "Festival list", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchFestivalsResponse" - } - } - } - }, - "400": { - "description": "Validation error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "401": { - "description": "Authentication failed — invalid or missing API key", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "501": { - "$ref": "#/components/responses/ResearchDataSourceUnsupported" - } - } - } - }, - "/api/research/genres": { - "get": { - "description": "List all available genre IDs and names. Use these IDs with the discover endpoint at GET /api/research/discover.", - "responses": { - "200": { - "description": "Genre list", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchGenresResponse" - } - } - } - }, - "400": { - "description": "Validation error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "401": { - "description": "Authentication failed — invalid or missing API key", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "501": { - "$ref": "#/components/responses/ResearchDataSourceUnsupported" - } - } - } - }, "/api/research/insights": { "get": { "description": "Get AI-generated insights about an artist — automatically surfaced trends, milestones, and observations.", @@ -1860,68 +1440,6 @@ } } }, - "/api/research/instagram-posts": { - "get": { - "description": "Get an artist's top Instagram posts and reels, sorted by engagement.", - "parameters": [ - { - "name": "artist", - "in": "query", - "required": false, - "description": "Artist name. Required unless `id` is provided.", - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "query", - "required": false, - "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", - "schema": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", - "example": "artist_123" - } - } - ], - "responses": { - "200": { - "description": "Top Instagram posts and reels", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchInstagramPostsResponse" - } - } - } - }, - "400": { - "description": "Validation error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "401": { - "description": "Authentication failed — invalid or missing API key", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "501": { - "$ref": "#/components/responses/ResearchDataSourceUnsupported" - } - } - } - }, "/api/research/lookup": { "get": { "description": "Look up an artist by a platform URL or ID — Spotify URL, Spotify ID, Apple Music URL, etc.", @@ -2194,75 +1712,6 @@ } } }, - "/api/research/playlist": { - "get": { - "description": "Get playlist metadata — name, description, follower count, track count, and curator info.\n\nFetches provider data for `/playlist/:platform/:id` detail lookup. **`id` is the provider's playlist ID, not the streaming-platform's native ID.** Discover the provider playlist ID via [GET /api/research](/api-reference/research/search) with `type=playlists` — the `id` field in each result is the provider ID to pass here.", - "parameters": [ - { - "name": "platform", - "in": "query", - "required": true, - "schema": { - "type": "string", - "enum": [ - "spotify", - "applemusic", - "deezer", - "amazon", - "youtube" - ] - }, - "description": "Streaming platform the configured provider playlist is sourced from." - }, - { - "name": "id", - "in": "query", - "required": true, - "description": "provider playlist ID (positive integer). This is **not** the streaming platform's native ID — e.g. Spotify's base62 ID `37i9dQZF1DXcBWIGoYBM5M` for RapCaviar maps to provider ID `848051`. Obtain via [GET /api/research](/api-reference/research/search) with `type=playlists`.", - "schema": { - "type": "string", - "pattern": "^[1-9][0-9]*$", - "example": "848051" - } - } - ], - "responses": { - "200": { - "description": "Playlist metadata", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchPlaylistResponse" - } - } - } - }, - "400": { - "description": "Validation error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "401": { - "description": "Authentication failed — invalid or missing API key", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "501": { - "$ref": "#/components/responses/ResearchDataSourceUnsupported" - } - } - } - }, "/api/research/playlists": { "get": { "description": "Get an artist's playlist placements — editorial, algorithmic, and indie playlists across platforms.", @@ -2451,58 +1900,18 @@ "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", "schema": { "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", - "example": "artist_123" - } - } - ], - "responses": { - "200": { - "description": "Artist profile", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchProfileResponse" - } - } - } - }, - "400": { - "description": "Validation error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "401": { - "description": "Authentication failed — invalid or missing API key", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "example": "artist_123" } - }, - "501": { - "$ref": "#/components/responses/ResearchDataSourceUnsupported" } - } - } - }, - "/api/research/radio": { - "get": { - "description": "List radio stations tracked by the configured provider. NOT artist-scoped. Returns station names, formats, and markets.", + ], "responses": { "200": { - "description": "Radio station list", + "description": "Artist profile", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ResearchRadioResponse" + "$ref": "#/components/schemas/ResearchProfileResponse" } } } @@ -2913,68 +2322,6 @@ } } }, - "/api/research/venues": { - "get": { - "description": "Get venues an artist has performed at, including capacity and location data.", - "parameters": [ - { - "name": "artist", - "in": "query", - "required": false, - "description": "Artist name. Required unless `id` is provided.", - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "query", - "required": false, - "description": "Provider artist ID returned by lookup or search. Required unless `artist` is provided.", - "schema": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", - "example": "artist_123" - } - } - ], - "responses": { - "200": { - "description": "Venue list", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchVenuesResponse" - } - } - } - }, - "400": { - "description": "Validation error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "401": { - "description": "Authentication failed — invalid or missing API key", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchErrorResponse" - } - } - } - }, - "501": { - "$ref": "#/components/responses/ResearchDataSourceUnsupported" - } - } - } - }, "/api/research/web": { "post": { "description": "Search the web for real-time information. Returns ranked results with titles, URLs, and content snippets. Use for narrative context, press coverage, and cultural research that structured data endpoints don't cover.", @@ -4329,183 +3676,6 @@ } } }, - "ResearchChartsResponse": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "success", - "error" - ], - "example": "success" - }, - "length": { - "type": "integer", - "description": "Number of entries returned in `data` (typically 50)." - }, - "data": { - "type": "array", - "description": "Chart entries for the requested platform/type/country/interval combination. Entry shape varies by platform; common fields include track/artist names, ranks, and platform-specific metrics.", - "items": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "ResearchCitiesEntry": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "City name." - }, - "country": { - "type": "string", - "description": "ISO country code." - }, - "listeners": { - "type": "integer", - "description": "Listener count in this city." - } - } - }, - "ResearchCitiesResponse": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "success", - "error" - ], - "example": "success" - }, - "cities": { - "type": "array", - "description": "Cities ranked by listener concentration.", - "items": { - "$ref": "#/components/schemas/ResearchCitiesEntry" - } - } - } - }, - "ResearchCuratorResponse": { - "type": "object", - "description": "Curator profile. Follower counts are split by platform (`instagram_followers`, `facebook_followers`, `twitter_followers`, `youtube_subscribers`, `soundcloud_followers`, `tiktok_followers`), each nullable when unknown. Aggregated signals live under `cm_statistics`.", - "properties": { - "status": { - "type": "string", - "enum": [ - "success", - "error" - ], - "example": "success" - }, - "id": { - "type": "integer", - "description": "curator ID." - }, - "user_id": { - "type": "string", - "description": "Platform-native curator/user ID (e.g. Spotify user ID)." - }, - "name": { - "type": "string" - }, - "image_url": { - "type": "string", - "format": "uri", - "nullable": true - }, - "submithub_id": { - "type": "string", - "nullable": true - }, - "last_updated": { - "type": "string", - "description": "ISO timestamp of the last provider sync." - }, - "suspicion_score": { - "type": "integer", - "description": "Provider-internal suspicion score (lower is better)." - }, - "tags": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "tag_ids": { - "type": "array", - "items": { - "type": "integer" - } - }, - "tag_names": { - "type": "array", - "items": { - "type": "string" - } - }, - "spotifySocialUrls": { - "type": "array", - "description": "Spotify-linked social URLs for the curator.", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "cm_statistics": { - "type": "object", - "description": "Aggregated the configured provider statistics — playlist reach, editorial counts, velocity deltas, etc.", - "additionalProperties": true - }, - "instagram_followers": { - "type": "integer", - "nullable": true - }, - "facebook_followers": { - "type": "integer", - "nullable": true - }, - "facebook_fans": { - "type": "integer", - "nullable": true - }, - "twitter_followers": { - "type": "integer", - "nullable": true - }, - "twitter_retweets": { - "type": "integer", - "nullable": true - }, - "youtube_subscribers": { - "type": "integer", - "nullable": true - }, - "youtube_views": { - "type": "integer", - "nullable": true - }, - "soundcloud_followers": { - "type": "integer", - "nullable": true - }, - "tiktok_followers": { - "type": "integer", - "nullable": true - }, - "tiktok_likes": { - "type": "integer", - "nullable": true - } - }, - "additionalProperties": true - }, "ResearchDeepRequest": { "type": "object", "required": [ @@ -4540,59 +3710,7 @@ "description": "Source URLs cited in the report.", "items": { "type": "string", - "format": "uri" - } - } - } - }, - "ResearchDiscoverArtist": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "sp_monthly_listeners": { - "type": "integer", - "nullable": true - }, - "sp_followers": { - "type": "integer", - "nullable": true - }, - "tiktok_followers": { - "type": "integer", - "nullable": true - }, - "ins_followers": { - "type": "integer", - "nullable": true - }, - "country": { - "type": "string", - "nullable": true, - "description": "ISO country code." - } - }, - "additionalProperties": true - }, - "ResearchDiscoverResponse": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "success", - "error" - ], - "example": "success" - }, - "artists": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ResearchDiscoverArtist" + "format": "uri" } } } @@ -4765,72 +3883,6 @@ } } }, - "ResearchFestival": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "city": { - "type": "string", - "nullable": true - }, - "country": { - "type": "string", - "nullable": true - } - }, - "additionalProperties": true - }, - "ResearchFestivalsResponse": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "success", - "error" - ], - "example": "success" - }, - "festivals": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ResearchFestival" - } - } - } - }, - "ResearchGenre": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "id": { - "type": "integer" - } - } - }, - "ResearchGenresResponse": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "success", - "error" - ], - "example": "success" - }, - "genres": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ResearchGenre" - } - } - } - }, "ResearchInsight": { "type": "object", "properties": { @@ -4860,61 +3912,6 @@ } } }, - "ResearchInstagramPostsResponse": { - "type": "object", - "description": "Top Instagram posts and reels sorted by engagement. Fields are returned directly from the configured provider's the configured research provider.", - "properties": { - "status": { - "type": "string", - "example": "success" - }, - "top_posts": { - "type": "array", - "description": "Top posts ranked by engagement.", - "items": { - "type": "object", - "properties": { - "url": { - "type": "string" - }, - "likes": { - "type": "integer" - }, - "comments": { - "type": "integer" - }, - "timestamp": { - "type": "string" - } - }, - "additionalProperties": true - } - }, - "top_reels": { - "type": "array", - "description": "Top reels ranked by engagement.", - "items": { - "type": "object", - "properties": { - "url": { - "type": "string" - }, - "likes": { - "type": "integer" - }, - "comments": { - "type": "integer" - }, - "timestamp": { - "type": "string" - } - }, - "additionalProperties": true - } - } - }, - "additionalProperties": true - }, "ResearchLookupResponse": { "type": "object", "description": "Artist profile resolved from a platform URL or ID. Cross-platform IDs are nested inside a `data` object.", @@ -5214,138 +4211,6 @@ }, "additionalProperties": true }, - "ResearchPlaylistResponse": { - "type": "object", - "description": "Playlist metadata — name, description, follower count, track count, curator info, and Provider-derived signals (editorial flag, suspicion score, active-ratio, week/month delta, mood/activity/genre tags).", - "properties": { - "status": { - "type": "string" - }, - "id": { - "type": "integer", - "description": "provider playlist ID (same as the `id` query param)." - }, - "name": { - "type": "string" - }, - "description": { - "type": "string", - "nullable": true - }, - "image_url": { - "type": "string", - "nullable": true - }, - "playlist_id": { - "type": "string", - "description": "Platform-native playlist ID (e.g. Spotify base62)." - }, - "owner_name": { - "type": "string", - "description": "Curator display name (what used to be advertised as `curator_name`).", - "nullable": true - }, - "owner_id": { - "type": "integer", - "nullable": true - }, - "user_id": { - "type": "string", - "nullable": true - }, - "followers": { - "type": "integer", - "nullable": true - }, - "num_track": { - "type": "integer", - "description": "Track count on the playlist (note: field is `num_track`, not `num_tracks`)." - }, - "editorial": { - "type": "boolean", - "description": "True if this is a first-party editorial playlist (e.g. Spotify's RapCaviar)." - }, - "personalized": { - "type": "boolean" - }, - "code2": { - "type": "string", - "nullable": true, - "description": "ISO-3166 alpha-2 country code associated with the playlist, when known." - }, - "catalog": { - "type": "string", - "nullable": true - }, - "active_ratio": { - "type": "integer", - "description": "Fraction of days recently that the playlist was updated (Provider-internal)." - }, - "suspicion_score": { - "type": "integer", - "description": "Provider-internal playlist-suspicion score (lower is better)." - }, - "fdiff_week": { - "type": "integer", - "description": "Follower change over the last week." - }, - "fdiff_month": { - "type": "integer", - "description": "Follower change over the last month." - }, - "last_updated": { - "type": "string", - "description": "ISO timestamp the playlist was last modified upstream." - }, - "sys_last_updated": { - "type": "string", - "description": "ISO timestamp the provider last synced the playlist." - }, - "genres": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "moods": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "activities": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "mood_smart_ordered": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "activity_smart_ordered": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "tags": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - } - }, - "additionalProperties": true - }, "ResearchPlaylistsResponse": { "type": "object", "properties": { @@ -5517,27 +4382,6 @@ }, "additionalProperties": true }, - "ResearchRadioResponse": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "success", - "error" - ], - "example": "success" - }, - "stations": { - "type": "array", - "description": "Radio stations tracked by the configured provider.", - "items": { - "type": "object", - "additionalProperties": true - } - } - } - }, "ResearchRankResponse": { "type": "object", "properties": { @@ -5912,55 +4756,6 @@ } } }, - "ResearchVenue": { - "type": "object", - "properties": { - "venue_name": { - "type": "string", - "description": "Name of the venue." - }, - "venue_capacity": { - "type": "integer", - "nullable": true, - "description": "Venue capacity (seats or standing)." - }, - "city_name": { - "type": "string", - "description": "City where the venue is located." - }, - "country": { - "type": "string", - "description": "Two-letter country code." - }, - "events": { - "type": "array", - "description": "Events the artist performed at this venue.", - "items": { - "type": "object", - "additionalProperties": true - } - } - } - }, - "ResearchVenuesResponse": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "success", - "error" - ], - "example": "success" - }, - "venues": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ResearchVenue" - } - } - } - }, "ResearchWebRequest": { "type": "object", "required": [ diff --git a/api-reference/research/charts.mdx b/api-reference/research/charts.mdx deleted file mode 100644 index 1c490447..00000000 --- a/api-reference/research/charts.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Charts' -openapi: "/api-reference/openapi/research.json GET /api/research/charts" ---- diff --git a/api-reference/research/cities.mdx b/api-reference/research/cities.mdx deleted file mode 100644 index ff425397..00000000 --- a/api-reference/research/cities.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Listener Cities' -openapi: "/api-reference/openapi/research.json GET /api/research/cities" ---- diff --git a/api-reference/research/curator.mdx b/api-reference/research/curator.mdx deleted file mode 100644 index f9fd7a38..00000000 --- a/api-reference/research/curator.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Curator' -openapi: "/api-reference/openapi/research.json GET /api/research/curator" ---- diff --git a/api-reference/research/discover.mdx b/api-reference/research/discover.mdx deleted file mode 100644 index 60de0f9a..00000000 --- a/api-reference/research/discover.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Discover Artists' -openapi: "/api-reference/openapi/research.json GET /api/research/discover" ---- diff --git a/api-reference/research/festivals.mdx b/api-reference/research/festivals.mdx deleted file mode 100644 index 896d15c7..00000000 --- a/api-reference/research/festivals.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Festivals' -openapi: "/api-reference/openapi/research.json GET /api/research/festivals" ---- diff --git a/api-reference/research/genres.mdx b/api-reference/research/genres.mdx deleted file mode 100644 index 050cefe1..00000000 --- a/api-reference/research/genres.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Genres' -openapi: "/api-reference/openapi/research.json GET /api/research/genres" ---- diff --git a/api-reference/research/instagram-posts.mdx b/api-reference/research/instagram-posts.mdx deleted file mode 100644 index 5ec8adba..00000000 --- a/api-reference/research/instagram-posts.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Instagram Posts' -openapi: "/api-reference/openapi/research.json GET /api/research/instagram-posts" ---- diff --git a/api-reference/research/playlist.mdx b/api-reference/research/playlist.mdx deleted file mode 100644 index 2b90c61f..00000000 --- a/api-reference/research/playlist.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Playlist' -openapi: "/api-reference/openapi/research.json GET /api/research/playlist" ---- diff --git a/api-reference/research/radio.mdx b/api-reference/research/radio.mdx deleted file mode 100644 index feed4173..00000000 --- a/api-reference/research/radio.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Radio Stations' -openapi: "/api-reference/openapi/research.json GET /api/research/radio" ---- diff --git a/api-reference/research/venues.mdx b/api-reference/research/venues.mdx deleted file mode 100644 index 00727b55..00000000 --- a/api-reference/research/venues.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Venues' -openapi: "/api-reference/openapi/research.json GET /api/research/venues" ---- diff --git a/docs.json b/docs.json index 43aa4750..274985bb 100644 --- a/docs.json +++ b/docs.json @@ -106,8 +106,6 @@ "pages": [ "api-reference/research/metrics", "api-reference/research/audience", - "api-reference/research/cities", - "api-reference/research/genres", "api-reference/research/insights", "api-reference/research/career", "api-reference/research/milestones", @@ -125,33 +123,15 @@ ] }, { - "group": "Playlists & curators", + "group": "Playlists", "pages": [ "api-reference/research/playlists", - "api-reference/research/playlist", - "api-reference/research/track-playlists", - "api-reference/research/curator" - ] - }, - { - "group": "Charts & radio", - "pages": [ - "api-reference/research/charts", - "api-reference/research/radio", - "api-reference/research/discover" - ] - }, - { - "group": "Touring & live", - "pages": [ - "api-reference/research/festivals", - "api-reference/research/venues" + "api-reference/research/track-playlists" ] }, { "group": "Web & social", "pages": [ - "api-reference/research/instagram-posts", "api-reference/research/web", "api-reference/research/deep", "api-reference/research/people", From 531de15b618e6bc27a2f580b99426153b8312505 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 3 Jun 2026 21:12:53 -0500 Subject: [PATCH 8/8] docs: drop dead beta/platforms search params (Chartmetric-only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `beta` (Chartmetric beta ranking engine) and `platforms` had no SongStats equivalent — the mapper strips them via withoutLegacySearchParams before the call, so they did nothing. Remove from the search contract; active params are q, type, limit, offset. mintlify validate passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- api-reference/openapi/research.json | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/api-reference/openapi/research.json b/api-reference/openapi/research.json index 7ffc8da7..bd43fcc1 100644 --- a/api-reference/openapi/research.json +++ b/api-reference/openapi/research.json @@ -931,29 +931,6 @@ "default": 10 } }, - { - "name": "beta", - "in": "query", - "required": false, - "description": "Legacy search option. Accepted for compatibility; ignored by providers that do not support it.", - "schema": { - "type": "string", - "enum": [ - "true", - "false" - ] - } - }, - { - "name": "platforms", - "in": "query", - "required": false, - "description": "Comma-separated platforms to include in legacy search results. Accepted for compatibility; ignored by providers that do not support it.", - "schema": { - "type": "string", - "example": "spotify" - } - }, { "name": "offset", "in": "query",