# Subscribr API Reference **Base URL:** `https://subscribr.ai/api/v1` **Version:** v1.0 (Stable) Welcome to the Subscribr API reference. This API follows RESTful conventions and uses standard HTTP response codes, authentication, and verbs. ## Table of Contents - **Getting Started** - [Quick Start](#quick-start) - [Core Concepts](#core-concepts) - [Authentication](#authentication) - [Rate Limits](#rate-limits) - [Errors](#errors) - **YouTube Intel** - [Channel Lookup](#channel-lookup) - [Channel Search](#channel-search) - [Video Lookup](#video-lookup) - [Video Search](#video-search) - [Manage Bookmarks](#intel-bookmarks) - **Teams** - [Get Team Details](#get-team) - [Get Credits](#get-credits) - [List API Tokens](#list-tokens) - [Create API Token](#create-token) - [Delete API Token](#delete-token) - **Channels** - [List Channels](#list-channels) - [Get Channel](#get-channel) - [Channel Templates](#channel-templates) - [Channel Voices](#channel-voices) - [Manage Competitors](#channel-competitors) - **Ideas** - [Create Idea](#create-idea) - [List Channel Ideas](#list-ideas) - [Get Idea Details](#get-idea) - [Write Idea](#write-idea) - [Generate Ideas](#generate-ideas) - [Generate Ideas From Video](#generate-ideas-from-video) - [Generate Ideas From Channel](#generate-ideas-from-channel) - [Change Idea Topic](#change-idea-topic) - **Scripts** - [Create Script](#create-script) - [List Channel Scripts](#list-scripts) - [Get Script](#get-script) - [Get Script Content](#get-script-content) - [Generate Outline](#generate-outline) - [Generate Script](#generate-script) - [Poll Generation Status](#poll-generation) - [Export Script](#export-script) - **Thumbnails** - [Get Thumbnail Usage](#get-thumbnail-usage) - [Create Thumbnail Generation](#create-thumbnail-generation) - [Get Generation Status](#get-thumbnail-generation) - [List Thumbnail Generations](#list-thumbnail-generations) - **Webhooks** - [List Webhooks](#list-webhooks) - [Create Webhook](#create-webhook) - [Get Webhook](#get-webhook) - [Update Webhook](#update-webhook) - [Delete Webhook](#delete-webhook) - [Test Webhook](#test-webhook) - [Webhook Events](#webhook-events) - [Webhook Security](#webhook-security) ## Getting Started {#getting-started} ### Quick Start {#quick-start} Getting started with the Subscribr API is straightforward. Within a few minutes, you can generate your first API token and make your first request. The Subscribr API provides programmatic access to your team's channels, scripts, and YouTube intelligence data. All requests require Bearer token authentication and are subject to rate limits based on your subscription plan. **1. Generate an API token** Navigate to your Subscribr dashboard settings and generate an API token with the required permissions (abilities) for your use case. Tokens are tied to your team and inherit your plan's rate limits. **2. Make your first request** Use curl or any HTTP client to make a request to the API. Include your token in the Authorization header as a Bearer token. **3. Explore further** Check out the endpoint documentation below to see what you can do with the API. Most endpoints return JSON and include examples in their documentation. ```bash curl -X GET https://subscribr.ai/api/v1/team \ -H "Authorization: Bearer sk_live_your_token_here" \ -H "Accept: application/json" ``` > ⚠️ **Need help?** > > If you run into any issues getting started, check out the Authentication and Errors sections below, join our Discord community at discord.gg/ZdUA4jEnU2, or contact hello@subscribr.ai for assistance. ### Core Concepts {#core-concepts} Understanding how Subscribr's key resources relate to each other and how to work with them via the API. #### YouTube Intel YouTube Intelligence endpoints provide data about YouTube channels and videos. Look up specific channels by URL or handle, search for channels and videos, and bookmark your findings. This data is sourced from YouTube and external intelligence services. **API Endpoint:** `POST /api/v1/intel/channels/lookup` #### Teams Teams are the top-level organization unit in Subscribr. All API tokens are scoped to a team, and all resources (channels, scripts, ideas) belong to a team. Teams have subscription plans, credit balances, and can manage API tokens. **API Endpoint:** `GET /api/v1/team` #### Channels A Channel is the top-level container for your content creation work. Each channel represents either a YouTube channel or a content planning space. Channels hold voice profiles, audience information, and serve as the parent for scripts and ideas. **API Endpoint:** `GET /api/v1/channels` #### Ideas Ideas are content concepts within a channel. They can be created manually or generated by AI. Ideas serve as the basis for scripts and can be developed into full scripts with titles, outlines, and content. **API Endpoint:** `POST /api/v1/channels/{channel}/ideas` #### Scripts A Script is a video project within a channel. Scripts progress through multiple stages from outline to full draft. You can generate scripts from ideas, export them, and poll generation status. **API Endpoint:** `POST /api/v1/channels/{channel}/scripts` #### Thumbnails AI-powered thumbnail generation for your video ideas. Generate concept sketches via brainstorm mode, produce final 2K thumbnails from ideas, or clone the style of an existing thumbnail. Generations run asynchronously — poll for status or use a callback URL for notification. **API Endpoint:** `POST /api/v1/channels/{channel}/thumbnails/generations` #### Typical Workflow 1. List your channels to find the one you want to work with (GET /channels) 2. Create ideas manually or generate them from competitor videos 3. Write/develop ideas to add structure and details 4. Generate outlines and scripts for your ideas 5. Export the finished script in your preferred format 6. Generate AI thumbnails for your ideas — brainstorm concepts or produce final 2K images 7. Set up webhooks to be notified when generation is complete ### Authentication {#authentication} The Subscribr API uses Bearer token authentication. All requests must include a valid API token in the Authorization header. API authentication uses Bearer tokens issued by your Subscribr account. These tokens are tied to your team and include specific abilities (permissions) that control what the token can access. ```bash curl -X GET https://subscribr.ai/api/v1/team \ -H "Authorization: Bearer sk_live_your_token_here" ``` > ℹ️ **Team-scoped tokens** > > Each API token is bound to the team that created it. Tokens can only access resources (channels, scripts, ideas) that belong to their team. You cannot use a token from one team to access another team's data. #### Available Token Abilities | Ability | Description | | --- | --- | | intel:read | Read-only access to YouTube Intel lookup, search, and bookmark retrieval endpoints | | intel:write | Create and delete Intel bookmarks (requires intel:read) | | scripts:read | Read scripts, ideas, and script content | | scripts:write | Create scripts and ideas, generate outlines and scripts (requires scripts:read) | | channels:read | List and get channel details, templates, and voices | | thumbnails:read | Check thumbnail usage quota and poll generation status | | thumbnails:write | Create thumbnail generations (requires thumbnails:read) | | webhooks:read | List and get webhook details | | webhooks:write | Create, update, and delete webhooks (requires webhooks:read) | ### Rate Limits {#rate-limits} The Subscribr API implements rate limiting to ensure fair usage and maintain service quality. Rate limits are enforced per API token and are based on your subscription plan. Different endpoints have different rate limits depending on their computational cost. #### Rate Limits by Endpoint Category | Category | Limit | Notes | | --- | --- | --- | | General API | 50 requests/minute | Default limit for most endpoints | | Channel/Video Search | 5 requests/minute | Resource-intensive semantic search | | Script Generation | 5 requests/minute | AI generation endpoints | | Channel/Video Lookup | 50 requests/minute | Fast direct lookups by ID or handle | | Thumbnail Generation | 12 requests/minute | Async thumbnail creation | | Thumbnail Status Polling | 120 requests/minute | Generation status checks and listing | | Thumbnail Usage | 60 requests/minute | Quota and usage lookups | #### Rate Limit Response Headers | Header | Description | | --- | --- | | X-RateLimit-Limit | The maximum number of requests allowed in the current window | | X-RateLimit-Remaining | The number of requests remaining in the current window | | X-RateLimit-Reset | Unix timestamp when the rate limit window resets | | Retry-After | Seconds to wait before retrying (only present on 429 responses) | ### Errors {#errors} The API returns standard HTTP status codes to indicate the success or failure of requests. All API error responses include a JSON body with details about what went wrong. Use these responses to debug integration issues. #### HTTP Status Codes | Code | Meaning | Common Causes | | --- | --- | --- | | 200 | OK | Request succeeded | | 201 | Created | Resource was successfully created | | 400 | Bad Request | Invalid request parameters or malformed JSON | | 401 | Unauthorized | Missing or invalid API token | | 403 | Forbidden | Token lacks required abilities for this endpoint | | 404 | Not Found | Resource doesn't exist or belongs to different team | | 409 | Conflict | Resource state conflict (e.g., duplicate creation) | | 422 | Validation Failed | Request data failed validation | | 429 | Too Many Requests | Rate limit exceeded | | 500 | Server Error | Internal server error (please contact support) | ## Teams {#team-resources} ### Get Team Details {#get-team} Returns information about your team including subscription status and user role. #### Request ``` GET https://subscribr.ai/api/v1/team ``` **Authentication Required:** Yes (Bearer Token) No request body required. No query parameters. #### Response **Status Code:** 200 Team object with current subscription and credit information **Response Fields:** | Field | Type | Description | | --- | --- | --- | | team.id | integer | Unique team identifier | | team.name | string | Team name | | team.user_role | string | Current user's role in the team (admin, member) | | team.owner | object | Team owner information | | team.owner.id | integer | Owner user ID | | team.owner.name | string | Owner user name | | team.subscription | object | Team subscription details | | team.subscription.plan | string | Current subscription plan | | team.subscription.status | string | Subscription status (active, past_due, canceled, etc.) | | team.subscription.trial_ends_at | string\|null | Trial end date (ISO 8601) or null if not in trial | | team.subscription.is_paused | boolean | Whether the subscription is paused | **Example Response:** ```json { "team": { "id": 456, "name": "My Content Team", "user_role": "admin", "owner": { "id": 123, "name": "John Doe" }, "subscription": { "plan": "Creator", "status": "active", "trial_ends_at": null, "is_paused": false } } } ``` ### Get Credits {#get-credits} Returns your team's current credit balance and usage information. #### Request ``` GET https://subscribr.ai/api/v1/team/credits ``` **Authentication Required:** Yes (Bearer Token) No request body required. No query parameters. #### Response **Status Code:** 200 Credit information for the team **Response Fields:** | Field | Type | Description | | --- | --- | --- | | credits.current_credits | integer | Current available credits | | credits.plan_credits | integer | Total credits allocated for this period | | credits.credits_used_this_period | integer | Credits consumed in the current period | | credits.credits_remaining | integer | Credits remaining in the current period | | credits.credits_expire_at | string | ISO 8601 timestamp when current credits expire | | credits.next_credit_refresh | string | ISO 8601 timestamp of next credit refresh | **Example Response:** ```json { "credits": { "current_credits": 850, "plan_credits": 1000, "credits_used_this_period": 150, "credits_remaining": 850, "credits_expire_at": "2026-02-28T00:00:00Z", "next_credit_refresh": "2026-03-01T00:00:00Z" } } ``` ### List API Tokens {#list-tokens} List all API tokens for the current team. Team admin required. #### Request ``` GET https://subscribr.ai/api/v1/team/tokens ``` **Authentication Required:** Yes (Bearer Token) No request body required. #### Response **Status Code:** 200 Array of API token objects **Response Fields:** | Field | Type | Description | | --- | --- | --- | | tokens | array | Array of token objects | | tokens[].id | integer | Token identifier | | tokens[].name | string | Human-readable token name | | tokens[].abilities | array | Array of permission strings | | tokens[].last_used_at | string\|null | ISO 8601 last usage timestamp (null if never used) | **Example Response:** ```json { "tokens": [ { "id": 123, "name": "Zapier Integration", "abilities": [ "intel:read", "scripts:read" ], "last_used_at": "2026-02-01T18:22:00Z" } ] } ``` ### Create API Token {#create-token} Create a new API token. Team admin required. #### Request ``` POST https://subscribr.ai/api/v1/team/tokens ``` **Authentication Required:** Yes (Bearer Token) JSON body with token configuration **Request Body:** ```json { "name": "string (required) - Human-readable name for the token", "abilities": "array (required) - Abilities to grant the token, e.g., [\"intel:read\", \"scripts:write\"]" } ``` **Example:** ```bash curl -X POST 'https://subscribr.ai/api/v1/team/tokens' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 201 The newly created token (token string only shown once) **Response Fields:** | Field | Type | Description | | --- | --- | --- | | token.id | integer | Token identifier | | token.name | string | Token name as provided | | token.token | string | The full API token (shown only when created) | | token.abilities | array | Granted abilities | **Example Response:** ```json { "token": { "id": 456, "name": "Automation Token", "abilities": [ "intel:read", "scripts:read", "channels:read" ], "token": "plain-text-token-value" } } ``` ### Delete API Token {#delete-token} Delete a token by ID. Team admin required. #### Request ``` DELETE https://subscribr.ai/api/v1/team/tokens/{token} ``` **Authentication Required:** Yes (Bearer Token) No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | token | integer | The token ID to delete (path parameter) | #### Response **Status Code:** 200 Confirmation message **Response Fields:** | Field | Type | Description | | --- | --- | --- | | message | string | Success confirmation message | **Example Response:** ```json { "message": "Token deleted successfully" } ``` ## YouTube Intel {#youtube-intel} ### Channel Lookup {#channel-lookup} Get detailed information about YouTube channels. Accepts handles, channel IDs, or URLs. #### Request ``` POST https://subscribr.ai/api/v1/intel/channels/lookup ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** intel:read Array of channel identifiers **Request Body:** ```json { "identifiers": "array (required) - Array of 1-5 identifiers (handle with/without @, channel ID starting with UC, or full YouTube URL)" } ``` **Example:** ```bash curl -X POST 'https://subscribr.ai/api/v1/intel/channels/lookup' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 200 Detailed YouTube channel information with success status and error tracking **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Whether the request succeeded | | data | object | Response data containing channels and errors | | data.channels | array | Array of channel objects when multiple identifiers are provided | | data.channels[].channel_id | string | YouTube channel ID | | data.channels[].handle | string | Handle without @ symbol | | data.channels[].title | string | Channel title | | data.channels[].description | string | Channel description (truncated to 500 chars) | | data.channels[].thumbnails | object | Thumbnail URLs | | data.channels[].subscriber_count | integer | Subscriber count | | data.channels[].video_count | integer | Total videos | | data.channels[].view_count | integer | Total views | | data.channels[].published_at | string | Channel publish timestamp (ISO 8601) | | data.channels[].country | string | Country code | | data.errors | array | Array of lookup errors: identifier, error, error_type | **Example Response:** ```json { "success": true, "data": { "channels": [ { "channel_id": "UCBJycsmduvYEL83R_U4JriQ", "handle": "mkbhd", "title": "Marques Brownlee", "description": "Channel description (truncated to 500 chars)", "thumbnails": { "default": { "url": "https://i.ytimg.com/..." } }, "subscriber_count": 19000000, "video_count": 1650, "view_count": 4200000000, "published_at": "2008-01-20T00:00:00Z", "country": "US" } ], "errors": [] } } ``` ### Channel Search {#channel-search} Search for YouTube channels by keywords, niche, or other criteria. #### Request ``` POST https://subscribr.ai/api/v1/intel/channels/search ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** intel:read JSON body with search query **Request Body:** ```json { "query": "string (required) - Natural language query", "limit": "integer (optional) - 1-20. Default 10" } ``` **Example:** ```bash curl -X POST 'https://subscribr.ai/api/v1/intel/channels/search' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 200 Array of matching YouTube channels with search metadata **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Whether the request succeeded | | data.channels[].channel_id | string | YouTube channel ID | | data.channels[].handle | string | Handle without @ | | data.channels[].title | string | Channel title | | data.channels[].description | string\|null | Channel description (nullable) | | data.channels[].thumbnails | object\|null | Thumbnail URLs (nullable) | | data.channels[].published_at | string | Channel publish timestamp | | data.channels[].subscriber_count | integer | Subscriber count | | data.channels[].video_count | integer | Total uploaded videos | | data.channels[].view_count | integer | Total views | | data.channels[].country | string\|null | Country code (nullable) | | data.query | string | The search query used | | data.total_results | integer | Total results found | | data.message | string | Search completion message | **Example Response:** ```json { "success": true, "data": { "channels": [ { "channel_id": "UCBJycsmduvYEL83R_U4JriQ", "handle": "mkbhd", "title": "Marques Brownlee", "description": "Channel description (truncated to 500 chars)", "thumbnails": { "default": { "url": "https://i.ytimg.com/..." } }, "published_at": "2008-01-20T00:00:00Z", "subscriber_count": 19000000, "video_count": 1650, "view_count": 4200000000, "country": "US" } ], "query": "tech reviews", "total_results": 1, "message": "Channels found matching your criteria." } } ``` ### Video Lookup {#video-lookup} Get detailed information about specific YouTube videos. #### Request ``` POST https://subscribr.ai/api/v1/intel/videos/lookup ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** intel:read Array of video identifiers **Request Body:** ```json { "identifiers": "array (required) - Array of 1-5 video IDs or URLs" } ``` **Example:** ```bash curl -X POST 'https://subscribr.ai/api/v1/intel/videos/lookup' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 200 Detailed YouTube video information with success status and error tracking **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Whether the request succeeded | | data.videos | array | Array of video objects when multiple identifiers are provided | | data.videos[].video_id | string | YouTube video ID | | data.videos[].channel | object | Channel summary with channel_id, handle, and title | | data.videos[].title | string | Video title | | data.videos[].description | string | Video description | | data.videos[].published_at | string | Publish timestamp (ISO 8601) | | data.videos[].thumbnail_url | string | Best available thumbnail URL | | data.videos[].duration | string | ISO 8601 duration | | data.videos[].view_count | integer | Total views | | data.videos[].like_count | integer\|null | Total likes (nullable) | | data.videos[].comment_count | integer\|null | Total comments (nullable) | | data.videos[].outlier_score | float\|null | Performance vs channel baseline (nullable) | | data.videos[].format | string\|null | Parsed format (nullable) | | data.videos[].topic | string\|null | Parsed topic (nullable) | | data.videos[].angle | string\|null | Parsed angle (nullable) | | data.videos[].goals | string\|null | Parsed goals (nullable) | | data.errors | array | Array of lookup errors: identifier, error, error_type | **Example Response:** ```json { "success": true, "data": { "videos": [ { "video_id": "dQw4w9WgXcQ", "channel": { "channel_id": "UCuAXFkgsw1L7xaCfnd5JJOw", "handle": "RickAstleyVEVO", "title": "Rick Astley" }, "title": "Video Title", "description": "Video description", "published_at": "2009-10-25T06:57:33Z", "thumbnail_url": "https://i.ytimg.com/...", "duration": "PT3M33S", "view_count": 1000000, "like_count": 50000, "comment_count": 10000, "outlier_score": 1.25, "format": "Music", "topic": "Pop", "angle": "Nostalgia", "goals": "Entertainment" } ], "errors": [] } } ``` ### Video Search {#video-search} Search for videos matching specific criteria to identify trends and opportunities. #### Request ``` POST https://subscribr.ai/api/v1/intel/videos/search ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** intel:read JSON body with search query **Request Body:** ```json { "query": "string (required) - Natural language query", "limit": "integer (optional) - 1-20. Default 10" } ``` **Example:** ```bash curl -X POST 'https://subscribr.ai/api/v1/intel/videos/search' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 200 Array of matching YouTube videos with search metadata **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Whether the request succeeded | | data.videos[].video_id | string | YouTube video ID | | data.videos[].channel | object | Channel summary with channel_id, handle, and title | | data.videos[].title | string | Video title | | data.videos[].description | string | Truncated description (max 300 chars) | | data.videos[].published_at | string | Publish timestamp | | data.videos[].thumbnail_url | string | Best available thumbnail URL | | data.videos[].view_count | integer | Total views | | data.videos[].like_count | integer | Total likes | | data.videos[].comment_count | integer | Total comments | | data.videos[].duration | string | ISO 8601 duration | | data.videos[].format | string\|null | Detected format (nullable) | | data.videos[].topic | string\|null | Detected topic (nullable) | | data.videos[].angle | string\|null | Detected angle (nullable) | | data.videos[].goals | string\|null | Detected goals (nullable) | | data.videos[].outlier_score | float\|null | Outlier score relative to channel baseline (nullable) | | data.query | string | The search query used | | data.total_results | integer | Total results found | | data.message | string | Search completion message | **Example Response:** ```json { "success": true, "data": { "videos": [ { "video_id": "abc123", "channel": { "channel_id": "UCBJycsmduvYEL83R_U4JriQ", "handle": "mkbhd", "title": "Marques Brownlee" }, "title": "iPhone 15 Review", "description": "Description (truncated to 300 chars)...", "published_at": "2026-01-01T00:00:00Z", "thumbnail_url": "https://i.ytimg.com/...", "view_count": 500000, "like_count": 15000, "comment_count": 1200, "duration": "PT12M", "format": "Review", "topic": "Smartphones", "angle": "Hands-on", "goals": "Inform", "outlier_score": 1.12 } ], "query": "iPhone 15 review", "total_results": 1, "message": "Videos found matching your criteria." } } ``` ### Bookmarks {#intel-bookmarks} Save and retrieve channels or videos to your team's research library. #### Request ``` GET/POST/DELETE https://subscribr.ai/api/v1/intel/bookmarks ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** intel:read, intel:write Different operations based on HTTP method **Request Body:** ```json { "type": "string (required for POST) - \"channel\" or \"video\"", "external_id": "string (required for POST) - External YouTube identifier (channel ID or video ID)", "title": "string (optional for POST) - Bookmark title", "url": "string (optional for POST) - URL to the YouTube resource", "notes": "string (optional for POST) - Optional notes", "tags": "array (optional for POST) - Array of tag names" } ``` **Example:** ```bash curl -X GET/POST/DELETE 'https://subscribr.ai/api/v1/intel/bookmarks' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 200 Bookmark list or created bookmark (varies by operation) **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Whether the request succeeded | | data.id | integer | Bookmark ID | | data.type | string | "channel" or "video" | | data.external_id | string | External YouTube identifier (channel ID or video ID) | | data.title | string | Bookmark title | | data.url | string | URL to the YouTube resource | | data.notes | string\|null | Optional notes | | data.tags | array | Array of tag names | | data.pagination | object | Pagination metadata (GET only) - current_page, per_page, total, last_page | **Example Response:** ```json { "success": true, "data": { "id": 321, "type": "channel", "external_id": "UCBcRF18a7Qf58cCRy5xuWwQ", "title": "Competitor: MKBHD", "url": "https://youtube.com/@mkbhd", "notes": "Strong product review format", "tags": [ "competitor", "tech" ] } } ``` ## Channels {#channels} ### List Channels {#list-channels} List all channels accessible to the current team. #### Request ``` GET https://subscribr.ai/api/v1/channels ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** channels:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | search | string | Search by channel title or handle. | | per_page | integer | 1-100, default 20. | | page | integer | Page number (default 1). | #### Response **Status Code:** 200 Paginated list of channel objects with YouTube details **Response Fields:** | Field | Type | Description | | --- | --- | --- | | id | integer | Channel ID (internal). | | yt_handle | string | Handle stored on the channel. | | details | object | Summary details from YouTube (see fields in example above). | | scripts_count | integer | Total scripts for the channel. | **Example Response:** ```json { "success": true, "data": [ { "id": 123, "yt_handle": "mkbhd", "details": { "title": "Marques Brownlee", "custom_url": "mkbhd", "channel_id": "UCBJycsmduvYEL83R_U4JriQ", "subscriber_count": 19000000, "video_count": 1650, "view_count": 4200000000, "description": "Channel description", "country": "US", "default_language": "en", "thumbnails": { "default": { "url": "https://i.ytimg.com/..." } } }, "scripts_count": 42 } ], "pagination": { "current_page": 1, "per_page": 20, "total": 1, "last_page": 1 } } ``` ### Get Channel {#get-channel} Get detailed information about a specific channel. #### Request ``` GET https://subscribr.ai/api/v1/channels/{id} ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** channels:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Channel ID (path parameter). | #### Response **Status Code:** 200 Detailed channel information with nested data **Response Fields:** | Field | Type | Description | | --- | --- | --- | | id | integer | Channel ID (internal). | | yt_handle | string | Handle stored on the channel. | | details | object | Channel detail fields: title, custom_url, channel_id, subscriber_count, video_count, view_count, description, country, default_language, thumbnails. | | settings | object | Key-value settings map for the channel. | | voice | object | Default channel voice (see voice fields below). | | scripts_count | integer | Total scripts for the channel. | | scripts_idea_count | integer | Scripts in idea status. | | scripts_active_count | integer | Scripts in active status. | **Example Response:** ```json { "success": true, "data": { "id": 123, "yt_handle": "mkbhd", "details": { "title": "Marques Brownlee", "custom_url": "mkbhd", "channel_id": "UCBJycsmduvYEL83R_U4JriQ", "subscriber_count": 19000000, "video_count": 1650, "view_count": 4200000000, "description": "Channel description", "default_language": "en", "country": "US", "thumbnails": { "default": { "url": "https://i.ytimg.com/..." } } }, "settings": { "language": "English", "audience": "Tech enthusiasts" }, "voice": { "id": 77, "name": "Default Voice", "instructions": "Voice instructions", "voice": "neutral" }, "scripts_count": 42, "scripts_idea_count": 4, "scripts_active_count": 38 } } ``` ### List Channel Templates {#channel-templates} List available templates for channel (system + custom). #### Request ``` GET https://subscribr.ai/api/v1/channels/{id}/templates ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** channels:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Channel ID (path parameter). | #### Response **Status Code:** 200 Array of available templates with default template ID **Response Fields:** | Field | Type | Description | | --- | --- | --- | | default_template_id | integer | Default template ID for this channel. | | templates | array | Array of template objects. | | templates[].id | integer | Template ID. | | templates[].name | string | Template name. | | templates[].category | string | Template category. | | templates[].description | string | Short description. | | templates[].is_active | boolean | Whether this is the active template. | | templates[].is_system | boolean | System template flag. | **Example Response:** ```json { "success": true, "data": { "default_template_id": 22, "templates": [ { "id": 22, "name": "Base Script", "category": "base", "description": "Standard outline", "is_active": true, "is_system": true } ] } } ``` ### List Channel Voices {#channel-voices} List available voices for channel and the default voice. #### Request ``` GET https://subscribr.ai/api/v1/channels/{id}/voices ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** channels:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Channel ID (path parameter). | #### Response **Status Code:** 200 Array of available voices with default voice ID **Response Fields:** | Field | Type | Description | | --- | --- | --- | | default_voice_id | integer | Default voice ID for this channel. | | voices | array | Array of voice objects. | | voices[].id | integer | Voice ID. | | voices[].name | string | Voice name. | | voices[].instructions | string | Voice guidelines. | | voices[].voice | string | Voice identifier. | **Example Response:** ```json { "success": true, "data": { "default_voice_id": 77, "voices": [ { "id": 77, "name": "Default Voice", "instructions": "Voice instructions", "voice": "neutral" } ] } } ``` ### Manage Competitors {#channel-competitors} List, add, delete competitors. GET: returns competitors array. POST: request with identifier. DELETE: no body. #### Request ``` GET/POST/DELETE https://subscribr.ai/api/v1/channels/{id}/competitors ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** channels:read, scripts:write Different operations based on HTTP method **Request Body:** ```json { "GET": "No body required", "POST": "JSON with identifier (URL or channel ID)", "DELETE": "No body required" } ``` #### Response **Status Code:** 200 **Response Fields:** | Field | Type | Description | | --- | --- | --- | | channel_id | string | YouTube channel ID. | | title | string | Channel title. | | custom_url | string | Handle without @. | | custom_url_with_at | string | Handle with @. | | youtube_url | string | Full YouTube URL. | | thumbnails | object | Thumbnail images object. | | subscriber_count | integer | YouTube subscriber count. | | video_count | integer | Total video count. | | view_count | integer | Total view count. | ## Ideas {#ideas} Create and manage video ideas within your channels. ### Create Idea {#create-idea} Create a new video idea in the ideas system. #### Request ``` POST https://subscribr.ai/api/v1/channels/{id}/ideas ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:write JSON body with idea configuration **Request Body:** ```json { "title": "string (required) - Idea title (max 255)", "topic": "string (optional) - Topic (max 5000)", "angle": "string (optional) - Angle (max 1000)", "suggested_length": "integer (optional) - Word count (200-20000)", "suggested_template_id": "integer (optional) - Template ID", "thumbnail_concept": "string (optional) - Thumbnail concept text", "notes": "string (optional) - Notes (max 2000)" } ``` **Example:** ```bash curl -X POST 'https://subscribr.ai/api/v1/channels/{id}/ideas' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 201 The newly created idea **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | data.id | integer | Idea ID | | data.channel_id | integer | Channel ID | | data.title | string | Idea title | | data.status | string | Idea status | | data.script_id | integer\|null | Linked script ID (nullable) | | data.source_type | string | Source type (e.g. user_added) | **Example Response:** ```json { "success": true, "data": { "id": 456, "channel_id": 123, "title": "Why Apple's AI Changes Everything", "topic": "Analysis of Apple Intelligence", "angle": "Reveal the hidden implication that most reviews miss.", "suggested_length": 1200, "suggested_template_id": 22, "status": "new", "script_id": null, "source_type": "user_added", "thumbnail_concept": "Split-screen before/after with bold headline", "outlier_score": null, "notes": null } } ``` ### List Ideas {#list-ideas} List ideas for a channel with optional filters. #### Request ``` GET https://subscribr.ai/api/v1/channels/{id}/ideas ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Channel ID (path parameter) | | status | string | Filter by status (new, considering, rejected, writing, complete) | | source_type | string | Filter by source (user_added, ai_generated, from_video, from_channel, outlier, chat_saved) | | search | string | Search in title, topic, or angle | | per_page | integer | 1-100, default 20 | | page | integer | Page number (default 1) | #### Response **Status Code:** 200 Array of idea objects with pagination **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | data[].id | integer | Idea ID | | data[].channel_id | integer | Channel ID | | data[].title | string | Idea title | | data[].topic | string\|null | Idea topic (nullable) | | data[].status | string | Idea status | | data[].script_id | integer\|null | Linked script ID (nullable) | | pagination.total | integer | Total results | **Example Response:** ```json { "success": true, "data": [ { "id": 456, "channel_id": 123, "title": "Why Apple's AI Changes Everything", "topic": "Analysis of Apple Intelligence", "angle": "Reveal the hidden implication...", "suggested_length": 1200, "suggested_template_id": 22, "status": "new", "script_id": null, "source_type": "user_added", "thumbnail_concept": "Split-screen before/after with bold headline", "outlier_score": null, "notes": null } ], "pagination": { "current_page": 1, "per_page": 20, "total": 1, "last_page": 1 } } ``` ### Get Idea Details {#idea-details} Get a single idea by ID. #### Request ``` GET https://subscribr.ai/api/v1/ideas/{id} ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Idea ID (path parameter) | #### Response **Status Code:** 200 Detailed idea information **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | data.id | integer | Idea ID | | data.channel_id | integer | Channel ID | | data.title | string | Idea title | | data.topic | string\|null | Idea topic (nullable) | | data.angle | string\|null | Idea angle (nullable) | | data.status | string | Idea status | | data.script_id | integer\|null | Linked script ID (nullable) | | data.source_type | string | Source type (e.g. user_added) | **Example Response:** ```json { "success": true, "data": { "id": 456, "channel_id": 123, "title": "Why Apple's AI Changes Everything", "topic": "Analysis of Apple Intelligence", "angle": "Reveal the hidden implication...", "suggested_length": 1200, "suggested_template_id": 22, "status": "new", "script_id": null, "source_type": "user_added", "thumbnail_concept": "Split-screen before/after", "outlier_score": null, "notes": null } } ``` ### Write Idea {#write-idea} Convert an idea into a canvas script (create script from idea). #### Request ``` POST https://subscribr.ai/api/v1/ideas/{id}/write ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:write JSON body with conversion parameters **Request Body:** ```json { "id": "integer (required) - Idea ID (path parameter)" } ``` #### Response **Status Code:** 200 Script created from idea **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | data.script_id | integer | Newly created script ID | | data.script_number | integer | Sequential number per channel | | data.thread_id | integer | Associated thread ID | | data.canvas_url | string | Canvas URL for editing | **Example Response:** ```json { "success": true, "data": { "script_id": 789, "script_number": 12, "thread_id": 456, "status": "active", "canvas_url": "https://subscribr.com/chat/{thread}/canvas/{script}" } } ``` ### Generate Ideas {#generate-ideas} Generate a batch of new ideas for a channel. #### Request ``` POST https://subscribr.ai/api/v1/channels/{id}/ideas/generate ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:write JSON body with generation parameters **Request Body:** ```json { "count": "integer (optional) - 1-20. Default 10" } ``` **Example:** ```bash curl -X POST 'https://subscribr.ai/api/v1/channels/{id}/ideas/generate' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 202 Async generation started **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | message | string | Status message | | data.channel_id | integer | Channel ID | | data.count | integer | Number of ideas to generate | **Example Response:** ```json { "success": true, "message": "Idea generation started.", "data": { "channel_id": 123, "count": 10 } } ``` ### Generate Ideas From Video {#generate-ideas-from-video} Generate ideas based on a specific YouTube video. #### Request ``` POST https://subscribr.ai/api/v1/channels/{id}/ideas/generate-from-video ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:write JSON body with video reference **Request Body:** ```json { "video_url": "string (optional) - YouTube video URL. Max 500 chars", "video_id": "string (optional) - YouTube video ID", "count": "integer (optional) - 1-20. Default 10" } ``` **Example:** ```bash curl -X POST 'https://subscribr.ai/api/v1/channels/{id}/ideas/generate-from-video' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 202 Async generation started **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | message | string | Status message | | data.channel_id | integer | Channel ID | | data.count | integer | Number of ideas to generate | **Example Response:** ```json { "success": true, "message": "Idea generation from video started.", "data": { "channel_id": 123, "count": 10 } } ``` ## Scripts {#scripts} Create and manage video scripts, generate outlines and content. ### Create Script {#create-script} Create a new canvas script (chat-first) within a channel. Scripts can be created with research URLs, topics, angles, and templates. #### Request ``` POST https://subscribr.ai/api/v1/channels/{id}/scripts ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:write JSON body with script configuration **Request Body:** ```json { "title": "string (required) - Script title (max 255)", "topic": "string (required) - Concept/brief (min 10, max 5000)", "length": "integer (required) - Target word count (200-20000)", "angle": "string (optional) - Angle/hook (max 1000)", "language": "string (optional) - Language (defaults to channel language)", "template_id": "integer (optional) - Must belong to the channel", "voice_id": "integer (optional) - Voice ID for the channel", "voice": "string (optional) - Raw voice string", "prompt": "string (optional) - Custom starting prompt", "research_urls": "array (optional) - URLs for research (max 10)", "research_texts": "array (optional) - Text snippets for research (max 10)" } ``` **Example:** ```bash curl -X POST 'https://subscribr.ai/api/v1/channels/{id}/scripts' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 201 The newly created script **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | data.script_id | integer | Script identifier | | data.script_number | integer | Sequential number per channel | | data.thread_id | integer | Associated thread ID | | data.status | string | Script status | | data.canvas_url | string | Canvas URL for editing | **Example Response:** ```json { "success": true, "data": { "script_id": 789, "script_number": 12, "thread_id": 456, "status": "active", "canvas_url": "https://subscribr.com/chat/{thread}/canvas/{script}" } } ``` ### List Channel Scripts {#list-scripts} List scripts for a specific channel with optional filtering. #### Request ``` GET https://subscribr.ai/api/v1/channels/{id}/scripts ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Channel ID (path parameter) | | status | string | Filter by status (active, idea) | | format | string | Filter by format (Tutorial, Documentary, News, Interview, Compilation) | | search | string | Search in title/topic | | per_page | integer | Results per page (default: 20) | | page | integer | Page number (default: 1) | #### Response **Status Code:** 200 Array of script objects with pagination **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | data[].id | integer | Script ID | | data[].channel_id | integer | Channel ID | | data[].script_number | integer | Sequential number per channel | | data[].title | string | Script title | | data[].status | string | Script status (idea or active) | | data[].has_outline | boolean | Outline exists | | data[].has_script | boolean | Script exists | | pagination.total | integer | Total results | **Example Response:** ```json { "success": true, "data": [ { "id": 789, "channel_id": 123, "script_number": 12, "title": "Why Apple's AI Changes Everything", "topic": "Break down the key changes...", "angle": "Explain the hidden implication most reviews miss", "length": 1200, "status": "active", "format": "Tutorial", "language": "English", "voice": "neutral", "template_id": 22, "production_status": "scripting", "thread_id": 456, "canvas_url": "https://subscribr.com/chat/{thread}/canvas/{script}", "has_outline": true, "has_script": false } ], "pagination": { "current_page": 1, "per_page": 20, "total": 1, "last_page": 1 } } ``` ### Get Script {#get-script} Get detailed information about a specific script. #### Request ``` GET https://subscribr.ai/api/v1/scripts/{id} ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Script ID (path parameter) | #### Response **Status Code:** 200 Detailed script information **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | data.id | integer | Script ID | | data.channel_id | integer | Channel ID | | data.script_number | integer | Sequential number per channel | | data.title | string | Script title | | data.topic | string | Topic/brief | | data.angle | string | Angle/hook | | data.status | string | Script status (idea or active) | | data.has_outline | boolean | Outline exists | | data.has_script | boolean | Script exists | | data.notes | string\|null | Notes (nullable) | | data.hook | string\|null | Hook text (nullable) | | data.thumbnail | string\|null | Thumbnail text (nullable) | **Example Response:** ```json { "success": true, "data": { "id": 789, "channel_id": 123, "script_number": 12, "title": "Why Apple's AI Changes Everything", "topic": "Break down the key changes...", "angle": "Explain the hidden implication most reviews miss", "length": 1200, "format": "Tutorial", "language": "English", "voice": "neutral", "template_id": 22, "status": "active", "production_status": "scripting", "thread_id": 456, "canvas_url": "https://subscribr.com/chat/{thread}/canvas/{script}", "has_outline": true, "has_script": false, "notes": "Notes for collaborators", "hook": "Open with a surprising fact", "thumbnail": "Apple AI Shock" } } ``` ### Get Script Content {#get-script-content} Fetch outline and script content for a canvas script. #### Request ``` GET https://subscribr.ai/api/v1/scripts/{id}/content ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Script ID (path parameter) | #### Response **Status Code:** 200 Script content with outline and script versions **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | data.script_id | integer | Script identifier | | data.outline | string | Generated outline markdown | | data.script | string | Generated script markdown | | data.outline_version | integer | Outline version number | | data.content_version | integer | Script content version number | **Example Response:** ```json { "success": true, "data": { "script_id": 789, "outline": "# Outline\\n...", "script": "# Script\\n...", "outline_version": 3, "content_version": 7 } } ``` ### Generate Outline {#generate-outline} Start outline generation for a script. #### Request ``` POST https://subscribr.ai/api/v1/scripts/{id}/outline/generate ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:write JSON body (usually empty or no body) #### Response **Status Code:** 202 Async generation job created **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | data.run_id | string | Run ID for polling | | data.status | string | Initial status (queued) | **Example Response:** ```json { "success": true, "data": { "run_id": "run_123", "status": "queued" } } ``` ### Generate Script {#generate-script} Start script generation (requires outline). #### Request ``` POST https://subscribr.ai/api/v1/scripts/{id}/script/generate ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:write JSON body (usually empty or no body) #### Response **Status Code:** 202 Async generation job created **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | data.run_id | string | Run ID for polling | | data.status | string | Initial status (queued) | **Example Response:** ```json { "success": true, "data": { "run_id": "run_456", "status": "queued" } } ``` ### Poll Generation Status {#poll-generation} Poll generation status using a run_id. Returns full content only when completed. #### Request ``` GET https://subscribr.ai/api/v1/scripts/{id}/generate/poll ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:read Query parameters for polling **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Script ID (path parameter) | | run_id | string | Run UUID returned from generate endpoints | #### Response **Status Code:** 200 Generation status with content if completed **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | data.run_id | string | Run ID | | data.status | string | Status (queued, processing, completed, failed) | | data.content_type | string | Type of content (outline or script) | | data.outline | string\|null | Generated outline (if available) | | data.script | string\|null | Generated script (if available) | **Example Response:** ```json { "success": true, "data": { "run_id": "run_456", "status": "completed", "content_type": "script", "outline": "# Outline\\n...", "script": "# Script\\n..." } } ``` ### Export Script {#export-script} Export a completed script in various formats. #### Request ``` GET https://subscribr.ai/api/v1/scripts/{id}/export ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:read Query parameters for export **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Script ID (path parameter) | | format | string | Required. text, html, markdown | | include_headings | boolean | Optional. true, false (default true) | #### Response **Status Code:** 200 Exported script content **Response Fields:** | Field | Type | Description | | --- | --- | --- | | success | boolean | Request success | | data.script_id | integer | Script ID | | data.title | string | Script title | | data.format | string | Format used | | data.content | string | Exported content | | data.include_headings | boolean | Whether headings were included | **Example Response:** ```json { "success": true, "data": { "script_id": 789, "title": "Why Apple's AI Changes Everything", "format": "markdown", "content": "# Script\\n...", "include_headings": true } } ``` ## Thumbnails {#thumbnails} ### Get Thumbnail Usage {#get-thumbnail-usage} Get current thumbnail generation quota and usage for your team. Returns up to three quota pools: the free pool (included with every plan), an optional add-on pool (monthly subscription), and an optional pack pool (one-time purchased credits). The total_remaining field provides the combined remaining generations across all pools. #### Request ``` GET https://subscribr.ai/api/v1/team/thumbnails/usage ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:read, thumbnails:read No request body required. #### Response **Status Code:** 200 Thumbnail usage and quota details **Response Fields:** | Field | Type | Description | | --- | --- | --- | | data.eligible | boolean | Whether the team is eligible for thumbnail generation (requires active subscription) | | data.free_pool | object | Free pool included with every plan | | data.free_pool.limit | integer | Monthly free generation limit | | data.free_pool.used | integer | Completed generations this period | | data.free_pool.reserved | integer | In-progress generations (counted against remaining until completed or released) | | data.free_pool.remaining | integer | Available generations (limit minus used minus reserved) | | data.free_pool.next_reset | string\|null | Pool reset date (Y-m-d format) | | data.addon_pool | object\|null | Monthly add-on subscription pool (null if not subscribed). Same fields as free_pool. | | data.pack_pool | object\|null | One-time purchased credit pack (null if none purchased). Same fields as free_pool. next_reset is always null — pack credits do not expire. | | data.total_remaining | integer | Combined remaining across all active pools | **Example Response:** ```json { "success": true, "data": { "eligible": true, "free_pool": { "limit": 5, "used": 2, "reserved": 1, "remaining": 2, "next_reset": "2026-04-01" }, "addon_pool": { "limit": 50, "used": 10, "reserved": 0, "remaining": 40, "next_reset": "2026-04-15" }, "pack_pool": { "limit": 100, "used": 5, "reserved": 0, "remaining": 95, "next_reset": null }, "total_remaining": 137 } } ``` ### Create Thumbnail Generation {#create-thumbnail-generation} Create an asynchronous thumbnail generation for a channel. Three main modes: (1) **Idea-based** — provide an idea_id to generate a final 2K thumbnail from an existing idea. (2) **Brainstorm** — provide a prompt (no idea_id) to generate concept sketches at 1K resolution. Brainstorm results are stored as concept variations on a new idea; to produce final 2K thumbnails, call this endpoint again with the returned idea_id. (3) **Clone** — provide a clone_strategy and reference_image_url to generate thumbnails that match an existing thumbnail's style. The generation runs in the background; poll the returned run_id for status or use callback_url for async notification. #### Request ``` POST https://subscribr.ai/api/v1/channels/{id}/thumbnails/generations ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:write, thumbnails:write JSON body with generation parameters. The id path parameter is the channel ID. **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Channel ID (path parameter). | **Request Body:** ```json { "idea_id": "integer (optional) \u2014 Generate thumbnail from an existing idea. Required for improvement_mode.", "prompt": "string (required if no idea_id, clone_strategy, or improvement_mode, max 2000) \u2014 Custom prompt or video title for generation", "topic": "string (optional, max 1000) \u2014 Additional topic context for brainstorm mode; improves concept quality", "num_variations": "integer (optional, 1-8) \u2014 Number of variations to generate. Default: 4 for brainstorm, 1 for idea-based.", "callback_url": "string (optional, HTTPS URL, max 2048) \u2014 Webhook URL for async completion notification", "callback_secret": "string (optional, max 255) \u2014 Secret used to sign callback payloads via X-Subscribr-Signature header", "improvement_mode": "boolean (optional) \u2014 When true, iterates on an existing idea using feedback. Requires idea_id.", "feedback": "string (required if improvement_mode=true, max 5000) \u2014 Feedback describing desired changes to the thumbnail", "reference_variation_url": "url (required if improvement_mode=true) \u2014 HTTPS URL of the variation to improve upon", "clone_strategy": "string (optional) \u2014 Clone an existing thumbnail style. Values: direct_reference (immediate generation) or style_analysis (two-step: analyze first, then generate)", "reference_image_url": "url (required if clone_strategy is set) \u2014 HTTPS URL of the reference thumbnail image to clone" } ``` **Example:** ```bash curl -X POST 'https://subscribr.ai/api/v1/channels/{id}/thumbnails/generations' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 202 The generation has been queued. When using clone_strategy=style_analysis, the response shape differs — see the style_analysis note below. **Response Fields:** | Field | Type | Description | | --- | --- | --- | | data.run_id | string | Primary generation run ID (UUID). Use this to poll status via the Get Generation Status endpoint. | | data.run_ids | array | All run IDs for this request. When num_variations > 1 with an idea_id, each variation gets its own run ID that can be polled individually. | | data.status | string | Initial status (always "queued") | | data.idea_id | integer | Returned only for clone modes — the auto-created idea ID | **Example Response:** ```json { "success": true, "data": { "run_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "run_ids": [ "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" ], "status": "queued" } } ``` #### Notes **Clone with style_analysis:** When clone_strategy=style_analysis, the response has a different shape: { idea_id, status: "style_analysis_pending", message }. Style analysis runs asynchronously before thumbnail generation. Use a callback_url to be notified when analysis completes, then call this endpoint again with the returned idea_id to generate the final thumbnail. **Quota consumption:** Each generation consumes one unit from your quota pool, regardless of the number of variations. Quota is reserved at request time and committed on completion. If a generation fails, the reserved quota is released back to your pool. ### Get Thumbnail Generation Status {#get-thumbnail-generation} Get the current status and results of a thumbnail generation. When the status is "completed", the output_urls array contains URLs to the generated thumbnail images. Poll this endpoint after creating a generation to track progress. #### Request ``` GET https://subscribr.ai/api/v1/channels/{id}/thumbnails/generations/{runId} ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:read, thumbnails:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Channel ID (path parameter). | | runId | string | Generation run ID returned from the Create endpoint (path parameter). | #### Response **Status Code:** 200 Generation status and output **Response Fields:** | Field | Type | Description | | --- | --- | --- | | data.run_id | string | Generation run identifier (UUID) | | data.status | string | Status: queued (processing), completed (output ready), or failed | | data.idea_id | integer\|null | Linked idea ID (present when using idea-based or clone modes) | | data.output_urls | array | Generated thumbnail image URLs (populated when status is "completed", empty otherwise) | | data.output_urls[] | string | URL to a generated thumbnail image | | data.created_at | string | ISO 8601 creation timestamp | | data.updated_at | string | ISO 8601 last update timestamp (indicates when status last changed) | **Example Response:** ```json { "success": true, "data": { "run_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "status": "completed", "idea_id": null, "output_urls": [ "https://subscribr.ai/storage/thumbnails/9b1deb4d_v1.png", "https://subscribr.ai/storage/thumbnails/9b1deb4d_v2.png" ], "created_at": "2026-02-27T10:30:00Z", "updated_at": "2026-02-27T10:30:45Z" } } ``` ### List Thumbnail Generations {#list-thumbnail-generations} List past thumbnail generation runs for a channel. Returns a paginated list (15 per page) ordered by most recent first. Optionally filter by source_type to narrow results to a specific generation mode. #### Request ``` GET https://subscribr.ai/api/v1/channels/{id}/thumbnails/generations ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** scripts:read, thumbnails:read Optional query parameters to filter and paginate results. **Parameters:** | Name | Type | Description | | --- | --- | --- | | id | integer | Channel ID (path parameter). | | source_type | string | Filter by generation mode: api_generate, idea_improve, thumbnail_brainstorm, thumbnail_clone | | page | integer | Page number (default 1). | #### Response **Status Code:** 200 Paginated list of thumbnail generation runs **Response Fields:** | Field | Type | Description | | --- | --- | --- | | data[] | array | List of generation run objects | | data[].run_id | string | Run identifier (UUID) — use with the Get Generation Status endpoint | | data[].status | string | Status: queued, completed, or failed | | data[].source_type | string | How the generation was triggered (api_generate, idea_improve, thumbnail_brainstorm, thumbnail_clone) | | data[].idea_id | integer\|null | Linked idea ID (null for brainstorm runs without a pre-existing idea) | | data[].output_urls | array | Generated thumbnail URLs (empty for queued/failed runs) | | data[].created_at | string | ISO 8601 creation timestamp | | meta.current_page | integer | Current page number | | meta.last_page | integer | Total number of pages | | meta.per_page | integer | Items per page (15) | | meta.total | integer | Total matching generation runs | **Example Response:** ```json { "success": true, "data": [ { "run_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "status": "completed", "source_type": "api_generate", "idea_id": 42, "output_urls": [ "https://subscribr.ai/storage/thumbnails/9b1deb4d_v1.png" ], "created_at": "2026-02-27T10:30:00Z" } ], "meta": { "current_page": 1, "last_page": 3, "per_page": 15, "total": 42 } } ``` ## Webhooks {#webhooks} ### List Webhooks {#list-webhooks} Retrieve a paginated list of all webhooks configured for your team. #### Request ``` GET https://subscribr.ai/api/v1/webhooks ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** webhooks:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | per_page | integer | Webhooks per page (default: 15, max: 100) | | page | integer | Page number for pagination (default: 1) | #### Response **Status Code:** 200 Array of webhook objects with pagination **Response Fields:** | Field | Type | Description | | --- | --- | --- | | id | string | Webhook identifier | | url | string | Webhook URL endpoint | | events | array | Events this webhook is subscribed to | | active | boolean | Whether webhook is active | | created_at | string | ISO 8601 creation timestamp | **Example Response:** ```json [ { "id": "webhook_123", "url": "https://example.com/webhooks/subscribr", "events": [ "script.generated", "idea.created" ], "active": true, "created_at": "2024-01-10T08:00:00Z" } ] ``` ### Create Webhook {#create-webhook} Create a new webhook to receive notifications about events in your Subscribr account. #### Request ``` POST https://subscribr.ai/api/v1/webhooks ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** webhooks:write JSON body with webhook configuration **Request Body:** ```json { "url": "string (required) - HTTPS URL where webhooks will be sent", "events": "array (required) - Events to subscribe to, e.g., [\"script.generated\", \"idea.created\", \"thumbnail.generated\"]", "active": "boolean (optional) - Activate webhook immediately (default: true)" } ``` **Example:** ```bash curl -X POST 'https://subscribr.ai/api/v1/webhooks' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 201 The newly created webhook **Response Fields:** | Field | Type | Description | | --- | --- | --- | | id | string | Webhook identifier | | url | string | Webhook URL endpoint | | events | array | Subscribed events | | active | boolean | Active status | | secret | string | Secret for HMAC signature verification | | created_at | string | ISO 8601 creation timestamp | **Example Response:** ```json { "id": "webhook_456", "url": "https://example.com/webhooks/subscribr", "events": [ "script.generated", "idea.created" ], "active": true, "secret": "whk_secret_abcdef123456", "created_at": "2024-01-26T10:00:00Z" } ``` ### Get Webhook {#get-webhook} Retrieve detailed information about a specific webhook. #### Request ``` GET https://subscribr.ai/api/v1/webhooks/{webhook} ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** webhooks:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | webhook | string | Webhook ID (path parameter) | #### Response **Status Code:** 200 Detailed webhook information **Response Fields:** | Field | Type | Description | | --- | --- | --- | | id | string | Webhook identifier | | url | string | Webhook URL endpoint | | events | array | Subscribed events | | active | boolean | Active status | | last_triggered_at | string\|null | Last webhook trigger timestamp | | created_at | string | ISO 8601 creation timestamp | **Example Response:** ```json { "id": "webhook_456", "url": "https://example.com/webhooks/subscribr", "events": [ "script.generated", "idea.created" ], "active": true, "last_triggered_at": "2024-01-25T14:22:00Z", "created_at": "2024-01-26T10:00:00Z" } ``` ### Update Webhook {#update-webhook} Update an existing webhook's configuration including URL, events, and active status. #### Request ``` PUT https://subscribr.ai/api/v1/webhooks/{webhook} ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** webhooks:write JSON body with updates (all fields optional) **Request Body:** ```json { "url": "string (optional) - New webhook URL", "events": "array (optional) - Updated list of events", "active": "boolean (optional) - Activate or deactivate webhook" } ``` **Example:** ```bash curl -X PUT 'https://subscribr.ai/api/v1/webhooks/{webhook}' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Content-Type: application/json' ``` #### Response **Status Code:** 200 Updated webhook object **Response Fields:** | Field | Type | Description | | --- | --- | --- | | id | string | Webhook identifier | | url | string | Webhook URL endpoint | | events | array | Subscribed events | | active | boolean | Active status | | updated_at | string | ISO 8601 last update timestamp | **Example Response:** ```json { "id": "webhook_456", "url": "https://example.com/webhooks/subscribr", "events": [ "script.generated" ], "active": false, "updated_at": "2024-01-26T11:00:00Z" } ``` ### Delete Webhook {#delete-webhook} Delete a webhook. No further events will be sent to the webhook URL. #### Request ``` DELETE https://subscribr.ai/api/v1/webhooks/{webhook} ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** webhooks:write No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | webhook | string | Webhook ID (path parameter) | #### Response **Status Code:** 204 No content. Webhook has been deleted. ### Test Webhook {#test-webhook} Send a test webhook event to your configured URL. Useful for testing your webhook handler. #### Request ``` POST https://subscribr.ai/api/v1/webhooks/{webhook}/test ``` **Authentication Required:** Yes (Bearer Token) **Required Abilities:** webhooks:read No request body required. **Parameters:** | Name | Type | Description | | --- | --- | --- | | webhook | string | Webhook ID (path parameter) | #### Response **Status Code:** 200 Test webhook delivery status **Response Fields:** | Field | Type | Description | | --- | --- | --- | | status | string | Delivery status (sent, failed) | | event_type | string | Event type sent in test | | sent_at | string | ISO 8601 timestamp when test was sent | **Example Response:** ```json { "status": "sent", "event_type": "webhook.test", "sent_at": "2024-01-26T11:30:00Z" } ``` ### Webhook Events {#webhook-events} Reference documentation for all webhook events that can be subscribed to. Webhooks allow you to receive real-time notifications about important events in your Subscribr account. Subscribe to the events you care about and your webhook endpoint will receive POST requests with event details. ### Webhook Security {#webhook-security} Learn how to secure and verify webhook requests from Subscribr. All webhook requests from Subscribr are signed with HMAC SHA-256. You should verify this signature to ensure the request is authentic and hasn't been tampered with. The signature is provided in the `X-Subscribr-Signature` header. It's computed using your webhook secret and the raw request body. ```javascript const crypto = require("crypto"); function verifyWebhookSignature(body, signature, secret) { const hash = crypto .createHmac("sha256", secret) .update(body, "utf8") .digest("hex"); return crypto.timingSafeEqual( Buffer.from(hash), Buffer.from(signature) ); } // In your webhook handler: const signature = req.headers["x-subscribr-signature"]; const body = req.rawBody; // Raw request body as string const secret = process.env.WEBHOOK_SECRET; if (!verifyWebhookSignature(body, signature, secret)) { return res.status(401).json({ error: "Invalid signature" }); } // Process webhook... res.status(200).json({ success: true }); ```