From 65f46c28d4d965e5578f2fedda39944a8a8449b9 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 6 Aug 2026 10:07:56 -0500 Subject: [PATCH 1/2] docs(catalogs): GET /api/accounts/{id}/catalogs returns a valuation and an owner (chat#1943) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A catalog card on /catalogs shows a name, a song count and a date — the list response has nothing else to render. Both fields already exist in the product: the report page derives a band for one catalog, and account_catalogs knows whether a catalog is yours or an organization's. This is the contract that puts both on the list; the api implementation follows. - CatalogListItem: the catalog plus measured_song_count, valuation and owner. Separate from Catalog on purpose — the create and rename responses return the bare catalog and must not claim these fields. - CatalogValuation: low/mid/high, explicitly null when nothing is measured, so a client renders "not measured" rather than $0. - CatalogOwner: id, name, image, is_organization. Nullable name/image because account_info.image is null for some accounts (the Recoup org's is), and a client should fall back to initials. - A catalog owned both directly and through an organization reports the organization: that is the ownership worth flagging to a member. Co-Authored-By: Claude Opus 5 (1M context) --- api-reference/openapi/releases.json | 103 +++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 3 deletions(-) diff --git a/api-reference/openapi/releases.json b/api-reference/openapi/releases.json index 9d64331..64586cd 100644 --- a/api-reference/openapi/releases.json +++ b/api-reference/openapi/releases.json @@ -1262,7 +1262,7 @@ }, "/api/accounts/{id}/catalogs": { "get": { - "description": "Retrieve the catalogs visible to a specific account. Returns the catalogs the account owns directly, plus the catalogs owned by any organization the account belongs to - so every member of an organization sees that organization's catalogs without passing the organization's id. A catalog owned both directly and through an organization is returned once. An account that belongs to no organization receives only its own catalogs. Create an organization-owned catalog with [Create catalog](/api-reference/songs/catalogs-create) or [Run valuation](/api-reference/songs/valuation-run).", + "description": "Retrieve the catalogs visible to a specific account. Returns the catalogs the account owns directly, plus the catalogs owned by any organization the account belongs to - so every member of an organization sees that organization's catalogs without passing the organization's id. A catalog owned both directly and through an organization is returned once. An account that belongs to no organization receives only its own catalogs. Create an organization-owned catalog with [Create catalog](/api-reference/songs/catalogs-create) or [Run valuation](/api-reference/songs/valuation-run).\n\nEach catalog carries its estimated value band and its owner, so a list of catalogs can be ranked by worth and a member can tell an organization's catalog from their own. `valuation` is null for a catalog with no measured songs; `owner.image` is null when the owner account has no avatar.", "parameters": [ { "name": "id", @@ -2945,6 +2945,103 @@ } } }, + "CatalogValuation": { + "type": "object", + "description": "Estimated value band for a catalog, derived at read time from the latest Spotify play counts with the same model as [Get catalog measurements](/api-reference/songs/catalog-measurements) - annual run-rate from lifetime streams over the catalog's age, converted to net label share and multiplied by a 10-16x master-catalog multiple. Null when the catalog has no measured songs.", + "required": [ + "low", + "mid", + "high" + ], + "properties": { + "low": { + "type": "number", + "description": "Low end of the band, USD" + }, + "mid": { + "type": "number", + "description": "Midpoint of the band, USD - the central estimate" + }, + "high": { + "type": "number", + "description": "High end of the band, USD" + } + } + }, + "CatalogOwner": { + "type": "object", + "description": "The account a catalog belongs to. A catalog owned both directly and through an organization reports the organization - that is the ownership worth surfacing to a member.", + "required": [ + "id", + "name", + "image", + "is_organization" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Owner account id" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Owner display name, null when the account has none" + }, + "image": { + "type": "string", + "nullable": true, + "description": "Owner avatar URL from account_info, null when the account has no image. Clients should fall back to initials rather than render a broken image." + }, + "is_organization": { + "type": "boolean", + "description": "True when the owner is an organization the caller belongs to, false when it is a personal account" + } + } + }, + "CatalogListItem": { + "type": "object", + "description": "A catalog as returned by [Get catalogs](/api-reference/songs/catalogs): the catalog plus its estimated value and its owner. Only this list carries those two fields - the create and rename responses return the bare catalog.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier for the catalog" + }, + "name": { + "type": "string", + "description": "Name of the catalog" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO timestamp of when the catalog was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "ISO timestamp of when the catalog was last updated" + }, + "measured_song_count": { + "type": "integer", + "description": "Songs in the catalog with at least one play-count measurement. 0 for a catalog that was never measured, in which case valuation is null." + }, + "valuation": { + "oneOf": [ + { + "$ref": "#/components/schemas/CatalogValuation" + }, + { + "type": "null" + } + ], + "description": "Estimated value band, or null when nothing in the catalog has been measured" + }, + "owner": { + "$ref": "#/components/schemas/CatalogOwner" + } + } + }, "CatalogSong": { "type": "object", "description": "A song within a catalog with its metadata and associated artists", @@ -3082,9 +3179,9 @@ "catalogs": { "type": "array", "items": { - "$ref": "#/components/schemas/Catalog" + "$ref": "#/components/schemas/CatalogListItem" }, - "description": "Array of catalog objects" + "description": "Array of catalog objects, each with its estimated value and its owner" }, "error": { "type": "string", From 2e87b63122f1ebbdf3ac3024ea283e78b6e5f0ea Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 6 Aug 2026 10:33:52 -0500 Subject: [PATCH 2/2] review: reuse ValuationBand, require every list-item field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two valid findings on docs#294: - cubic P3: CatalogValuation duplicated the existing ValuationBand — same low/mid/high, same model — and the two were already drifting in prose ("10-16x" vs "10x/13x/16x"). Deleted it; the list item now points at ValuationBand, which is exactly the schema GET /catalogs/{id}/measurements returns, so the card and the report it opens are documented as the same number rather than merely described alike. - CodeRabbit + cubic: CatalogListItem had no `required` array, so a generated client could treat id/valuation/owner as absent. All seven properties are always present in the 200 — `valuation` is nullable, which is not the same as optional. Co-Authored-By: Claude Opus 5 (1M context) --- api-reference/openapi/releases.json | 36 +++++++++-------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/api-reference/openapi/releases.json b/api-reference/openapi/releases.json index 64586cd..03c2577 100644 --- a/api-reference/openapi/releases.json +++ b/api-reference/openapi/releases.json @@ -2945,29 +2945,6 @@ } } }, - "CatalogValuation": { - "type": "object", - "description": "Estimated value band for a catalog, derived at read time from the latest Spotify play counts with the same model as [Get catalog measurements](/api-reference/songs/catalog-measurements) - annual run-rate from lifetime streams over the catalog's age, converted to net label share and multiplied by a 10-16x master-catalog multiple. Null when the catalog has no measured songs.", - "required": [ - "low", - "mid", - "high" - ], - "properties": { - "low": { - "type": "number", - "description": "Low end of the band, USD" - }, - "mid": { - "type": "number", - "description": "Midpoint of the band, USD - the central estimate" - }, - "high": { - "type": "number", - "description": "High end of the band, USD" - } - } - }, "CatalogOwner": { "type": "object", "description": "The account a catalog belongs to. A catalog owned both directly and through an organization reports the organization - that is the ownership worth surfacing to a member.", @@ -3002,6 +2979,15 @@ "CatalogListItem": { "type": "object", "description": "A catalog as returned by [Get catalogs](/api-reference/songs/catalogs): the catalog plus its estimated value and its owner. Only this list carries those two fields - the create and rename responses return the bare catalog.", + "required": [ + "id", + "name", + "created_at", + "updated_at", + "measured_song_count", + "valuation", + "owner" + ], "properties": { "id": { "type": "string", @@ -3029,13 +3015,13 @@ "valuation": { "oneOf": [ { - "$ref": "#/components/schemas/CatalogValuation" + "$ref": "#/components/schemas/ValuationBand" }, { "type": "null" } ], - "description": "Estimated value band, or null when nothing in the catalog has been measured" + "description": "Estimated value band - the same schema and model as [Get catalog measurements](/api-reference/songs/catalog-measurements) returns, so a list and the report it opens cannot disagree. Null when nothing in the catalog has been measured; clients should say so rather than render $0." }, "owner": { "$ref": "#/components/schemas/CatalogOwner"