From 3bddec3f59b648b0491503c13b4dbf1fd7a4ac79 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 4 Jun 2026 09:45:50 -0500 Subject: [PATCH 1/2] docs(research): align response schemas with SongStats implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The provider-neutral migration (docs#232) made IDs neutral and dropped Chartmetric-only routes, but left the /research/* response schemas in Chartmetric shape. Verified against the live api#635 preview and the official SongStats SDK, this updates the documented contract to the actual SongStats-shaped payloads. - Add shared Songstats* component schemas (ArtistRef, ArtistInfo, Activity, CatalogItem, TrackInfo, MetricStat, Link, AudioAnalysis…). - lookup/profile -> {status, result, message, artist_info}. - metrics -> {status, result, message, stats[], artist_info, source_ids}. - audience -> {status, result, message, audience[], artist_info, source_ids}. - track -> {status, result, message, track_info, audio_analysis[]}. - career/insights/milestones -> shared SongstatsActivity items. - albums/tracks -> shared SongstatsCatalogItem items. - similar -> drop `total`; items are provider-neutral artist refs. - search: restrict `type` enum to artists/tracks/labels (SongStats-supported). - rank: document as 501 (SongStats has no rank endpoint); drop the 200. playlists is intentionally unchanged — it returns a metric descriptor instead of placements, which is an api-side fix tracked in api#635. Refs recoupable/chat#1777 Co-Authored-By: Claude Opus 4.8 (1M context) --- api-reference/openapi/research.json | 1166 +++++++++++---------------- 1 file changed, 453 insertions(+), 713 deletions(-) diff --git a/api-reference/openapi/research.json b/api-reference/openapi/research.json index bd43fcc1..f56e1046 100644 --- a/api-reference/openapi/research.json +++ b/api-reference/openapi/research.json @@ -907,16 +907,13 @@ "name": "type", "in": "query", "required": false, - "description": "Entity type to search.", + "description": "Entity type to search. Songstats supports artists, tracks, and labels.", "schema": { "type": "string", "enum": [ "artists", "tracks", - "albums", - "playlists", - "curators", - "stations" + "labels" ], "default": "artists" } @@ -1921,7 +1918,7 @@ }, "/api/research/rank": { "get": { - "description": "Get an artist's global ranking as a single integer value when the configured research source supports that ranking.", + "description": "Artist global rank. **Not supported by the current data source (Songstats has no rank endpoint)** — always returns `501`.", "parameters": [ { "name": "artist", @@ -1945,16 +1942,6 @@ } ], "responses": { - "200": { - "description": "Artist rank", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResearchRankResponse" - } - } - } - }, "400": { "description": "Validation error", "content": { @@ -3457,187 +3444,60 @@ } } }, - "ResearchAlbum": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "release_date": { - "type": "string", - "nullable": true - } - }, - "additionalProperties": true - }, "ResearchAlbumsResponse": { "type": "object", + "description": "Artist catalog from Songstats `/artists/catalog`.", "properties": { "status": { "type": "string", - "enum": [ - "success", - "error" - ], "example": "success" }, "albums": { "type": "array", "items": { - "$ref": "#/components/schemas/ResearchAlbum" + "$ref": "#/components/schemas/SongstatsCatalogItem" } } } }, "ResearchAudienceResponse": { "type": "object", - "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`.", + "description": "Audience demographics from Songstats `/artists/audience` for the requested `platform`.", "properties": { "status": { "type": "string", "example": "success" }, - "timestp": { + "result": { "type": "string", - "description": "ISO timestamp at which this audience snapshot was captured." - }, - "followers": { - "type": "integer", - "description": "Follower count on the underlying platform at snapshot time." - }, - "avg_likes_per_post": { - "type": "integer" - }, - "avg_commments_per_post": { - "type": "integer", - "description": "Note: field name is misspelled upstream as `avg_commments_per_post`." - }, - "engagement_rate": { - "type": "number" - }, - "top_countries": { - "type": "array", - "description": "Top countries by audience share.", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "top_cities": { - "type": "array", - "description": "Top cities by audience concentration.", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "likers_top_countries": { - "type": "array", - "description": "Top countries for the likers (engagement) cohort.", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "likers_top_cities": { - "type": "array", - "description": "Top cities for the likers cohort.", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "audience_genders": { - "type": "array", - "description": "Gender breakdown of the follower base.", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "audience_genders_per_age": { - "type": "array", - "description": "Follower gender split per age bracket.", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "audience_ethnicities": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "audience_interests": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "audience_brand_affinities": { - "type": "array", - "description": "Brand affinity scores for the follower cohort.", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "audience_likers_genders": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "audience_likers_genders_per_age": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } + "example": "success" }, - "audience_likers_ethnicities": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } + "message": { + "type": "string", + "example": "Data Retrieved." }, - "audience_likers_interests": { + "audience": { "type": "array", + "description": "Per-platform audience demographic entries. May be empty when the provider has no data for the artist/platform.", "items": { "type": "object", "additionalProperties": true } }, - "audience_likers_brand_affinities": { - "type": "array", - "description": "Brand affinity scores for the likers cohort.", - "items": { - "type": "object", - "additionalProperties": true - } + "artist_info": { + "$ref": "#/components/schemas/SongstatsArtistRef" }, - "notable_followers": { + "source_ids": { "type": "array", - "description": "Notable public/influential accounts in the follower base.", "items": { - "type": "object", - "additionalProperties": true + "type": "string" } } - }, - "additionalProperties": true + } }, "ResearchCareerResponse": { "type": "object", - "description": "Career timeline — milestones, trajectory, and career stage history.", + "description": "Career activity feed from Songstats `/artists/activities`.", "properties": { "status": { "type": "string", @@ -3645,10 +3505,8 @@ }, "career": { "type": "array", - "description": "Career timeline data points", "items": { - "type": "object", - "additionalProperties": true + "$ref": "#/components/schemas/SongstatsActivity" } } } @@ -3860,142 +3718,76 @@ } } }, - "ResearchInsight": { - "type": "object", - "properties": { - "text": { - "type": "string", - "description": "The insight text." - } - }, - "additionalProperties": true - }, "ResearchInsightsResponse": { "type": "object", + "description": "Noteworthy activity highlights from Songstats `/artists/activities`.", "properties": { "status": { "type": "string", - "enum": [ - "success", - "error" - ], "example": "success" }, "insights": { "type": "array", "items": { - "$ref": "#/components/schemas/ResearchInsight" + "$ref": "#/components/schemas/SongstatsActivity" } } } }, "ResearchLookupResponse": { "type": "object", - "description": "Artist profile resolved from a platform URL or ID. Cross-platform IDs are nested inside a `data` object.", + "description": "Artist resolved from a Spotify URL or ID via Songstats.", "properties": { "status": { "type": "string", "example": "success" }, - "data": { - "type": "object", - "description": "Cross-platform artist identifiers returned by the configured provider.", - "properties": { - "id": { - "type": "integer", - "description": "provider artist ID" - }, - "spotify_id": { - "type": "string" - }, - "apple_music_id": { - "type": "string" - }, - "deezer_id": { - "type": "string" - } - }, - "additionalProperties": true + "result": { + "type": "string", + "example": "success" + }, + "message": { + "type": "string", + "example": "Data Retrieved." + }, + "artist_info": { + "$ref": "#/components/schemas/SongstatsArtistInfo" } } }, "ResearchMetricsResponse": { "type": "object", - "description": "Time-series metrics for the artist on the selected platform. Shape varies by platform — fields at the root (not wrapped in a `data` envelope) each hold their own time-series array of `{timestp, value}` points.\n\nFor Spotify, expect `followers`, `listeners`, `popularity`, and `followers_to_listeners_ratio`. Other platforms expose their own set (e.g. `subscribers` and `views` for `youtube_channel`).", + "description": "Current statistics for the artist on the requested `source` (Songstats `/artists/stats`).", "properties": { "status": { - "type": "string" + "type": "string", + "example": "success" }, - "link": { + "result": { "type": "string", - "description": "Canonical source URL on the upstream platform (e.g. the artist's Spotify URL)." + "example": "success" }, - "followers": { - "type": "array", - "description": "Time series of follower counts, when applicable to the source.", - "items": { - "type": "object", - "properties": { - "timestp": { - "type": "string" - }, - "value": { - "type": "number" - } - }, - "additionalProperties": true - } + "message": { + "type": "string", + "example": "Data Retrieved." }, - "listeners": { + "stats": { "type": "array", - "description": "Time series of listener counts (e.g. Spotify monthly listeners), when applicable.", "items": { - "type": "object", - "properties": { - "timestp": { - "type": "string" - }, - "value": { - "type": "number" - } - }, - "additionalProperties": true + "$ref": "#/components/schemas/SongstatsMetricStat" } }, - "popularity": { - "type": "array", - "description": "Time series of the platform's popularity score, when applicable.", - "items": { - "type": "object", - "properties": { - "timestp": { - "type": "string" - }, - "value": { - "type": "number" - } - }, - "additionalProperties": true - } + "artist_info": { + "$ref": "#/components/schemas/SongstatsArtistRef" }, - "followers_to_listeners_ratio": { + "source_ids": { "type": "array", - "description": "Derived time series, when both followers and listeners are available (Spotify).", + "description": "Sources included in `stats`.", "items": { - "type": "object", - "properties": { - "timestp": { - "type": "string" - }, - "value": { - "type": "number" - } - }, - "additionalProperties": true + "type": "string" } } - }, - "additionalProperties": true + } }, "ResearchRefreshPendingResponse": { "type": "object", @@ -4026,46 +3818,18 @@ } } }, - "ResearchMilestone": { - "type": "object", - "properties": { - "date": { - "type": "string", - "description": "Date of the milestone event." - }, - "summary": { - "type": "string", - "description": "Human-readable description of the event." - }, - "platform": { - "type": "string", - "description": "Platform where the event occurred (e.g. spotify, apple_music)." - }, - "track_name": { - "type": "string", - "description": "Name of the track involved, if any." - }, - "stars": { - "type": "integer", - "description": "Importance rating (1-5 stars)." - } - } - }, "ResearchMilestonesResponse": { "type": "object", + "description": "Milestone activity entries from Songstats `/artists/activities` (same shape as career/insights).", "properties": { "status": { "type": "string", - "enum": [ - "success", - "error" - ], "example": "success" }, "milestones": { "type": "array", "items": { - "$ref": "#/components/schemas/ResearchMilestone" + "$ref": "#/components/schemas/SongstatsActivity" } } } @@ -4209,494 +3973,134 @@ }, "ResearchProfileResponse": { "type": "object", - "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.", + "description": "Full artist profile from Songstats `/artists/info`. For longitudinal platform metrics call `GET /api/research/metrics`.", "properties": { "status": { - "type": "string" + "type": "string", + "example": "success" + }, + "result": { + "type": "string", + "example": "success" + }, + "message": { + "type": "string", + "example": "Data Retrieved." + }, + "artist_info": { + "$ref": "#/components/schemas/SongstatsArtistInfo" + } + } + }, + "ResearchSearchResponse": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "success", + "error" + ], + "example": "success" }, + "results": { + "type": "array", + "description": "Matching artists with IDs, names, and basic metadata.", + "items": { + "$ref": "#/components/schemas/ResearchSearchResult" + } + } + } + }, + "ResearchSearchResult": { + "type": "object", + "description": "Search result. The `songstats_*_id` field present depends on `type` (artists | tracks | labels).", + "properties": { "id": { - "type": "integer", - "description": "provider artist ID." + "type": "string", + "description": "Provider-neutral ID (alias of the `songstats_*_id` for the result type)." }, "name": { "type": "string" }, - "description": { + "avatar": { "type": "string", "nullable": true }, - "image_url": { + "site_url": { "type": "string", "nullable": true }, - "cover_url": { + "songstats_artist_id": { "type": "string", - "nullable": true + "description": "Present when `type=artists`." }, - "code2": { + "songstats_track_id": { "type": "string", - "nullable": true, - "description": "ISO country code associated with the artist." + "description": "Present when `type=tracks`." }, - "isni": { + "songstats_label_id": { "type": "string", - "nullable": true - }, - "band": { - "type": "boolean", - "description": "True if the artist is a band/group rather than a solo act." - }, - "band_members": { - "nullable": true, - "description": "Band-member objects when known." - }, - "gender": { - "type": "string", - "nullable": true - }, - "gender_title": { - "type": "string", - "nullable": true - }, - "pronoun_title": { - "type": "string", - "nullable": true - }, - "hometown_city": { - "nullable": true, - "description": "Provider hometown-city object (null when unknown)." - }, - "current_city": { - "type": "string", - "nullable": true - }, - "current_city_id": { - "type": "integer", - "nullable": true - }, - "cm_artist_rank": { - "type": "integer", - "description": "Overall Provider artist rank (lower is better)." - }, - "cm_artist_score": { - "type": "number", - "description": "Provider artist-score aggregate (higher is better)." - }, - "cm_statistics": { - "type": "object", - "additionalProperties": true, - "description": "Aggregated cross-platform snapshot — Spotify followers/monthly_listeners, Instagram/TikTok/YouTube counts, chart positions, etc. Fields may be null per-platform." - }, - "career_status": { - "type": "object", - "additionalProperties": true, - "description": "Current career stage + momentum (e.g. `superstar`/`mainstream`/`developing` + `growth`/`steady`/`decline`)." - }, - "genres": { - "description": "Primary/secondary/sub-genre breakdown. Object with keyed subgroups, not a flat array.", - "type": "object", - "additionalProperties": true - }, - "genre_smart_ordered": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "genreRank": { - "type": "object", - "additionalProperties": true - }, - "subGenreRank1": { - "type": "object", - "additionalProperties": true - }, - "subGenreRank2": { - "type": "object", - "additionalProperties": true - }, - "moods": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "activities": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "record_label": { - "type": "string", - "nullable": true - }, - "booking_agent": { - "type": "string", - "nullable": true - }, - "press_contact": { - "type": "string", - "nullable": true - }, - "general_manager": { - "type": "string", - "nullable": true - }, - "topSongwriterCollaborators": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true - } - }, - "created_at": { - "type": "string" - } - }, - "additionalProperties": true - }, - "ResearchRankResponse": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "success", - "error" - ], - "example": "success" - }, - "rank": { - "type": "integer", - "nullable": true, - "description": "Global artist rank from the configured research source." - } - } - }, - "ResearchSearchResponse": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "success", - "error" - ], - "example": "success" - }, - "results": { - "type": "array", - "description": "Matching artists with IDs, names, and basic metadata.", - "items": { - "$ref": "#/components/schemas/ResearchSearchResult" - } - } - } - }, - "ResearchSearchResult": { - "type": "object", - "description": "Shape varies by `type` and search engine. Common fields are listed here; `additionalProperties: true` lets engine-specific fields pass through.\n\n**Default engine (`beta=false`):** entries include `name`, `id`, `image_url`, plus type-specific fields like `artist_names[]` (tracks), `isrc` (tracks), `sp_followers`/`sp_monthly_listeners`/`cm_artist_score` (artists), `owner_name` (playlists), etc.\n\n**Beta engine (`beta=true`):** entries include `name`, `id`, `imageUrl`, `target` (the entity type — e.g. `track`, `artist`, `playlist`), `platform` (e.g. `cm`, `spotify`), and `match_strength` (numeric relevance score, higher is better).", - "properties": { - "name": { - "type": "string" - }, - "id": { - "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" - } - ] - }, - "image_url": { - "type": "string", - "nullable": true, - "description": "Default-engine image URL. (Beta engine returns `imageUrl` instead.)" - }, - "imageUrl": { - "type": "string", - "nullable": true, - "description": "Beta-engine image URL. (Default engine returns `image_url` instead.)" - }, - "match_strength": { - "type": "number", - "description": "Beta-only relevance score — sort entries by this descending. Not present in default-engine results." - }, - "target": { - "type": "string", - "description": "Beta-only entity type (`track`, `artist`, `playlist`, etc.). Not present in default-engine results." - }, - "platform": { - "type": "string", - "description": "Beta-only source platform (e.g. `cm`, `spotify`). Not present in default-engine results." - }, - "spotify_id": { - "type": "string", - "nullable": true - }, - "artist_names": { - "type": "array", - "nullable": true, - "items": { - "type": "string" - }, - "description": "Default-engine tracks only." - } - }, - "additionalProperties": true - }, - "ResearchSimilarArtist": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "id": { - "oneOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Provider artist ID." - }, - "similarity": { - "type": "number", - "description": "Similarity score (0-1)." - }, - "career_stage": { - "type": "string", - "description": "undiscovered, developing, mid-level, mainstream, superstar, or legendary." - }, - "recent_momentum": { - "type": "string", - "description": "decline, gradual decline, steady, growth, or explosive growth." - }, - "sp_followers": { - "type": "integer", - "nullable": true - }, - "sp_monthly_listeners": { - "type": "integer", - "nullable": true + "description": "Present when `type=labels`." } }, "additionalProperties": true }, "ResearchSimilarResponse": { "type": "object", + "description": "Similar artists from Songstats. Note: similarity is unweighted (no score/ranking fields).", "properties": { "status": { "type": "string", - "enum": [ - "success", - "error" - ], "example": "success" }, "artists": { "type": "array", "items": { - "$ref": "#/components/schemas/ResearchSimilarArtist" + "$ref": "#/components/schemas/SongstatsArtistRef" } - }, - "total": { - "type": "integer" } } }, - "ResearchTrackItem": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "album_names": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - } - }, - "additionalProperties": true - }, "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 provider response includes additional fields (e.g. `cm_statistics`, `activities`, `moods`) that pass through under `additionalProperties`.", + "description": "Track metadata and audio analysis from Songstats `/tracks/info`.", "properties": { "status": { - "type": "string" - }, - "id": { - "type": "integer", - "description": "provider track ID" - }, - "name": { - "type": "string", - "description": "Track title" - }, - "isrc": { - "type": "string", - "nullable": true - }, - "image_url": { - "type": "string", - "nullable": true - }, - "duration_ms": { - "type": "integer", - "nullable": true - }, - "release_date": { "type": "string", - "description": "ISO date string", - "nullable": true + "example": "success" }, - "album_label": { + "result": { "type": "string", - "nullable": true - }, - "score": { - "type": "integer", - "description": "Provider track score", - "nullable": true - }, - "explicit": { - "type": "boolean", - "nullable": true - }, - "artists": { - "type": "array", - "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": "provider artist ID" - }, - "name": { - "type": "string" - }, - "image_url": { - "type": "string", - "nullable": true - } - }, - "additionalProperties": true - } - }, - "albums": { - "type": "array", - "description": "Albums the track appears on.", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "provider album ID" - }, - "name": { - "type": "string" - }, - "upc": { - "type": "string", - "nullable": true - }, - "release_date": { - "type": "string", - "nullable": true - }, - "label": { - "type": "string", - "nullable": true - }, - "image_url": { - "type": "string", - "nullable": true - }, - "popularity": { - "type": "integer", - "nullable": true - } - }, - "additionalProperties": true - } - }, - "genres": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": true - } - }, - "cm_statistics": { - "type": "object", - "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 + "example": "success" }, - "tempo": { - "type": "number", - "nullable": true, - "description": "Track tempo in BPM, when available." + "message": { + "type": "string", + "example": "Data Retrieved." }, - "moods": { - "type": "array", - "nullable": true, - "description": "Provider-inferred mood tags (e.g. `Happy`, `Melancholic`).", - "items": { - "type": "object", - "additionalProperties": true - } + "track_info": { + "$ref": "#/components/schemas/SongstatsTrackInfo" }, - "activities": { + "audio_analysis": { "type": "array", - "nullable": true, - "description": "Provider-inferred activity tags (e.g. `Workout`, `Studying`).", "items": { - "type": "object", - "additionalProperties": true + "$ref": "#/components/schemas/SongstatsAudioAnalysisEntry" } } - }, - "additionalProperties": true + } }, "ResearchTracksResponse": { "type": "object", + "description": "Artist catalog from Songstats `/artists/catalog`.", "properties": { "status": { "type": "string", - "enum": [ - "success", - "error" - ], "example": "success" }, "tracks": { "type": "array", "items": { - "$ref": "#/components/schemas/ResearchTrackItem" + "$ref": "#/components/schemas/SongstatsCatalogItem" } } } @@ -4907,6 +4311,342 @@ "example": "Missing required parameter: artist" } } + }, + "SongstatsArtistRef": { + "type": "object", + "description": "Provider-neutral artist reference as returned by Songstats.", + "properties": { + "songstats_artist_id": { + "type": "string", + "description": "Provider-neutral Songstats artist ID.", + "example": "wjcgfd9i" + }, + "avatar": { + "type": "string", + "description": "Artist image URL." + }, + "site_url": { + "type": "string", + "description": "Songstats artist page URL." + }, + "name": { + "type": "string", + "example": "Drake" + }, + "id": { + "type": "string", + "description": "Alias of `songstats_artist_id`." + } + } + }, + "SongstatsArtistMini": { + "type": "object", + "description": "Minimal artist reference embedded in catalog/activity payloads.", + "properties": { + "name": { + "type": "string" + }, + "songstats_artist_id": { + "type": "string" + } + } + }, + "SongstatsLink": { + "type": "object", + "description": "External platform link.", + "properties": { + "source": { + "type": "string", + "description": "Platform name, e.g. `spotify`, `apple_music`." + }, + "external_id": { + "type": "string", + "description": "Platform-specific ID." + }, + "url": { + "type": "string" + }, + "isrc": { + "type": "string", + "description": "ISRC (tracks only)." + } + }, + "additionalProperties": true + }, + "SongstatsArtistInfo": { + "type": "object", + "description": "Full Songstats artist record (identity, bio, genres, platform links, related artists).", + "properties": { + "songstats_artist_id": { + "type": "string", + "example": "wjcgfd9i" + }, + "avatar": { + "type": "string" + }, + "site_url": { + "type": "string" + }, + "name": { + "type": "string" + }, + "country": { + "type": "string", + "description": "ISO country code.", + "nullable": true + }, + "bio": { + "type": "string", + "nullable": true + }, + "genres": { + "type": "array", + "items": { + "type": "string" + } + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SongstatsLink" + } + }, + "related_artists": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SongstatsArtistRef" + } + } + }, + "additionalProperties": true + }, + "SongstatsActivityTrackInfo": { + "type": "object", + "description": "Track context attached to an activity entry.", + "properties": { + "songstats_track_id": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "title": { + "type": "string" + }, + "release_date": { + "type": "string", + "nullable": true + }, + "site_url": { + "type": "string" + }, + "artists": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SongstatsArtistMini" + } + } + }, + "additionalProperties": true + }, + "SongstatsActivity": { + "type": "object", + "description": "Dated activity event from Songstats `/artists/activities` (used by career, insights, and milestones).", + "properties": { + "source": { + "type": "string", + "description": "Platform, e.g. `spotify`." + }, + "activity_text": { + "type": "string", + "description": "Human-readable activity description." + }, + "activity_type": { + "type": "string", + "description": "e.g. `playlist`, `chart`." + }, + "activity_date": { + "type": "string", + "example": "2026-06-04" + }, + "activity_url": { + "type": "string", + "nullable": true + }, + "activity_avatar": { + "type": "string", + "nullable": true + }, + "activity_tier": { + "type": "integer", + "description": "Importance tier (lower = more significant)." + }, + "track_info": { + "$ref": "#/components/schemas/SongstatsActivityTrackInfo" + } + }, + "additionalProperties": true + }, + "SongstatsCatalogItem": { + "type": "object", + "description": "Catalog entry from Songstats `/artists/catalog`. Note: unfiltered, so high-catalog artists include remixes/features.", + "properties": { + "songstats_track_id": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "title": { + "type": "string" + }, + "release_date": { + "type": "string", + "nullable": true + }, + "site_url": { + "type": "string" + }, + "isrcs": { + "type": "array", + "items": { + "type": "string" + } + }, + "artists": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SongstatsArtistMini" + } + } + }, + "additionalProperties": true + }, + "SongstatsCollaborator": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "songstats_collaborator_id": { + "type": "string" + } + }, + "additionalProperties": true + }, + "SongstatsMetricStat": { + "type": "object", + "description": "Per-source current statistics from Songstats `/artists/stats`.", + "properties": { + "source": { + "type": "string", + "description": "Platform the metrics belong to, e.g. `spotify`, `tiktok`." + }, + "data": { + "type": "object", + "description": "Platform-specific metric fields. Keys vary by `source`.", + "additionalProperties": { + "type": "number" + }, + "example": { + "monthly_listeners_current": 99222567, + "followers_total": 112035525, + "popularity_current": 100, + "streams_total": 137582496099, + "playlists_current": 169220, + "charts_current": 184 + } + } + }, + "additionalProperties": true + }, + "SongstatsAudioAnalysisEntry": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Audio feature name, e.g. `acousticness`, `tempo`." + }, + "value": { + "type": "string", + "description": "Feature value (string-encoded)." + } + } + }, + "SongstatsTrackInfo": { + "type": "object", + "description": "Full Songstats track record.", + "properties": { + "songstats_track_id": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "site_url": { + "type": "string" + }, + "release_date": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string" + }, + "artists": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SongstatsArtistMini" + } + }, + "is_remix": { + "type": "boolean" + }, + "collaborators": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SongstatsCollaborator" + } + }, + "labels": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "distributors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": true + } + }, + "genres": { + "type": "array", + "items": { + "type": "string" + } + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SongstatsLink" + } + } + }, + "additionalProperties": true } }, "responses": { From dd42d724f80840a497685355063cdbc2e2fb6510 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 4 Jun 2026 09:59:56 -0500 Subject: [PATCH 2/2] docs(research): provider-neutral schemas; drop unsupported rank endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review on docs#233: - Remove all provider-specific ("Songstats") mentions: rename Songstats* component schemas to Research*, neutralize descriptions, and stop naming provider-specific `songstats_*` fields (they pass through via additionalProperties; the neutral `id` is documented instead). - YAGNI: delete `GET /api/research/rank` entirely (path + schema + reference page + nav) — the current data source has no rank capability. Matching api route removal lands alongside. Refs recoupable/chat#1777 --- api-reference/openapi/research.json | 225 +++++++++------------------- api-reference/research/rank.mdx | 4 - docs.json | 1 - 3 files changed, 71 insertions(+), 159 deletions(-) delete mode 100644 api-reference/research/rank.mdx diff --git a/api-reference/openapi/research.json b/api-reference/openapi/research.json index f56e1046..ffed8efd 100644 --- a/api-reference/openapi/research.json +++ b/api-reference/openapi/research.json @@ -907,7 +907,7 @@ "name": "type", "in": "query", "required": false, - "description": "Entity type to search. Songstats supports artists, tracks, and labels.", + "description": "Entity type to search: artists, tracks, or labels.", "schema": { "type": "string", "enum": [ @@ -1916,58 +1916,6 @@ } } }, - "/api/research/rank": { - "get": { - "description": "Artist global rank. **Not supported by the current data source (Songstats has no rank endpoint)** — always returns `501`.", - "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": { - "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/similar": { "get": { "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.", @@ -3446,7 +3394,7 @@ }, "ResearchAlbumsResponse": { "type": "object", - "description": "Artist catalog from Songstats `/artists/catalog`.", + "description": "Artist catalog (albums view).", "properties": { "status": { "type": "string", @@ -3455,14 +3403,14 @@ "albums": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsCatalogItem" + "$ref": "#/components/schemas/ResearchCatalogItem" } } } }, "ResearchAudienceResponse": { "type": "object", - "description": "Audience demographics from Songstats `/artists/audience` for the requested `platform`.", + "description": "Audience demographics for the requested `platform`.", "properties": { "status": { "type": "string", @@ -3485,7 +3433,7 @@ } }, "artist_info": { - "$ref": "#/components/schemas/SongstatsArtistRef" + "$ref": "#/components/schemas/ResearchArtistRef" }, "source_ids": { "type": "array", @@ -3497,7 +3445,7 @@ }, "ResearchCareerResponse": { "type": "object", - "description": "Career activity feed from Songstats `/artists/activities`.", + "description": "Career activity feed.", "properties": { "status": { "type": "string", @@ -3506,7 +3454,7 @@ "career": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsActivity" + "$ref": "#/components/schemas/ResearchActivity" } } } @@ -3720,7 +3668,7 @@ }, "ResearchInsightsResponse": { "type": "object", - "description": "Noteworthy activity highlights from Songstats `/artists/activities`.", + "description": "Noteworthy activity highlights.", "properties": { "status": { "type": "string", @@ -3729,14 +3677,14 @@ "insights": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsActivity" + "$ref": "#/components/schemas/ResearchActivity" } } } }, "ResearchLookupResponse": { "type": "object", - "description": "Artist resolved from a Spotify URL or ID via Songstats.", + "description": "Artist resolved from a Spotify URL or ID.", "properties": { "status": { "type": "string", @@ -3751,13 +3699,13 @@ "example": "Data Retrieved." }, "artist_info": { - "$ref": "#/components/schemas/SongstatsArtistInfo" + "$ref": "#/components/schemas/ResearchArtistInfo" } } }, "ResearchMetricsResponse": { "type": "object", - "description": "Current statistics for the artist on the requested `source` (Songstats `/artists/stats`).", + "description": "Current statistics for the artist on the requested `source`.", "properties": { "status": { "type": "string", @@ -3774,11 +3722,11 @@ "stats": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsMetricStat" + "$ref": "#/components/schemas/ResearchMetricStat" } }, "artist_info": { - "$ref": "#/components/schemas/SongstatsArtistRef" + "$ref": "#/components/schemas/ResearchArtistRef" }, "source_ids": { "type": "array", @@ -3820,7 +3768,7 @@ }, "ResearchMilestonesResponse": { "type": "object", - "description": "Milestone activity entries from Songstats `/artists/activities` (same shape as career/insights).", + "description": "Milestone activity entries (same shape as career/insights).", "properties": { "status": { "type": "string", @@ -3829,7 +3777,7 @@ "milestones": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsActivity" + "$ref": "#/components/schemas/ResearchActivity" } } } @@ -3973,7 +3921,7 @@ }, "ResearchProfileResponse": { "type": "object", - "description": "Full artist profile from Songstats `/artists/info`. For longitudinal platform metrics call `GET /api/research/metrics`.", + "description": "Full artist profile. For longitudinal platform metrics call `GET /api/research/metrics`.", "properties": { "status": { "type": "string", @@ -3988,7 +3936,7 @@ "example": "Data Retrieved." }, "artist_info": { - "$ref": "#/components/schemas/SongstatsArtistInfo" + "$ref": "#/components/schemas/ResearchArtistInfo" } } }, @@ -4014,11 +3962,11 @@ }, "ResearchSearchResult": { "type": "object", - "description": "Search result. The `songstats_*_id` field present depends on `type` (artists | tracks | labels).", + "description": "Search result. Additional type-specific identifier fields pass through. The set depends on `type` (artists | tracks | labels).", "properties": { "id": { "type": "string", - "description": "Provider-neutral ID (alias of the `songstats_*_id` for the result type)." + "description": "Provider-neutral ID." }, "name": { "type": "string" @@ -4030,25 +3978,13 @@ "site_url": { "type": "string", "nullable": true - }, - "songstats_artist_id": { - "type": "string", - "description": "Present when `type=artists`." - }, - "songstats_track_id": { - "type": "string", - "description": "Present when `type=tracks`." - }, - "songstats_label_id": { - "type": "string", - "description": "Present when `type=labels`." } }, "additionalProperties": true }, "ResearchSimilarResponse": { "type": "object", - "description": "Similar artists from Songstats. Note: similarity is unweighted (no score/ranking fields).", + "description": "Similar artists. Similarity is unweighted (no score/ranking fields).", "properties": { "status": { "type": "string", @@ -4057,14 +3993,14 @@ "artists": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsArtistRef" + "$ref": "#/components/schemas/ResearchArtistRef" } } } }, "ResearchTrackResponse": { "type": "object", - "description": "Track metadata and audio analysis from Songstats `/tracks/info`.", + "description": "Track metadata and audio analysis.", "properties": { "status": { "type": "string", @@ -4079,19 +4015,19 @@ "example": "Data Retrieved." }, "track_info": { - "$ref": "#/components/schemas/SongstatsTrackInfo" + "$ref": "#/components/schemas/ResearchTrackInfo" }, "audio_analysis": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsAudioAnalysisEntry" + "$ref": "#/components/schemas/ResearchAudioAnalysisEntry" } } } }, "ResearchTracksResponse": { "type": "object", - "description": "Artist catalog from Songstats `/artists/catalog`.", + "description": "Artist catalog (tracks view).", "properties": { "status": { "type": "string", @@ -4100,7 +4036,7 @@ "tracks": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsCatalogItem" + "$ref": "#/components/schemas/ResearchCatalogItem" } } } @@ -4312,46 +4248,43 @@ } } }, - "SongstatsArtistRef": { + "ResearchArtistRef": { "type": "object", - "description": "Provider-neutral artist reference as returned by Songstats.", + "description": "Provider-neutral artist reference.", "properties": { - "songstats_artist_id": { + "id": { "type": "string", - "description": "Provider-neutral Songstats artist ID.", + "description": "Provider-neutral artist ID.", "example": "wjcgfd9i" }, - "avatar": { - "type": "string", - "description": "Artist image URL." - }, - "site_url": { - "type": "string", - "description": "Songstats artist page URL." - }, "name": { "type": "string", "example": "Drake" }, - "id": { + "avatar": { + "type": "string", + "description": "Artist image URL.", + "nullable": true + }, + "site_url": { "type": "string", - "description": "Alias of `songstats_artist_id`." + "description": "Artist page URL.", + "nullable": true } - } + }, + "additionalProperties": true }, - "SongstatsArtistMini": { + "ResearchArtistMini": { "type": "object", "description": "Minimal artist reference embedded in catalog/activity payloads.", "properties": { "name": { "type": "string" - }, - "songstats_artist_id": { - "type": "string" } - } + }, + "additionalProperties": true }, - "SongstatsLink": { + "ResearchLink": { "type": "object", "description": "External platform link.", "properties": { @@ -4373,13 +4306,12 @@ }, "additionalProperties": true }, - "SongstatsArtistInfo": { + "ResearchArtistInfo": { "type": "object", - "description": "Full Songstats artist record (identity, bio, genres, platform links, related artists).", + "description": "Full artist record — identity, bio, genres, platform links, and related artists.", "properties": { - "songstats_artist_id": { - "type": "string", - "example": "wjcgfd9i" + "name": { + "type": "string" }, "avatar": { "type": "string" @@ -4387,9 +4319,6 @@ "site_url": { "type": "string" }, - "name": { - "type": "string" - }, "country": { "type": "string", "description": "ISO country code.", @@ -4408,31 +4337,28 @@ "links": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsLink" + "$ref": "#/components/schemas/ResearchLink" } }, "related_artists": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsArtistRef" + "$ref": "#/components/schemas/ResearchArtistRef" } } }, "additionalProperties": true }, - "SongstatsActivityTrackInfo": { + "ResearchActivityTrackInfo": { "type": "object", "description": "Track context attached to an activity entry.", "properties": { - "songstats_track_id": { + "title": { "type": "string" }, "avatar": { "type": "string" }, - "title": { - "type": "string" - }, "release_date": { "type": "string", "nullable": true @@ -4443,15 +4369,15 @@ "artists": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsArtistMini" + "$ref": "#/components/schemas/ResearchArtistMini" } } }, "additionalProperties": true }, - "SongstatsActivity": { + "ResearchActivity": { "type": "object", - "description": "Dated activity event from Songstats `/artists/activities` (used by career, insights, and milestones).", + "description": "Dated activity event. Shared by career, insights, and milestones.", "properties": { "source": { "type": "string", @@ -4482,24 +4408,21 @@ "description": "Importance tier (lower = more significant)." }, "track_info": { - "$ref": "#/components/schemas/SongstatsActivityTrackInfo" + "$ref": "#/components/schemas/ResearchActivityTrackInfo" } }, "additionalProperties": true }, - "SongstatsCatalogItem": { + "ResearchCatalogItem": { "type": "object", - "description": "Catalog entry from Songstats `/artists/catalog`. Note: unfiltered, so high-catalog artists include remixes/features.", + "description": "Catalog entry. Unfiltered — high-catalog artists include remixes/features.", "properties": { - "songstats_track_id": { + "title": { "type": "string" }, "avatar": { "type": "string" }, - "title": { - "type": "string" - }, "release_date": { "type": "string", "nullable": true @@ -4516,13 +4439,13 @@ "artists": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsArtistMini" + "$ref": "#/components/schemas/ResearchArtistMini" } } }, "additionalProperties": true }, - "SongstatsCollaborator": { + "ResearchCollaborator": { "type": "object", "properties": { "name": { @@ -4533,16 +4456,13 @@ "items": { "type": "string" } - }, - "songstats_collaborator_id": { - "type": "string" } }, "additionalProperties": true }, - "SongstatsMetricStat": { + "ResearchMetricStat": { "type": "object", - "description": "Per-source current statistics from Songstats `/artists/stats`.", + "description": "Per-source current statistics.", "properties": { "source": { "type": "string", @@ -4566,7 +4486,7 @@ }, "additionalProperties": true }, - "SongstatsAudioAnalysisEntry": { + "ResearchAudioAnalysisEntry": { "type": "object", "properties": { "key": { @@ -4579,11 +4499,11 @@ } } }, - "SongstatsTrackInfo": { + "ResearchTrackInfo": { "type": "object", - "description": "Full Songstats track record.", + "description": "Full track record.", "properties": { - "songstats_track_id": { + "title": { "type": "string" }, "avatar": { @@ -4596,13 +4516,10 @@ "type": "string", "nullable": true }, - "title": { - "type": "string" - }, "artists": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsArtistMini" + "$ref": "#/components/schemas/ResearchArtistMini" } }, "is_remix": { @@ -4611,7 +4528,7 @@ "collaborators": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsCollaborator" + "$ref": "#/components/schemas/ResearchCollaborator" } }, "labels": { @@ -4642,7 +4559,7 @@ "links": { "type": "array", "items": { - "$ref": "#/components/schemas/SongstatsLink" + "$ref": "#/components/schemas/ResearchLink" } } }, diff --git a/api-reference/research/rank.mdx b/api-reference/research/rank.mdx deleted file mode 100644 index b08cbbe5..00000000 --- a/api-reference/research/rank.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Artist Rank' -openapi: "/api-reference/openapi/research.json GET /api/research/rank" ---- diff --git a/docs.json b/docs.json index 274985bb..43a7e87a 100644 --- a/docs.json +++ b/docs.json @@ -109,7 +109,6 @@ "api-reference/research/insights", "api-reference/research/career", "api-reference/research/milestones", - "api-reference/research/rank", "api-reference/research/similar", "api-reference/research/urls" ]