From 61c76de0ef08bcfee64c88563458c8e614e047f4 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Wed, 29 Apr 2026 03:02:40 +0530 Subject: [PATCH 1/9] docs(docs): document /api/email, /api/ai/models, /api/youtube/channel-info Adds OpenAPI specs and reference pages for misc-reads endpoints landing on api as part of the chat to api migration. Co-Authored-By: Claude Opus 4.7 (1M context) --- api-reference/ai/models.mdx | 4 + api-reference/email/track.mdx | 4 + api-reference/openapi/ai.json | 122 ++++++++++++++ api-reference/openapi/email.json | 96 +++++++++++ api-reference/openapi/youtube.json | 224 +++++++++++++++++++++++++ api-reference/youtube/channel-info.mdx | 4 + docs.json | 18 ++ 7 files changed, 472 insertions(+) create mode 100644 api-reference/ai/models.mdx create mode 100644 api-reference/email/track.mdx create mode 100644 api-reference/openapi/ai.json create mode 100644 api-reference/openapi/email.json create mode 100644 api-reference/openapi/youtube.json create mode 100644 api-reference/youtube/channel-info.mdx diff --git a/api-reference/ai/models.mdx b/api-reference/ai/models.mdx new file mode 100644 index 0000000..6bd25e0 --- /dev/null +++ b/api-reference/ai/models.mdx @@ -0,0 +1,4 @@ +--- +title: 'List Models' +openapi: '/api-reference/openapi/ai.json GET /api/ai/models' +--- diff --git a/api-reference/email/track.mdx b/api-reference/email/track.mdx new file mode 100644 index 0000000..b07451e --- /dev/null +++ b/api-reference/email/track.mdx @@ -0,0 +1,4 @@ +--- +title: 'Track Email' +openapi: '/api-reference/openapi/email.json GET /api/email' +--- diff --git a/api-reference/openapi/ai.json b/api-reference/openapi/ai.json new file mode 100644 index 0000000..f579519 --- /dev/null +++ b/api-reference/openapi/ai.json @@ -0,0 +1,122 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Recoup API - AI", + "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", + "license": { + "name": "MIT" + }, + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://recoup-api.vercel.app" + } + ], + "paths": { + "/api/ai/models": { + "get": { + "description": "Returns the catalog of language models available through the Vercel AI Gateway. Embedding models are filtered out. The full schema mirrors the Vercel AI Gateway response (`GatewayLanguageModelEntry` from `@ai-sdk/gateway`); the documented properties below are the load-bearing fields used by clients. No authentication required.", + "responses": { + "200": { + "description": "Model catalog retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AIModelsResponse" + } + } + } + }, + "500": { + "description": "Internal server error - failed to fetch models from the gateway", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AIModelsErrorResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AIModelsResponse": { + "type": "object", + "required": [ + "models" + ], + "properties": { + "models": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GatewayLanguageModelEntry" + }, + "description": "List of language models available through the Vercel AI Gateway" + } + } + }, + "GatewayLanguageModelEntry": { + "type": "object", + "additionalProperties": true, + "description": "A language model entry as returned by the Vercel AI Gateway. Full schema mirrors the upstream `GatewayLanguageModelEntry` type from `@ai-sdk/gateway`.", + "properties": { + "id": { + "type": "string", + "description": "Fully qualified model identifier (e.g., 'anthropic/claude-3-5-sonnet')", + "example": "anthropic/claude-3-5-sonnet" + }, + "name": { + "type": "string", + "description": "Human-readable display name for the model", + "example": "Claude 3.5 Sonnet" + }, + "description": { + "type": "string", + "description": "Provider-supplied description of the model" + }, + "modelType": { + "type": "string", + "description": "Type of model - embedding models are filtered out before the response is returned", + "example": "language" + }, + "pricing": { + "type": "object", + "additionalProperties": true, + "description": "Per-token pricing information as reported by the gateway", + "properties": { + "input": { + "type": "string", + "description": "Cost per input token (USD)" + }, + "output": { + "type": "string", + "description": "Cost per output token (USD)" + } + } + }, + "specification": { + "type": "object", + "additionalProperties": true, + "description": "Model specification metadata (provider, version, capabilities). Shape mirrors the gateway response." + } + } + }, + "AIModelsErrorResponse": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "description": "Error message describing what went wrong" + } + } + } + } + } +} diff --git a/api-reference/openapi/email.json b/api-reference/openapi/email.json new file mode 100644 index 0000000..65bb731 --- /dev/null +++ b/api-reference/openapi/email.json @@ -0,0 +1,96 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Recoup API - Email", + "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", + "license": { + "name": "MIT" + }, + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://recoup-api.vercel.app" + } + ], + "paths": { + "/api/email": { + "get": { + "description": "Fire-and-forget email tracking endpoint that upserts a contact in Loops. Used to track sign-ups and other lifecycle events. No authentication required.", + "parameters": [ + { + "name": "email", + "in": "query", + "description": "URL-encoded email address to track in Loops", + "required": true, + "schema": { + "type": "string", + "format": "email" + } + } + ], + "responses": { + "200": { + "description": "Email tracked successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailTrackResponse" + } + } + } + }, + "400": { + "description": "Bad request - tracking failed (invalid email or upstream error)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailTrackErrorResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "EmailTrackResponse": { + "type": "object", + "required": [ + "success", + "message", + "id" + ], + "properties": { + "success": { + "type": "boolean", + "description": "Whether the Loops contact upsert succeeded" + }, + "message": { + "type": "string", + "description": "Human-readable status message from Loops", + "example": "Email tracked successfully" + }, + "id": { + "type": "string", + "description": "The Loops contact ID for the tracked email" + } + } + }, + "EmailTrackErrorResponse": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "description": "Error message describing what went wrong" + } + } + } + } + } +} diff --git a/api-reference/openapi/youtube.json b/api-reference/openapi/youtube.json new file mode 100644 index 0000000..2ed3bfa --- /dev/null +++ b/api-reference/openapi/youtube.json @@ -0,0 +1,224 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Recoup API - YouTube", + "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", + "license": { + "name": "MIT" + }, + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://recoup-api.vercel.app" + } + ], + "paths": { + "/api/youtube/channel-info": { + "get": { + "description": "Fetches the YouTube channel summary for an artist using their stored OAuth tokens. Returns the channel's snippet, statistics, and branding fields. The response always uses HTTP 200 (errors are surfaced via the `tokenStatus` field) except for missing-parameter requests, which return 400.", + "parameters": [ + { + "name": "artist_account_id", + "in": "query", + "description": "The unique identifier of the artist account whose YouTube channel info should be fetched", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Channel info request processed. Inspect `tokenStatus` to distinguish success from token/API failures.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/YouTubeChannelInfoResponse" + } + } + } + }, + "400": { + "description": "Bad request - missing `artist_account_id` query parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/YouTubeChannelInfoResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "YouTubeChannelInfoResponse": { + "type": "object", + "required": [ + "success", + "tokenStatus" + ], + "properties": { + "success": { + "type": "boolean", + "description": "Whether the channel data was fetched successfully" + }, + "channels": { + "type": "array", + "nullable": true, + "items": { + "$ref": "#/components/schemas/YouTubeChannelData" + }, + "description": "Array of channels owned by the authenticated artist, or null when the request failed" + }, + "tokenStatus": { + "type": "string", + "enum": [ + "valid", + "invalid", + "missing_param", + "api_error", + "error" + ], + "description": "Status of the OAuth token check and downstream call. `valid` indicates a successful response; other values indicate why channel data is null." + }, + "error": { + "type": "string", + "description": "Human-readable error message (only present when `success` is false)" + }, + "details": { + "type": "string", + "description": "Additional error context (only present for unexpected server errors)" + } + } + }, + "YouTubeChannelData": { + "type": "object", + "required": [ + "id", + "title", + "description", + "thumbnails", + "statistics", + "publishedAt" + ], + "description": "Summary of a YouTube channel as returned by the YouTube Data API, mapped to the shape used by Recoup clients.", + "properties": { + "id": { + "type": "string", + "description": "YouTube channel ID" + }, + "title": { + "type": "string", + "description": "Channel display title" + }, + "description": { + "type": "string", + "description": "Channel description text" + }, + "thumbnails": { + "type": "object", + "description": "Thumbnail URLs at default, medium, and high resolutions", + "properties": { + "default": { + "type": "object", + "properties": { + "url": { + "type": "string", + "nullable": true, + "description": "URL of the default-resolution thumbnail" + } + } + }, + "medium": { + "type": "object", + "properties": { + "url": { + "type": "string", + "nullable": true, + "description": "URL of the medium-resolution thumbnail" + } + } + }, + "high": { + "type": "object", + "properties": { + "url": { + "type": "string", + "nullable": true, + "description": "URL of the high-resolution thumbnail" + } + } + } + } + }, + "statistics": { + "type": "object", + "required": [ + "subscriberCount", + "videoCount", + "viewCount" + ], + "properties": { + "subscriberCount": { + "type": "string", + "description": "Number of subscribers (string-encoded integer; '0' if hidden)" + }, + "videoCount": { + "type": "string", + "description": "Total number of public videos uploaded (string-encoded integer)" + }, + "viewCount": { + "type": "string", + "description": "Total channel view count (string-encoded integer)" + }, + "hiddenSubscriberCount": { + "type": "boolean", + "description": "True when the channel hides its subscriber count" + } + } + }, + "customUrl": { + "type": "string", + "nullable": true, + "description": "Channel custom URL handle, if set" + }, + "country": { + "type": "string", + "nullable": true, + "description": "Two-letter country code declared by the channel, if set" + }, + "publishedAt": { + "type": "string", + "format": "date-time", + "description": "ISO timestamp of when the channel was created" + }, + "uploadsPlaylistId": { + "type": "string", + "description": "Playlist ID containing all of the channel's uploaded videos" + }, + "branding": { + "type": "object", + "description": "Channel branding settings (only present when branding fields are requested)", + "properties": { + "keywords": { + "type": "string", + "nullable": true, + "description": "Channel keywords configured in YouTube Studio" + }, + "defaultLanguage": { + "type": "string", + "nullable": true, + "description": "Default language code for the channel" + } + } + } + } + } + } + } +} diff --git a/api-reference/youtube/channel-info.mdx b/api-reference/youtube/channel-info.mdx new file mode 100644 index 0000000..9063d35 --- /dev/null +++ b/api-reference/youtube/channel-info.mdx @@ -0,0 +1,4 @@ +--- +title: 'Channel Info' +openapi: '/api-reference/openapi/youtube.json GET /api/youtube/channel-info' +--- diff --git a/docs.json b/docs.json index c339033..833f97a 100644 --- a/docs.json +++ b/docs.json @@ -262,6 +262,12 @@ "pages": [ "api-reference/apify/scraper" ] + }, + { + "group": "YouTube", + "pages": [ + "api-reference/youtube/channel-info" + ] } ] }, @@ -342,6 +348,18 @@ "pages": [ "api-reference/notifications/create" ] + }, + { + "group": "Email", + "pages": [ + "api-reference/email/track" + ] + }, + { + "group": "AI", + "pages": [ + "api-reference/ai/models" + ] } ] } From 04bb4e0eab5d59083953406fa10f6f67c96facf7 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Wed, 29 Apr 2026 03:26:33 +0530 Subject: [PATCH 2/9] fix(docs): address AI review on misc-reads PR - replace OpenAPI 3.0 nullable:true with 3.1 type:[..., "null"] arrays - email.json: split 400 (validation) from 500 (upstream/internal) Co-Authored-By: Claude Opus 4.7 (1M context) --- api-reference/openapi/email.json | 12 +++++++++++- api-reference/openapi/youtube.json | 24 ++++++++---------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/api-reference/openapi/email.json b/api-reference/openapi/email.json index 65bb731..23f71d5 100644 --- a/api-reference/openapi/email.json +++ b/api-reference/openapi/email.json @@ -41,7 +41,17 @@ } }, "400": { - "description": "Bad request - tracking failed (invalid email or upstream error)", + "description": "Bad request - missing or invalid `email` query parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailTrackErrorResponse" + } + } + } + }, + "500": { + "description": "Internal server error - upstream Loops failure or unexpected server error", "content": { "application/json": { "schema": { diff --git a/api-reference/openapi/youtube.json b/api-reference/openapi/youtube.json index 2ed3bfa..1b32a97 100644 --- a/api-reference/openapi/youtube.json +++ b/api-reference/openapi/youtube.json @@ -68,8 +68,7 @@ "description": "Whether the channel data was fetched successfully" }, "channels": { - "type": "array", - "nullable": true, + "type": ["array", "null"], "items": { "$ref": "#/components/schemas/YouTubeChannelData" }, @@ -128,8 +127,7 @@ "type": "object", "properties": { "url": { - "type": "string", - "nullable": true, + "type": ["string", "null"], "description": "URL of the default-resolution thumbnail" } } @@ -138,8 +136,7 @@ "type": "object", "properties": { "url": { - "type": "string", - "nullable": true, + "type": ["string", "null"], "description": "URL of the medium-resolution thumbnail" } } @@ -148,8 +145,7 @@ "type": "object", "properties": { "url": { - "type": "string", - "nullable": true, + "type": ["string", "null"], "description": "URL of the high-resolution thumbnail" } } @@ -183,13 +179,11 @@ } }, "customUrl": { - "type": "string", - "nullable": true, + "type": ["string", "null"], "description": "Channel custom URL handle, if set" }, "country": { - "type": "string", - "nullable": true, + "type": ["string", "null"], "description": "Two-letter country code declared by the channel, if set" }, "publishedAt": { @@ -206,13 +200,11 @@ "description": "Channel branding settings (only present when branding fields are requested)", "properties": { "keywords": { - "type": "string", - "nullable": true, + "type": ["string", "null"], "description": "Channel keywords configured in YouTube Studio" }, "defaultLanguage": { - "type": "string", - "nullable": true, + "type": ["string", "null"], "description": "Default language code for the channel" } } From 41fc74d05276a9eece151c1c65da5f15708c1776 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Wed, 29 Apr 2026 04:17:31 +0530 Subject: [PATCH 3/9] fix(docs): change /api/email from GET to POST Reflects the api-side change: email tracking upserts a Loops contact (a write) and now takes a JSON body instead of a URL query param. Co-Authored-By: Claude Opus 4.7 (1M context) --- api-reference/email/track.mdx | 2 +- api-reference/openapi/email.json | 30 ++++++++++++++++++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/api-reference/email/track.mdx b/api-reference/email/track.mdx index b07451e..e7b5f07 100644 --- a/api-reference/email/track.mdx +++ b/api-reference/email/track.mdx @@ -1,4 +1,4 @@ --- title: 'Track Email' -openapi: '/api-reference/openapi/email.json GET /api/email' +openapi: '/api-reference/openapi/email.json POST /api/email' --- diff --git a/api-reference/openapi/email.json b/api-reference/openapi/email.json index 23f71d5..983c120 100644 --- a/api-reference/openapi/email.json +++ b/api-reference/openapi/email.json @@ -15,20 +15,26 @@ ], "paths": { "/api/email": { - "get": { + "post": { "description": "Fire-and-forget email tracking endpoint that upserts a contact in Loops. Used to track sign-ups and other lifecycle events. No authentication required.", - "parameters": [ - { - "name": "email", - "in": "query", - "description": "URL-encoded email address to track in Loops", - "required": true, - "schema": { - "type": "string", - "format": "email" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["email"], + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Email address to track in Loops." + } + } + } } } - ], + }, "responses": { "200": { "description": "Email tracked successfully", @@ -41,7 +47,7 @@ } }, "400": { - "description": "Bad request - missing or invalid `email` query parameter", + "description": "Bad request - missing or invalid `email` in request body", "content": { "application/json": { "schema": { From dd19a97a6cdd89aa839ecf5a3b50b53c2fd23632 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 30 Apr 2026 02:10:59 +0530 Subject: [PATCH 4/9] revert(docs): drop /api/email documentation Loops is no longer used; the email tracking endpoint is removed from this migration entirely. Co-Authored-By: Claude Opus 4.7 (1M context) --- api-reference/email/track.mdx | 4 -- api-reference/openapi/email.json | 112 ------------------------------- docs.json | 6 -- 3 files changed, 122 deletions(-) delete mode 100644 api-reference/email/track.mdx delete mode 100644 api-reference/openapi/email.json diff --git a/api-reference/email/track.mdx b/api-reference/email/track.mdx deleted file mode 100644 index e7b5f07..0000000 --- a/api-reference/email/track.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Track Email' -openapi: '/api-reference/openapi/email.json POST /api/email' ---- diff --git a/api-reference/openapi/email.json b/api-reference/openapi/email.json deleted file mode 100644 index 983c120..0000000 --- a/api-reference/openapi/email.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "Recoup API - Email", - "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", - "license": { - "name": "MIT" - }, - "version": "1.0.0" - }, - "servers": [ - { - "url": "https://recoup-api.vercel.app" - } - ], - "paths": { - "/api/email": { - "post": { - "description": "Fire-and-forget email tracking endpoint that upserts a contact in Loops. Used to track sign-ups and other lifecycle events. No authentication required.", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["email"], - "properties": { - "email": { - "type": "string", - "format": "email", - "description": "Email address to track in Loops." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Email tracked successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmailTrackResponse" - } - } - } - }, - "400": { - "description": "Bad request - missing or invalid `email` in request body", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmailTrackErrorResponse" - } - } - } - }, - "500": { - "description": "Internal server error - upstream Loops failure or unexpected server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmailTrackErrorResponse" - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "EmailTrackResponse": { - "type": "object", - "required": [ - "success", - "message", - "id" - ], - "properties": { - "success": { - "type": "boolean", - "description": "Whether the Loops contact upsert succeeded" - }, - "message": { - "type": "string", - "description": "Human-readable status message from Loops", - "example": "Email tracked successfully" - }, - "id": { - "type": "string", - "description": "The Loops contact ID for the tracked email" - } - } - }, - "EmailTrackErrorResponse": { - "type": "object", - "required": [ - "message" - ], - "properties": { - "message": { - "type": "string", - "description": "Error message describing what went wrong" - } - } - } - } - } -} diff --git a/docs.json b/docs.json index 833f97a..950b59d 100644 --- a/docs.json +++ b/docs.json @@ -349,12 +349,6 @@ "api-reference/notifications/create" ] }, - { - "group": "Email", - "pages": [ - "api-reference/email/track" - ] - }, { "group": "AI", "pages": [ From 46bef4fa0a6c82cb49bd1403125e84c10c3c1e2b Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 30 Apr 2026 02:49:59 +0530 Subject: [PATCH 5/9] docs(docs): slim youtube channel-info response schema Mirrors the api response cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) --- api-reference/openapi/youtube.json | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/api-reference/openapi/youtube.json b/api-reference/openapi/youtube.json index 1b32a97..c5a36e8 100644 --- a/api-reference/openapi/youtube.json +++ b/api-reference/openapi/youtube.json @@ -16,7 +16,7 @@ "paths": { "/api/youtube/channel-info": { "get": { - "description": "Fetches the YouTube channel summary for an artist using their stored OAuth tokens. Returns the channel's snippet, statistics, and branding fields. The response always uses HTTP 200 (errors are surfaced via the `tokenStatus` field) except for missing-parameter requests, which return 400.", + "description": "Fetches the YouTube channel summary for an artist using their stored OAuth tokens. Returns the channel's snippet, statistics, and branding fields. The response always uses HTTP 200 (errors are surfaced via `success: false`) except for missing-parameter requests, which return 400.", "parameters": [ { "name": "artist_account_id", @@ -31,7 +31,7 @@ ], "responses": { "200": { - "description": "Channel info request processed. Inspect `tokenStatus` to distinguish success from token/API failures.", + "description": "Channel info request processed. Inspect `success` to distinguish a successful response from token/API failures.", "content": { "application/json": { "schema": { @@ -59,8 +59,7 @@ "YouTubeChannelInfoResponse": { "type": "object", "required": [ - "success", - "tokenStatus" + "success" ], "properties": { "success": { @@ -73,25 +72,6 @@ "$ref": "#/components/schemas/YouTubeChannelData" }, "description": "Array of channels owned by the authenticated artist, or null when the request failed" - }, - "tokenStatus": { - "type": "string", - "enum": [ - "valid", - "invalid", - "missing_param", - "api_error", - "error" - ], - "description": "Status of the OAuth token check and downstream call. `valid` indicates a successful response; other values indicate why channel data is null." - }, - "error": { - "type": "string", - "description": "Human-readable error message (only present when `success` is false)" - }, - "details": { - "type": "string", - "description": "Additional error context (only present for unexpected server errors)" } } }, From 02cc2c934d6994feb801498d8d6291f1138fc233 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 30 Apr 2026 03:13:03 +0530 Subject: [PATCH 6/9] docs(docs): mirror api youtube response convention Drop the `success` field from the YouTube channel-info OpenAPI schema; align with the api-side `{ status: "success", channels }` envelope. Re-auth requirement is now signalled by `channels: null`. Co-Authored-By: Claude Opus 4.7 (1M context) --- api-reference/openapi/youtube.json | 32 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/api-reference/openapi/youtube.json b/api-reference/openapi/youtube.json index c5a36e8..e72111a 100644 --- a/api-reference/openapi/youtube.json +++ b/api-reference/openapi/youtube.json @@ -16,7 +16,7 @@ "paths": { "/api/youtube/channel-info": { "get": { - "description": "Fetches the YouTube channel summary for an artist using their stored OAuth tokens. Returns the channel's snippet, statistics, and branding fields. The response always uses HTTP 200 (errors are surfaced via `success: false`) except for missing-parameter requests, which return 400.", + "description": "Fetches the YouTube channel summary for an artist using their stored OAuth tokens. Returns the channel's snippet, statistics, and branding fields. The response uses HTTP 200 with `channels: null` to signal that re-authentication is required (token missing/expired/refresh failed). HTTP 400 is returned only when `artist_account_id` is missing.", "parameters": [ { "name": "artist_account_id", @@ -31,7 +31,7 @@ ], "responses": { "200": { - "description": "Channel info request processed. Inspect `success` to distinguish a successful response from token/API failures.", + "description": "Channel info request processed. `channels` is the array of channels on success, or `null` when re-auth is required.", "content": { "application/json": { "schema": { @@ -45,7 +45,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/YouTubeChannelInfoResponse" + "$ref": "#/components/schemas/YouTubeChannelInfoErrorResponse" } } } @@ -59,19 +59,35 @@ "YouTubeChannelInfoResponse": { "type": "object", "required": [ - "success" + "status", + "channels" ], "properties": { - "success": { - "type": "boolean", - "description": "Whether the channel data was fetched successfully" + "status": { + "type": "string", + "enum": ["success"], + "description": "Always `\"success\"` for HTTP 200 responses" }, "channels": { "type": ["array", "null"], "items": { "$ref": "#/components/schemas/YouTubeChannelData" }, - "description": "Array of channels owned by the authenticated artist, or null when the request failed" + "description": "Array of channels owned by the authenticated artist, or `null` when re-authentication is required" + } + } + }, + "YouTubeChannelInfoErrorResponse": { + "type": "object", + "required": ["status", "message"], + "properties": { + "status": { + "type": "string", + "enum": ["error"] + }, + "message": { + "type": "string", + "description": "Human-readable error message" } } }, From b65316b77b9050ceba29e9546035919fb0c3cf58 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 30 Apr 2026 03:23:05 +0530 Subject: [PATCH 7/9] docs(docs): add 401/502 to youtube channel-info schema Co-Authored-By: Claude Opus 4.7 (1M context) --- api-reference/openapi/youtube.json | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/api-reference/openapi/youtube.json b/api-reference/openapi/youtube.json index e72111a..2aef500 100644 --- a/api-reference/openapi/youtube.json +++ b/api-reference/openapi/youtube.json @@ -16,7 +16,7 @@ "paths": { "/api/youtube/channel-info": { "get": { - "description": "Fetches the YouTube channel summary for an artist using their stored OAuth tokens. Returns the channel's snippet, statistics, and branding fields. The response uses HTTP 200 with `channels: null` to signal that re-authentication is required (token missing/expired/refresh failed). HTTP 400 is returned only when `artist_account_id` is missing.", + "description": "Fetches the YouTube channel summary for an artist using their stored OAuth tokens. Returns the channel's snippet, statistics, and branding fields. HTTP 401 indicates the stored YouTube tokens could not be validated/refreshed and the user must re-authenticate. HTTP 502 indicates an upstream YouTube API failure. HTTP 400 is returned when `artist_account_id` is missing.", "parameters": [ { "name": "artist_account_id", @@ -31,7 +31,7 @@ ], "responses": { "200": { - "description": "Channel info request processed. `channels` is the array of channels on success, or `null` when re-auth is required.", + "description": "Channel info fetched successfully.", "content": { "application/json": { "schema": { @@ -49,6 +49,26 @@ } } } + }, + "401": { + "description": "YouTube authentication required - the stored tokens are missing, expired, or could not be refreshed. The client should prompt the user to re-authenticate with YouTube.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/YouTubeChannelInfoErrorResponse" + } + } + } + }, + "502": { + "description": "Upstream YouTube API failure (rate limit, 5xx from Google, or transient network error).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/YouTubeChannelInfoErrorResponse" + } + } + } } } } @@ -69,11 +89,11 @@ "description": "Always `\"success\"` for HTTP 200 responses" }, "channels": { - "type": ["array", "null"], + "type": "array", "items": { "$ref": "#/components/schemas/YouTubeChannelData" }, - "description": "Array of channels owned by the authenticated artist, or `null` when re-authentication is required" + "description": "Array of channels owned by the authenticated artist" } } }, From e4292452c7bb29e404d6350883ce8ab72504a5bc Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 30 Apr 2026 04:24:25 +0530 Subject: [PATCH 8/9] docs(docs): add auth + 403 to youtube channel-info schema Co-Authored-By: Claude Opus 4.7 (1M context) --- api-reference/openapi/youtube.json | 34 ++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/api-reference/openapi/youtube.json b/api-reference/openapi/youtube.json index 2aef500..0c37b96 100644 --- a/api-reference/openapi/youtube.json +++ b/api-reference/openapi/youtube.json @@ -16,7 +16,15 @@ "paths": { "/api/youtube/channel-info": { "get": { - "description": "Fetches the YouTube channel summary for an artist using their stored OAuth tokens. Returns the channel's snippet, statistics, and branding fields. HTTP 401 indicates the stored YouTube tokens could not be validated/refreshed and the user must re-authenticate. HTTP 502 indicates an upstream YouTube API failure. HTTP 400 is returned when `artist_account_id` is missing.", + "description": "Fetches the YouTube channel summary for an artist using their stored OAuth tokens. Returns the channel's snippet, statistics, and branding fields. Requires authentication and access to the requested artist account. HTTP 401 indicates missing/invalid Recoup auth or that the stored YouTube tokens could not be validated/refreshed (the user must re-authenticate). HTTP 403 indicates the authenticated caller has no access to the requested artist. HTTP 502 indicates an upstream YouTube API failure. HTTP 400 is returned when `artist_account_id` is missing.", + "security": [ + { + "bearerAuth": [] + }, + { + "apiKeyAuth": [] + } + ], "parameters": [ { "name": "artist_account_id", @@ -51,7 +59,17 @@ } }, "401": { - "description": "YouTube authentication required - the stored tokens are missing, expired, or could not be refreshed. The client should prompt the user to re-authenticate with YouTube.", + "description": "Unauthorized - Recoup auth is missing/invalid, or the stored YouTube tokens are missing, expired, or could not be refreshed. The client should authenticate (or prompt the user to re-authenticate with YouTube).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/YouTubeChannelInfoErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden - the authenticated caller does not have access to the requested artist account.", "content": { "application/json": { "schema": { @@ -75,6 +93,18 @@ } }, "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer" + }, + "apiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "x-api-key", + "description": "Your Recoup API key. [Learn more](/quickstart#api-keys)." + } + }, "schemas": { "YouTubeChannelInfoResponse": { "type": "object", From 2809e71494694700d9d91c5f7140d0d0a4dcb2f7 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Thu, 30 Apr 2026 04:54:47 +0530 Subject: [PATCH 9/9] revert(docs): drop youtube channel-info schema Co-Authored-By: Claude Opus 4.7 (1M context) --- api-reference/openapi/youtube.json | 262 ------------------------- api-reference/youtube/channel-info.mdx | 4 - docs.json | 6 - 3 files changed, 272 deletions(-) delete mode 100644 api-reference/openapi/youtube.json delete mode 100644 api-reference/youtube/channel-info.mdx diff --git a/api-reference/openapi/youtube.json b/api-reference/openapi/youtube.json deleted file mode 100644 index 0c37b96..0000000 --- a/api-reference/openapi/youtube.json +++ /dev/null @@ -1,262 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "Recoup API - YouTube", - "description": "API documentation for the Recoup platform - an AI agent platform for the music industry", - "license": { - "name": "MIT" - }, - "version": "1.0.0" - }, - "servers": [ - { - "url": "https://recoup-api.vercel.app" - } - ], - "paths": { - "/api/youtube/channel-info": { - "get": { - "description": "Fetches the YouTube channel summary for an artist using their stored OAuth tokens. Returns the channel's snippet, statistics, and branding fields. Requires authentication and access to the requested artist account. HTTP 401 indicates missing/invalid Recoup auth or that the stored YouTube tokens could not be validated/refreshed (the user must re-authenticate). HTTP 403 indicates the authenticated caller has no access to the requested artist. HTTP 502 indicates an upstream YouTube API failure. HTTP 400 is returned when `artist_account_id` is missing.", - "security": [ - { - "bearerAuth": [] - }, - { - "apiKeyAuth": [] - } - ], - "parameters": [ - { - "name": "artist_account_id", - "in": "query", - "description": "The unique identifier of the artist account whose YouTube channel info should be fetched", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "200": { - "description": "Channel info fetched successfully.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/YouTubeChannelInfoResponse" - } - } - } - }, - "400": { - "description": "Bad request - missing `artist_account_id` query parameter", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/YouTubeChannelInfoErrorResponse" - } - } - } - }, - "401": { - "description": "Unauthorized - Recoup auth is missing/invalid, or the stored YouTube tokens are missing, expired, or could not be refreshed. The client should authenticate (or prompt the user to re-authenticate with YouTube).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/YouTubeChannelInfoErrorResponse" - } - } - } - }, - "403": { - "description": "Forbidden - the authenticated caller does not have access to the requested artist account.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/YouTubeChannelInfoErrorResponse" - } - } - } - }, - "502": { - "description": "Upstream YouTube API failure (rate limit, 5xx from Google, or transient network error).", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/YouTubeChannelInfoErrorResponse" - } - } - } - } - } - } - } - }, - "components": { - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer" - }, - "apiKeyAuth": { - "type": "apiKey", - "in": "header", - "name": "x-api-key", - "description": "Your Recoup API key. [Learn more](/quickstart#api-keys)." - } - }, - "schemas": { - "YouTubeChannelInfoResponse": { - "type": "object", - "required": [ - "status", - "channels" - ], - "properties": { - "status": { - "type": "string", - "enum": ["success"], - "description": "Always `\"success\"` for HTTP 200 responses" - }, - "channels": { - "type": "array", - "items": { - "$ref": "#/components/schemas/YouTubeChannelData" - }, - "description": "Array of channels owned by the authenticated artist" - } - } - }, - "YouTubeChannelInfoErrorResponse": { - "type": "object", - "required": ["status", "message"], - "properties": { - "status": { - "type": "string", - "enum": ["error"] - }, - "message": { - "type": "string", - "description": "Human-readable error message" - } - } - }, - "YouTubeChannelData": { - "type": "object", - "required": [ - "id", - "title", - "description", - "thumbnails", - "statistics", - "publishedAt" - ], - "description": "Summary of a YouTube channel as returned by the YouTube Data API, mapped to the shape used by Recoup clients.", - "properties": { - "id": { - "type": "string", - "description": "YouTube channel ID" - }, - "title": { - "type": "string", - "description": "Channel display title" - }, - "description": { - "type": "string", - "description": "Channel description text" - }, - "thumbnails": { - "type": "object", - "description": "Thumbnail URLs at default, medium, and high resolutions", - "properties": { - "default": { - "type": "object", - "properties": { - "url": { - "type": ["string", "null"], - "description": "URL of the default-resolution thumbnail" - } - } - }, - "medium": { - "type": "object", - "properties": { - "url": { - "type": ["string", "null"], - "description": "URL of the medium-resolution thumbnail" - } - } - }, - "high": { - "type": "object", - "properties": { - "url": { - "type": ["string", "null"], - "description": "URL of the high-resolution thumbnail" - } - } - } - } - }, - "statistics": { - "type": "object", - "required": [ - "subscriberCount", - "videoCount", - "viewCount" - ], - "properties": { - "subscriberCount": { - "type": "string", - "description": "Number of subscribers (string-encoded integer; '0' if hidden)" - }, - "videoCount": { - "type": "string", - "description": "Total number of public videos uploaded (string-encoded integer)" - }, - "viewCount": { - "type": "string", - "description": "Total channel view count (string-encoded integer)" - }, - "hiddenSubscriberCount": { - "type": "boolean", - "description": "True when the channel hides its subscriber count" - } - } - }, - "customUrl": { - "type": ["string", "null"], - "description": "Channel custom URL handle, if set" - }, - "country": { - "type": ["string", "null"], - "description": "Two-letter country code declared by the channel, if set" - }, - "publishedAt": { - "type": "string", - "format": "date-time", - "description": "ISO timestamp of when the channel was created" - }, - "uploadsPlaylistId": { - "type": "string", - "description": "Playlist ID containing all of the channel's uploaded videos" - }, - "branding": { - "type": "object", - "description": "Channel branding settings (only present when branding fields are requested)", - "properties": { - "keywords": { - "type": ["string", "null"], - "description": "Channel keywords configured in YouTube Studio" - }, - "defaultLanguage": { - "type": ["string", "null"], - "description": "Default language code for the channel" - } - } - } - } - } - } - } -} diff --git a/api-reference/youtube/channel-info.mdx b/api-reference/youtube/channel-info.mdx deleted file mode 100644 index 9063d35..0000000 --- a/api-reference/youtube/channel-info.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 'Channel Info' -openapi: '/api-reference/openapi/youtube.json GET /api/youtube/channel-info' ---- diff --git a/docs.json b/docs.json index 950b59d..0ea3137 100644 --- a/docs.json +++ b/docs.json @@ -262,12 +262,6 @@ "pages": [ "api-reference/apify/scraper" ] - }, - { - "group": "YouTube", - "pages": [ - "api-reference/youtube/channel-info" - ] } ] },