From c058e9027c683d2594c53562ffaf574277219bd5 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 16 Apr 2026 10:30:25 -0500 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20tighten=20/research/track=20spec=20?= =?UTF-8?q?=E2=80=94=20add=20artist=20disambiguation=20+=20404?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surfaced while testing api PR recoupable/api#366 on preview: every common track query ("God's Plan", "Hotline Bling", "Thriller", "Sicko Mode", "Flowers") returned the wrong track. Root cause is upstream: Chartmetric `/search?type=tracks&limit=1` picks an arbitrary low-quality match with no popularity sort and no artist disambiguation knob. Spec changes: - Endpoint description explains the resolver flow and recommends passing a Spotify URL or the new `artist` param for ambiguous names. - `q` description: clarify that plain names are searched (and may be ambiguous), while Spotify URLs resolve directly. - Add optional `artist` query param for case-insensitive artist disambiguation against candidate tracks' `artist_names`. - Add 404 response for the case where no track matches. Implementation in the api repo follows in a separate PR. Co-Authored-By: Claude Opus 4.7 (1M context) --- api-reference/openapi/research.json | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/api-reference/openapi/research.json b/api-reference/openapi/research.json index db3e11e..b369f41 100644 --- a/api-reference/openapi/research.json +++ b/api-reference/openapi/research.json @@ -2351,13 +2351,22 @@ }, "/api/research/track": { "get": { - "description": "Get track metadata — title, artist, album, release date, popularity, and platform IDs.", + "description": "Get track metadata — title, artist, album, release date, popularity, and platform IDs.\n\nResolves `q` against Chartmetric's track search and returns details for the top-ranked match. Common track names are ambiguous (e.g. \"Flowers\", \"God's Plan\") — for reliable disambiguation, either pass a Spotify track URL as `q`, or pair `q` with the `artist` query param.", "parameters": [ { "name": "q", "in": "query", "required": true, - "description": "Track name or Spotify URL.", + "description": "Track name or Spotify track URL. URLs (e.g. `https://open.spotify.com/track/`) resolve directly without ambiguity. Plain names are searched via Chartmetric's track search; results may not match user intent without an `artist` filter.", + "schema": { + "type": "string" + } + }, + { + "name": "artist", + "in": "query", + "required": false, + "description": "Optional artist name used to disambiguate when `q` is a plain track name. Matched case-insensitively against the candidate tracks' artist names; the top-ranked candidate whose artist names contain this value wins. Recommended whenever `q` is not a Spotify URL.", "schema": { "type": "string" } @@ -2393,6 +2402,16 @@ } } } + }, + "404": { + "description": "No track matched the supplied `q` (and `artist`, when present)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResearchErrorResponse" + } + } + } } } } From 1bd59112af8d2925ed15cf7afbf8575aead5a92d Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 16 Apr 2026 10:42:33 -0500 Subject: [PATCH 2/2] docs: correct /research/track response schema to match actual response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified against preview deployment of recoupable/api#366 (https://recoup-eu765jr34-recoupable-ad724970.vercel.app) with two queries (plain name + Spotify URL). The previous schema was badly out of sync with reality: - `album_name` (string) — does not exist in the response - `artist_names` (string) — does not exist; response has `artists[]` (array of full Chartmetric artist objects) Replaced with the actually-returned top-level fields: `artists[]`, `albums[]`, `genres[]`, `image_url`, `duration_ms`, `album_label`, `score`, `explicit`. Kept `additionalProperties: true` so lesser-used upstream fields (`cm_statistics`, `activities`, `moods`, `tags`, etc.) still pass through without enumerating every nullable sub-field. Co-Authored-By: Claude Opus 4.7 (1M context) --- api-reference/openapi/research.json | 112 +++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 10 deletions(-) diff --git a/api-reference/openapi/research.json b/api-reference/openapi/research.json index b369f41..c1a327d 100644 --- a/api-reference/openapi/research.json +++ b/api-reference/openapi/research.json @@ -4732,29 +4732,121 @@ }, "ResearchTrackResponse": { "type": "object", - "description": "Track metadata — title, artist, album, release date, popularity, and platform IDs.", + "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`.", "properties": { "status": { "type": "string" }, - "name": { - "type": "string" - }, "id": { "type": "integer", "description": "Chartmetric track ID" }, + "name": { + "type": "string", + "description": "Track title" + }, "isrc": { - "type": "string" + "type": "string", + "nullable": true }, - "album_name": { - "type": "string" + "image_url": { + "type": "string", + "nullable": true + }, + "duration_ms": { + "type": "integer", + "nullable": true }, "release_date": { - "type": "string" + "type": "string", + "description": "ISO date string", + "nullable": true }, - "artist_names": { - "type": "string" + "album_label": { + "type": "string", + "nullable": true + }, + "score": { + "type": "integer", + "description": "Chartmetric track score", + "nullable": true + }, + "explicit": { + "type": "boolean", + "nullable": true + }, + "artists": { + "type": "array", + "description": "Credited artists. Each entry is a Chartmetric artist object; at minimum `id` and `name` are populated.", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Chartmetric 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": "Chartmetric 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 + } } }, "additionalProperties": true