docs: tighten /research/track spec — add artist disambiguation + 404#138
Conversation
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) <noreply@anthropic.com>
📝 WalkthroughWalkthroughUpdated OpenAPI documentation for the Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@api-reference/openapi/research.json`:
- Around line 2406-2412: The OpenAPI spec now references ResearchErrorResponse
for 404 in the endpoint response block but the components/schemas description
for ResearchErrorResponse currently states it's only for 400/401; update the
ResearchErrorResponse schema description under components/schemas (symbol:
ResearchErrorResponse) to include 404 and align its wording with its usage in
responses (or alternatively adjust the endpoint's 404 response to reference the
correct schema). Ensure the schema description and every response referencing
ResearchErrorResponse (including the endpoint that currently lists 404)
consistently reflect the same set of status codes and error wording.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 277959f0-a194-4012-8f39-6fa2ae720613
📒 Files selected for processing (1)
api-reference/openapi/research.json
| "404": { | ||
| "description": "No track matched the supplied `q` (and `artist`, when present)", | ||
| "content": { | ||
| "application/json": { | ||
| "schema": { | ||
| "$ref": "#/components/schemas/ResearchErrorResponse" | ||
| } |
There was a problem hiding this comment.
Align shared error-schema wording with the new 404 usage.
You now return ResearchErrorResponse for 404 here, but Line 5021 describes that schema as only for 400/401. This creates contradictory docs.
Proposed docs fix
- "description": "Error response returned by all research endpoints for validation failures (400) and authentication errors (401).",
+ "description": "Error response returned by research endpoints for client errors (for example, 400 validation failures, 401 authentication failures, and 404 not found where documented).",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@api-reference/openapi/research.json` around lines 2406 - 2412, The OpenAPI
spec now references ResearchErrorResponse for 404 in the endpoint response block
but the components/schemas description for ResearchErrorResponse currently
states it's only for 400/401; update the ResearchErrorResponse schema
description under components/schemas (symbol: ResearchErrorResponse) to include
404 and align its wording with its usage in responses (or alternatively adjust
the endpoint's 404 response to reference the correct schema). Ensure the schema
description and every response referencing ResearchErrorResponse (including the
endpoint that currently lists 404) consistently reflect the same set of status
codes and error wording.
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) <noreply@anthropic.com>
|
Added a second commit to this PR: correct the Verified against the preview deployment of recoupable/api#366 ( Previous schema was wrong:
New schema enumerates the actually-returned top-level fields: Wrong-match bug from the original comment still repros on preview: |
Addresses PR #366 review (#366 (comment)): every common track query ("God's Plan", "Hotline Bling", "Flowers", "Sicko Mode", "Thriller") returned the wrong track because the resolver called Chartmetric /search?type=tracks&limit=1 and took tracks[0] with no ranking and no artist filter. Changes: - Raise the upstream limit to 25 (was 1) so real candidates make it into the pool. - New pickBestTrackMatch(tracks, q, artist?) — pure ranking function: 1. If artist supplied, drop candidates whose artist_names don't contain artist (case-insensitive substring). 2. Within the remaining pool, prefer a track whose name equals q (trimmed, case-insensitive) — fixes "God's Plan" vs "God's". 3. Otherwise fall back to the first remaining track. - Validator accepts an optional `artist` query param. - Handler returns 404 with both q and artist in the message when the artist filter yields nothing (matches the OpenAPI 404 added in recoupable/docs#138). Docs PR (now merged): recoupable/docs#138 tightens the /research/track spec to advertise the new `artist` param, the 404 response, and the actual response shape (artists[], albums[], etc. — not the wrong album_name/artist_names the spec claimed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…fied Follow-up to #138. The api PR (recoupable/api#366) is being simplified to strictly one-upstream-call-per-endpoint so credit charges are predictable and ambiguity never gets silently swallowed by composite resolution. /research/track - Drop q and artist query params; require numeric `id` instead. - Description reframes the endpoint as a thin /track/:id proxy and points callers at GET /api/research?type=tracks&beta=true for discovery. - 400 now refers to id validation, 404 refers to unknown Chartmetric id. /research/playlist - No schema change (still takes platform + id), but the description now explicitly names this a /playlist/:platform/:id proxy and points callers at GET /api/research for name-based discovery. - id description calls out that format varies by platform. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…fied (#139) * docs: /research/track becomes pure id-proxy; /research/playlist clarified Follow-up to #138. The api PR (recoupable/api#366) is being simplified to strictly one-upstream-call-per-endpoint so credit charges are predictable and ambiguity never gets silently swallowed by composite resolution. /research/track - Drop q and artist query params; require numeric `id` instead. - Description reframes the endpoint as a thin /track/:id proxy and points callers at GET /api/research?type=tracks&beta=true for discovery. - 400 now refers to id validation, 404 refers to unknown Chartmetric id. /research/playlist - No schema change (still takes platform + id), but the description now explicitly names this a /playlist/:platform/:id proxy and points callers at GET /api/research for name-based discovery. - id description calls out that format varies by platform. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: clarify /research/playlist id is Chartmetric numeric, not native Verified against preview of recoupable/api#366: GET /playlist?platform=spotify&id=37i9dQZF1DXcBWIGoYBM5M → 400 GET /playlist?platform=spotify&id=848051 → 200 RapCaviar Chartmetric's /playlist/:platform/:id accepts Chartmetric's own numeric playlist IDs, not the streaming platform's native IDs. Search results already expose the correct `id` field, so the workflow is: search via /api/research, feed `id` into /research/playlist. - Param description now explicitly calls out "not the native ID" - Added pattern ^[1-9][0-9]*$ + example 848051 - Endpoint description leads with "Chartmetric's own numeric playlist ID" Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: /research/albums becomes pure artist_id-proxy Mirrors the api change in recoupable/api#366 (cb6152b2). The handler no longer does a composite name→artist_id→discography resolve (which was returning wrong data for artist=Drake and artist=Taylor Swift on the preview), so the spec is updated: - Param renamed `artist` → `artist_id` - Typed as positive-integer string (pattern ^[1-9][0-9]*$), example 3380 - Description points callers at GET /api/research?type=artists&beta=true for discovery - 400 description specific to the new validation No change needed for /research/charts — the spec already enumerates `type` to {regional, viral}, `interval` to {daily, weekly}; the api change tightens the validator to actually enforce those at our layer (turning opaque upstream 400s into specific ones). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: /research/albums adds is_primary, limit, offset params Mirrors api change in recoupable/api#366 (969f5f8a). The api now sends isPrimary=true by default to Chartmetric so /artist/:id/albums returns the artist's own discography (not features, soundtracks, DJ mixes). Spec additions: - `is_primary` (default "true") — opt into features with "false" - `limit` (positive integer) — pagination page size - `offset` (non-negative integer) — pagination offset Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: /api/research gains beta/platforms/offset + realistic result schema Mirrors the api change in recoupable/api#366 (a28deb25). /api/research has been the discovery primitive for the research endpoints since that commit, but the spec still described it as "Search for artists by name" and didn't advertise the new passthrough params. Spec additions: - Description now frames /api/research as the discovery primitive for /track, /playlist, /albums (via type=tracks/playlists/artists). - Recommends beta=true for ambiguous queries, citing the upstream bug where default-engine returns 1 low-quality match for common terms like "Hotline Bling" or "Flowers". - New params: beta (enum "true"/"false"), platforms (comma-separated, beta-only), offset (non-negative integer for pagination). - ResearchSearchResult schema loosened to reflect reality: the default engine and beta engine return different shapes, and default-engine shape varies by type. Common fields enumerated; additionalProperties: true keeps engine-specific fields passing through without over-specifying. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: link detail endpoints ↔ /api/research search endpoint The discovery-then-detail flow is core to the new "one call per endpoint" design, so the references between endpoints should be clickable — both for humans reading the docs and for LLMs using the .md sitemap. Per the existing convention (e.g. the chat endpoints already use [text](/api-reference/chat/update)), updated: - /research/playlist: link to /api-reference/research/search in both the endpoint description and the `id` param description - /research/track: same, in both places - /research/albums: same, in both places - /api/research (search): forward-links to the three detail endpoints (/albums, /track, /playlist) in the endpoint description Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: /research/curator and /research/track/playlists link to search Audit of Chartmetric-id params after 15bee59 found two more endpoints that require a numeric Chartmetric ID but didn't link to the discovery endpoint: - /research/curator (id): now links to GET /api/research?type=curators&beta=true. Kept the existing hint about finding curators via /research/playlists as a secondary path. - /research/track/playlists (id): now links to GET /api/research?type=tracks&beta=true. (Note: this endpoint still has a composite `q`/`artist` name-lookup fallback with the same wrong-match risk as the old /research/track behavior; KISS-ifying it is a separate follow-up.) All five endpoints that reference a Chartmetric id now point callers at the same discovery primitive: /research/albums (artist_id) ✓ /research/curator (id) ✓ /research/playlist (id) ✓ /research/track (id) ✓ /research/track/playlists (id) ✓ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add 30 research API endpoints, 28 MCP tools, Zod validation, and tests
Research primitive — provider-agnostic music industry research:
- 30 REST endpoints under /api/research/ (Chartmetric, Perplexity, Exa, Parallel)
- 28 MCP tools with proper auth (resolveAccountId) and credit deduction
- 2 shared handlers (handleArtistResearch, handleResearchRequest) for DRY
- Zod validation on discover endpoint
- 10 test files (token, proxy, artist resolution, charts, lookup, similar, search, track, web, discover)
- Source param allowlist on metrics to prevent path injection
- proxyToChartmetric wrapped in try/catch for consistent error contract
- All 1767 tests passing, 0 lint errors in research files
Made-with: Cursor
* feat: add GET /api/research/track/playlists endpoint
Track-level playlist lookup — returns editorial, indie, and algorithmic
playlists for a specific track. Accepts Chartmetric track ID or track
name (resolved via search). Proxies to Chartmetric
/track/{id}/{platform}/{status}/playlists.
Includes route handler, domain handler, MCP tool, and 8 unit tests.
Made-with: Cursor
* fix: use Spotify-powered track search for reliable q= resolution
Adds resolveTrack() — searches Spotify first (accurate matching with
artist: filter), maps Spotify track ID to Chartmetric ID, falls back
to Chartmetric search if Spotify fails. Adds optional artist= param
to track/playlists endpoint and MCP tool.
Made-with: Cursor
* fix: resolve tracks via ISRC for reliable Chartmetric ID mapping
Spotify search returns ISRC, which maps to Chartmetric more reliably
than Spotify track ID. Tries /track/isrc/{isrc} first, then
/track/spotify/{id}, then falls back to Chartmetric text search.
Made-with: Cursor
* fix: resolve track ID via artist playlists + tracks matching
Spotify search finds exact track name, then we match against the
artist's Chartmetric playlists/tracks by name to get the cm_track ID.
Avoids Chartmetric's broken text search and unreliable ID mapping.
Made-with: Cursor
* fix: use Chartmetric /track/:type/:id/get-ids for track resolution
Maps ISRC → chartmetric_ids via the correct endpoint path. Falls back
to Spotify track ID if ISRC lookup fails. Platform-agnostic.
Made-with: Cursor
* style: fix formatting in research track playlists files
Made-with: Cursor
* fix(lint): remove unused getCorsHeaders import
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(review): SRP on chartmetric token cache; drop MCP research tools
Per review feedback on this PR:
SRP — resetTokenCache in its own file:
- Extract shared cache state into lib/chartmetric/chartmetricTokenCache.ts
- Move resetTokenCache to lib/chartmetric/resetTokenCache.ts
- getChartmetricToken now reads/writes the shared cache module
Remove MCP research tools to keep this PR focused on the API surface:
- Delete lib/mcp/tools/research/ (29 register* files + index)
- Remove registerAllResearchTools import/call from lib/mcp/tools/index.ts
- MCP tools can land in a follow-up PR once the HTTP endpoints ship
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: replace handleArtistResearch orchestrator with small composable helpers
Ports 15 artist-scoped research handlers off the template-method
orchestrator (handleArtistResearch) onto three focused helpers:
requireArtist, getArtistResearch, and jsonSuccess/jsonError. Each
handler now reads top-to-bottom in ~10 lines and explicitly names
its response key (no more magic { status } spreading collision).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: rename getArtistResearch → handleArtistResearch (no functional change)
Pure rename: file, symbol, test file, and all 15 handler call sites.
Keeps the new composable-helper implementation — only the name now
matches the legacy naming.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: consolidate JSON response helpers (DRY)
errorResponse was duplicated across lib/networking/errorResponse.ts
(existing) and a new lib/networking/jsonResponse.ts helper added with
this PR's research refactor. Merge them:
- Update errorResponse to return { status: "error", error } (was just
{ error }) so success/error envelopes are symmetric.
- Move jsonSuccess into a dedicated successResponse.ts file (one
exported function per file per repo convention).
- Delete lib/networking/jsonResponse.ts.
- Point the 15 research handlers + requireArtist at the unified
helpers.
The one pre-existing caller of errorResponse (validateAgentVerifyBody)
now gets status: "error" in its body. Its tests only assert status
codes, not body shape — no test changes needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: align research handler naming + shape with codebase conventions
- validateArtistRequest (was requireArtist) — matches validate*Request family
- Composed validators for metrics/playlists/similar endpoints whose inline
validation was non-trivial
- Add try/catch + explicit Promise<NextResponse> return type to all 15
artist-scoped research handlers
- Rename `gate` local to `validated` to match the rest of the codebase
No behavior change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(kiss): spread validated into handleArtistResearch call
The handlers were manually re-assembling { artist, accountId } from
validated into handleArtistResearch's params object — pure ceremony
since validated already carries exactly those fields. Switch to
`{ ...validated, path, ... }` across the 12 simple handlers, and use
destructure-into-rest for the 3 that carry endpoint-specific extras.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: align charts + discover handlers with validator/helper conventions
- New handleResearchProxy({ accountId, path, query?, credits? })
returns { data } | { error, status } — same shape as
handleArtistResearch. No auth (validators own that now).
- New validateGetResearchChartsRequest.ts and renamed
validateDiscoverQuery -> validateGetResearchDiscoverRequest; both
now do auth + param validation per the validate*Request convention.
- Charts + discover handlers: try/catch, Promise<NextResponse>,
successResponse / errorResponse, compose validator + proxy helper.
- handleResearchRequest is left in place — still used by radio,
genres, curator, and festivals handlers (out of scope here).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: port final 4 research handlers; delete handleResearchRequest
- getResearchRadioHandler
- getResearchGenresHandler
- getResearchCuratorHandler
- getResearchFestivalsHandler (non-artist-scoped: /festival/list is a
global Chartmetric endpoint, so it uses handleResearchProxy)
Each now uses its own validateGet<X>Request validator and
handleResearchProxy. None of the four were artist-scoped — all four
hit global Chartmetric paths (/radio/station-list, /genres,
/curator/{platform}/{id}, /festival/list). Deletes the last
template-method orchestrator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(kiss): rename handleResearchProxy → handleResearch, proxyToChartmetric → fetchChartmetric
Drop the "proxy" terminology per review feedback:
- handleResearchProxy → handleResearch
- proxyToChartmetric → fetchChartmetric
Renames the symbols, file names, and test file names to match.
No behavior change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move fetchChartmetric to lib/chartmetric
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(dry): share CHARTMETRIC_BASE between token exchange and fetchChartmetric
Extracts the base URL into lib/chartmetric/chartmetricBase.ts so both
getChartmetricToken and fetchChartmetric reuse the same const.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: port 5 non-artist research GET handlers to validator+handleResearch pattern
- getResearchLookupHandler
- getResearchSearchHandler
- getResearchTrackHandler
- getResearchTrackPlaylistsHandler
- getResearchPlaylistHandler
Each now has a dedicated validateGetResearch<X>Request.ts (auth + param
validation) and composes handleResearch + errorResponse / successResponse
inside try/catch. Removes hand-rolled auth + direct deductCredits calls
from the handlers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: charge credits for each Chartmetric hop in track+playlist lookups
Both getResearchTrackHandler and getResearchPlaylistHandler do a
name-resolution search before the detail fetch. Previously the resolve
step used fetchChartmetric (no credit charge), so a name-based lookup
cost the same as a direct-ID lookup (5 credits) despite hitting the
upstream twice.
Swap the resolve step to handleResearch so each successful upstream
hit deducts 5 credits. Failed searches still deduct nothing
(handleResearch skips deduction on non-200).
Cost mapping:
- Direct ID → 1 call, 5 credits (unchanged)
- Name lookup → 2 calls, 10 credits (was 5)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(review): address 9 review comments
- Rename `gate` -> `validated` in 3 composed validators (KISS)
- Spotify artist URL regex only appears in validateGetResearchLookupRequest;
no duplication, left as-is
- resolveTrack now takes accountId and calls handleResearch so
Chartmetric searches deduct credits (was hand-rolled fetchChartmetric)
- Extract validator files for 4 POST research handlers:
validatePostResearchWebRequest, ...PeopleRequest, ...ExtractRequest,
...EnrichRequest. Handlers now compose validator + existing AI-provider
call inside try/catch with Promise<NextResponse> return types.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(review): tighten validateGetResearchCuratorRequest with clear errors
Chartmetric's /curator/:platform/:id endpoint only supports spotify,
applemusic, and deezer and requires a numeric curator id. Callers were
passing string handles like "spotify" / "filtr" and getting opaque
upstream 400s back.
Now reject up front with helpful messages:
- "Invalid platform. Must be one of: spotify, applemusic, deezer"
- "id must be a numeric Chartmetric curator ID (e.g. 2 for Spotify)"
Matches the tightened docs spec in recoupable/docs#137.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(research/track): add artist disambiguation + better match ranking
Addresses PR #366 review (#366 (comment)):
every common track query ("God's Plan", "Hotline Bling", "Flowers",
"Sicko Mode", "Thriller") returned the wrong track because the resolver
called Chartmetric /search?type=tracks&limit=1 and took tracks[0] with
no ranking and no artist filter.
Changes:
- Raise the upstream limit to 25 (was 1) so real candidates make it into
the pool.
- New pickBestTrackMatch(tracks, q, artist?) — pure ranking function:
1. If artist supplied, drop candidates whose artist_names don't
contain artist (case-insensitive substring).
2. Within the remaining pool, prefer a track whose name equals q
(trimmed, case-insensitive) — fixes "God's Plan" vs "God's".
3. Otherwise fall back to the first remaining track.
- Validator accepts an optional `artist` query param.
- Handler returns 404 with both q and artist in the message when the
artist filter yields nothing (matches the OpenAPI 404 added in
recoupable/docs#138).
Docs PR (now merged): recoupable/docs#138 tightens the /research/track
spec to advertise the new `artist` param, the 404 response, and the
actual response shape (artists[], albums[], etc. — not the wrong
album_name/artist_names the spec claimed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(research/search): pass through beta, platforms, offset to Chartmetric
Chartmetric's default search engine returns exactly 1 result for every
track query (verified against preview: q=Hotline Bling → only T703R
'Bling', q=Flowers → only a Yuda cover, q=Drake&type=tracks → 1 track).
Its docs describe beta=true as "improved beta search engine for higher
relevance and accuracy", and platforms[] is beta-only.
This adds diagnostic passthrough so we can observe the beta response
shape live, and unblocks follow-up work on /api/research/track. All
three params are optional and only forwarded when explicitly set, so
default behavior is unchanged.
Handler also falls back to `suggestions` (beta response shape) when no
artists/tracks/albums array is populated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(research): /track and /playlist become pure ID proxies (KISS)
The original handlers chained /search → /track/:id (and /search → /playlist/:platform/:id
on non-numeric ids). The compound behavior produced wrong results silently,
unpredictable credit charges, and asymmetric error paths that we kept having
to patch (see #366 review). We're reverting both endpoints to
single-call proxies. Discovery is the caller's job via GET /api/research —
which already exposes beta=true for high-relevance search ranking
(a28deb2).
/api/research/track
- Takes a numeric Chartmetric track `id`, single upstream call to /track/:id
- Drops the `q` search param, the `artist` disambiguation param, and the
client-side pickBestTrackMatch ranking helper (not needed — search is the
caller's job)
- 400 when `id` is missing or non-numeric
/api/research/playlist
- Unchanged public shape: still takes platform + id
- Drops the non-numeric-id name-search fallback inside the handler
- Single upstream call to /playlist/:platform/:id for all inputs
Tests: 146 research tests green. `pickBestTrackMatch` and its 8 unit tests
deleted (never shipped; only lived on this branch in fda5592).
Follow-up docs PR will align /research/track's OpenAPI contract with the
new shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(research): /albums id-proxy + /charts enum validation (KISS)
Addresses the remaining two items from the PR #366 review comment.
/api/research/albums
- Replaces the composite name→artist_id→discography flow (same shape
that was returning "DJ Mix" feeds for artist=Drake and duplicated
"Elizabeth Taylor" albums for artist=Taylor Swift) with a thin
/artist/:id/albums proxy.
- New dedicated validator validateGetResearchAlbumsRequest.ts takes a
numeric artist_id; drops the old name/UUID fuzzy-resolve path.
- Discovery is the caller's job via GET /api/research?type=artists&beta=true.
- Note: the shared validateArtistRequest + handleArtistResearch
composites still power ~17 other artist-scoped endpoints (similar,
metrics, cities, audience, career, etc.). Those carry the same risk
class and should get the same treatment in follow-up PRs — out of
scope here.
/api/research/charts
- Validator now enforces the documented enums at our layer:
type ∈ {regional, viral}
interval ∈ {daily, weekly}
latest ∈ {true, false}
- This converts opaque upstream 400s (e.g. type=top → Chartmetric 400)
into specific 400s from us that name the valid values. Nothing is
silently ignored: params not in the spec (date, artist) were never
forwarded and now surface clearly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(research/albums): default isPrimary=true to exclude features
Per Chartmetric docs, /artist/:id/albums has an isPrimary query param
that defaults to false, meaning the response includes every album where
the artist is *featured* — not just their own discography. That's why
canonical ids were returning wrong data on the preview:
artist_id=3380 (Drake) → "nila: welcome to the aprtment (DJ Mix)" etc.
artist_id=3963 (Ariana Grande) → "Wicked: For Good" soundtrack etc.
Our handler now defaults to isPrimary=true so "albums" means the
artist's own discography by default. Callers who actually want features
and compilations can opt in with is_primary=false.
Also exposes limit and offset for pagination (Chartmetric defaults to
limit=100, offset=0). Sort params (sortColumn, sortOrderDesc) not
exposed — upstream defaults (release_date desc) are sensible; can
add later if a caller asks.
Validator rejects:
- is_primary values other than "true"/"false" (400)
- non-positive-integer limit (400)
- negative offset (400)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Recoupable <sidney@recoupable.com>
Co-authored-by: Sweets Sweetman <sweetmantech@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Surfaced while testing api PR recoupable/api#366 on preview (comment): every common track query ("God's Plan", "Hotline Bling", "Thriller", "Sicko Mode", "Flowers") returned the wrong track.
Root cause is upstream behavior: Chartmetric
/search?type=tracks&limit=1picks an arbitrary low-quality match with no popularity sort and no upstream `artist` filter (confirmed against the Chartmetric Search docs — no `artist` param, no sort param, default ordering is whatever upstream's relevance engine returns).This PR tightens the spec so callers can disambiguate, and the api PR will follow with the implementation.
Spec changes
Follow-up
Implementation in the api repo (
recoupable/api) follows in a separate PR — handler will switch Chartmetric's beta search engine on, raise `limit`, sort by `match_strength`, and apply the new `artist` filter against `artist_names[]` before picking `[0]`.Test plan
🤖 Generated with Claude Code
Summary by cubic
Tightened the
/api/research/trackspec to add artist disambiguation, document a 404 for no matches, and fix the response schema to match the actual API. This helps callers resolve ambiguous titles and parse a stable payload.New Features
artistquery param for case-insensitive disambiguation whenqis a plain name; recommend using a Spotify track URL orartistfor ambiguous titles.q(andartist, if provided).Bug Fixes
album_nameandartist_names; enumeratedartists[],albums[],genres[],image_url,duration_ms,album_label,score,explicit, and keptadditionalProperties: true.Written for commit 1bd5911. Summary will update on new commits.
Summary by CodeRabbit
/api/research/trackendpoint documentation with improved clarity on track search resolution methods.artistquery parameter to help disambiguate tracks with commonly used names.