# Faceless.so API, full reference for agents > Concatenated API reference and guides. Regenerated by npm run generate:agents. # Faceless.so API reference > Generated from src/backend/api/v1/spec/registry.mjs by npm run generate:agents. Do not edit by hand. Base URL: `https://faceless.so/api/v1` Authentication: `Authorization: Bearer fl_live_...` (or `X-API-Key`). Create keys at https://faceless.so/developers. Envelope: success responses are `{ "success": true, "data": ..., "pagination"?: { page, limit, total } }`. Errors are `{ "success": false, "error": { "type", "message" } }` with types: `invalid_input`, `unauthorized`, `forbidden_scope`, `not_found`, `conflict`, `insufficient_credits`, `usage_limit_reached`, `rate_limited`, `internal_error`. ## me ### Identify the caller `GET /me` Returns the team this API key belongs to, its credit balance, subscription plan and the scopes granted to the key. Call this first to verify authentication and discover the team context every other operation runs in. - Scopes: any valid key - Credits: none - CLI: `faceless whoami` - MCP tool: `faceless_get_me` Example: ```bash curl -s "https://faceless.so/api/v1/me" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": { "team": { "id": "665f1b2a9c31a2b3c4d5e601", "name": "My Channel", "credits": 340 }, "plan": "creator", "auth": { "via": "api_key", "keyPrefix": "fl_live_a1b2c3d4", "scopes": [ "videos:read", "videos:write" ], "legacy": false } } } ``` ## credits ### Credit balance and history `GET /credits` Returns the team's current credit balance and a paginated ledger of credit transactions (purchases, subscription grants, video spends, refunds). Video creation charges credits at creation time; rendering and publishing are free. Costs: storyboard 20, motion_lite 50, motion_pro 100 credits per video. - Scopes: `credits:read` - Credits: none - CLI: `faceless credits` - MCP tool: `faceless_get_credits` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `page` | integer | no | Page number, starting at 1 | | `limit` | integer | no | Items per page (max 100) | Example: ```bash curl -s "https://faceless.so/api/v1/credits" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": { "balance": 340, "history": [ { "id": "665f1b2a9c31a2b3c4d5e701", "credits": -50, "type": "spending", "description": "Faceless video (motion_lite)", "createdAt": "2026-07-30T10:00:00.000Z" } ] }, "pagination": { "page": 1, "limit": 20, "total": 1 } } ``` ## videos ### Create a faceless video from a script `POST /videos` Creates an AI faceless video from a finished narration script: TTS voiceover, AI-generated visuals per scene, captions and optional background music. Generation is asynchronous; the response returns the video id immediately. Poll GET /videos/{id}/status until status is completed, then call POST /videos/{id}/render to produce the final MP4. Credits are charged now, at creation. - Scopes: `videos:write` - Credits: 20 credits (storyboard), 50 (motion_lite) or 100 (motion_pro) per video, charged at creation - Rate limit: 10 per 60s - Supports `Idempotency-Key` header - CLI: `faceless videos create` - MCP tool: `faceless_create_video` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `script` | string | yes | The full narration script the video is generated from | | `voiceId` | string | yes | TTS voice id for the narration. Full list: GET /voices | | `model` | `storyboard` \| `motion_lite` \| `motion_pro` | no | Generation model: storyboard (20 credits, static scenes), motion_lite (50, animated transitions), motion_pro (100, cinematic) | | `style` | string | no | Visual style for generated imagery. Full list: GET /options?kind=styles | | `language` | string | no | Script language, e.g. English. Full list: GET /options?kind=languages | | `name` | string | no | Project name; defaults to the start of the script | | `enableBackgroundMusic` | boolean | no | Mix background music under the narration | | `masterStyle` | string | no | Extra style directive applied to every generated scene | | `globalNegativePrompt` | string | no | Things the image model should avoid in every scene | Example: ```bash curl -s -X POST "https://faceless.so/api/v1/videos" \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"script":"Did you know the ocean has lakes and rivers of its own? ...","voiceId":"EXAVITQu4vr4xnSDxMaL","model":"storyboard"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e801", "name": "Did you kn...", "model": "storyboard", "status": "processing", "creditsUsed": 20, "statusUrl": "/api/v1/videos/665f1b2a9c31a2b3c4d5e801/status" } } ``` ### Caption an existing video or audio file `POST /videos/captions` Creates a project from a video or audio URL you already have: transcribes it, adds styled captions and emojis, and makes it editable. Use this to caption existing footage rather than generate new AI visuals (for that, use POST /videos). Processing is asynchronous; poll GET /videos/{id}/status. - Scopes: `videos:write` - Credits: none - Rate limit: 10 per 300s - Supports `Idempotency-Key` header - CLI: `faceless videos captions` - MCP tool: `faceless_create_caption_video` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `videoUrl` | string | no | Public URL of the video file to caption | | `audioUrl` | string | no | Public URL of an audio file to turn into a captioned video | | `name` | string | no | Project name | | `language` | string | no | Spoken language of the file, e.g. English | Example: ```bash curl -s -X POST "https://faceless.so/api/v1/videos/captions" \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"videoUrl":"https://example.com/clip.mp4","language":"English"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e802", "status": "processing", "statusUrl": "/api/v1/videos/665f1b2a9c31a2b3c4d5e802/status" } } ``` ### List the team's videos `GET /videos` Returns a paginated list of the team's video projects, newest first, with generation status and the rendered MP4 URL when available. - Scopes: `videos:read` - Credits: none - CLI: `faceless videos list` - MCP tool: `faceless_list_videos` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `page` | integer | no | Page number, starting at 1 | | `limit` | integer | no | Items per page (max 100) | | `archived` | boolean | no | Only archived videos when true | Example: ```bash curl -s "https://faceless.so/api/v1/videos" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5e801", "name": "Ocean facts", "platform": "tts", "model": "storyboard", "renderedVideoUrl": null, "createdAt": "2026-07-30T10:00:00.000Z" } ], "pagination": { "page": 1, "limit": 20, "total": 1 } } ``` ### Get one video `GET /videos/{id}` Returns a single video project: script, voice, model, generation state, per-platform post metadata and the rendered MP4 URL once rendered. - Scopes: `videos:read` - Credits: none - CLI: `faceless videos get` - MCP tool: `faceless_get_video` Example: ```bash curl -s "https://faceless.so/api/v1/videos/" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e801", "name": "Ocean facts", "platform": "tts", "model": "storyboard", "script": "Did you know...", "voiceId": "EXAVITQu4vr4xnSDxMaL", "status": "completed", "renderedVideoUrl": "https://exports.faceless.so/renders/665f1b2a9c31a2b3c4d5e801/1722333444555.mp4", "createdAt": "2026-07-30T10:00:00.000Z" } } ``` ### Update a video's name or post metadata `PATCH /videos/{id}` Updates a video's name and/or its per-platform post metadata (titles, captions, descriptions). Set the metadata for every platform you intend to schedule to BEFORE calling POST /posts/schedule; scheduling rejects platforms whose metadata is missing. - Scopes: `videos:write` - Credits: none - CLI: `faceless videos update` - MCP tool: `faceless_update_video` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Project name | | `youtubePost` | object | no | YouTube post metadata; required before scheduling to YouTube | | `tiktokPost` | object | no | TikTok post metadata; required before scheduling to TikTok | | `instagramPost` | object | no | Instagram post metadata; required before scheduling to Instagram | | `xPost` | object | no | X post metadata; required before scheduling to X | | `facebookPost` | object | no | Facebook post metadata; required before scheduling to Facebook | | `linkedinPost` | object | no | LinkedIn post metadata; required before scheduling to LinkedIn | | `threadsPost` | object | no | Threads post metadata; required before scheduling to Threads | Example: ```bash curl -s -X PATCH "https://faceless.so/api/v1/videos/" \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"youtubePost":{"title":"The ocean has rivers underwater","privacyStatus":"public"},"tiktokPost":{"title":"underwater rivers are real #ocean"}}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e801", "name": "Ocean facts", "youtubePost": { "title": "The ocean has rivers underwater" } } } ``` ### Poll video generation progress `GET /videos/{id}/status` Returns generation progress for a video: overall status (pending, processing, completed, failed), percent complete, per-step events and any error messages. Poll every 10-30 seconds after POST /videos until status is completed, then render it with POST /videos/{id}/render. A failed generation refunds nothing automatically; check errorMessages. - Scopes: `videos:read` - Credits: none - Terminal states: `completed`, `failed` - CLI: `faceless videos status` - MCP tool: `faceless_get_video_status` Example: ```bash curl -s "https://faceless.so/api/v1/videos//status" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e801", "status": "processing", "percentCompleted": 62, "readyForEditing": false, "errorMessages": [], "renderedVideoUrl": null } } ``` ### Render a video to MP4 `POST /videos/{id}/render` Starts a cloud render of a generated video and returns a renderId. Rendering is asynchronous: poll GET /renders/{renderId} until status is done to get the MP4 URL. The video must have finished generating first (getVideoStatus status completed). Rendering is free; credits were charged at creation. - Scopes: `videos:write` - Credits: none - Rate limit: 10 per 300s - Supports `Idempotency-Key` header - CLI: `faceless videos render` - MCP tool: `faceless_render_video` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `codec` | `h264` \| `vp8` | no | Output codec; h264 for MP4 (default) | Example: ```bash curl -s -X POST "https://faceless.so/api/v1/videos//render" \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"codec":"h264"}' ``` Response: ```json { "success": true, "data": { "renderId": "abcd1234efgh", "statusUrl": "/api/v1/renders/abcd1234efgh" } } ``` ### Delete a video `DELETE /videos/{id}` Permanently deletes a video project and its generated assets. This cannot be undone and does not refund credits. - Scopes: `videos:write` - Credits: none - CLI: `faceless videos delete` - MCP tool: `faceless_delete_video` Example: ```bash curl -s -X DELETE "https://faceless.so/api/v1/videos/" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": { "deleted": true } } ``` ## renders ### Poll render progress `GET /renders/{id}` Returns the progress of a render started by POST /videos/{id}/render: status in-progress (with overallProgress 0-1), done (with the final MP4 url) or error. Poll every 5-15 seconds; renders typically take under two minutes. - Scopes: `videos:read` - Credits: none - Terminal states: `done`, `error` - CLI: `faceless renders get` - MCP tool: `faceless_get_render` Example: ```bash curl -s "https://faceless.so/api/v1/renders/" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": { "renderId": "abcd1234efgh", "status": "done", "overallProgress": 1, "url": "https://exports.faceless.so/renders/665f1b2a9c31a2b3c4d5e801/1722333444555.mp4" } } ``` ## series ### Create an automated video series `POST /series` Creates a series that automatically generates and posts a new faceless video on a schedule (a set-and-forget channel). Choose a content source and niche or custom prompt, a voice, style and posting cadence; each generated episode charges credits like a single video. The first episode starts generating right after creation. Use GET /series/{id}/episodes to track output, POST /series/{id}/generate to force the next episode now. - Scopes: `series:write` - Credits: none at creation; each generated episode charges its model's video cost (20 to 100 credits) - Rate limit: 5 per 300s - Supports `Idempotency-Key` header - CLI: `faceless series create` - MCP tool: `faceless_create_series` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Series name shown in the dashboard | | `source` | string | yes | Content source that scripts every episode, e.g. "Facts & stories", "Reddit post", "Custom prompt". Full list: GET /options?kind=sources | | `niche` | string | no | Content niche, e.g. scary stories, motivation. Full list: GET /options?kind=niches | | `customPrompt` | string | no | Custom topic prompt used instead of (or alongside) a niche | | `voice` | string | no | TTS voice id for narration. Full list: GET /voices | | `style` | string | no | Visual style for generated imagery. Full list: GET /options?kind=styles | | `language` | string | no | Video language, e.g. English. Full list: GET /options?kind=languages | | `duration` | `30` \| `60` \| `90` | no | Target episode length in seconds (30, 60 or 90) | | `destination` | string | no | Primary auto-post destination platform, e.g. youtube or tiktok | | `destinationAccounts` | array | no | Connected account ids to auto-post to (from GET /accounts) | | `autoPostTime` | string | no | Daily auto-post time "HH:mm" in the series timezone | | `postingDays` | array | no | Days of the week to post, e.g. ["Monday","Wednesday"]. Omit for every day | | `timezone` | string | no | IANA timezone for scheduling, e.g. America/New_York | | `captionStyle` | string | no | Caption theme name. Full list: GET /options?kind=captionThemes | | `subreddit` | string | no | Subreddit to pull posts from when source is "Reddit post" | | `backgroundVideo` | string | no | Background gameplay/footage id. Full list: GET /options?kind=backgrounds | | `useRandomBackgroundVideo` | boolean | no | Pick a random background video per episode | | `layout` | string | no | Video layout variant | | `brollModel` | string | no | Generation model for visuals: storyboard, motion_lite or motion_pro | | `showEmojis` | boolean | no | Overlay emojis on captions | | `enableBackgroundMusic` | boolean | no | Mix background music under the narration | | `backgroundMusicMood` | string | no | Background music mood. Full list: GET /options?kind=music | | `hashtags` | string | no | Hashtags appended to post captions | | `tone` | string | no | Writing tone for generated scripts | | `youtubePrivacyStatus` | `public` \| `unlisted` \| `private` | no | Privacy for auto-posted YouTube videos | Example: ```bash curl -s -X POST "https://faceless.so/api/v1/series" \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Deep sea facts","source":"Facts & stories","niche":"Ocean facts","voice":"EXAVITQu4vr4xnSDxMaL","duration":"60","autoPostTime":"18:00","timezone":"America/New_York"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e901", "name": "Deep sea facts", "source": "Facts & stories", "niche": "Ocean facts", "createdAt": "2026-07-30T10:00:00.000Z" } } ``` ### List the team's series `GET /series` Returns all automated series for the team with their source, niche, cadence and pause state. - Scopes: `series:read` - Credits: none - CLI: `faceless series list` - MCP tool: `faceless_list_series` Example: ```bash curl -s "https://faceless.so/api/v1/series" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5e901", "name": "Deep sea facts", "source": "Facts & stories", "niche": "Ocean facts", "paused": false } ] } ``` ### Get one series `GET /series/{id}` Returns a single series with its full configuration: source, niche, voice, style, schedule, destinations and pause state. - Scopes: `series:read` - Credits: none - CLI: `faceless series get` - MCP tool: `faceless_get_series` Example: ```bash curl -s "https://faceless.so/api/v1/series/" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e901", "name": "Deep sea facts", "source": "Facts & stories", "niche": "Ocean facts", "voice": "EXAVITQu4vr4xnSDxMaL", "autoPostTime": "18:00", "paused": false } } ``` ### Update a series `PATCH /series/{id}` Updates any subset of a series' configuration (niche, voice, schedule, destinations, pause state and so on). Only the fields you send change. - Scopes: `series:write` - Credits: none - CLI: `faceless series update` - MCP tool: `faceless_update_series` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Series name shown in the dashboard | | `source` | string | no | Content source that scripts every episode, e.g. "Facts & stories", "Reddit post", "Custom prompt". Full list: GET /options?kind=sources | | `niche` | string | no | Content niche, e.g. scary stories, motivation. Full list: GET /options?kind=niches | | `customPrompt` | string | no | Custom topic prompt used instead of (or alongside) a niche | | `voice` | string | no | TTS voice id for narration. Full list: GET /voices | | `style` | string | no | Visual style for generated imagery. Full list: GET /options?kind=styles | | `language` | string | no | Video language, e.g. English. Full list: GET /options?kind=languages | | `duration` | `30` \| `60` \| `90` | no | Target episode length in seconds (30, 60 or 90) | | `destination` | string | no | Primary auto-post destination platform, e.g. youtube or tiktok | | `destinationAccounts` | array | no | Connected account ids to auto-post to (from GET /accounts) | | `autoPostTime` | string | no | Daily auto-post time "HH:mm" in the series timezone | | `postingDays` | array | no | Days of the week to post, e.g. ["Monday","Wednesday"]. Omit for every day | | `timezone` | string | no | IANA timezone for scheduling, e.g. America/New_York | | `captionStyle` | string | no | Caption theme name. Full list: GET /options?kind=captionThemes | | `subreddit` | string | no | Subreddit to pull posts from when source is "Reddit post" | | `backgroundVideo` | string | no | Background gameplay/footage id. Full list: GET /options?kind=backgrounds | | `useRandomBackgroundVideo` | boolean | no | Pick a random background video per episode | | `layout` | string | no | Video layout variant | | `brollModel` | string | no | Generation model for visuals: storyboard, motion_lite or motion_pro | | `showEmojis` | boolean | no | Overlay emojis on captions | | `enableBackgroundMusic` | boolean | no | Mix background music under the narration | | `backgroundMusicMood` | string | no | Background music mood. Full list: GET /options?kind=music | | `hashtags` | string | no | Hashtags appended to post captions | | `tone` | string | no | Writing tone for generated scripts | | `youtubePrivacyStatus` | `public` \| `unlisted` \| `private` | no | Privacy for auto-posted YouTube videos | | `paused` | boolean | no | Pause or resume automatic episode generation | Example: ```bash curl -s -X PATCH "https://faceless.so/api/v1/series/" \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"autoPostTime":"09:00","paused":false}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e901", "name": "Deep sea facts", "autoPostTime": "09:00", "paused": false } } ``` ### Delete a series `DELETE /series/{id}` Deletes a series and stops all future automatic generation. Already-generated videos are kept. - Scopes: `series:write` - Credits: none - CLI: `faceless series delete` - MCP tool: `faceless_delete_series` Example: ```bash curl -s -X DELETE "https://faceless.so/api/v1/series/" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": { "deleted": true } } ``` ### Generate the next episode now `POST /series/{id}/generate` Immediately generates the next episode of a series instead of waiting for its schedule. Charges the series' per-episode credit cost. The episode appears in GET /series/{id}/episodes; poll the created video's status like any other video. - Scopes: `series:write` - Credits: the series' per-episode video cost (20 to 100 credits depending on model) - Rate limit: 5 per 300s - Supports `Idempotency-Key` header - CLI: `faceless series generate` - MCP tool: `faceless_generate_series_episode` Example: ```bash curl -s -X POST "https://faceless.so/api/v1/series//generate" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": { "projectId": "665f1b2a9c31a2b3c4d5ea01", "statusUrl": "/api/v1/videos/665f1b2a9c31a2b3c4d5ea01/status" } } ``` ### List a series' episodes `GET /series/{id}/episodes` Returns the videos generated by a series, newest first, with their generation status, scheduled post time and posted state. - Scopes: `series:read` - Credits: none - CLI: `faceless series episodes` - MCP tool: `faceless_list_series_episodes` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `page` | integer | no | Page number, starting at 1 | | `limit` | integer | no | Items per page (max 100) | Example: ```bash curl -s "https://faceless.so/api/v1/series//episodes" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5eb01", "projectId": "665f1b2a9c31a2b3c4d5ea01", "status": "posted", "scheduledTime": "2026-07-30T18:00:00.000Z" } ], "pagination": { "page": 1, "limit": 20, "total": 1 } } ``` ## posts ### Publish a rendered video to a platform now `POST /posts` Immediately publishes a rendered video to one connected platform (youtube, tiktok, instagram, x, facebook, linkedin or threads). The video must be rendered first (renderedVideoUrl present, or pass videoUrl explicitly) and the team must have that platform connected (GET /accounts). For future-dated posting use POST /posts/schedule instead. - Scopes: `posts:write` - Credits: none - Rate limit: 10 per 300s - Supports `Idempotency-Key` header - CLI: `faceless posts publish` - MCP tool: `faceless_publish_post` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `videoId` | string | yes | The video (project) to publish | | `platform` | `youtube` \| `tiktok` \| `instagram` \| `x` \| `facebook` \| `linkedin` \| `threads` | yes | Destination platform; must be connected (see GET /accounts) | | `title` | string | no | Post title or caption (platform-appropriate); falls back to the video's stored post metadata | | `description` | string | no | Longer description for platforms that support one (YouTube, Facebook, LinkedIn) | | `privacyStatus` | `public` \| `unlisted` \| `private` | no | YouTube only; defaults to public | | `authId` | string | no | Specific connected account id when the team has several for the platform (from GET /accounts) | Example: ```bash curl -s -X POST "https://faceless.so/api/v1/posts" \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"videoId":"665f1b2a9c31a2b3c4d5e801","platform":"youtube","title":"The ocean has rivers underwater"}' ``` Response: ```json { "success": true, "data": { "platform": "youtube", "videoId": "665f1b2a9c31a2b3c4d5e801", "postUrl": "https://youtube.com/shorts/dQw4w9WgXcQ" } } ``` ### Schedule a video to one or more platforms `POST /posts/schedule` Schedules a video to be rendered (if needed) and posted to multiple platforms at a future time. Every platform in the list must have its post metadata already set on the video (PATCH /videos/{id}); the request is rejected otherwise. Cancel with DELETE /posts/{videoId}. See the posting calendar with GET /calendar. - Scopes: `posts:write` - Credits: none - Rate limit: 20 per 300s - Supports `Idempotency-Key` header - CLI: `faceless posts schedule` - MCP tool: `faceless_schedule_post` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `videoId` | string | yes | The video (project) to schedule | | `platforms` | array | yes | Platforms to post to; each needs its post metadata set on the video first | | `scheduledTime` | string | yes | When to post (ISO 8601, future) | Example: ```bash curl -s -X POST "https://faceless.so/api/v1/posts/schedule" \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"videoId":"665f1b2a9c31a2b3c4d5e801","platforms":["youtube","tiktok"],"scheduledTime":"2026-08-01T18:00:00Z"}' ``` Response: ```json { "success": true, "data": { "taskId": "665f1b2a9c31a2b3c4d5ec01", "platforms": [ "youtube", "tiktok" ], "scheduledTime": "2026-08-01T18:00:00Z" } } ``` ### Cancel a scheduled post `DELETE /posts/{id}` Cancels the pending scheduled posting for a video (the id is the video id used in POST /posts/schedule). Posts that already went out are not affected. - Scopes: `posts:write` - Credits: none - CLI: `faceless posts cancel` - MCP tool: `faceless_cancel_post` Example: ```bash curl -s -X DELETE "https://faceless.so/api/v1/posts/" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": { "cancelled": true } } ``` ### Posting calendar `GET /calendar` Returns the team's social posts (scheduled, posted and failed) within a date range, across all platforms. Use it to see what is queued before scheduling more. - Scopes: `posts:read` - Credits: none - CLI: `faceless calendar` - MCP tool: `faceless_get_calendar` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `startDate` | string | yes | Range start (inclusive) | | `endDate` | string | yes | Range end (inclusive) | | `platform` | `youtube` \| `tiktok` \| `instagram` \| `x` \| `facebook` \| `linkedin` \| `threads` | no | Filter to one platform | | `status` | string | no | Filter by post status, e.g. scheduled, posted, failed | Example: ```bash curl -s "https://faceless.so/api/v1/calendar" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "665f1b2a9c31a2b3c4d5ed01", "projectId": "665f1b2a9c31a2b3c4d5e801", "platform": "youtube", "status": "scheduled", "scheduledTime": "2026-08-01T18:00:00.000Z" } ] } ``` ## accounts ### List connected social accounts `GET /accounts` Returns the social accounts connected to the team (YouTube, TikTok, Instagram, X, Facebook, LinkedIn, Threads) with their ids. Use the ids as authId in publishPost and destinationAccounts in series. Connecting accounts itself happens in the dashboard (OAuth), not over the API. - Scopes: `accounts:read` - Credits: none - CLI: `faceless accounts` - MCP tool: `faceless_list_accounts` Example: ```bash curl -s "https://faceless.so/api/v1/accounts" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": { "youtube": [ { "id": "665f1b2a9c31a2b3c4d5ee01", "channelName": "Deep Sea Facts" } ], "tiktok": [], "instagram": [] } } ``` ## catalog ### List TTS voices `GET /voices` Returns the text-to-speech voices available for narration, including the team's custom cloned voices. Use a voice's id as voiceId in createVideo or voice in createSeries. - Scopes: `catalog:read` - Credits: none - CLI: `faceless voices` - MCP tool: `faceless_list_voices` Example: ```bash curl -s "https://faceless.so/api/v1/voices" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": [ { "id": "EXAVITQu4vr4xnSDxMaL", "name": "Sarah", "previewUrl": "https://storage.googleapis.com/eleven-public-prod/premade/voices/EXAVITQu4vr4xnSDxMaL.mp3", "isCustom": false } ] } ``` ### List content option catalogs `GET /options` Enumerates the option catalogs used by createVideo and createSeries: sources, styles, niches, languages, durations, models (with credit costs), captionThemes, music moods, background videos and example subreddits. Call without kind to list the available kinds; pass kind to get that catalog's items. - Scopes: `catalog:read` - Credits: none - CLI: `faceless options` - MCP tool: `faceless_list_options` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `sources` \| `styles` \| `niches` \| `languages` \| `durations` \| `models` \| `captionThemes` \| `music` \| `backgrounds` \| `subreddits` | no | Which catalog to return; omit to list available kinds | Example: ```bash curl -s "https://faceless.so/api/v1/options" \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"kind":"models"}' ``` Response: ```json { "success": true, "data": { "kind": "models", "items": [ { "value": "storyboard", "name": "Faceless Storyboard", "credits": 20 } ] } } ``` ## assets ### Register a media asset by URL `POST /assets` Registers an already-hosted media file (video, image or audio URL) as a team asset usable in projects. This endpoint takes a URL, not raw bytes; host the file anywhere public (or use the dashboard for direct uploads). - Scopes: `assets:write` - Credits: none - Supports `Idempotency-Key` header - CLI: `faceless assets create` - MCP tool: `faceless_create_asset` Body fields: | Name | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | Public URL of the media file | | `name` | string | no | Display name for the asset | | `fileType` | string | no | MIME type hint, e.g. video/mp4 | Example: ```bash curl -s -X POST "https://faceless.so/api/v1/assets" \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/broll.mp4","name":"Intro b-roll"}' ``` Response: ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5ef01", "url": "https://example.com/broll.mp4", "name": "Intro b-roll" } } ``` ## analytics ### Cross-platform posting analytics `GET /analytics` Returns aggregated performance for the team's published posts (views, likes, comments and posting volume) across YouTube, TikTok, Instagram and Facebook, over a trailing window. - Scopes: `analytics:read` - Credits: none - CLI: `faceless analytics` - MCP tool: `faceless_get_analytics` Query parameters: | Name | Type | Required | Description | | --- | --- | --- | --- | | `platform` | `youtube` \| `tiktok` \| `instagram` \| `facebook` | no | Filter to one platform | | `authId` | string | no | Filter to one connected account | | `range` | integer | no | Trailing window in days (default 30) | Example: ```bash curl -s "https://faceless.so/api/v1/analytics" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Response: ```json { "success": true, "data": { "totals": { "posts": 42, "views": 128530, "likes": 9210 }, "platforms": { "youtube": { "posts": 20, "views": 88000 } } } } ``` --- # Getting started The Faceless API turns a narration script into a finished faceless video (AI visuals, TTS voiceover, captions) and publishes it to connected YouTube, TikTok, Instagram, X, Facebook, LinkedIn and Threads accounts. This guide takes you from an empty terminal to a published video. Base URL: `https://faceless.so/api/v1`. Every response uses one envelope: `{ "success": true, "data": ..., "pagination"? }` on success, `{ "success": false, "error": { "type", "message" } }` on failure. ## 1. Create an API key Create a key in the dashboard, in your team settings at https://faceless.so/team. Pick the scopes the key needs; for this guide use `catalog:read`, `videos:read`, `videos:write` and `posts:write`. Keys look like `fl_live_...` and the full key is shown once at creation, so store it somewhere safe (an environment variable or secret manager). ```bash export FACELESS_API_KEY=fl_live_... ``` ## 2. Verify auth with /me `GET /me` works with any valid key and returns your team, credit balance, plan and the scopes granted to the key. ```bash curl -s https://faceless.so/api/v1/me \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Or with the CLI: ```bash npm install -g faceless-cli faceless login # or rely on the FACELESS_API_KEY env var faceless whoami --json ``` ## 3. Pick a voice Every video needs a TTS voice for the narration. List the catalog and note a voice `id`: ```bash curl -s https://faceless.so/api/v1/voices \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` ```bash faceless voices --json ``` Other catalogs (visual styles, niches, models with their credit costs) live at `GET /options?kind=...`; see `faceless options`. ## 4. Create a video `POST /videos` takes a finished script and starts generation. Credits are charged now, at creation: 20 for `storyboard`, 50 for `motion_lite`, 100 for `motion_pro`. The call returns immediately with the video id. ```bash curl -s -X POST https://faceless.so/api/v1/videos \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"script": "Did you know the ocean has lakes and rivers of its own? ...", "voiceId": "EXAVITQu4vr4xnSDxMaL", "model": "storyboard"}' ``` ```bash faceless videos create --script "Did you know..." --voice-id EXAVITQu4vr4xnSDxMaL --model storyboard --wait --json ``` ## 5. Poll until generation completes Poll `GET /videos/{id}/status` every 10 to 30 seconds until `status` is `completed` (or `failed`; check `errorMessages`). The CLI's `--wait` flag in step 4 does this for you. ```bash curl -s https://faceless.so/api/v1/videos//status \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` ## 6. Render the MP4 Rendering is a separate, free step. `POST /videos/{id}/render` returns a `renderId`; poll `GET /renders/{renderId}` every 5 to 15 seconds until `status` is `done`, which includes the final MP4 `url`. ```bash curl -s -X POST https://faceless.so/api/v1/videos//render \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"codec": "h264"}' curl -s https://faceless.so/api/v1/renders/ \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` ```bash faceless videos render --wait --json ``` ## 7. Publish Publishing is free. Post the rendered video to a connected platform now: ```bash curl -s -X POST https://faceless.so/api/v1/posts \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"videoId": "", "platform": "youtube", "title": "The ocean has rivers underwater"}' ``` ```bash faceless posts publish --video-id --platform youtube --title "The ocean has rivers underwater" --json ``` Connecting accounts (OAuth) happens in the web app, not over the API; `GET /accounts` lists what is connected. For future-dated posting see [publishing and scheduling](/developers/docs/publishing-and-scheduling). ## Next steps - [Authentication and scopes](/developers/docs/authentication) - [Videos](/developers/docs/videos) for the full lifecycle in detail - [Series](/developers/docs/series) to automate a whole channel - [CLI](/developers/docs/cli) and [MCP](/developers/docs/mcp) - Full endpoint reference: [/developers/reference](/developers/reference) or https://faceless.so/llms.txt for agents --- # Authentication Every request to `https://faceless.so/api/v1` is authenticated with an API key. Keys are created in the dashboard, in your team settings at https://faceless.so/team, and look like `fl_live_...`. ## Sending the key Two headers are accepted; use whichever fits your HTTP client: ```bash curl -s https://faceless.so/api/v1/me \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` ```bash curl -s https://faceless.so/api/v1/me \ -H "X-API-Key: $FACELESS_API_KEY" ``` Keys are accepted in headers only. Never put a key in a URL or query string; URLs end up in logs. A missing or invalid key returns `401` with error type `unauthorized`. ## Scopes Each key carries a set of scopes chosen at creation. Calling an endpoint the key is not scoped for returns `403` with error type `forbidden_scope`. - `credits:read`: credit balance and ledger (GET /credits) - `videos:read`: list and read videos, poll generation status and renders (GET /videos, GET /videos/{id}, GET /videos/{id}/status, GET /renders/{id}) - `videos:write`: create, caption, update, render and delete videos (POST /videos, POST /videos/captions, PATCH /videos/{id}, POST /videos/{id}/render, DELETE /videos/{id}) - `series:read`: list and read series and their episodes (GET /series, GET /series/{id}, GET /series/{id}/episodes) - `series:write`: create, update and delete series, force the next episode (POST /series, PATCH /series/{id}, DELETE /series/{id}, POST /series/{id}/generate) - `posts:read`: the posting calendar (GET /calendar) - `posts:write`: publish, schedule and cancel posts (POST /posts, POST /posts/schedule, DELETE /posts/{id}) - `accounts:read`: list connected social accounts (GET /accounts) - `assets:read` / `assets:write`: read and register media assets (POST /assets) - `analytics:read`: cross-platform performance (GET /analytics) - `catalog:read`: voices and option catalogs (GET /voices, GET /options) `GET /me` works with any valid key regardless of scopes; use it to check which scopes a key has (`data.auth.scopes`). ## Legacy team keys Teams created before scoped keys have a legacy team API key (a UUID). It still works in the same headers and is treated as having every scope. Prefer creating a scoped `fl_live_` key: it can be revoked on its own and limits the blast radius of a leak. `GET /me` reports `data.auth.legacy: true` when a legacy key is used. ## Managing keys Everything happens in team settings at https://faceless.so/team: - Create a key with a name and the scopes it needs - The full key is shown once, at creation; only a prefix (like `fl_live_a1b2c3d4`) is visible afterwards - Revoke a key at any time; revoked keys stop working immediately ## Security notes - Store keys in environment variables or a secret manager, never in source control - Give each integration its own key with the minimum scopes it needs, so one leak has a small blast radius and revocation does not break everything else - If a key leaks, revoke it in the dashboard and create a new one - Keys act on behalf of your team; treat them like passwords --- # Videos A video moves through four stages: create (credits charged), generate (poll status), render (free, produces the MP4) and publish (free). This guide walks the whole lifecycle; [publishing and scheduling](/developers/docs/publishing-and-scheduling) covers the last stage in depth. ## Models and cost Pick the model in `POST /videos`. Credits are charged at creation, not at render: | Model | Credits | What you get | | --- | --- | --- | | `storyboard` | 20 | Static AI scenes with motion effects | | `motion_lite` | 50 | Animated transitions between scenes | | `motion_pro` | 100 | Cinematic AI motion video | `GET /options?kind=models` returns the same catalog with costs, so an agent can decide programmatically. ## 1. Create (POST /videos, scope videos:write) Send a finished narration script and a voice id (from `GET /voices`). Optional fields: `style`, `language`, `name`, `enableBackgroundMusic`, `masterStyle`, `globalNegativePrompt`. ```bash curl -s -X POST https://faceless.so/api/v1/videos \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"script": "Did you know the ocean has lakes and rivers of its own? ...", "voiceId": "EXAVITQu4vr4xnSDxMaL", "model": "storyboard"}' ``` Response (`201`): ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e801", "name": "Did you kn...", "model": "storyboard", "status": "processing", "creditsUsed": 20, "statusUrl": "/api/v1/videos/665f1b2a9c31a2b3c4d5e801/status" } } ``` Common errors: `402 insufficient_credits` (top up before retrying), `403 usage_limit_reached` (plan project limit, upgrade to create more), `429 rate_limited` (10 creations per 60s per key; wait for `Retry-After`). ## 2. Poll generation (GET /videos/{id}/status, scope videos:read) Generation is asynchronous and typically takes a few minutes. Poll every 10 to 30 seconds until `status` is `completed` or `failed`: ```bash curl -s https://faceless.so/api/v1/videos/665f1b2a9c31a2b3c4d5e801/status \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5e801", "status": "processing", "percentCompleted": 62, "readyForEditing": false, "errorMessages": [], "renderedVideoUrl": null } } ``` On `failed`, read `errorMessages` for the reason. With the CLI, `faceless videos create --wait` and `faceless videos status ` handle the polling. ## 3. Render (POST /videos/{id}/render, scope videos:write) Rendering assembles the final MP4 in the cloud. It is free (credits were charged at creation) and requires generation to be `completed` first. ```bash curl -s -X POST https://faceless.so/api/v1/videos/665f1b2a9c31a2b3c4d5e801/render \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"codec": "h264"}' ``` The `202` response contains a `renderId`. Poll `GET /renders/{renderId}` (scope videos:read) every 5 to 15 seconds; renders typically finish in under two minutes: ```bash curl -s https://faceless.so/api/v1/renders/abcd1234efgh \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` ```json { "success": true, "data": { "renderId": "abcd1234efgh", "status": "done", "overallProgress": 1, "url": "https://exports.faceless.so/renders/665f1b2a9c31a2b3c4d5e801/1722333444555.mp4" } } ``` `status` is `in-progress` (with `overallProgress` 0 to 1), `done` (with the MP4 `url`) or `error`. The MP4 `url` is a direct download; it also appears as `renderedVideoUrl` on the video. ## 4. Publish Publish immediately with `POST /posts`, or set per-platform metadata with `PATCH /videos/{id}` and schedule with `POST /posts/schedule`. Both are free; see [publishing and scheduling](/developers/docs/publishing-and-scheduling). ## Captioning existing footage (POST /videos/captions) To caption a video or audio file you already have (no AI visuals, no credit cost), send its public URL. Processing is asynchronous; poll `GET /videos/{id}/status` like any other video. ```bash curl -s -X POST https://faceless.so/api/v1/videos/captions \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"videoUrl": "https://example.com/clip.mp4", "language": "English"}' ``` Provide `videoUrl` or `audioUrl` (an audio file becomes a captioned video). Rate limit: 10 per 300s. ## Managing videos - `GET /videos` (scope videos:read): paginated list, newest first, with `renderedVideoUrl` when available. `?archived=true` for archived only. - `GET /videos/{id}`: one video with script, voice, model, status and post metadata. - `PATCH /videos/{id}` (scope videos:write): rename, or set per-platform post metadata before scheduling. - `DELETE /videos/{id}` (scope videos:write): permanent, no credit refund. A `404 not_found` on any of these means the id does not exist or belongs to another team. --- # Series A series is a set-and-forget channel: it generates a new faceless video on a schedule and can auto-post each episode to connected accounts. Creating a series is free; each generated episode charges the credit cost of its model (20 to 100 credits), exactly like a single video. ## Create a series (POST /series, scope series:write) Required: `name`, `source`, and a `niche` or `customPrompt`. Everything else is optional and gets product defaults. Enumerate valid values first: `GET /options?kind=sources`, `?kind=niches`, `?kind=styles`, `?kind=languages`, `?kind=backgrounds`, `?kind=music`, `?kind=captionThemes`, and `GET /voices`. ```bash curl -s -X POST https://faceless.so/api/v1/series \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "name": "Deep sea facts", "source": "Facts & stories", "niche": "Ocean facts", "voice": "EXAVITQu4vr4xnSDxMaL", "duration": "60", "autoPostTime": "18:00", "timezone": "America/New_York" }' ``` The first episode starts generating right after creation. Useful optional fields: - `destination` and `destinationAccounts`: auto-post platform and connected account ids (from `GET /accounts`) - `postingDays`: e.g. `["Monday", "Wednesday", "Friday"]`; omit for every day - `brollModel`: `storyboard`, `motion_lite` or `motion_pro`, which sets the per-episode credit cost - `subreddit`: when `source` is `"Reddit post"` - `backgroundVideo` or `useRandomBackgroundVideo`: gameplay/footage behind the captions - `captionStyle`, `showEmojis`, `enableBackgroundMusic`, `backgroundMusicMood`, `hashtags`, `tone`, `youtubePrivacyStatus` Rate limit: 5 series creations per 300s per key. ## Track episodes (GET /series/{id}/episodes, scope series:read) Episodes are regular videos plus scheduling state. Newest first, paginated: ```bash curl -s "https://faceless.so/api/v1/series/665f1b2a9c31a2b3c4d5e901/episodes?limit=10" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` ```json { "success": true, "data": [{ "id": "665f1b2a9c31a2b3c4d5eb01", "projectId": "665f1b2a9c31a2b3c4d5ea01", "status": "posted", "scheduledTime": "2026-07-30T18:00:00.000Z" }], "pagination": { "page": 1, "limit": 10, "total": 1 } } ``` Poll an episode's generation with `GET /videos/{projectId}/status` like any other video. ## Force the next episode now (POST /series/{id}/generate, scope series:write) Generates the next episode immediately instead of waiting for the schedule. Charges the series' per-episode cost. Returns the new `projectId` and its `statusUrl`. Rate limit: 5 per 300s. ```bash curl -s -X POST https://faceless.so/api/v1/series/665f1b2a9c31a2b3c4d5e901/generate \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ## Update, pause, delete - `GET /series` and `GET /series/{id}` (scope series:read): list and inspect configuration. - `PATCH /series/{id}` (scope series:write): change any subset of fields; only the fields you send change. Pass `{"paused": true}` to pause automatic generation, `false` to resume. - `DELETE /series/{id}` (scope series:write): stops all future generation; already-generated videos are kept. ```bash curl -s -X PATCH https://faceless.so/api/v1/series/665f1b2a9c31a2b3c4d5e901 \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"autoPostTime": "09:00", "paused": false}' ``` ## Budgeting An everyday series on `storyboard` spends 20 credits per episode: a daily series is about 600 credits per month, a Monday/Wednesday/Friday series about 260. Check the balance with `GET /credits` before creating aggressive schedules; an episode that cannot be paid for fails with `insufficient_credits`. See [credits and limits](/developers/docs/credits-and-limits). --- # Publishing and scheduling A rendered video can go out two ways: `POST /posts` publishes to one platform right now; `POST /posts/schedule` queues one or more platforms for a future time. Both are free. Supported platforms: `youtube`, `tiktok`, `instagram`, `x`, `facebook`, `linkedin`, `threads`. Publishing requires a connected account for the platform. Connecting accounts (OAuth) happens in the dashboard, not over the API. ## Who can you post as? (GET /accounts, scope accounts:read) ```bash curl -s https://faceless.so/api/v1/accounts \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` ```json { "success": true, "data": { "youtube": [{ "id": "665f1b2a9c31a2b3c4d5ee01", "channelName": "Deep Sea Facts" }], "tiktok": [], "instagram": [] } } ``` Use an account's `id` as `authId` in `POST /posts` when the team has several accounts on one platform, and as `destinationAccounts` in series. ## Publish now (POST /posts, scope posts:write) The video must be rendered first (`renderedVideoUrl` present; see [videos](/developers/docs/videos)). One platform per call: ```bash curl -s -X POST https://faceless.so/api/v1/posts \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"videoId": "665f1b2a9c31a2b3c4d5e801", "platform": "youtube", "title": "The ocean has rivers underwater"}' ``` ```json { "success": true, "data": { "platform": "youtube", "videoId": "665f1b2a9c31a2b3c4d5e801", "postUrl": "https://youtube.com/shorts/dQw4w9WgXcQ" } } ``` `title` and `description` are optional; when omitted the video's stored post metadata is used. `privacyStatus` (`public`, `unlisted`, `private`) applies to YouTube only. Rate limit: 10 per 300s. ## Schedule for later: metadata first, then schedule Scheduling posts to multiple platforms at once, at a future time, and renders the video first if needed. It validates that every platform in the list already has its post metadata stored on the video, so the flow is always two steps. ### 1. Set per-platform metadata (PATCH /videos/{id}, scope videos:write) ```bash curl -s -X PATCH https://faceless.so/api/v1/videos/665f1b2a9c31a2b3c4d5e801 \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "youtubePost": {"title": "The ocean has rivers underwater", "privacyStatus": "public"}, "tiktokPost": {"title": "underwater rivers are real #ocean"} }' ``` The blocks per platform: `youtubePost` (`title` max 100, optional `description`, `privacyStatus`), `tiktokPost` (`title`), `instagramPost` (`caption`), `xPost` (`text` max 280), `facebookPost` (`description`), `linkedinPost` (`description`), `threadsPost` (`text` max 500). ### 2. Schedule (POST /posts/schedule, scope posts:write) ```bash curl -s -X POST https://faceless.so/api/v1/posts/schedule \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"videoId": "665f1b2a9c31a2b3c4d5e801", "platforms": ["youtube", "tiktok"], "scheduledTime": "2026-08-01T18:00:00Z"}' ``` `scheduledTime` is ISO 8601 and must be in the future. A platform without its metadata block set is rejected with `invalid_input`; set it in step 1 and retry. Rate limit: 20 per 300s. ## Cancel (DELETE /posts/{id}, scope posts:write) The id is the video id you scheduled. Cancels the pending scheduled posting; posts that already went out are unaffected. Cancelling something no longer scheduled returns `409 conflict`. ```bash curl -s -X DELETE https://faceless.so/api/v1/posts/665f1b2a9c31a2b3c4d5e801 \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` ## See what is queued (GET /calendar, scope posts:read) Scheduled, posted and failed posts across all platforms in a date range. Check it before scheduling more: ```bash curl -s "https://faceless.so/api/v1/calendar?startDate=2026-08-01&endDate=2026-08-31&platform=youtube" \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` Optional filters: `platform` and `status` (`scheduled`, `posted`, `failed`). ## With the CLI ```bash faceless posts publish --video-id --platform youtube --json faceless videos update --youtube-title "..." --tiktok-title "..." faceless posts schedule --video-id --platforms youtube,tiktok --scheduled-time "2026-08-01T18:00:00Z" --json faceless posts cancel faceless calendar --start-date 2026-08-01 --end-date 2026-08-31 --json ``` --- # Assets Assets are media files (video, image or audio) in your team's library, usable in projects and the editor. Over the API, assets are registered by URL: you host the file anywhere public and hand Faceless the link. Raw byte uploads are a dashboard feature, not an API one; upload files in the web app when you do not have a public URL. ## Register an asset (POST /assets, scope assets:write) ```bash curl -s -X POST https://faceless.so/api/v1/assets \ -H "Authorization: Bearer $FACELESS_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"url": "https://example.com/broll.mp4", "name": "Intro b-roll", "fileType": "video/mp4"}' ``` Response (`201`): ```json { "success": true, "data": { "id": "665f1b2a9c31a2b3c4d5ef01", "url": "https://example.com/broll.mp4", "name": "Intro b-roll" } } ``` Fields: - `url` (required): a public URL to the media file. The platform fetches from it, so it must be reachable without auth and should stay live for as long as projects reference it. - `name` (optional): display name in the library; defaults from the URL. - `fileType` (optional): MIME type hint so the file lands in the right bucket, e.g. `video/mp4`, `image/png`, `audio/mpeg`. Inferred from the URL when omitted. Registering the same URL twice creates two library entries; use an `Idempotency-Key` on retries to avoid duplicates (see [errors and rate limits](/developers/docs/errors-and-rate-limits)). ## With the CLI ```bash faceless assets create --url https://example.com/broll.mp4 --name "Intro b-roll" --json ``` ## Common errors - `invalid_input` (400): the `url` is missing or not a valid URL. - `forbidden_scope` (403): the key lacks `assets:write`. No credits are charged for registering assets. --- # Credits and limits Credits are the currency for video generation. Everything else in the API (rendering, publishing, scheduling, captioning existing footage, registering assets, all reads) is free. A subscription is not required to create videos: any team with enough credits can generate, whether the credits came from a plan's monthly grant or a one-time top-up. Credit packs are sold at https://faceless.so/billing?tab=credits ($10 for 100 credits, with bonus credits on larger packs). Automated series remain a plan feature: creating a series requires an active subscription, and each episode then spends credits like any other video. ## What costs credits Credits are charged once, at video creation, based on the model: | Operation | Cost | | --------------------------------------------------------------- | --------------------------------- | | `POST /videos` with `model: "storyboard"` | 20 credits | | `POST /videos` with `model: "motion_lite"` | 50 credits | | `POST /videos` with `model: "motion_pro"` | 100 credits | | Each series episode (automatic or `POST /series/{id}/generate`) | the series' model cost, 20 to 100 | Rendering the MP4 and publishing it are free, and re-rendering does not charge again: once a video is created, you can render and publish it without spending more. Deleting a video does not refund its credits. `GET /options?kind=models` returns the model catalog with live costs, so agents can pick a model by budget. ## Check the balance (GET /credits, scope credits:read) ```bash curl -s https://faceless.so/api/v1/credits \ -H "Authorization: Bearer $FACELESS_API_KEY" ``` ```json { "success": true, "data": { "balance": 340, "history": [ { "id": "665f1b2a9c31a2b3c4d5e701", "credits": -50, "type": "spending", "description": "Faceless video (motion_lite)", "createdAt": "2026-07-30T10:00:00.000Z" } ] }, "pagination": { "page": 1, "limit": 20, "total": 1 } } ``` The ledger lists purchases, subscription grants, spends and refunds. `GET /me` also returns the balance if you only need the number. A well-behaved agent checks the balance before a batch of creations and reports spend after. ## Running out: 402 insufficient_credits A creation the team cannot pay for fails with `402` and error type `insufficient_credits`. Nothing is charged and nothing is created. Do not retry until the balance changes; top up or upgrade in the dashboard at https://faceless.so/billing. ## Plan limits: 403 usage_limit_reached Paid plans cap how many video projects a team can have. Creating a video past the cap fails with `403` and error type `usage_limit_reached` even when the credit balance is fine. This is not a scope problem (that is `forbidden_scope`); the fix is upgrading the plan or deleting old projects, not changing the key. Teams without a subscription have no project cap; they are metered purely by credits, so they only ever see `402 insufficient_credits`. ## Rate limits Limits are per API key. Exceeding one returns `429 rate_limited` with a `Retry-After` header in seconds. | Operation | Limit | | ---------------------------- | ----------- | | Default (all operations) | 300 per 60s | | `POST /videos` | 10 per 60s | | `POST /videos/captions` | 10 per 300s | | `POST /videos/{id}/render` | 10 per 300s | | `POST /series` | 5 per 300s | | `POST /series/{id}/generate` | 5 per 300s | | `POST /posts` | 10 per 300s | | `POST /posts/schedule` | 20 per 300s | Polling `GET /videos/{id}/status` every 10 to 30 seconds and `GET /renders/{id}` every 5 to 15 seconds sits comfortably inside the default limit. See [errors and rate limits](/developers/docs/errors-and-rate-limits) for how to react to `429`s and how `Idempotency-Key` makes retries safe without double-charging. --- # Errors and rate limits Every error response uses one envelope, whatever the endpoint: ```json { "success": false, "error": { "type": "rate_limited", "message": "Too many requests, slow down" } } ``` Branch on `error.type`, not on the message text; messages can change, types will not. ## Error types - `invalid_input` (400, and 405 for a wrong HTTP method): the request body, query or method is wrong. Fix the request against the [API reference](/developers/reference); the message says which field failed. Also returned by `POST /posts/schedule` when a platform's post metadata is missing on the video. - `unauthorized` (401): missing, malformed or revoked API key. Check the `Authorization: Bearer fl_live_...` (or `X-API-Key`) header; with the CLI, re-run `faceless login` or fix `FACELESS_API_KEY`. - `insufficient_credits` (402): the team cannot pay for a credit-costing operation (`POST /videos`, series episodes). Nothing was charged or created. Top up at https://faceless.so/billing; do not retry until the balance changes. - `forbidden_scope` (403): the key is valid but lacks a required scope. Create a key with the needed scopes in team settings at https://faceless.so/team; `GET /me` shows the scopes a key has. - `usage_limit_reached` (403): the team's plan caps its number of video projects and the cap is hit. Upgrading the plan (or deleting old projects) fixes it; changing keys does not. - `not_found` (404): the resource id does not exist or belongs to another team. Also returned for malformed object ids. - `conflict` (409): the request clashes with current state, most commonly cancelling a post that is no longer scheduled, or reusing an `Idempotency-Key` with a different request body. Do not blind-retry; re-read the resource state first. - `rate_limited` (429): too many requests for this key. Wait for the number of seconds in the `Retry-After` header, then retry. Default limit 300 per 60s, with tighter per-operation limits on creation and publishing endpoints (see [credits and limits](/developers/docs/credits-and-limits)). - `internal_error` (500 and other 5xx): something failed on our side. Retry with exponential backoff; if it persists, contact support with the request path and time. ## HTTP status to type mapping - 400: `invalid_input` - 401: `unauthorized` - 402: `insufficient_credits` - 403: `forbidden_scope` or `usage_limit_reached` - 404: `not_found` - 405: `invalid_input` (method not allowed on this endpoint) - 409: `conflict` - 429: `rate_limited` - 5xx: `internal_error` ## Idempotency and the 409 conflict Mutating operations marked idempotent in the API reference (`POST /videos`, `POST /videos/captions`, `POST /videos/{id}/render`, `POST /series`, `POST /series/{id}/generate`, `POST /posts`, `POST /posts/schedule`, `POST /assets`) accept an `Idempotency-Key` header (any unique string, for example a UUID). Successful responses are stored for 24 hours; retrying with the same key and the same body replays the stored response instead of re-running the operation, which makes timeouts safe to retry and prevents double credit charges. The CLI generates a key automatically for every mutating command; MCP tools accept an `idempotencyKey` argument with the same semantics. Reusing a key with a different body is treated as a bug in your client and returns `409`: ```json { "success": false, "error": { "type": "conflict", "message": "This Idempotency-Key was already used with a different request body" } } ``` Generate a fresh key per logical operation, and reuse it only for retries of exactly that operation. ## Async failures are not HTTP errors A video whose generation fails reports it through polling, not through an error status: `GET /videos/{id}/status` returns `200` with `status: "failed"` and the reasons in `errorMessages`. Likewise a render ends as `status: "error"` on `GET /renders/{id}`. Poll to terminal states and branch on the data, not just on HTTP codes. --- # CLI The `faceless` CLI (npm package `faceless-cli`) wraps the whole API in commands that work for humans and for scripts and agents. ## Install ```bash npm install -g faceless-cli faceless --version ``` The command is `faceless` (alias `fcl`). Without installing, use `npx -y faceless-cli `. ## Authenticate Two options: - `faceless login`: paste an API key from your team settings at https://faceless.so/team; it is stored in `~/.faceless/config.json` for future commands - `FACELESS_API_KEY` environment variable: nothing to store; ideal for CI and agents Verify with `faceless whoami`. Global flags on every command: `--json` (raw API envelope output), `--api-key` (override the key for one call) and `--api-url` (point at another deployment, also via `FACELESS_API_URL`). ## Commands - `faceless whoami`: introspect the API key (GET /me) - `faceless credits`: credit balance and ledger (GET /credits) - `faceless videos create`: create a video from a script (POST /videos) - `faceless videos captions`: caption an existing video or audio URL (POST /videos/captions) - `faceless videos list` / `faceless videos get `: browse videos (GET /videos, GET /videos/{id}) - `faceless videos status `: generation progress (GET /videos/{id}/status) - `faceless videos update `: rename or set per-platform post metadata (PATCH /videos/{id}) - `faceless videos render `: render the MP4 (POST /videos/{id}/render) - `faceless videos delete `: delete a video (DELETE /videos/{id}) - `faceless renders get `: render progress and final MP4 url (GET /renders/{id}) - `faceless series create` / `list` / `get ` / `update ` / `delete `: manage series - `faceless series generate `: force the next episode now (POST /series/{id}/generate) - `faceless series episodes `: list a series' episodes (GET /series/{id}/episodes) - `faceless posts publish`: publish a rendered video now (POST /posts) - `faceless posts schedule`: schedule to one or more platforms (POST /posts/schedule) - `faceless posts cancel `: cancel a scheduled post (DELETE /posts/{id}) - `faceless calendar`: posting calendar (GET /calendar) - `faceless accounts`: connected social accounts (GET /accounts) - `faceless voices`: TTS voice catalog (GET /voices) - `faceless options`: option catalogs, e.g. `faceless options --kind models` (GET /options) - `faceless assets create`: register a media asset by URL (POST /assets) - `faceless analytics`: cross-platform performance (GET /analytics) - `faceless mcp`: run the local stdio MCP server (see [MCP](/developers/docs/mcp)) ## Scripting and agents: --json Always pass `--json` when running from a script or agent, and parse the `success` field. The output is the raw API envelope, so the [API reference](/developers/reference) documents every field. ```bash faceless videos create --script "Did you know..." --voice-id EXAVITQu4vr4xnSDxMaL \ --model storyboard --wait --json faceless posts publish --video-id --platform youtube --json ``` The CLI sends an `Idempotency-Key` automatically on every mutating command, so a timed-out command is safe to re-run without double credit charges. ## --wait and --timeout Asynchronous operations (`videos create`, `videos captions`, `videos render`, `series generate`) accept `--wait`: the CLI polls status at the recommended cadence and exits when the operation reaches a terminal state, printing the final resource. Cap the polling with `--timeout `. Without `--wait`, poll yourself with `faceless videos status --json` or `faceless renders get --json`. ```bash faceless videos create --script "..." --voice-id --wait --timeout 900 --json faceless videos render --wait --json ``` ## Exit codes - `0`: success - `2`: authentication failed (`unauthorized`); re-run `faceless login` or fix `FACELESS_API_KEY` - `3`: `insufficient_credits`; top up at https://faceless.so/billing - `4`: `rate_limited`; wait and retry (respect Retry-After) - `1`: any other error --- # MCP Faceless ships a Model Context Protocol (MCP) server so agent runtimes (Claude Code, Claude Desktop, Cursor and any other MCP client) can call the API as tools. There are two ways to connect: the remote server over streamable HTTP, or a local stdio server. Both need an API key from your team settings at https://faceless.so/team. ## Remote server (nothing to install) The remote server lives at `https://faceless.so/api/v1/mcp` and speaks the streamable HTTP transport. Authenticate with the same `Authorization` header as the REST API. Claude Code: ```bash claude mcp add --transport http faceless https://faceless.so/api/v1/mcp \ --header "Authorization: Bearer fl_live_..." ``` Any MCP client that supports streamable HTTP works the same way: point it at the URL and send `Authorization: Bearer fl_live_...` with every request. ## Local server (stdio) For clients that only speak stdio, run the server locally. Two equivalent ways: `npx -y faceless-mcp`, or `faceless mcp` if the [CLI](/developers/docs/cli) is installed. Both read the key from the `FACELESS_API_KEY` environment variable. Claude Code: ```bash claude mcp add faceless --env FACELESS_API_KEY=fl_live_... -- npx -y faceless-mcp ``` Claude Desktop (Settings > Developer > Edit Config, `claude_desktop_config.json`): ```json { "mcpServers": { "faceless": { "command": "npx", "args": ["-y", "faceless-mcp"], "env": { "FACELESS_API_KEY": "fl_live_..." } } } } ``` Cursor (`.cursor/mcp.json` in your project, or the global `~/.cursor/mcp.json`): ```json { "mcpServers": { "faceless": { "command": "npx", "args": ["-y", "faceless-mcp"], "env": { "FACELESS_API_KEY": "fl_live_..." } } } } ``` Use the remote server when you want zero install and always-current tools; use the local one when your client only speaks stdio or your network policy prefers a locally spawned process. ## Tools All 27 API operations are exposed as tools named `faceless_*`, and each respects the key's scopes (a missing scope surfaces as a `forbidden_scope` error). The core ones an agent reaches for: - `faceless_get_me`, `faceless_get_credits`: identity, scopes and budget - `faceless_list_voices`, `faceless_list_options`: catalogs to choose voices, styles and models - `faceless_create_video`, `faceless_get_video_status`, `faceless_render_video`, `faceless_get_render`: the create, poll, render, poll lifecycle - `faceless_create_caption_video`: caption existing footage - `faceless_update_video`, `faceless_publish_post`, `faceless_schedule_post`, `faceless_cancel_post`, `faceless_get_calendar`: publishing and scheduling - `faceless_create_series`, `faceless_list_series`, `faceless_get_series`, `faceless_update_series`, `faceless_delete_series`, `faceless_generate_series_episode`, `faceless_list_series_episodes`: automated series - `faceless_list_videos`, `faceless_get_video`, `faceless_delete_video`, `faceless_list_accounts`, `faceless_create_asset`, `faceless_get_analytics`: the rest of the surface Mutating tools accept an optional `idempotencyKey` argument with the same semantics as the REST `Idempotency-Key` header: retries with the same key and arguments replay the stored result instead of re-running (and re-charging). Pass one when a tool call might be retried. Tool results use the same envelope and error types as the REST API; see [errors and rate limits](/developers/docs/errors-and-rate-limits). Rate limits also apply per key, so a busy agent should back off on `rate_limited`. ## Related - Prefer commands over tools? The [CLI](/developers/docs/cli) covers the same surface. - A packaged Claude Skill with ready-made workflows: `npx skills add Side-Products/faceless-skill` (https://github.com/Side-Products/faceless-skill). - The machine-readable surface: https://faceless.so/llms.txt and https://faceless.so/api/v1/openapi.json.