Scripts
/channels/{id}/scripts
POST /channels/{id}/scriptsCreate a new canvas script (chat-first) within a channel. Scripts can be created with research URLs, topics, angles, and templates. Creating a script charges credits based on the target length (same as Canvas creation).
Request
JSON body with script configuration. Credits for the target `length` are charged immediately on create.
Request Body Fields
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | Script title. Max 255 characters. |
| topic | string | Yes | Concept or brief. Min 10, max 5000 characters. |
| length | integer | Yes | Target word count. Min 200, max 20000. Determines credits charged on create. |
| angle | string | No | Angle or hook. Max 1000 characters. |
| language | string | No | Spoken/written language. Defaults to the channel language setting. Max 50 characters. |
| template_id | integer | No | Channel template ID. Must belong to this channel. |
| voice_id | integer | No | Channel voice profile ID. Must belong to this channel. Takes precedence over `voice` when both are sent. |
| voice | string | No | Raw voice label when not using `voice_id`. Max 100 characters. |
| prompt | string | No | Custom opening chat message for the script thread. Max 8000 characters. Auto-generated when omitted. |
| research_urls | array | No | Up to 10 source URLs to ingest into the script thread research context. Each URL max 500 characters. |
| research_texts | array | No | Up to 10 text snippets for research context. Each snippet max 5000 characters. |
{
"title": "Why Apple's AI Changes Everything",
"topic": "Break down the key changes in Apple Intelligence and what they mean.",
"length": 1200,
"angle": "Explain the hidden implication most reviews miss",
"voice_id": 55,
"language": "English",
"template_id": 22,
"research_urls": [
"https://example.com/source-1"
],
"research_texts": [
"Key bullet points"
],
"prompt": "Custom starting prompt"
}
Response (201)
{
"success": true,
"data": {
"script_id": 789,
"script_number": 12,
"thread_id": 456,
"status": "active",
"canvas_url": "https://subscribr.ai/chat/my-channel-thread/canvas/789"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Request success |
| data.script_id | integer | New script ID |
| data.script_number | integer | Sequential script number within the channel |
| data.thread_id | integer | Chat thread ID backing the canvas |
| data.status | string | Script lifecycle status (`active` on create) |
| data.canvas_url | string|null | Browser URL to open the script canvas; null if the thread slug is not available yet |
Error Responses
| Status | Error code | Description |
|---|---|---|
| 402 | (credits) | Insufficient credits for target length |
| 403 | subscription paused | Team billing paused |
| 403 | You do not have access to this channel | No channel access |
| 422 | Invalid voice_id for this channel | voice_id not on channel |
| 422 | Invalid template_id for this channel | template_id not on channel |
/channels/{id}/scripts
GET /channels/{id}/scriptsList scripts for a specific channel with optional filtering.
Request
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. Min 1, max 100. Default 20. |
| page | integer | Page number. Min 1. Default 1. |
Response (200)
{
"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.ai/chat/my-channel-thread/canvas/789",
"has_outline": true,
"has_script": false
}
],
"pagination": {
"current_page": 1,
"per_page": 20,
"total": 1,
"last_page": 1
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Request success |
| data[].id | integer | Script ID |
| data[].channel_id | integer | Owning channel ID |
| data[].script_number | integer | Sequential script number within the channel |
| data[].title | string | Script title |
| data[].topic | string | Topic or brief |
| data[].angle | string|null | Angle or hook |
| data[].length | integer | Target word count |
| data[].format | string|null | Content format label (e.g. Tutorial, Documentary) |
| data[].language | string | Language |
| data[].voice | string|null | Resolved voice label |
| data[].template_id | integer|null | Template ID when set |
| data[].status | string | Script lifecycle status: `idea` or `active` |
| data[].production_status | string | Production pipeline stage (e.g. `planning`, `scripting`, `recording`, `published`) |
| data[].thread_id | integer|null | Chat thread ID |
| data[].canvas_url | string|null | Browser canvas URL when available |
| data[].has_outline | boolean | True when non-empty outline content exists |
| data[].has_script | boolean | True when non-empty script body exists |
| pagination.current_page | integer | Current page index |
| pagination.per_page | integer | Page size used for this response |
| pagination.total | integer | Total scripts matching filters |
| pagination.last_page | integer | Last available page number |
Error Responses
| Status | Error code | Description |
|---|---|---|
| 403 | You do not have access to this channel | No channel access |
| 404 | (not found) | Channel not found |
/scripts/{id}
GET /scripts/{id}Get detailed information about a specific script.
Request
No request body required.
Parameters
| Name | Type | Description |
|---|---|---|
| id | integer | Script ID (path parameter) |
Response (200)
{
"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.ai/chat/my-channel-thread/canvas/789",
"has_outline": true,
"has_script": false,
"notes": "Notes for collaborators",
"hook": "Open with a surprising fact",
"thumbnail": "Apple AI Shock"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Request success |
| data.id | integer | Script ID |
| data.channel_id | integer | Owning channel ID |
| data.script_number | integer | Sequential script number within the channel |
| data.title | string | Script title |
| data.topic | string | Topic or brief |
| data.angle | string|null | Angle or hook |
| data.length | integer | Target word count |
| data.format | string|null | Content format label |
| data.language | string | Language |
| data.voice | string|null | Resolved voice label |
| data.template_id | integer|null | Template ID when set |
| data.status | string | Script lifecycle status: `idea` or `active` |
| data.production_status | string | Production pipeline stage |
| data.thread_id | integer|null | Chat thread ID |
| data.canvas_url | string|null | Browser canvas URL when available |
| data.has_outline | boolean | True when non-empty outline content exists |
| data.has_script | boolean | True when non-empty script body exists |
| data.notes | string|null | Collaborator notes |
| data.hook | string|null | Hook line or opening angle text |
| data.thumbnail | string|null | Thumbnail concept or title text |
Error Responses
| Status | Error code | Description |
|---|---|---|
| 403 | You do not have access to this script | No channel access |
| 404 | (not found) | Script not found |
/scripts/{id}/content
GET /scripts/{id}/contentFetch raw outline and script markdown for a canvas script. Use Get Script for metadata only.
Request
No request body required.
Parameters
| Name | Type | Description |
|---|---|---|
| id | integer | Script ID (path parameter) |
Response (200)
{
"success": true,
"data": {
"script_id": 789,
"outline": "# Outline\\n...",
"script": "# Script\\n...",
"outline_version": 3,
"content_version": 7
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Request success |
| data.script_id | integer | Script ID |
| data.outline | string | Outline markdown. Empty string when no outline has been written yet. |
| data.script | string | Script body markdown. Empty string when no script has been written yet. |
| data.outline_version | integer | Monotonic outline revision counter |
| data.content_version | integer | Monotonic script body revision counter |
Error Responses
| Status | Error code | Description |
|---|---|---|
| 403 | You do not have access to this script | No channel access |
| 404 | (not found) | Script not found |
/scripts/{id}/outline/generate
POST /scripts/{id}/outline/generateStart outline generation for a canvas script. Does not charge script-generation credits by itself. Returns a UUID `run_id` to poll via Poll Generation Status. Script-generation credits are charged when you call Generate Script (once per script).
Request
No request body required.
Response (202)
{
"success": true,
"data": {
"run_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "queued"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Request success |
| data.run_id | string (UUID) | Run ID — pass as `run_id` query param to Poll Generation Status |
| data.status | string | Initial status (`queued`) |
Error Responses
| Status | Error code | Description |
|---|---|---|
| 403 | You do not have access to this script | No channel access |
/scripts/{id}/script/generate
POST /scripts/{id}/script/generateStart full script generation for a canvas script. Requires an existing outline. Returns a UUID `run_id` for Poll Generation Status.
Request
No request body required.
Response (202)
{
"success": true,
"data": {
"run_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "queued"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Request success |
| data.run_id | string (UUID) | Run ID — pass as `run_id` query param to Poll Generation Status |
| data.status | string | Initial status (`queued`) |
Error Responses
| Status | Error code | Description |
|---|---|---|
| 409 | (message) | When no outline exists: `{ "error": "Outline must be generated before script generation.", "outline_required": true }` |
/scripts/{id}/script/humanize
POST /scripts/{id}/script/humanizeRewrite the script to sound more natural and human. Uses the channel voice profile when available. Counts toward the per-script regeneration limit.
Request
No request body required.
Response (202)
{
"success": true,
"data": {
"run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "queued"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Request success |
| data.run_id | string (UUID) | Run ID — pass as `run_id` query param to Poll Generation Status |
| data.status | string | Initial status (`queued`) |
- Humanizing is free (no credits charged) but counts toward the per-script regeneration limit alongside Generate Script and Start Fresh.
- Returns 422 with error=regeneration_limit_reached when the limit is exhausted.
- Poll the result using the Poll Generation Status endpoint with the returned run_id.
Error Responses
| Status | Error code | Description |
|---|---|---|
| 409 | no_script_content | Script has no canvas body content to humanize yet |
| 422 | regeneration_limit_reached | Per-script regeneration limit exhausted |
/scripts/{id}/generate/poll
GET /scripts/{id}/generate/pollPoll canvas outline/script/humanize jobs using the UUID `run_id` from Generate Outline, Generate Script, or Humanize Script. This is separate from Agent Mode polling (integer run IDs). Returns outline/script markdown only when `status` is `completed`.
Request
Query parameters for polling
Parameters
| Name | Type | Description |
|---|---|---|
| id | integer | Script ID (path parameter) |
| run_id | string (UUID) | Run UUID from Generate Outline, Generate Script, or Humanize Script |
Response (200)
{
"success": true,
"data": {
"run_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "completed",
"content_type": "script",
"outline": "# Outline\n\n## Section 1\n...",
"script": "# Script\n\nOpening hook...\n"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Request success |
| data.run_id | string (UUID) | Run ID from the poll query |
| data.status | string | Status: `queued`, `running`, `completed`, `failed`, or `canceled` |
| data.content_type | string|null | Content type for the run (`outline` or `script`); null while queued before the run record exists |
| data.outline | string|null | Outline markdown (only when `status` is `completed`) |
| data.script | string|null | Script markdown (only when `status` is `completed`) |
Error Responses
| Status | Error code | Description |
|---|---|---|
| 400 | Missing run_id | run_id query param required |
| 403 | You do not have access to this script | No channel access |
/scripts/{id}/agent/generate
POST /scripts/{id}/agent/generateTrigger an Agent Mode run that autonomously researches, outlines, and writes a complete script. The agent uses your channel's voice profile, templates, and research context to produce a production-ready draft. This is an asynchronous operation — use Agent Mode — Poll Status to track progress.
Request
Optional JSON body. Defaults to included Kimi K2.6 (kimi_k26) with no BYOK.
Request Body Fields
| Field | Type | Required | Description |
|---|---|---|---|
| model | string | No | kimi_k26, minimax_m27, glm_51 (included). claude_* and gpt* require BYOK. |
| research | string | No | auto, on, off, or deep_research (+3 credits when deep_research runs). Default auto. |
| visual_cues | string | No | off or on. Default off. |
{
"model": "minimax_m27",
"research": "deep_research",
"visual_cues": "on"
}
Response (202)
{
"run_id": 42,
"status": "queued",
"research": "auto",
"visual_cues": "off",
"model": "kimi_k26",
"resolved_model": "kimi-k2.6",
"poll_url": "https://subscribr.ai/api/v1/scripts/789/agent/runs/42",
"cancel_url": "https://subscribr.ai/api/v1/scripts/789/agent/runs/42/cancel",
"message": "Script agent run queued. Poll the poll_url for status updates."
}
Response Fields
| Field | Type | Description |
|---|---|---|
| run_id | integer | Agent run ID — use in Agent Mode — Poll Status and Cancel Run paths |
| status | string | Initial status (`queued`) |
| research | string | Resolved research mode for this run (`auto`, `on`, `off`, or `deep_research` when enabled) |
| visual_cues | string | Resolved visual cues mode (`on` or `off`) |
| model | string | Requested Agent Mode model key |
| resolved_model | string | Provider-specific model id the runtime will call |
| poll_url | string | Absolute URL for Agent Mode — Poll Status |
| cancel_url | string | Absolute URL for Agent Mode — Cancel Run |
| message | string | Human-readable queue confirmation |
Error Responses
| Status | Error code | Description |
|---|---|---|
| 402 | insufficient_credits | Team does not have enough credits for an agent run |
| 403 | subscription_paused | Team subscription is paused |
| 409 | run_already_active | A run is already in progress for this script |
| 422 | prerequisites_not_met | Script is missing required context (channel, topic, or length) |
| 422 | script_agent_byok_required | The selected model requires a BYOK key that is not connected. Includes required_provider, model, resolved_model, settings_url, and reason_code. |
| 429 | rate_limit_exceeded | Too many agent runs — wait before triggering another |
| 503 | script_agent_factory_access_unavailable | The included-model service is temporarily unavailable; no credits were charged |
| 503 | agent_run_queue_failed | The run could not be queued; any credits charged were refunded |
/scripts/{id}/agent/runs/{run_id}
GET /scripts/{id}/agent/runs/{run_id}Check the progress of an Agent Mode run. Returns the current status, active step, and elapsed time. When the run completes, the response includes a link to the script. Poll this endpoint at a reasonable interval (e.g. every 5–10 seconds).
Request
No request body required.
Parameters
| Name | Type | Description |
|---|---|---|
| id | integer | Script ID (path parameter) |
| run_id | integer | Agent run ID returned from Agent Mode — Generate |
Response (200)
{
"success": true,
"data": {
"run_id": 42,
"status": "running",
"current_step": "generating_script",
"current_step_label": "Writing script",
"elapsed_seconds": 94,
"executor": "claude_sdk",
"auth_path": "byok"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Request success |
| data.run_id | integer | Agent run identifier (integer, from Agent Mode — Generate) |
| data.status | string | Run status: `queued`, `pending`, `running`, `completed`, `completed_with_issues`, `failed`, `cancelled` |
| data.current_step | string|null | Machine-readable step key (e.g. `generating_script`, `deep_research`, `done`); null when not started |
| data.current_step_label | string|null | Human-readable label from the Agent Mode step catalog |
| data.elapsed_seconds | integer|null | Seconds since the run started (null while queued) |
| data.executor | string|null | Executor used once running (e.g. `claude_sdk`); omitted while queued |
| data.auth_path | string|null | Credential path (e.g. `byok`, `factory`); omitted while queued |
| data.script_url | string|null | API URL to GET the script (only when `status` is `completed`) |
| data.error | string | User-facing error (when `status` is `failed` or `cancelled`) |
| data.error_detail | string | Raw diagnostic error for logs/support |
/scripts/{id}/agent/runs/{run_id}/cancel
POST /scripts/{id}/agent/runs/{run_id}/cancelCancel a queued or running Agent Mode run. A cancelled run never completes, so its credits are refunded automatically.
Request
No request body required.
Parameters
| Name | Type | Description |
|---|---|---|
| id | integer | Script ID (path parameter) |
| run_id | integer | Agent run ID returned from Agent Mode — Generate |
Response (200)
{
"cancelled": true,
"run_id": 42,
"status": "cancelled",
"refunded": true,
"message": "Run cancelled. Credits were refunded."
}
Response Fields
| Field | Type | Description |
|---|---|---|
| cancelled | boolean | Whether the run was cancelled |
| run_id | integer | Agent run identifier |
| status | string | Run status after cancellation (cancelled) |
| refunded | boolean | True when the per-run Agent Mode fee was refunded (full-mode runs). False if refund could not be completed. |
| message | string | Human-readable cancellation summary |
Error Responses
| Status | Error code | Description |
|---|---|---|
| 404 | run_not_found | No such run for this script |
| 409 | run_not_active | The run is already completed, failed, or cancelled |
/scripts/{id}/export
GET /scripts/{id}/exportExport the current canvas script body. Requires non-empty script content.
Request
Query parameters for export
Parameters
| Name | Type | Description |
|---|---|---|
| id | integer | Script ID (path parameter) |
| format | string | Output format: `markdown` (raw markdown), `html` (converted HTML), or `text` (plain text with markdown markers stripped) |
| include_headings | boolean | When false, strips markdown heading markers before export. Default true. |
Response (200)
{
"success": true,
"data": {
"script_id": 789,
"title": "Why Apple's AI Changes Everything",
"format": "markdown",
"content": "# Script\\n...",
"include_headings": true
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Request success |
| data.script_id | integer | Script ID |
| data.title | string | Script title at export time |
| data.format | string | Format that was applied (`text`, `html`, or `markdown`) |
| data.content | string | Full exported body in the requested format |
| data.include_headings | boolean | Whether markdown headings were preserved in the export |
Error Responses
| Status | Error code | Description |
|---|---|---|
| 400 | Script export failed | Returned when the script has no canvas content to export |