From 3d12c97f2c75aa3834310f6380904f746357206e Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 29 Jul 2026 14:28:10 -0500 Subject: [PATCH] docs(catalogs): GET /api/catalogs/{catalogId}/valuations - persisted valuation history Contract for chat#1889 row 15 (the keystone): the valuation band is persisted wherever it is computed (valuation runs; measurement reads at most once per catalog per day) and read back as a latest-first series. limit=1 is the current value. History is what makes week-over-week deltas possible - the retention hook the weekly report needs. Co-Authored-By: Claude Opus 5 (1M context) --- api-reference/openapi/releases.json | 136 ++++++++++++++++++++- api-reference/songs/catalog-valuations.mdx | 4 + docs.json | 1 + 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 api-reference/songs/catalog-valuations.mdx diff --git a/api-reference/openapi/releases.json b/api-reference/openapi/releases.json index ee25dc8..57e0068 100644 --- a/api-reference/openapi/releases.json +++ b/api-reference/openapi/releases.json @@ -1414,7 +1414,7 @@ "/api/valuation": { "post": { "summary": "Run valuation", - "description": "Generate a catalog from a Spotify artist in one call. Resolves the artist's releases, captures current Spotify play counts (spending the account's credits), materializes an account-owned catalog from the resulting snapshot (idempotent - see [Create catalog](/api-reference/songs/catalogs-create)), and returns the catalog with its estimated value band. The owning account is taken from the credentials, never the body. The searched artist is also linked to the caller's roster (so a funnel signup lands with a populated `GET /api/artists` it can confirm); when the catalog's songs already resolve a canonical artist that one is used, otherwise the searched Spotify artist is linked directly. Synchronous: the request waits for the capture to land (typically under two minutes).", + "description": "Generate a catalog from a Spotify artist in one call. Resolves the artist's releases, captures current Spotify play counts (spending the account's credits), materializes an account-owned catalog from the resulting snapshot (idempotent - see [Create catalog](/api-reference/songs/catalogs-create)), and returns the catalog with its estimated value band. The owning account is taken from the credentials, never the body. The searched artist is also linked to the caller's roster (so a funnel signup lands with a populated `GET /api/artists` it can confirm); when the catalog's songs already resolve a canonical artist that one is used, otherwise the searched Spotify artist is linked directly. Synchronous: the request waits for the capture to land (typically under two minutes). Each run also persists a row in the catalog's valuation history, readable via [Get Catalog Valuations](/api-reference/songs/catalog-valuations).", "security": [ { "apiKeyAuth": [] @@ -1649,6 +1649,85 @@ } } }, + "/api/catalogs/{catalogId}/valuations": { + "get": { + "description": "Get the persisted valuation history for a catalog, latest first. A row is written each time a valuation band is computed for the whole catalog (valuation runs and measurement reads persist at most one row per catalog per day). Use limit=1 for the current value. History is what makes week-over-week deltas possible.", + "security": [ + { + "apiKeyAuth": [] + }, + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "catalogId", + "in": "path", + "description": "The unique identifier of the catalog. The catalog must belong to the authenticated account. Malformed (non-uuid) values are rejected with 400.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Maximum number of valuation rows to return, latest first (default 30, max 100). limit=1 returns the current value. Invalid values are rejected with 400.", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 30 + } + } + ], + "responses": { + "200": { + "description": "The catalog's persisted valuation rows, latest first. Empty when no valuation has been persisted yet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CatalogValuationsResponse" + } + } + } + }, + "400": { + "description": "Malformed catalogId or limit", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Missing or invalid credentials", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Catalog not found or not owned by the authenticated account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, "/api/catalogs/{catalogId}/measurements": { "get": { "description": "Get the latest play counts and a derived valuation band for a catalog. Measurements are captured by [Create measurement job](/api-reference/research/measurement-jobs) runs; the band is computed at read time from the latest capture per song.", @@ -2821,6 +2900,61 @@ } } }, + "CatalogValuationsResponse": { + "type": "object", + "required": [ + "status", + "valuations" + ], + "properties": { + "status": { + "type": "string", + "example": "success" + }, + "valuations": { + "type": "array", + "description": "Persisted valuation rows, latest first.", + "items": { + "type": "object", + "required": [ + "low", + "mid", + "high", + "measured_song_count", + "total_streams", + "measured_at" + ], + "properties": { + "low": { + "type": "number", + "description": "Low end of the estimated catalog value band, USD." + }, + "mid": { + "type": "number", + "description": "Midpoint of the estimated catalog value band, USD." + }, + "high": { + "type": "number", + "description": "High end of the estimated catalog value band, USD." + }, + "measured_song_count": { + "type": "integer", + "description": "Songs measured in the capture this valuation was computed from." + }, + "total_streams": { + "type": "integer", + "description": "Whole-catalog lifetime stream total at measurement time." + }, + "measured_at": { + "type": "string", + "format": "date-time", + "description": "When the underlying measurement was taken." + } + } + } + } + } + }, "CreateArtistRequest": { "type": "object", "required": [ diff --git a/api-reference/songs/catalog-valuations.mdx b/api-reference/songs/catalog-valuations.mdx new file mode 100644 index 0000000..61c6b72 --- /dev/null +++ b/api-reference/songs/catalog-valuations.mdx @@ -0,0 +1,4 @@ +--- +title: Get Catalog Valuations +openapi: get /api/catalogs/{catalogId}/valuations +--- diff --git a/docs.json b/docs.json index 4fdaf99..d144947 100644 --- a/docs.json +++ b/docs.json @@ -164,6 +164,7 @@ "api-reference/songs/catalogs-create", "api-reference/songs/valuation-run", "api-reference/songs/catalog-measurements", + "api-reference/songs/catalog-valuations", "api-reference/songs/catalog-songs", "api-reference/songs/catalog-songs-add", "api-reference/songs/catalog-songs-delete"