{
  "quickstart": {
    "curl": "curl --fail-with-body --request POST \\\n  --url \"$XPILOT_API_BASE_URL/v1/video-agents/conversations\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --header 'Content-Type: application/json' \\\n  --header 'Idempotency-Key: lesson-request-001' \\\n  --data '{\"prompt\":\"Create a short English lesson about solar energy.\",\"require_outline_confirmation\":true}'",
    "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/video-agents/conversations\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": \"lesson-request-001\",\n  },\n  body: JSON.stringify({\n  \"prompt\": \"Create a short English lesson about solar energy.\",\n  \"require_outline_confirmation\": true\n}),\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
    "python": "import os, json, requests\n\nresponse = requests.request(\n    \"POST\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/video-agents/conversations\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": \"lesson-request-001\",\n    },\n    json=json.loads(\"{\\\"prompt\\\":\\\"Create a short English lesson about solar energy.\\\",\\\"require_outline_confirmation\\\":true}\"),\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
  },
  "version": "1.1.0",
  "apiBaseUrl": "https://api.x-pilot.ai",
  "mcpUrl": "https://mcp.x-pilot.ai/mcp",
  "groups": [
    {
      "id": "video",
      "title": {
        "en": "Video Agent",
        "zh": "视频 Agent"
      }
    },
    {
      "id": "renders",
      "title": {
        "en": "Renders",
        "zh": "渲染"
      }
    },
    {
      "id": "brand-kits",
      "title": {
        "en": "Brand kits",
        "zh": "品牌套件"
      }
    },
    {
      "id": "account",
      "title": {
        "en": "Account & capacity",
        "zh": "账号与容量"
      }
    },
    {
      "id": "assets",
      "title": {
        "en": "Assets",
        "zh": "素材"
      }
    },
    {
      "id": "audio",
      "title": {
        "en": "Voices & audio",
        "zh": "声音与音频"
      }
    },
    {
      "id": "avatars",
      "title": {
        "en": "Avatars",
        "zh": "数字人"
      }
    }
  ],
  "endpoints": [
    {
      "id": "list-conversations",
      "group": "video",
      "method": "GET",
      "path": "/v1/video-agents/conversations",
      "scope": "video_agents:read",
      "title": {
        "en": "List conversations",
        "zh": "列出会话"
      },
      "description": {
        "en": "Owned conversations, newest first, with cumulative cost_credits. Conversation status follows its latest message. status: queued, running, stopping, paused, completed, failed or stopped. next_action is set only when paused: confirm_outline (confirm or revise the outline) or reply (answer the assistant with a prompt). Times are Unix seconds.",
        "zh": "本人会话，按创建时间倒序，含累计 cost_credits。会话状态跟随最新一条消息。status：queued、running、stopping、paused、completed、failed、stopped。next_action 仅在 paused 时有值：confirm_outline（确认或修改大纲）或 reply（用 prompt 回答助手）。时间为 Unix 秒。"
      },
      "fields": [
        {
          "name": "limit",
          "type": "integer",
          "location": "query",
          "description": {
            "en": "Results per page, from 1 to 100.",
            "zh": "每页返回数量，范围 1–100。"
          },
          "default": 20,
          "min": 1,
          "max": 100
        },
        {
          "name": "offset",
          "type": "integer",
          "location": "query",
          "description": {
            "en": "Number of items to skip.",
            "zh": "跳过的条目数量。"
          },
          "default": 0,
          "min": 0
        }
      ],
      "sample": {
        "data": {
          "items": [
            {
              "conversation_id": "<conversation_id>",
              "status": "paused",
              "next_action": "confirm_outline",
              "created_at": 1791849600,
              "updated_at": 1791849600,
              "cost_credits": 12.5,
              "preview_video_url": null
            }
          ],
          "total": 1,
          "limit": 20,
          "offset": 0
        }
      },
      "status": 200,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/video-agents/conversations?limit=20&offset=0\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/video-agents/conversations?limit=20&offset=0\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/video-agents/conversations?limit=20&offset=0\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "items": [
              {
                "conversation_id": "<conversation_id>",
                "status": "paused",
                "next_action": "confirm_outline",
                "created_at": 1791849600,
                "updated_at": 1791849600,
                "cost_credits": 12.5,
                "preview_video_url": null
              }
            ],
            "total": 1,
            "limit": 20,
            "offset": 0
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "create-conversation",
      "group": "video",
      "method": "POST",
      "path": "/v1/video-agents/conversations",
      "scope": "video_agents:write",
      "title": {
        "en": "Create conversation",
        "zh": "创建会话"
      },
      "description": {
        "en": "Creates a conversation and starts the first paid message immediately. Check credits and capacity first. By default the video is generated straight through; set require_outline_confirmation=true to review the outline first. Each files item may use an owned asset_id or a third-party public URL, with its own files_role. Billing matches the app: the platform reserve is held, released at the end, then actual usage is charged (a short balance is charged to 0, never negative). cost_credits is the held reserve until the message settles. Poll list messages at least 5 seconds apart; there is no streaming.",
        "zh": "创建会话并立即开始首条付费消息。先查询额度与容量。默认直接生成；需要先审阅大纲时设 require_outline_confirmation=true。files 每项可传本人素材 asset_id 或第三方公网 URL，并在该项设置 files_role。计费与平台一致：先预扣，结束时退还预扣，再按实际用量扣费（余额不足时扣到 0，不会为负）。cost_credits 在结算前为预扣额度。至少间隔 5 秒轮询消息列表，本期无流式。"
      },
      "fields": [
        {
          "name": "prompt",
          "type": "string",
          "location": "body",
          "description": {
            "en": "The brief: audience and goal first, then a # title and ## sections with concrete facts, numbers, formulas, dates or ordered steps. Each ## section becomes roughly one scene; keep one video to about 12 scenes. Do not name visual components. Up to 20,000 characters.",
            "zh": "视频 brief：先写受众与目标，再用 # 标题和若干 ## 小节写具体事实、数字、公式、日期或有序步骤。每个 ## 小节大约对应一个场景，单条视频约 12 个场景以内。不要指定视觉组件。最多 20,000 字符。"
          },
          "required": true,
          "maxLength": 20000,
          "example": "Create a short English lesson about solar energy."
        },
        {
          "name": "require_outline_confirmation",
          "type": "boolean",
          "location": "body",
          "description": {
            "en": "Default false: generate straight through with no pause. true: the first message pauses with status=paused, next_action=confirm_outline and an outline summary; then send outline_action=confirm (no prompt needed) or outline_action=revise with a prompt.",
            "zh": "默认 false：直接生成，不暂停。true：首条消息会暂停，status=paused、next_action=confirm_outline，并返回大纲摘要；之后发送 outline_action=confirm（无需 prompt）或带 prompt 的 outline_action=revise。"
          },
          "default": false,
          "example": false
        },
        {
          "name": "video_config",
          "type": "object",
          "location": "body",
          "description": {
            "en": "Video settings applied at creation; they cannot be changed by a follow-up message. Unknown fields or unsupported values return 400 invalid_parameter with param and allowed.",
            "zh": "创建时生效的视频设置，后续消息不能修改。未知字段或不支持的值返回 400 invalid_parameter，并给出 param 与 allowed。"
          },
          "example": {
            "target_language": "en",
            "video_duration": "short",
            "video_style": "clean_lecture"
          },
          "children": [
            {
              "name": "target_language",
              "type": "string",
              "description": {
                "en": "Narration and subtitle language: ISO 639-1 code or BCP-47 tag (en, zh, zh-CN, ja, de, fr, es…). Default en. Changing it later means a new video.",
                "zh": "旁白与字幕语言：ISO 639-1 代码或 BCP-47 标签（en、zh、zh-CN、ja、de、fr、es…）。默认 en。之后修改语言等于重做视频。"
              },
              "default": "en"
            },
            {
              "name": "video_duration",
              "type": "string",
              "description": {
                "en": "short: 1–3 min. deep: 3–10 min. long: 10–20 min.",
                "zh": "short：1–3 分钟。deep：3–10 分钟。long：10–20 分钟。"
              },
              "enum": [
                "short",
                "deep",
                "long"
              ],
              "default": "short"
            },
            {
              "name": "video_style",
              "type": "string",
              "description": {
                "en": "auto lets the agent choose. clean_lecture: courses. science_explainer: science processes. professional_training: workplace training. product_showcase: product demos. cinematic_story: history and narrative.",
                "zh": "auto 由 Agent 自动选择。clean_lecture：课程讲解。science_explainer：科学原理。professional_training：企业培训。product_showcase：产品展示。cinematic_story：历史与叙事。"
              },
              "enum": [
                "auto",
                "clean_lecture",
                "science_explainer",
                "professional_training",
                "product_showcase",
                "cinematic_story"
              ],
              "default": "auto"
            },
            {
              "name": "voice_id",
              "type": "string",
              "description": {
                "en": "Narration voice from list voices. Default picks a voice for target_language.",
                "zh": "旁白声音，取自查找声音接口。默认按 target_language 选择。"
              }
            },
            {
              "name": "voice_version",
              "type": "integer",
              "description": {
                "en": "Version of voice_id. Requires voice_id.",
                "zh": "voice_id 的版本，需同时传 voice_id。"
              },
              "enum": [
                1,
                2
              ],
              "default": 1
            },
            {
              "name": "locale",
              "type": "string",
              "description": {
                "en": "Optional native locale of voice_id, for example en-US. Requires voice_id.",
                "zh": "可选，voice_id 的原生地区，例如 en-US。需同时传 voice_id。"
              }
            },
            {
              "name": "avatar_id",
              "type": "string | integer",
              "description": {
                "en": "Presenter from list avatars. Must have selectable=true, otherwise 409 avatar_not_ready.",
                "zh": "出镜数字人，取自列出数字人接口。必须 selectable=true，否则返回 409 avatar_not_ready。"
              }
            },
            {
              "name": "brand_kit_id",
              "type": "string | integer",
              "description": {
                "en": "Brand kit for logo, colors and tone.",
                "zh": "品牌套件，提供 Logo、配色与语气。"
              }
            },
            {
              "name": "enable_initial_ai_video",
              "type": "boolean",
              "description": {
                "en": "Generate an AI opening clip when the plan allows it.",
                "zh": "套餐允许时生成 AI 开场片段。"
              },
              "default": false
            }
          ]
        },
        {
          "name": "files",
          "type": "array",
          "location": "body",
          "description": {
            "en": "Up to 5 attachments. Each item uses either an owned asset_id or a caller-supplied public http(s) URL. Third-party URLs are downloaded and archived to the caller's storage before use. Item fields: asset_id or url (required), name (optional, ≤255), type (optional: document, image, video; inferred when omitted), and files_role (optional: primary or reference).",
            "zh": "最多 5 个附件。每项使用本人素材 asset_id，或调用方提供的第三方公网 http(s) URL；第三方内容会先下载并归档到当前账号存储，再用于生成。每项字段：asset_id 或 url（二选一，必填）、name（可选，≤255）、type（可选：document、image、video，不传则自动判断）、files_role（可选：primary 或 reference）。"
          },
          "example": [
            {
              "url": "https://cdn.example.com/course.pdf",
              "files_role": "primary"
            }
          ],
          "children": [
            {
              "name": "asset_id",
              "type": "string | integer",
              "description": {
                "en": "id returned by POST /v1/assets or list assets.",
                "zh": "POST /v1/assets 或列出素材返回的 id。"
              }
            },
            {
              "name": "url",
              "type": "string",
              "description": {
                "en": "Caller-supplied public http(s) URL. Third-party content is archived before use.",
                "zh": "调用方提供的第三方公网 http(s) URL，使用前会归档到当前账号存储。"
              }
            },
            {
              "name": "name",
              "type": "string",
              "description": {
                "en": "Optional display name, up to 255 characters; inferred when omitted.",
                "zh": "可选显示名，最多 255 字符；不传则自动推断。"
              }
            },
            {
              "name": "type",
              "type": "string",
              "description": {
                "en": "Optional; inferred from the file.",
                "zh": "可选，不传按文件自动判断。"
              },
              "enum": [
                "document",
                "image",
                "video"
              ]
            },
            {
              "name": "files_role",
              "type": "string",
              "description": {
                "en": "How this file shapes the video. primary: use it as main content and visual source. reference: use it only as background facts. Omit to let the agent decide.",
                "zh": "此文件在视频中的作用。primary：作为大纲与画面的主要内容；reference：仅作为事实参考。不传则由 Agent 判断。"
              },
              "enum": [
                "primary",
                "reference"
              ]
            }
          ]
        },
        {
          "name": "callback_url",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Optional webhook for this message only. Public HTTPS (port 443) up to 2,048 characters; private, loopback and http addresses return 400 invalid_webhook_url / unsafe_webhook_url. When the message reaches completed, paused, failed or stopped, X Pilot POSTs a video-agent.<status> event signed with the workspace callback secret (Developer console → Webhooks). Deliveries retry up to 6 times and are listed in the console.",
            "zh": "可选，仅本条消息生效的回调地址。须为公网 HTTPS（443 端口），最长 2,048 字符；内网、回环和 http 地址返回 400 invalid_webhook_url / unsafe_webhook_url。消息到达 completed、paused、failed 或 stopped 时，X Pilot 以 POST 发送 video-agent.<status> 事件，用工作区回调签名密钥签名（开发者控制台 → Webhooks）。失败最多重试 6 次，投递记录可在控制台查看。"
          },
          "maxLength": 2048,
          "example": "https://your-domain.com/xpilot/callback"
        },
        {
          "name": "callback_id",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Optional reference echoed back as data.callback_id in the callback, such as your order ID. Letters, digits, _ . : - up to 128 characters. Requires callback_url.",
            "zh": "可选，原样放在回调 data.callback_id 中返回的业务标识（如订单号）。字母、数字、_ . : -，最长 128 字符。需同时传 callback_url。"
          },
          "maxLength": 128,
          "pattern": "^[\\w.:-]{1,128}$",
          "example": "order-1024"
        }
      ],
      "sample": {
        "data": {
          "conversation_id": "<conversation_id>",
          "message_id": "<message_id>",
          "status": "running",
          "cost_credits": 12
        }
      },
      "status": 202,
      "idempotent": true,
      "paid": true,
      "examples": {
        "curl": "curl --fail-with-body --request POST \\\n  --url \"$XPILOT_API_BASE_URL/v1/video-agents/conversations\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --header 'Content-Type: application/json' \\\n  --header 'Idempotency-Key: lesson-request-001' \\\n  --data '{\"prompt\":\"Create a short English lesson about solar energy.\",\"require_outline_confirmation\":false,\"video_config\":{\"target_language\":\"en\",\"video_duration\":\"short\",\"video_style\":\"clean_lecture\"},\"files\":[{\"url\":\"https://cdn.example.com/course.pdf\",\"files_role\":\"primary\"}],\"callback_url\":\"https://your-domain.com/xpilot/callback\",\"callback_id\":\"order-1024\"}'",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/video-agents/conversations\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": \"lesson-request-001\",\n  },\n  body: JSON.stringify({\n  \"prompt\": \"Create a short English lesson about solar energy.\",\n  \"require_outline_confirmation\": false,\n  \"video_config\": {\n    \"target_language\": \"en\",\n    \"video_duration\": \"short\",\n    \"video_style\": \"clean_lecture\"\n  },\n  \"files\": [\n    {\n      \"url\": \"https://cdn.example.com/course.pdf\",\n      \"files_role\": \"primary\"\n    }\n  ],\n  \"callback_url\": \"https://your-domain.com/xpilot/callback\",\n  \"callback_id\": \"order-1024\"\n}),\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"POST\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/video-agents/conversations\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": \"lesson-request-001\",\n    },\n    json=json.loads(\"{\\\"prompt\\\":\\\"Create a short English lesson about solar energy.\\\",\\\"require_outline_confirmation\\\":false,\\\"video_config\\\":{\\\"target_language\\\":\\\"en\\\",\\\"video_duration\\\":\\\"short\\\",\\\"video_style\\\":\\\"clean_lecture\\\"},\\\"files\\\":[{\\\"url\\\":\\\"https://cdn.example.com/course.pdf\\\",\\\"files_role\\\":\\\"primary\\\"}],\\\"callback_url\\\":\\\"https://your-domain.com/xpilot/callback\\\",\\\"callback_id\\\":\\\"order-1024\\\"}\"),\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "202": {
          "data": {
            "conversation_id": "<conversation_id>",
            "message_id": "<message_id>",
            "status": "running",
            "cost_credits": 12
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "402": {
          "error": {
            "code": "insufficient_credits",
            "message": "The balance cannot cover the platform reserve, or the credential monthly budget is used up."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "409": {
          "error": {
            "code": "idempotency_conflict",
            "message": "The same idempotency key must use the exact same input. A conversation may already be busy."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "get-conversation",
      "group": "video",
      "method": "GET",
      "path": "/v1/video-agents/conversations/{conversation_id}",
      "scope": "video_agents:read",
      "title": {
        "en": "Get conversation",
        "zh": "获取会话"
      },
      "description": {
        "en": "Conversation summary. status: queued, running, stopping, paused, completed, failed or stopped. next_action is set only when paused: confirm_outline (confirm or revise the outline) or reply (answer the assistant with a prompt). Use list messages for the outline and per-message detail.",
        "zh": "会话摘要。status：queued、running、stopping、paused、completed、failed、stopped。next_action 仅在 paused 时有值：confirm_outline（确认或修改大纲）或 reply（用 prompt 回答助手）。大纲与每条消息详情请用消息列表。"
      },
      "fields": [
        {
          "name": "conversation_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "An owned video agent conversation ID.",
            "zh": "当前账号的视频 Agent 会话 ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "conversation_id": "<conversation_id>",
          "status": "paused",
          "next_action": "confirm_outline",
          "created_at": 1791849600,
          "updated_at": 1791849600,
          "cost_credits": 12.5,
          "preview_video_url": null
        }
      },
      "status": 200,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/video-agents/conversations/%3Cconversation_id%3E\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/video-agents/conversations/%3Cconversation_id%3E\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/video-agents/conversations/%3Cconversation_id%3E\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "conversation_id": "<conversation_id>",
            "status": "paused",
            "next_action": "confirm_outline",
            "created_at": 1791849600,
            "updated_at": 1791849600,
            "cost_credits": 12.5,
            "preview_video_url": null
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "send-message",
      "group": "video",
      "method": "POST",
      "path": "/v1/video-agents/conversations/{conversation_id}",
      "scope": "video_agents:write",
      "title": {
        "en": "Send message",
        "zh": "发送消息"
      },
      "description": {
        "en": "Send a follow-up turn to a conversation that is not running (409 conversation_busy otherwise). Paid. When next_action=confirm_outline, send outline_action=confirm (no prompt) or outline_action=revise with a prompt. When next_action=reply, answer with a prompt. Otherwise send an edit instruction. video_config and require_outline_confirmation are create-only (400 unsupported_parameter).",
        "zh": "向未在运行的会话发送后续消息（运行中返回 409 conversation_busy）。付费。next_action=confirm_outline 时发送 outline_action=confirm（无需 prompt）或带 prompt 的 outline_action=revise；next_action=reply 时用 prompt 回答；其他情况发送修改要求。video_config 与 require_outline_confirmation 只能在创建时设置（否则 400 unsupported_parameter）。"
      },
      "fields": [
        {
          "name": "conversation_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "An owned video agent conversation ID.",
            "zh": "当前账号的视频 Agent 会话 ID。"
          },
          "required": true
        },
        {
          "name": "prompt",
          "type": "string",
          "location": "body",
          "description": {
            "en": "The edit instruction or the answer to the assistant. Required unless outline_action=confirm; required with outline_action=revise to describe the change. Up to 20,000 characters.",
            "zh": "修改要求或对助手提问的回答。除 outline_action=confirm 外必填；outline_action=revise 时用它描述修改意见。最多 20,000 字符。"
          },
          "maxLength": 20000,
          "example": "Make scene 2 shorter and add a worked example."
        },
        {
          "name": "outline_action",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Only when next_action=confirm_outline. confirm continues with the outline; revise rewrites it using prompt.",
            "zh": "仅在 next_action=confirm_outline 时使用。confirm 按大纲继续；revise 按 prompt 重写大纲。"
          },
          "enum": [
            "confirm",
            "revise"
          ]
        },
        {
          "name": "files",
          "type": "array",
          "location": "body",
          "description": {
            "en": "Up to 5 attachments. Each item uses either an owned asset_id or a caller-supplied public http(s) URL. Third-party URLs are downloaded and archived to the caller's storage before use. Item fields: asset_id or url (required), name (optional, ≤255), type (optional: document, image, video; inferred when omitted), and files_role (optional: primary or reference).",
            "zh": "最多 5 个附件。每项使用本人素材 asset_id，或调用方提供的第三方公网 http(s) URL；第三方内容会先下载并归档到当前账号存储，再用于生成。每项字段：asset_id 或 url（二选一，必填）、name（可选，≤255）、type（可选：document、image、video，不传则自动判断）、files_role（可选：primary 或 reference）。"
          },
          "example": [
            {
              "url": "https://cdn.example.com/course.pdf",
              "files_role": "primary"
            }
          ],
          "children": [
            {
              "name": "asset_id",
              "type": "string | integer",
              "description": {
                "en": "id returned by POST /v1/assets or list assets.",
                "zh": "POST /v1/assets 或列出素材返回的 id。"
              }
            },
            {
              "name": "url",
              "type": "string",
              "description": {
                "en": "Caller-supplied public http(s) URL. Third-party content is archived before use.",
                "zh": "调用方提供的第三方公网 http(s) URL，使用前会归档到当前账号存储。"
              }
            },
            {
              "name": "name",
              "type": "string",
              "description": {
                "en": "Optional display name, up to 255 characters; inferred when omitted.",
                "zh": "可选显示名，最多 255 字符；不传则自动推断。"
              }
            },
            {
              "name": "type",
              "type": "string",
              "description": {
                "en": "Optional; inferred from the file.",
                "zh": "可选，不传按文件自动判断。"
              },
              "enum": [
                "document",
                "image",
                "video"
              ]
            },
            {
              "name": "files_role",
              "type": "string",
              "description": {
                "en": "How this file shapes the video. primary: use it as main content and visual source. reference: use it only as background facts. Omit to let the agent decide.",
                "zh": "此文件在视频中的作用。primary：作为大纲与画面的主要内容；reference：仅作为事实参考。不传则由 Agent 判断。"
              },
              "enum": [
                "primary",
                "reference"
              ]
            }
          ]
        },
        {
          "name": "callback_url",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Optional webhook for this message only. Public HTTPS (port 443) up to 2,048 characters; private, loopback and http addresses return 400 invalid_webhook_url / unsafe_webhook_url. When the message reaches completed, paused, failed or stopped, X Pilot POSTs a video-agent.<status> event signed with the workspace callback secret (Developer console → Webhooks). Deliveries retry up to 6 times and are listed in the console.",
            "zh": "可选，仅本条消息生效的回调地址。须为公网 HTTPS（443 端口），最长 2,048 字符；内网、回环和 http 地址返回 400 invalid_webhook_url / unsafe_webhook_url。消息到达 completed、paused、failed 或 stopped 时，X Pilot 以 POST 发送 video-agent.<status> 事件，用工作区回调签名密钥签名（开发者控制台 → Webhooks）。失败最多重试 6 次，投递记录可在控制台查看。"
          },
          "maxLength": 2048,
          "example": "https://your-domain.com/xpilot/callback"
        },
        {
          "name": "callback_id",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Optional reference echoed back as data.callback_id in the callback, such as your order ID. Letters, digits, _ . : - up to 128 characters. Requires callback_url.",
            "zh": "可选，原样放在回调 data.callback_id 中返回的业务标识（如订单号）。字母、数字、_ . : -，最长 128 字符。需同时传 callback_url。"
          },
          "maxLength": 128,
          "pattern": "^[\\w.:-]{1,128}$",
          "example": "order-1024"
        }
      ],
      "sample": {
        "data": {
          "conversation_id": "<conversation_id>",
          "message_id": "<message_id>",
          "status": "running",
          "cost_credits": 12
        }
      },
      "status": 202,
      "idempotent": true,
      "paid": true,
      "examples": {
        "curl": "curl --fail-with-body --request POST \\\n  --url \"$XPILOT_API_BASE_URL/v1/video-agents/conversations/%3Cconversation_id%3E\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --header 'Content-Type: application/json' \\\n  --header 'Idempotency-Key: lesson-request-001' \\\n  --data '{\"prompt\":\"Make scene 2 shorter and add a worked example.\",\"files\":[{\"url\":\"https://cdn.example.com/course.pdf\",\"files_role\":\"primary\"}],\"callback_url\":\"https://your-domain.com/xpilot/callback\",\"callback_id\":\"order-1024\"}'",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/video-agents/conversations/%3Cconversation_id%3E\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": \"lesson-request-001\",\n  },\n  body: JSON.stringify({\n  \"prompt\": \"Make scene 2 shorter and add a worked example.\",\n  \"files\": [\n    {\n      \"url\": \"https://cdn.example.com/course.pdf\",\n      \"files_role\": \"primary\"\n    }\n  ],\n  \"callback_url\": \"https://your-domain.com/xpilot/callback\",\n  \"callback_id\": \"order-1024\"\n}),\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"POST\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/video-agents/conversations/%3Cconversation_id%3E\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": \"lesson-request-001\",\n    },\n    json=json.loads(\"{\\\"prompt\\\":\\\"Make scene 2 shorter and add a worked example.\\\",\\\"files\\\":[{\\\"url\\\":\\\"https://cdn.example.com/course.pdf\\\",\\\"files_role\\\":\\\"primary\\\"}],\\\"callback_url\\\":\\\"https://your-domain.com/xpilot/callback\\\",\\\"callback_id\\\":\\\"order-1024\\\"}\"),\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "202": {
          "data": {
            "conversation_id": "<conversation_id>",
            "message_id": "<message_id>",
            "status": "running",
            "cost_credits": 12
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "402": {
          "error": {
            "code": "insufficient_credits",
            "message": "The balance cannot cover the platform reserve, or the credential monthly budget is used up."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "409": {
          "error": {
            "code": "idempotency_conflict",
            "message": "The same idempotency key must use the exact same input. A conversation may already be busy."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "list-messages",
      "group": "video",
      "method": "GET",
      "path": "/v1/video-agents/conversations/{conversation_id}/messages",
      "scope": "video_agents:read",
      "title": {
        "en": "List messages",
        "zh": "列出消息"
      },
      "description": {
        "en": "Messages in order. Poll for status, next_action, outline, cost_credits and preview_video_url. status: queued, running, stopping, paused, completed, failed or stopped. next_action is set only when paused: confirm_outline (confirm or revise the outline) or reply (answer the assistant with a prompt). completed, paused, failed and stopped are terminal for that message. outline is present once an outline exists: title, total_scenes, estimated_duration_seconds, visual_style, confirmed and scenes [{index,title,intent,type,estimated_duration_seconds}].",
        "zh": "按顺序返回消息。轮询 status、next_action、outline、cost_credits 与 preview_video_url。status：queued、running、stopping、paused、completed、failed、stopped。next_action 仅在 paused 时有值：confirm_outline（确认或修改大纲）或 reply（用 prompt 回答助手）。completed、paused、failed、stopped 为该消息终态。生成大纲后返回 outline：title、total_scenes、estimated_duration_seconds、visual_style、confirmed 和 scenes [{index,title,intent,type,estimated_duration_seconds}]。"
      },
      "fields": [
        {
          "name": "conversation_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "An owned video agent conversation ID.",
            "zh": "当前账号的视频 Agent 会话 ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "items": [
            {
              "conversation_id": "<conversation_id>",
              "message_id": "<message_id>",
              "status": "paused",
              "next_action": "confirm_outline",
              "outline": {
                "title": "Solar energy in 3 minutes",
                "total_scenes": 2,
                "estimated_duration_seconds": 45,
                "visual_style": "clean_lecture",
                "confirmed": false,
                "scenes": [
                  {
                    "index": 1,
                    "title": "Why the sun matters",
                    "intent": "Hook",
                    "type": "normal",
                    "estimated_duration_seconds": 20
                  },
                  {
                    "index": 2,
                    "title": "How panels convert light",
                    "intent": "Explain the photovoltaic effect",
                    "type": "normal",
                    "estimated_duration_seconds": 25
                  }
                ]
              },
              "created_at": 1791849600,
              "error": null,
              "query": "Create a short lesson about solar energy.",
              "answer": "...",
              "message_files": [],
              "preview_video_url": null,
              "cost_credits": 12.5
            }
          ],
          "total": 1
        }
      },
      "status": 200,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/video-agents/conversations/%3Cconversation_id%3E/messages\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/video-agents/conversations/%3Cconversation_id%3E/messages\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/video-agents/conversations/%3Cconversation_id%3E/messages\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "items": [
              {
                "conversation_id": "<conversation_id>",
                "message_id": "<message_id>",
                "status": "paused",
                "next_action": "confirm_outline",
                "outline": {
                  "title": "Solar energy in 3 minutes",
                  "total_scenes": 2,
                  "estimated_duration_seconds": 45,
                  "visual_style": "clean_lecture",
                  "confirmed": false,
                  "scenes": [
                    {
                      "index": 1,
                      "title": "Why the sun matters",
                      "intent": "Hook",
                      "type": "normal",
                      "estimated_duration_seconds": 20
                    },
                    {
                      "index": 2,
                      "title": "How panels convert light",
                      "intent": "Explain the photovoltaic effect",
                      "type": "normal",
                      "estimated_duration_seconds": 25
                    }
                  ]
                },
                "created_at": 1791849600,
                "error": null,
                "query": "Create a short lesson about solar energy.",
                "answer": "...",
                "message_files": [],
                "preview_video_url": null,
                "cost_credits": 12.5
              }
            ],
            "total": 1
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "stop-conversation",
      "group": "video",
      "method": "POST",
      "path": "/v1/video-agents/conversations/{conversation_id}/stop",
      "scope": "video_agents:write",
      "title": {
        "en": "Stop conversation",
        "zh": "停止会话"
      },
      "description": {
        "en": "Stops the running message (409 not_running when nothing runs). Poll list messages until stopped; cost already incurred may still be charged.",
        "zh": "停止正在运行的消息（没有运行中的消息时返回 409 not_running）。继续轮询消息至 stopped；已产生的费用仍可能扣除。"
      },
      "fields": [
        {
          "name": "conversation_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "An owned video agent conversation ID.",
            "zh": "当前账号的视频 Agent 会话 ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "conversation_id": "<conversation_id>",
          "message_id": "<message_id>",
          "status": "stopping",
          "cost_credits": 12
        }
      },
      "status": 200,
      "destructive": true,
      "examples": {
        "curl": "curl --fail-with-body --request POST \\\n  --url \"$XPILOT_API_BASE_URL/v1/video-agents/conversations/%3Cconversation_id%3E/stop\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/video-agents/conversations/%3Cconversation_id%3E/stop\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"POST\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/video-agents/conversations/%3Cconversation_id%3E/stop\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "conversation_id": "<conversation_id>",
            "message_id": "<message_id>",
            "status": "stopping",
            "cost_credits": 12
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "409": {
          "error": {
            "code": "idempotency_conflict",
            "message": "The same idempotency key must use the exact same input. A conversation may already be busy."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "delete-conversation",
      "group": "video",
      "method": "DELETE",
      "path": "/v1/video-agents/conversations/{conversation_id}",
      "scope": "video_agents:write",
      "title": {
        "en": "Delete conversation",
        "zh": "删除会话"
      },
      "description": {
        "en": "Deletes an owned conversation and stops its running message.",
        "zh": "删除本人会话，并停止其运行中的消息。"
      },
      "fields": [
        {
          "name": "conversation_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "An owned video agent conversation ID.",
            "zh": "当前账号的视频 Agent 会话 ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "conversation_id": "<conversation_id>",
          "deleted": true
        }
      },
      "status": 200,
      "destructive": true,
      "examples": {
        "curl": "curl --fail-with-body --request DELETE \\\n  --url \"$XPILOT_API_BASE_URL/v1/video-agents/conversations/%3Cconversation_id%3E\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/video-agents/conversations/%3Cconversation_id%3E\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"DELETE\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/video-agents/conversations/%3Cconversation_id%3E\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "conversation_id": "<conversation_id>",
            "deleted": true
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "list-renders",
      "group": "renders",
      "method": "GET",
      "path": "/v1/video-agents/conversations/{conversation_id}/renders",
      "scope": "video_agents:read",
      "title": {
        "en": "List renders",
        "zh": "列出渲染"
      },
      "description": {
        "en": "MP4 exports of a conversation. status: queued, running, completed, failed or superseded. progress is 0–100 when available, otherwise null.",
        "zh": "会话的 MP4 导出。status：queued、running、completed、failed、superseded；progress 为 0–100，暂不可用时为 null。"
      },
      "fields": [
        {
          "name": "conversation_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "An owned video agent conversation ID.",
            "zh": "当前账号的视频 Agent 会话 ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "items": [
            {
              "conversation_id": "<conversation_id>",
              "render_id": "<render_id>",
              "message_id": "<message_id>",
              "status": "running",
              "progress": 42,
              "video_url": null,
              "created_at": 1791849600
            }
          ],
          "total": 1
        }
      },
      "status": 200,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/video-agents/conversations/%3Cconversation_id%3E/renders\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/video-agents/conversations/%3Cconversation_id%3E/renders\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/video-agents/conversations/%3Cconversation_id%3E/renders\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "items": [
              {
                "conversation_id": "<conversation_id>",
                "render_id": "<render_id>",
                "message_id": "<message_id>",
                "status": "running",
                "progress": 42,
                "video_url": null,
                "created_at": 1791849600
              }
            ],
            "total": 1
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "create-render",
      "group": "renders",
      "method": "POST",
      "path": "/v1/video-agents/conversations/{conversation_id}/renders",
      "scope": "video_agents:write",
      "title": {
        "en": "Create render",
        "zh": "创建渲染"
      },
      "description": {
        "en": "Export an MP4 once preview_video_url is available (409 preview_not_ready otherwise). A preview is not an export. Retrying with the same Idempotency-Key returns the same render. Poll get render until completed.",
        "zh": "preview_video_url 就绪后导出 MP4（否则返回 409 preview_not_ready）。预览不等于导出。相同 Idempotency-Key 重试返回同一个渲染。轮询查询渲染直到 completed。"
      },
      "fields": [
        {
          "name": "conversation_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "An owned video agent conversation ID.",
            "zh": "当前账号的视频 Agent 会话 ID。"
          },
          "required": true
        },
        {
          "name": "message_id",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Message to export. Default: the latest message with a preview.",
            "zh": "要导出的消息，默认最新有预览的消息。"
          }
        },
        {
          "name": "force",
          "type": "boolean",
          "location": "body",
          "description": {
            "en": "Re-export even when a completed export exists.",
            "zh": "已有完成的导出时也重新导出。"
          },
          "default": false
        }
      ],
      "sample": {
        "data": {
          "conversation_id": "<conversation_id>",
          "render_id": "<render_id>",
          "message_id": "<message_id>",
          "status": "running",
          "progress": 0,
          "video_url": null,
          "created_at": null
        }
      },
      "status": 202,
      "idempotent": true,
      "paid": true,
      "examples": {
        "curl": "curl --fail-with-body --request POST \\\n  --url \"$XPILOT_API_BASE_URL/v1/video-agents/conversations/%3Cconversation_id%3E/renders\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --header 'Content-Type: application/json' \\\n  --header 'Idempotency-Key: lesson-request-001' \\\n  --data '{\"force\":false}'",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/video-agents/conversations/%3Cconversation_id%3E/renders\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": \"lesson-request-001\",\n  },\n  body: JSON.stringify({\n  \"force\": false\n}),\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"POST\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/video-agents/conversations/%3Cconversation_id%3E/renders\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": \"lesson-request-001\",\n    },\n    json=json.loads(\"{\\\"force\\\":false}\"),\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "202": {
          "data": {
            "conversation_id": "<conversation_id>",
            "render_id": "<render_id>",
            "message_id": "<message_id>",
            "status": "running",
            "progress": 0,
            "video_url": null,
            "created_at": null
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "402": {
          "error": {
            "code": "insufficient_credits",
            "message": "The balance cannot cover the platform reserve, or the credential monthly budget is used up."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "409": {
          "error": {
            "code": "idempotency_conflict",
            "message": "The same idempotency key must use the exact same input. A conversation may already be busy."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "get-render",
      "group": "renders",
      "method": "GET",
      "path": "/v1/video-agents/conversations/{conversation_id}/renders/{render_id}",
      "scope": "video_agents:read",
      "title": {
        "en": "Get render",
        "zh": "查询渲染"
      },
      "description": {
        "en": "Export status, progress (0–100 or null when unavailable), and video_url once completed.",
        "zh": "查询导出状态、progress（0–100，暂不可用时为 null），完成后返回 video_url。"
      },
      "fields": [
        {
          "name": "conversation_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "An owned video agent conversation ID.",
            "zh": "当前账号的视频 Agent 会话 ID。"
          },
          "required": true
        },
        {
          "name": "render_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "Export render ID.",
            "zh": "导出渲染任务 ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "conversation_id": "<conversation_id>",
          "render_id": "<render_id>",
          "message_id": "<message_id>",
          "status": "completed",
          "progress": 100,
          "video_url": "<mp4_url>",
          "created_at": 1791849600
        }
      },
      "status": 200,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/video-agents/conversations/%3Cconversation_id%3E/renders/%3Crender_id%3E\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/video-agents/conversations/%3Cconversation_id%3E/renders/%3Crender_id%3E\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/video-agents/conversations/%3Cconversation_id%3E/renders/%3Crender_id%3E\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "conversation_id": "<conversation_id>",
            "render_id": "<render_id>",
            "message_id": "<message_id>",
            "status": "completed",
            "progress": 100,
            "video_url": "<mp4_url>",
            "created_at": 1791849600
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "delete-render",
      "group": "renders",
      "method": "DELETE",
      "path": "/v1/video-agents/conversations/{conversation_id}/renders/{render_id}",
      "scope": "video_agents:write",
      "title": {
        "en": "Delete render",
        "zh": "删除渲染"
      },
      "description": {
        "en": "Delete an export render record.",
        "zh": "删除导出渲染记录。"
      },
      "fields": [
        {
          "name": "conversation_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "An owned video agent conversation ID.",
            "zh": "当前账号的视频 Agent 会话 ID。"
          },
          "required": true
        },
        {
          "name": "render_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "Export render ID.",
            "zh": "导出渲染任务 ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "conversation_id": "<conversation_id>",
          "render_id": "<render_id>",
          "deleted": true
        }
      },
      "status": 200,
      "destructive": true,
      "examples": {
        "curl": "curl --fail-with-body --request DELETE \\\n  --url \"$XPILOT_API_BASE_URL/v1/video-agents/conversations/%3Cconversation_id%3E/renders/%3Crender_id%3E\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/video-agents/conversations/%3Cconversation_id%3E/renders/%3Crender_id%3E\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"DELETE\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/video-agents/conversations/%3Cconversation_id%3E/renders/%3Crender_id%3E\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "conversation_id": "<conversation_id>",
            "render_id": "<render_id>",
            "deleted": true
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "list-brand-kits",
      "group": "brand-kits",
      "method": "GET",
      "path": "/v1/brand-kits",
      "scope": "brand_kits:read",
      "title": {
        "en": "List brand kits",
        "zh": "列出品牌套件"
      },
      "description": {
        "en": "Owned brand kits. Pass an id as video_config.brand_kit_id.",
        "zh": "本人 Brand Kit 列表，id 可作为 video_config.brand_kit_id。"
      },
      "fields": [
        {
          "name": "limit",
          "type": "integer",
          "location": "query",
          "description": {
            "en": "Results per page, from 1 to 100.",
            "zh": "每页返回数量，范围 1–100。"
          },
          "default": 20,
          "min": 1,
          "max": 100
        },
        {
          "name": "offset",
          "type": "integer",
          "location": "query",
          "description": {
            "en": "Number of items to skip.",
            "zh": "跳过的条目数量。"
          },
          "default": 0,
          "min": 0,
          "max": 1000
        }
      ],
      "sample": {
        "data": {
          "items": [
            {
              "id": 1,
              "brand_name": "Acme",
              "brand_logo": "<asset_url>",
              "content_style_description": "Friendly and concise.",
              "theme_color": {
                "primary": "#2563eb",
                "accent": "#f97316",
                "background": "#ffffff",
                "text": "#0f172a"
              },
              "created_at": 1791849600,
              "updated_at": 1791849600
            }
          ],
          "total": 1,
          "limit": 20,
          "offset": 0
        }
      },
      "status": 200,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/brand-kits?limit=20&offset=0\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/brand-kits?limit=20&offset=0\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/brand-kits?limit=20&offset=0\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "items": [
              {
                "id": 1,
                "brand_name": "Acme",
                "brand_logo": "<asset_url>",
                "content_style_description": "Friendly and concise.",
                "theme_color": {
                  "primary": "#2563eb",
                  "accent": "#f97316",
                  "background": "#ffffff",
                  "text": "#0f172a"
                },
                "created_at": 1791849600,
                "updated_at": 1791849600
              }
            ],
            "total": 1,
            "limit": 20,
            "offset": 0
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "create-brand-kit",
      "group": "brand-kits",
      "method": "POST",
      "path": "/v1/brand-kits",
      "scope": "brand_kits:write",
      "title": {
        "en": "Create brand kit",
        "zh": "创建品牌套件"
      },
      "description": {
        "en": "Create a brand kit referenced by video_config.brand_kit_id. Unknown fields return 400.",
        "zh": "创建可供 video_config.brand_kit_id 引用的 Brand Kit。未知字段返回 400。"
      },
      "fields": [
        {
          "name": "brand_name",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Brand name, 1–120 characters.",
            "zh": "品牌名称，1–120 字符。"
          },
          "required": true,
          "maxLength": 120,
          "example": "Acme"
        },
        {
          "name": "brand_logo",
          "type": "string",
          "location": "body",
          "description": {
            "en": "http(s) logo URL, preferably returned by POST /v1/assets.",
            "zh": "http(s) Logo 地址，建议使用 POST /v1/assets 返回的地址。"
          },
          "maxLength": 2048
        },
        {
          "name": "content_style_description",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Tone and style guidance for scripts, up to 20,000 characters.",
            "zh": "脚本语气与风格说明，最多 20,000 字符。"
          },
          "maxLength": 20000
        },
        {
          "name": "theme_color",
          "type": "object",
          "location": "body",
          "description": {
            "en": "Theme colors. Keys: primary, secondary, accent, background, text, textSecondary, success, warning, error, surface, surfaceLight, shadow. Values: #rgb, #rrggbb, #rrggbbaa or rgb()/rgba(); null clears a color.",
            "zh": "主题色。键：primary、secondary、accent、background、text、textSecondary、success、warning、error、surface、surfaceLight、shadow。值：#rgb、#rrggbb、#rrggbbaa 或 rgb()/rgba()；null 清空该色。"
          },
          "example": {
            "primary": "#2563eb",
            "accent": "#f97316"
          },
          "children": [
            {
              "name": "primary",
              "type": "string | null",
              "description": {
                "en": "primary color.",
                "zh": "primary 颜色。"
              }
            },
            {
              "name": "secondary",
              "type": "string | null",
              "description": {
                "en": "secondary color.",
                "zh": "secondary 颜色。"
              }
            },
            {
              "name": "accent",
              "type": "string | null",
              "description": {
                "en": "accent color.",
                "zh": "accent 颜色。"
              }
            },
            {
              "name": "background",
              "type": "string | null",
              "description": {
                "en": "background color.",
                "zh": "background 颜色。"
              }
            },
            {
              "name": "text",
              "type": "string | null",
              "description": {
                "en": "text color.",
                "zh": "text 颜色。"
              }
            },
            {
              "name": "textSecondary",
              "type": "string | null",
              "description": {
                "en": "textSecondary color.",
                "zh": "textSecondary 颜色。"
              }
            },
            {
              "name": "success",
              "type": "string | null",
              "description": {
                "en": "success color.",
                "zh": "success 颜色。"
              }
            },
            {
              "name": "warning",
              "type": "string | null",
              "description": {
                "en": "warning color.",
                "zh": "warning 颜色。"
              }
            },
            {
              "name": "error",
              "type": "string | null",
              "description": {
                "en": "error color.",
                "zh": "error 颜色。"
              }
            },
            {
              "name": "surface",
              "type": "string | null",
              "description": {
                "en": "surface color.",
                "zh": "surface 颜色。"
              }
            },
            {
              "name": "surfaceLight",
              "type": "string | null",
              "description": {
                "en": "surfaceLight color.",
                "zh": "surfaceLight 颜色。"
              }
            },
            {
              "name": "shadow",
              "type": "string | null",
              "description": {
                "en": "shadow color.",
                "zh": "shadow 颜色。"
              }
            }
          ]
        }
      ],
      "sample": {
        "data": {
          "id": 1,
          "brand_name": "Acme",
          "brand_logo": "<asset_url>",
          "content_style_description": "Friendly and concise.",
          "theme_color": {
            "primary": "#2563eb",
            "accent": "#f97316",
            "background": "#ffffff",
            "text": "#0f172a"
          },
          "created_at": 1791849600,
          "updated_at": 1791849600
        }
      },
      "status": 201,
      "examples": {
        "curl": "curl --fail-with-body --request POST \\\n  --url \"$XPILOT_API_BASE_URL/v1/brand-kits\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --header 'Content-Type: application/json' \\\n  --data '{\"brand_name\":\"Acme\",\"theme_color\":{\"primary\":\"#2563eb\",\"accent\":\"#f97316\"}}'",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/brand-kits\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n  \"brand_name\": \"Acme\",\n  \"theme_color\": {\n    \"primary\": \"#2563eb\",\n    \"accent\": \"#f97316\"\n  }\n}),\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"POST\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/brand-kits\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n        \"Content-Type\": \"application/json\",\n    },\n    json=json.loads(\"{\\\"brand_name\\\":\\\"Acme\\\",\\\"theme_color\\\":{\\\"primary\\\":\\\"#2563eb\\\",\\\"accent\\\":\\\"#f97316\\\"}}\"),\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "201": {
          "data": {
            "id": 1,
            "brand_name": "Acme",
            "brand_logo": "<asset_url>",
            "content_style_description": "Friendly and concise.",
            "theme_color": {
              "primary": "#2563eb",
              "accent": "#f97316",
              "background": "#ffffff",
              "text": "#0f172a"
            },
            "created_at": 1791849600,
            "updated_at": 1791849600
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "409": {
          "error": {
            "code": "idempotency_conflict",
            "message": "The same idempotency key must use the exact same input. A conversation may already be busy."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "get-brand-kit",
      "group": "brand-kits",
      "method": "GET",
      "path": "/v1/brand-kits/{brand_kit_id}",
      "scope": "brand_kits:read",
      "title": {
        "en": "Get brand kit",
        "zh": "查询品牌套件"
      },
      "description": {
        "en": "Read an owned brand kit.",
        "zh": "查询本人 Brand Kit。"
      },
      "fields": [
        {
          "name": "brand_kit_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "An owned brand kit ID.",
            "zh": "当前账号的 Brand Kit ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "id": 1,
          "brand_name": "Acme",
          "brand_logo": "<asset_url>",
          "content_style_description": "Friendly and concise.",
          "theme_color": {
            "primary": "#2563eb",
            "accent": "#f97316",
            "background": "#ffffff",
            "text": "#0f172a"
          },
          "created_at": 1791849600,
          "updated_at": 1791849600
        }
      },
      "status": 200,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/brand-kits/%3Cbrand_kit_id%3E\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/brand-kits/%3Cbrand_kit_id%3E\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/brand-kits/%3Cbrand_kit_id%3E\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "id": 1,
            "brand_name": "Acme",
            "brand_logo": "<asset_url>",
            "content_style_description": "Friendly and concise.",
            "theme_color": {
              "primary": "#2563eb",
              "accent": "#f97316",
              "background": "#ffffff",
              "text": "#0f172a"
            },
            "created_at": 1791849600,
            "updated_at": 1791849600
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "update-brand-kit",
      "group": "brand-kits",
      "method": "PUT",
      "path": "/v1/brand-kits/{brand_kit_id}",
      "scope": "brand_kits:write",
      "title": {
        "en": "Update brand kit",
        "zh": "更新品牌套件"
      },
      "description": {
        "en": "Update fields of an owned brand kit; omitted fields stay unchanged.",
        "zh": "更新本人 Brand Kit 的字段，未传的字段保持不变。"
      },
      "fields": [
        {
          "name": "brand_kit_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "An owned brand kit ID.",
            "zh": "当前账号的 Brand Kit ID。"
          },
          "required": true
        },
        {
          "name": "brand_name",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Brand name, 1–120 characters.",
            "zh": "品牌名称，1–120 字符。"
          },
          "required": false,
          "maxLength": 120,
          "example": "Acme"
        },
        {
          "name": "brand_logo",
          "type": "string",
          "location": "body",
          "description": {
            "en": "http(s) logo URL, preferably returned by POST /v1/assets.",
            "zh": "http(s) Logo 地址，建议使用 POST /v1/assets 返回的地址。"
          },
          "maxLength": 2048
        },
        {
          "name": "content_style_description",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Tone and style guidance for scripts, up to 20,000 characters.",
            "zh": "脚本语气与风格说明，最多 20,000 字符。"
          },
          "maxLength": 20000
        },
        {
          "name": "theme_color",
          "type": "object",
          "location": "body",
          "description": {
            "en": "Theme colors. Keys: primary, secondary, accent, background, text, textSecondary, success, warning, error, surface, surfaceLight, shadow. Values: #rgb, #rrggbb, #rrggbbaa or rgb()/rgba(); null clears a color.",
            "zh": "主题色。键：primary、secondary、accent、background、text、textSecondary、success、warning、error、surface、surfaceLight、shadow。值：#rgb、#rrggbb、#rrggbbaa 或 rgb()/rgba()；null 清空该色。"
          },
          "example": {
            "primary": "#2563eb",
            "accent": "#f97316"
          },
          "children": [
            {
              "name": "primary",
              "type": "string | null",
              "description": {
                "en": "primary color.",
                "zh": "primary 颜色。"
              }
            },
            {
              "name": "secondary",
              "type": "string | null",
              "description": {
                "en": "secondary color.",
                "zh": "secondary 颜色。"
              }
            },
            {
              "name": "accent",
              "type": "string | null",
              "description": {
                "en": "accent color.",
                "zh": "accent 颜色。"
              }
            },
            {
              "name": "background",
              "type": "string | null",
              "description": {
                "en": "background color.",
                "zh": "background 颜色。"
              }
            },
            {
              "name": "text",
              "type": "string | null",
              "description": {
                "en": "text color.",
                "zh": "text 颜色。"
              }
            },
            {
              "name": "textSecondary",
              "type": "string | null",
              "description": {
                "en": "textSecondary color.",
                "zh": "textSecondary 颜色。"
              }
            },
            {
              "name": "success",
              "type": "string | null",
              "description": {
                "en": "success color.",
                "zh": "success 颜色。"
              }
            },
            {
              "name": "warning",
              "type": "string | null",
              "description": {
                "en": "warning color.",
                "zh": "warning 颜色。"
              }
            },
            {
              "name": "error",
              "type": "string | null",
              "description": {
                "en": "error color.",
                "zh": "error 颜色。"
              }
            },
            {
              "name": "surface",
              "type": "string | null",
              "description": {
                "en": "surface color.",
                "zh": "surface 颜色。"
              }
            },
            {
              "name": "surfaceLight",
              "type": "string | null",
              "description": {
                "en": "surfaceLight color.",
                "zh": "surfaceLight 颜色。"
              }
            },
            {
              "name": "shadow",
              "type": "string | null",
              "description": {
                "en": "shadow color.",
                "zh": "shadow 颜色。"
              }
            }
          ]
        }
      ],
      "sample": {
        "data": {
          "id": 1,
          "brand_name": "Acme",
          "brand_logo": "<asset_url>",
          "content_style_description": "Friendly and concise.",
          "theme_color": {
            "primary": "#2563eb",
            "accent": "#f97316",
            "background": "#ffffff",
            "text": "#0f172a"
          },
          "created_at": 1791849600,
          "updated_at": 1791849600
        }
      },
      "status": 200,
      "examples": {
        "curl": "curl --fail-with-body --request PUT \\\n  --url \"$XPILOT_API_BASE_URL/v1/brand-kits/%3Cbrand_kit_id%3E\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --header 'Content-Type: application/json' \\\n  --data '{\"brand_name\":\"Acme\",\"theme_color\":{\"primary\":\"#2563eb\",\"accent\":\"#f97316\"}}'",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/brand-kits/%3Cbrand_kit_id%3E\", {\n  method: \"PUT\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n  \"brand_name\": \"Acme\",\n  \"theme_color\": {\n    \"primary\": \"#2563eb\",\n    \"accent\": \"#f97316\"\n  }\n}),\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"PUT\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/brand-kits/%3Cbrand_kit_id%3E\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n        \"Content-Type\": \"application/json\",\n    },\n    json=json.loads(\"{\\\"brand_name\\\":\\\"Acme\\\",\\\"theme_color\\\":{\\\"primary\\\":\\\"#2563eb\\\",\\\"accent\\\":\\\"#f97316\\\"}}\"),\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "id": 1,
            "brand_name": "Acme",
            "brand_logo": "<asset_url>",
            "content_style_description": "Friendly and concise.",
            "theme_color": {
              "primary": "#2563eb",
              "accent": "#f97316",
              "background": "#ffffff",
              "text": "#0f172a"
            },
            "created_at": 1791849600,
            "updated_at": 1791849600
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "delete-brand-kit",
      "group": "brand-kits",
      "method": "DELETE",
      "path": "/v1/brand-kits/{brand_kit_id}",
      "scope": "brand_kits:write",
      "title": {
        "en": "Delete brand kit",
        "zh": "删除品牌套件"
      },
      "description": {
        "en": "Delete an owned brand kit.",
        "zh": "删除本人 Brand Kit。"
      },
      "fields": [
        {
          "name": "brand_kit_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "An owned brand kit ID.",
            "zh": "当前账号的 Brand Kit ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "brand_kit_id": "<brand_kit_id>",
          "deleted": true
        }
      },
      "status": 200,
      "destructive": true,
      "examples": {
        "curl": "curl --fail-with-body --request DELETE \\\n  --url \"$XPILOT_API_BASE_URL/v1/brand-kits/%3Cbrand_kit_id%3E\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/brand-kits/%3Cbrand_kit_id%3E\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"DELETE\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/brand-kits/%3Cbrand_kit_id%3E\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "brand_kit_id": "<brand_kit_id>",
            "deleted": true
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "check-credits",
      "group": "account",
      "method": "GET",
      "path": "/v1/account/credits",
      "scope": "account:read",
      "title": {
        "en": "Check credits",
        "zh": "查询可用额度"
      },
      "description": {
        "en": "The API and platform share the same wallet and the same credit conversion as the app. Recharge in the owner console.",
        "zh": "API 与平台共用钱包，积分换算与平台一致；工作区所有者在控制台充值。"
      },
      "fields": [],
      "sample": {
        "data": {
          "balance_credits": 1000,
          "base_credits": 0,
          "monthly_free_credits": 0,
          "plan": "pro",
          "shared_with_platform": true,
          "payment_mode": "stripe_live"
        }
      },
      "status": 200,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/account/credits\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/account/credits\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/account/credits\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "balance_credits": 1000,
            "base_credits": 0,
            "monthly_free_credits": 0,
            "plan": "pro",
            "shared_with_platform": true,
            "payment_mode": "stripe_live"
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "check-capacity",
      "group": "account",
      "method": "GET",
      "path": "/v1/account/capacity",
      "scope": "account:read",
      "title": {
        "en": "Check capacity & budget",
        "zh": "查询容量与预算"
      },
      "description": {
        "en": "Check available concurrency and the credential monthly budget before starting a paid run.",
        "zh": "启动付费运行前检查可用并发槽位和凭证月度预算。"
      },
      "fields": [],
      "sample": {
        "data": {
          "rpm": 60,
          "concurrency": 2,
          "active_tasks": 0,
          "available_slots": 2,
          "monthly_budget_credits": 1000,
          "spent_credits": 0,
          "reserved_credits": 0,
          "remaining_budget_credits": 1000
        }
      },
      "status": 200,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/account/capacity\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/account/capacity\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/account/capacity\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "rpm": 60,
            "concurrency": 2,
            "active_tasks": 0,
            "available_slots": 2,
            "monthly_budget_credits": 1000,
            "spent_credits": 0,
            "reserved_credits": 0,
            "remaining_budget_credits": 1000
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "list-assets",
      "group": "assets",
      "method": "GET",
      "path": "/v1/assets",
      "scope": "assets:read",
      "title": {
        "en": "List owned assets",
        "zh": "列出本人素材"
      },
      "description": {
        "en": "Files uploaded to this account, newest first. Upload with POST /v1/assets.",
        "zh": "本账号上传的文件，按时间倒序。上传请用 POST /v1/assets。"
      },
      "fields": [
        {
          "name": "limit",
          "type": "integer",
          "location": "query",
          "description": {
            "en": "Results per page, from 1 to 100.",
            "zh": "每页返回数量，范围 1–100。"
          },
          "default": 20,
          "min": 1,
          "max": 100
        },
        {
          "name": "offset",
          "type": "integer",
          "location": "query",
          "description": {
            "en": "Number of items to skip.",
            "zh": "跳过的条目数量。"
          },
          "default": 0,
          "min": 0
        }
      ],
      "sample": {
        "data": {
          "items": [
            {
              "id": 1,
              "name": "lesson.pdf",
              "url": "<asset_url>",
              "mimetype": "application/pdf",
              "created_at": 1791849600,
              "updated_at": 1791849600
            }
          ],
          "total": 1,
          "limit": 20,
          "offset": 0
        }
      },
      "status": 200,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/assets?limit=20&offset=0\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/assets?limit=20&offset=0\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/assets?limit=20&offset=0\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "items": [
              {
                "id": 1,
                "name": "lesson.pdf",
                "url": "<asset_url>",
                "mimetype": "application/pdf",
                "created_at": 1791849600,
                "updated_at": 1791849600
              }
            ],
            "total": 1,
            "limit": 20,
            "offset": 0
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "upload-assets",
      "group": "assets",
      "method": "POST",
      "path": "/v1/assets",
      "scope": "assets:write",
      "title": {
        "en": "Upload assets",
        "zh": "上传素材"
      },
      "description": {
        "en": "Multipart upload, field name files, up to 5 files per request. Limits per file: documents (pdf, docx, pptx, md, markdown, mdx) 10 MB; images (jpg, jpeg, png, gif, webp, svg) 10 MB; audio (wav, mp3, m4a, aac, ogg, flac) 20 MB; video (mp4, webm, mov) 60 MB. Empty files are rejected. Errors return 400 invalid_parameter with reason (too_many, unsupported_type, empty, too_large), file and max_mb. Returned ids and urls are reused by conversations, voice cloning and avatar creation.",
        "zh": "multipart 上传，表单字段名 files，每次最多 5 个文件。单文件上限：文档（pdf、docx、pptx、md、markdown、mdx）10 MB；图片（jpg、jpeg、png、gif、webp、svg）10 MB；音频（wav、mp3、m4a、aac、ogg、flac）20 MB；视频（mp4、webm、mov）60 MB。空文件会被拒绝。错误返回 400 invalid_parameter，并带 reason（too_many、unsupported_type、empty、too_large）、file 与 max_mb。返回的 id 与 url 可用于会话、声音克隆和数字人创建。"
      },
      "fields": [
        {
          "name": "files",
          "type": "file",
          "location": "body",
          "description": {
            "en": "Multipart form field. Repeat files for multiple uploads (max 5).",
            "zh": "multipart 表单字段；多个文件重复 files 字段（最多 5 个）。"
          },
          "required": true,
          "example": "@solar.pdf",
          "multiple": true,
          "accept": ".pdf,.docx,.pptx,.md,.markdown,.mdx,.jpg,.jpeg,.png,.gif,.webp,.svg,.wav,.mp3,.m4a,.aac,.ogg,.flac,.mp4,.webm,.mov"
        }
      ],
      "sample": {
        "data": [
          {
            "id": 1,
            "name": "solar.pdf",
            "url": "<asset_url>",
            "mimetype": "application/pdf",
            "created_at": 1791849600,
            "updated_at": 1791849600
          }
        ]
      },
      "status": 201,
      "examples": {
        "curl": "curl --fail-with-body --request POST \\\n  --url \"$XPILOT_API_BASE_URL/v1/assets\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --form 'files=@\"solar.pdf\"'",
        "javascript": "const form = new FormData();\nform.append(\"files\", /* File|Blob */ solarPdf, \"solar.pdf\");\nconst response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/assets\", {\n  method: \"POST\",\n  headers: { Authorization: `Bearer ${process.env.XPILOT_API_KEY}` },\n  body: form,\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, requests\n\nresponse = requests.post(\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/assets\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"]},\n    files=[(\"files\", (\"solar.pdf\", open(\"solar.pdf\", \"rb\"), \"application/pdf\"))],\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "201": {
          "data": [
            {
              "id": 1,
              "name": "solar.pdf",
              "url": "<asset_url>",
              "mimetype": "application/pdf",
              "created_at": 1791849600,
              "updated_at": 1791849600
            }
          ]
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "409": {
          "error": {
            "code": "idempotency_conflict",
            "message": "The same idempotency key must use the exact same input. A conversation may already be busy."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "list-avatars",
      "group": "avatars",
      "method": "GET",
      "path": "/v1/avatars",
      "scope": "avatars:read",
      "title": {
        "en": "List owned avatars",
        "zh": "列出本人数字人"
      },
      "description": {
        "en": "Owned avatars and quota. Only avatars with selectable=true can be used as video_config.avatar_id.",
        "zh": "本人数字人与配额。只有 selectable=true 的数字人可以作为 video_config.avatar_id。"
      },
      "fields": [],
      "sample": {
        "data": {
          "items": [
            {
              "id": 11,
              "name": "Instructor",
              "avatar_type": "human_video",
              "status": "ready",
              "training_status": "ready",
              "preview_status": "ready",
              "selectable": true,
              "stage": null,
              "preview_image_url": "<image_url>",
              "preview_video_url": "<video_url>",
              "error": null,
              "created_at": 1791849600,
              "updated_at": 1791849680
            }
          ],
          "total": 1,
          "quota": {
            "used": 1,
            "limit": 6
          }
        }
      },
      "status": 200,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/avatars\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/avatars\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/avatars\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "items": [
              {
                "id": 11,
                "name": "Instructor",
                "avatar_type": "human_video",
                "status": "ready",
                "training_status": "ready",
                "preview_status": "ready",
                "selectable": true,
                "stage": null,
                "preview_image_url": "<image_url>",
                "preview_video_url": "<video_url>",
                "error": null,
                "created_at": 1791849600,
                "updated_at": 1791849680
              }
            ],
            "total": 1,
            "quota": {
              "used": 1,
              "limit": 6
            }
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "list-voices",
      "group": "audio",
      "method": "GET",
      "path": "/v1/voices",
      "scope": "voices:read",
      "title": {
        "en": "list voices",
        "zh": "查找声音"
      },
      "description": {
        "en": "Find public catalog voices or your own clones. Choose voice_id from the returned items; never guess provider identifiers. Use samples on list/detail items for fixed audition clips.",
        "zh": "查找公共声音或本人克隆声音。使用返回的 voice_id，不要填写厂家内部 ID。固定试听使用列表/详情中的 samples。"
      },
      "fields": [
        {
          "name": "voice_version",
          "type": "integer",
          "location": "query",
          "description": {
            "en": "Voice I (1, default) or Voice II (2) for the public catalog. Private clone listings include both versions.",
            "zh": "公共目录的声音版本：Voice I 为 1（默认），Voice II 为 2；本人克隆列表同时包含两个版本。"
          },
          "required": false,
          "enum": [
            1,
            2
          ],
          "default": 1
        },
        {
          "name": "type",
          "type": "string",
          "location": "query",
          "description": {
            "en": "public shared catalog or private owned clones.",
            "zh": "public 为共享目录，private 为本人克隆声音。"
          },
          "required": false,
          "enum": [
            "public",
            "private"
          ],
          "default": "public"
        },
        {
          "name": "language",
          "type": "string",
          "location": "query",
          "description": {
            "en": "Optional language filter aligned with the platform catalog.",
            "zh": "语言过滤，对齐平台目录。"
          },
          "required": false
        },
        {
          "name": "page",
          "type": "integer",
          "location": "query",
          "description": {
            "en": "Page, starting at 1.",
            "zh": "页码，从 1 开始。"
          },
          "required": false,
          "min": 1
        },
        {
          "name": "limit",
          "type": "integer",
          "location": "query",
          "description": {
            "en": "Items per page, up to 100.",
            "zh": "每页数量，最多 100 条。"
          },
          "required": false,
          "min": 1,
          "max": 100
        }
      ],
      "sample": {
        "data": {
          "items": [],
          "page": 1,
          "total": 0
        }
      },
      "status": 200,
      "idempotent": false,
      "paid": false,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/voices?voice_version=1&type=public\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/voices?voice_version=1&type=public\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/voices?voice_version=1&type=public\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "items": [],
            "page": 1,
            "total": 0
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "get-voice",
      "group": "audio",
      "method": "GET",
      "path": "/v1/voices/{voice_id}",
      "scope": "voices:read",
      "title": {
        "en": "get voice",
        "zh": "查询声音"
      },
      "description": {
        "en": "Read a public or owned voice, including readiness and samples. Private voices from another account are unavailable. Poll this after clone_voice: a Voice II clone moves from training to trained (selectable=true) or failed.",
        "zh": "查询可用声音、就绪状态与 samples；其他账号的私有声音不可访问。clone_voice 之后轮询此接口：Voice II 克隆从 training 变为 trained（selectable=true）或 failed。"
      },
      "fields": [
        {
          "name": "voice_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "Catalog or owned clone voice ID.",
            "zh": "声音目录返回的公开 ID。"
          },
          "required": true
        },
        {
          "name": "voice_version",
          "type": "integer",
          "location": "query",
          "description": {
            "en": "Voice version of the voice: Voice I (1, default) or Voice II (2).",
            "zh": "声音版本：Voice I 为 1，Voice II 为 2，默认 1；须与 voice_id 对应的声音一致。"
          },
          "required": false,
          "enum": [
            1,
            2
          ],
          "default": 1
        }
      ],
      "sample": {
        "data": {
          "_id": "<voice_id>",
          "title": "Instructor",
          "voice_version": 1,
          "state": "trained",
          "samples": [
            {
              "audio": "<sample_url>"
            }
          ]
        }
      },
      "status": 200,
      "idempotent": false,
      "paid": false,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/voices/%3Cvoice_id%3E?voice_version=1\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/voices/%3Cvoice_id%3E?voice_version=1\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/voices/%3Cvoice_id%3E?voice_version=1\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "_id": "<voice_id>",
            "title": "Instructor",
            "voice_version": 1,
            "state": "trained",
            "samples": [
              {
                "audio": "<sample_url>"
              }
            ]
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "generate-audio",
      "group": "audio",
      "method": "POST",
      "path": "/v1/voices/speech",
      "scope": "audio:write",
      "title": {
        "en": "generate audio",
        "zh": "生成配音"
      },
      "description": {
        "en": "Synthesize up to 1,000 characters with a catalog voice or one of your ready clones, in Voice I or Voice II. voice_version must match the voice. Returns MP3 audio_url (which may be a data URL). Uses platform TTS pricing; reuse the same Idempotency-Key for retries.",
        "zh": "使用 Voice I 或 Voice II 的目录声音、或本人已就绪的克隆声音合成最多 1,000 字符的 MP3；voice_version 须与声音一致。返回的 audio_url 可能是 data URL。沿用平台现有 TTS 计费规则；重试时保持 Idempotency-Key 和输入一致。"
      },
      "fields": [
        {
          "name": "text",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Spoken text.",
            "zh": "配音文本，最多 1,000 字符。"
          },
          "required": true,
          "maxLength": 1000,
          "example": "Welcome to our lesson."
        },
        {
          "name": "voice_id",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Catalog voice ID or owned ready clone ID.",
            "zh": "声音目录返回的公开 ID。"
          },
          "required": true
        },
        {
          "name": "voice_version",
          "type": "integer",
          "location": "body",
          "description": {
            "en": "Voice version of voice_id: Voice I (1, default) or Voice II (2). Never substitute one version for the other.",
            "zh": "声音版本：Voice I 为 1，Voice II 为 2，默认 1；须与 voice_id 对应的声音一致。"
          },
          "required": false,
          "enum": [
            1,
            2
          ],
          "default": 1
        },
        {
          "name": "locale",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Speech locale. Voice II catalog voices accept their native or secondary locales; Voice II clones accept the locales they support.",
            "zh": "合成语言与地区代码。Voice II 目录声音可用其母语或辅助语言；Voice II 克隆可用其支持的语言。"
          },
          "required": false
        },
        {
          "name": "speed",
          "type": "number",
          "location": "body",
          "description": {
            "en": "Playback speed.",
            "zh": "语速，可选范围 0.5–2。"
          },
          "required": false,
          "min": 0.5,
          "max": 2
        }
      ],
      "sample": {
        "data": {
          "audio_url": "data:audio/mpeg;base64,<mp3_bytes>",
          "format": "mp3",
          "voice_id": "<voice_id>",
          "voice_version": 1,
          "billing_basis": "platform_tts",
          "cost_credits": 0
        }
      },
      "status": 200,
      "idempotent": true,
      "paid": false,
      "examples": {
        "curl": "curl --fail-with-body --request POST \\\n  --url \"$XPILOT_API_BASE_URL/v1/voices/speech\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --header 'Content-Type: application/json' \\\n  --header 'Idempotency-Key: lesson-request-001' \\\n  --data '{\"text\":\"Welcome to our lesson.\",\"voice_id\":\"<voice_id>\",\"voice_version\":1}'",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/voices/speech\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": \"lesson-request-001\",\n  },\n  body: JSON.stringify({\n  \"text\": \"Welcome to our lesson.\",\n  \"voice_id\": \"<voice_id>\",\n  \"voice_version\": 1\n}),\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"POST\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/voices/speech\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": \"lesson-request-001\",\n    },\n    json=json.loads(\"{\\\"text\\\":\\\"Welcome to our lesson.\\\",\\\"voice_id\\\":\\\"<voice_id>\\\",\\\"voice_version\\\":1}\"),\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "audio_url": "data:audio/mpeg;base64,<mp3_bytes>",
            "format": "mp3",
            "voice_id": "<voice_id>",
            "voice_version": 1,
            "billing_basis": "platform_tts",
            "cost_credits": 0
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "409": {
          "error": {
            "code": "idempotency_conflict",
            "message": "The same idempotency key must use the exact same input. A conversation may already be busy."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "get-voice-clone-config",
      "group": "audio",
      "method": "GET",
      "path": "/v1/voices/clone/config",
      "scope": "voices:read",
      "title": {
        "en": "get voice clone config",
        "zh": "查询克隆授权要求"
      },
      "description": {
        "en": "Read Voice II clone requirements before clone_voice with voice_version 2: availability, the authorized company_name, supported locales with the consent_statement the speaker must read aloud, and sample duration/size limits. When voice_ii_available is false (for example voice_ii_unavailable_reason clone_disabled), do not attempt a Voice II clone; offer Voice I instead.",
        "zh": "Voice II 克隆前读取授权要求：是否可用、被授权公司名 company_name、支持的录音语言及说话人需朗读的 consent_statement，以及样本时长/大小限制。voice_ii_available 为 false（例如 voice_ii_unavailable_reason 为 clone_disabled）时不要尝试 Voice II 克隆，改用 Voice I。"
      },
      "fields": [],
      "sample": {
        "data": {
          "default_voice_version": 1,
          "voice_ii_available": true,
          "voice_ii_unavailable_reason": "",
          "company_name": "X-Pilot",
          "locales": [
            {
              "locale": "en-US",
              "consent_statement": "I {voice_talent_name} am aware that recordings of my voice will be used by {company_name} to create and use a synthetic version of my voice."
            }
          ],
          "sample_min_seconds": 5,
          "sample_max_seconds": 90,
          "max_sample_bytes": 31457280
        }
      },
      "status": 200,
      "idempotent": false,
      "paid": false,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/voices/clone/config\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/voices/clone/config\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/voices/clone/config\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "default_voice_version": 1,
            "voice_ii_available": true,
            "voice_ii_unavailable_reason": "",
            "company_name": "X-Pilot",
            "locales": [
              {
                "locale": "en-US",
                "consent_statement": "I {voice_talent_name} am aware that recordings of my voice will be used by {company_name} to create and use a synthetic version of my voice."
              }
            ],
            "sample_min_seconds": 5,
            "sample_max_seconds": 90,
            "max_sample_bytes": 31457280
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "clone-voice",
      "group": "audio",
      "method": "POST",
      "path": "/v1/voices/clone",
      "scope": "audio:write",
      "title": {
        "en": "clone voice",
        "zh": "克隆声音"
      },
      "description": {
        "en": "Clone a private voice from audio already uploaded to your account. Voice I (default) is an instant clone from one sample. Voice II needs the speaker's consent: call get_voice_clone_config, have the speaker record the consent_statement for the chosen locale with their name and company_name filled in, upload it separately, then pass voice_talent_name, locale, consent_audio_url and consent_accepted=true. Never fabricate consent or set consent_accepted without the speaker's explicit agreement. Voice II returns 403 voice_ii_unavailable while cloning is not open for the account. Poll get_voice until state is trained or failed. Reuse Idempotency-Key on retry.",
        "zh": "使用已上传至本人账号的音频克隆私有声音。Voice I（默认）用一段样本即时克隆。Voice II 需说话人授权：先查询克隆授权要求，让说话人按所选语言朗读填入本人姓名和 company_name 的 consent_statement 并单独上传，再传 voice_talent_name、locale、consent_audio_url 和 consent_accepted=true。不得伪造授权，或在说话人未明确同意时设置 consent_accepted。Voice II 克隆尚未对账号开放时返回 403 voice_ii_unavailable。轮询查询声音接口直到 trained 或 failed；重试时保持 Idempotency-Key 一致。"
      },
      "fields": [
        {
          "name": "name",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Voice title.",
            "zh": "名称。"
          },
          "required": true,
          "maxLength": 80
        },
        {
          "name": "audio_url",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Owned uploaded training audio URL. Voice II: 5–90 seconds of the same speaker.",
            "zh": "已上传至本人账号的训练音频地址。Voice II 需同一说话人 5–90 秒的录音。"
          },
          "required": true
        },
        {
          "name": "cover_image_url",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Optional owned uploaded cover image URL. Voice I only.",
            "zh": "可选，已上传至本人账号的封面图地址，仅 Voice I。"
          },
          "required": false
        },
        {
          "name": "voice_version",
          "type": "integer",
          "location": "body",
          "description": {
            "en": "Voice I (1, default) or Voice II (2, consent-based personal voice).",
            "zh": "Voice I 为 1（默认，即时克隆）；Voice II 为 2（需说话人授权的个人声音）。"
          },
          "required": false,
          "enum": [
            1,
            2
          ],
          "default": 1
        },
        {
          "name": "voice_talent_name",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Voice II only: the speaker's name exactly as spoken in the consent recording.",
            "zh": "仅 Voice II：说话人姓名，须与授权录音中朗读的一致。"
          },
          "required": false,
          "maxLength": 128
        },
        {
          "name": "locale",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Voice II only: recording locale from get_voice_clone_config, for example en-US or zh-CN.",
            "zh": "仅 Voice II：录音语言，取自查询克隆授权要求返回的 locales，如 en-US、zh-CN。"
          },
          "required": false,
          "maxLength": 16
        },
        {
          "name": "consent_audio_url",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Voice II only: owned uploaded recording of the speaker reading the consent_statement for locale. Must be a separate file from audio_url, at most 90 seconds.",
            "zh": "仅 Voice II：已上传至本人账号的授权录音地址，内容为说话人按所选语言朗读 consent_statement。须与 audio_url 为不同文件，最长 90 秒。"
          },
          "required": false
        },
        {
          "name": "consent_accepted",
          "type": "boolean",
          "location": "body",
          "description": {
            "en": "Voice II only: true when the speaker has explicitly agreed that their voice may be cloned. Required for Voice II.",
            "zh": "仅 Voice II：说话人已明确同意克隆其声音时传 true，Voice II 必填。"
          },
          "required": false,
          "enum": [
            true,
            false
          ]
        }
      ],
      "sample": {
        "data": {
          "_id": "<voice_id>",
          "title": "My clone",
          "voice_version": 2,
          "state": "training",
          "selectable": false
        }
      },
      "status": 202,
      "idempotent": true,
      "paid": false,
      "examples": {
        "curl": "curl --fail-with-body --request POST \\\n  --url \"$XPILOT_API_BASE_URL/v1/voices/clone\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --header 'Content-Type: application/json' \\\n  --header 'Idempotency-Key: lesson-request-001' \\\n  --data '{\"name\":\"<name>\",\"audio_url\":\"<audio_url>\",\"voice_version\":1}'",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/voices/clone\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": \"lesson-request-001\",\n  },\n  body: JSON.stringify({\n  \"name\": \"<name>\",\n  \"audio_url\": \"<audio_url>\",\n  \"voice_version\": 1\n}),\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"POST\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/voices/clone\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": \"lesson-request-001\",\n    },\n    json=json.loads(\"{\\\"name\\\":\\\"<name>\\\",\\\"audio_url\\\":\\\"<audio_url>\\\",\\\"voice_version\\\":1}\"),\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "202": {
          "data": {
            "_id": "<voice_id>",
            "title": "My clone",
            "voice_version": 2,
            "state": "training",
            "selectable": false
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "409": {
          "error": {
            "code": "idempotency_conflict",
            "message": "The same idempotency key must use the exact same input. A conversation may already be busy."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "delete-voice",
      "group": "audio",
      "method": "DELETE",
      "path": "/v1/voices/{voice_id}",
      "scope": "audio:write",
      "title": {
        "en": "delete voice",
        "zh": "删除声音"
      },
      "description": {
        "en": "Delete an owned cloned voice. Public catalog voices cannot be deleted. Reuse Idempotency-Key on retry.",
        "zh": "删除本人克隆声音。公共目录声音不可删除；重试时保持 Idempotency-Key 一致。"
      },
      "fields": [
        {
          "name": "voice_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "Owned clone voice ID.",
            "zh": "声音目录返回的公开 ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "id": "<voice_id>",
          "deleted": true
        }
      },
      "status": 200,
      "idempotent": true,
      "paid": false,
      "examples": {
        "curl": "curl --fail-with-body --request DELETE \\\n  --url \"$XPILOT_API_BASE_URL/v1/voices/%3Cvoice_id%3E\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --header 'Idempotency-Key: lesson-request-001'",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/voices/%3Cvoice_id%3E\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n    \"Idempotency-Key\": \"lesson-request-001\",\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"DELETE\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/voices/%3Cvoice_id%3E\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n        \"Idempotency-Key\": \"lesson-request-001\",\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "id": "<voice_id>",
            "deleted": true
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "get-avatar",
      "group": "avatars",
      "method": "GET",
      "path": "/v1/avatars/{avatar_id}",
      "scope": "avatars:read",
      "title": {
        "en": "get avatar",
        "zh": "查询数字人"
      },
      "description": {
        "en": "Read an owned avatar. status is the overall state (draft, generating, ready, failed, deleting, migration_required); training_status and preview_status track training and the preview sample; selectable must be true before video generation can use it; stage names the step in progress (queued, generating_training_video, provider_processing, generating_preview, deleting_provider_asset) or null when idle; error is {code, message} on failure. Training ready alone does not mean it can be used in a video.",
        "zh": "查询本人数字人。status 为汇总状态（draft、generating、ready、failed、deleting、migration_required）；training_status 与 preview_status 分别是训练和预览样片状态；selectable 为 true 才能用于视频生成；stage 是进行中的阶段（queued、generating_training_video、provider_processing、generating_preview、deleting_provider_asset），空闲时为 null；失败时 error 为 {code, message}。训练完成不代表可用于视频生成。"
      },
      "fields": [
        {
          "name": "avatar_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "Owned avatar ID.",
            "zh": "当前账号的数字人 ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "id": "<avatar_id>",
          "name": "Presenter",
          "avatar_type": "human_video",
          "status": "ready",
          "training_status": "ready",
          "preview_status": "ready",
          "selectable": true,
          "stage": null,
          "preview_image_url": "<image_url>",
          "preview_video_url": "<video_url>",
          "error": null,
          "created_at": 1791849600,
          "updated_at": 1791849680
        }
      },
      "status": 200,
      "idempotent": false,
      "paid": false,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/avatars/%3Cavatar_id%3E\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/avatars/%3Cavatar_id%3E\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/avatars/%3Cavatar_id%3E\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "id": "<avatar_id>",
            "name": "Presenter",
            "avatar_type": "human_video",
            "status": "ready",
            "training_status": "ready",
            "preview_status": "ready",
            "selectable": true,
            "stage": null,
            "preview_image_url": "<image_url>",
            "preview_video_url": "<video_url>",
            "error": null,
            "created_at": 1791849600,
            "updated_at": 1791849680
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "create-avatar",
      "group": "avatars",
      "method": "POST",
      "path": "/v1/avatars",
      "scope": "avatars:write",
      "title": {
        "en": "create avatar",
        "zh": "创建数字人"
      },
      "description": {
        "en": "Create an avatar from an owned asset_id or a public http(s) image/video URL. Third-party URLs are downloaded and archived to the caller’s owned storage before training. Requires platform entitlement. Poll get_avatar for preview_image_url and preview_video_url, or receive avatar.completed / avatar.failed through callback_url or a webhook subscription. Reuse Idempotency-Key on retry; never retrain automatically.",
        "zh": "用本人素材 asset_id，或调用方提供的公网 http(s) 图片/视频 URL 创建数字人。第三方 URL 会先由后端安全下载并归档到当前账号的 OSS 资产，再用于训练。需要平台权益；通过查询数字人接口轮询 preview_* 字段，或传 callback_url / 订阅 Webhook 接收 avatar.completed、avatar.failed 事件，不要自动重复训练。"
      },
      "fields": [
        {
          "name": "name",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Avatar name.",
            "zh": "名称。"
          },
          "required": true,
          "maxLength": 80
        },
        {
          "name": "avatar_type",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Training source.",
            "zh": "训练来源：真人视频或虚拟角色图片。"
          },
          "required": true,
          "enum": [
            "human_video",
            "virtual_character"
          ]
        },
        {
          "name": "asset_id",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Owned asset ID returned by POST /v1/assets. human_video uses a video asset; virtual_character uses an image asset.",
            "zh": "POST /v1/assets 返回的本人素材 ID。真人视频传视频素材，虚拟角色传图片素材。"
          },
          "required": false
        },
        {
          "name": "image_url",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Public http(s) image URL. The backend archives third-party content to your owned storage before training. Use this or asset_id for virtual_character.",
            "zh": "调用方提供的公网 http(s) 图片 URL。第三方内容会先归档到当前账号 OSS；私网地址、错误类型及超过 10MB 的图片会被拒绝。与 asset_id 二选一。"
          },
          "required": false
        },
        {
          "name": "video_url",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Public http(s) training video URL. The backend archives third-party content to your owned storage before training. Use this or asset_id for human_video.",
            "zh": "调用方提供的公网 http(s) 训练视频 URL。第三方内容会先归档到当前账号 OSS；私网地址、错误类型及超过 60MB 的视频会被拒绝。与 asset_id 二选一。"
          },
          "required": false
        },
        {
          "name": "gender",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Required for image avatars.",
            "zh": "图片数字人必填：Male 或 Female。"
          },
          "required": false,
          "enum": [
            "Male",
            "Female"
          ]
        },
        {
          "name": "callback_url",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Optional public HTTPS URL notified once with avatar.completed or avatar.failed. Signed with the workspace callback secret (X-XPilot-Signature). Only set it when you run a receiving service.",
            "zh": "可选，仅本次请求生效的回调地址。须为公网 HTTPS（443 端口），最长 2,048 字符；内网、回环和 http 地址返回 400 invalid_webhook_url / unsafe_webhook_url。到达终态时 X Pilot 以 POST 发送 avatar.completed 或 avatar.failed 事件，用工作区回调签名密钥签名（开发者控制台 → Webhooks）。失败最多重试 6 次，投递记录可在控制台查看。"
          },
          "required": false,
          "maxLength": 2048
        },
        {
          "name": "callback_id",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Optional reference echoed back as data.callback_id. Requires callback_url.",
            "zh": "可选，原样放在回调 data.callback_id 中返回的业务标识（如订单号）。字母、数字、_ . : -，最长 128 字符。需同时传 callback_url。"
          },
          "required": false,
          "maxLength": 128
        }
      ],
      "sample": {
        "data": {
          "id": "<avatar_id>",
          "name": "Presenter",
          "avatar_type": "human_video",
          "status": "generating",
          "training_status": "pending",
          "preview_status": "not_requested",
          "selectable": false,
          "stage": "provider_processing",
          "preview_image_url": null,
          "preview_video_url": null,
          "error": null,
          "created_at": 1791849600,
          "updated_at": 1791849600
        }
      },
      "status": 202,
      "idempotent": true,
      "paid": false,
      "examples": {
        "curl": "curl --fail-with-body --request POST \\\n  --url \"$XPILOT_API_BASE_URL/v1/avatars\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --header 'Content-Type: application/json' \\\n  --header 'Idempotency-Key: lesson-request-001' \\\n  --data '{\"name\":\"<name>\",\"avatar_type\":\"human_video\",\"video_url\":\"https://cdn.example.com/training.mp4\"}'",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/avatars\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": \"lesson-request-001\",\n  },\n  body: JSON.stringify({\n  \"name\": \"<name>\",\n  \"avatar_type\": \"human_video\",\n  \"video_url\": \"https://cdn.example.com/training.mp4\"\n}),\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"POST\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/avatars\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": \"lesson-request-001\",\n    },\n    json=json.loads(\"{\\\"name\\\":\\\"<name>\\\",\\\"avatar_type\\\":\\\"human_video\\\",\\\"video_url\\\":\\\"https://cdn.example.com/training.mp4\\\"}\"),\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "202": {
          "data": {
            "id": "<avatar_id>",
            "name": "Presenter",
            "avatar_type": "human_video",
            "status": "generating",
            "training_status": "pending",
            "preview_status": "not_requested",
            "selectable": false,
            "stage": "provider_processing",
            "preview_image_url": null,
            "preview_video_url": null,
            "error": null,
            "created_at": 1791849600,
            "updated_at": 1791849600
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "409": {
          "error": {
            "code": "idempotency_conflict",
            "message": "The same idempotency key must use the exact same input. A conversation may already be busy."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "delete-avatar",
      "group": "avatars",
      "method": "DELETE",
      "path": "/v1/avatars/{avatar_id}",
      "scope": "avatars:write",
      "title": {
        "en": "delete avatar",
        "zh": "删除数字人"
      },
      "description": {
        "en": "Delete an owned avatar. Poll get_avatar or list_avatars until it disappears. Reuse Idempotency-Key on retry.",
        "zh": "删除本人数字人。删除中请继续轮询列表或详情，直到资源消失；重试时保持 Idempotency-Key 一致。"
      },
      "fields": [
        {
          "name": "avatar_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "Owned avatar ID.",
            "zh": "当前账号的数字人 ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "id": "<avatar_id>",
          "status": "deleting"
        }
      },
      "status": 202,
      "idempotent": true,
      "paid": false,
      "examples": {
        "curl": "curl --fail-with-body --request DELETE \\\n  --url \"$XPILOT_API_BASE_URL/v1/avatars/%3Cavatar_id%3E\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --header 'Idempotency-Key: lesson-request-001'",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/avatars/%3Cavatar_id%3E\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n    \"Idempotency-Key\": \"lesson-request-001\",\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"DELETE\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/avatars/%3Cavatar_id%3E\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n        \"Idempotency-Key\": \"lesson-request-001\",\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "202": {
          "data": {
            "id": "<avatar_id>",
            "status": "deleting"
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "create-avatar-video",
      "group": "avatars",
      "method": "POST",
      "path": "/v1/avatars/{avatar_id}/videos",
      "scope": "avatars:write",
      "title": {
        "en": "create avatar video",
        "zh": "合成数字人视频"
      },
      "description": {
        "en": "Synthesize a talking-presenter video with an owned avatar whose training_status is ready. Drive it with text + voice_id (platform TTS, Voice I or Voice II) or with your own speech audio (asset_id or a public http(s) audio_url) — exactly one source, up to 5 minutes of speech. Output is a transparent-background WebM (VP9 with alpha) for compositing. Billed per rendered second at the digital-human video rate; the balance must cover the estimate before dispatch. Poll get_avatar_video, or receive avatar-video.completed / avatar-video.failed through callback_url or a webhook subscription. Reuse Idempotency-Key on retry; never resubmit automatically.",
        "zh": "用 training_status 为 ready 的本人数字人合成口播视频。语音来源二选一：text + voice_id（平台 TTS，Voice I 或 Voice II），或自备音频（asset_id 或公网 http(s) audio_url），最长 5 分钟。输出透明背景 WebM（VP9 + alpha），便于二次合成。按实际渲染秒数以数字人视频费率计费；提交前余额须覆盖预估费用。通过查询数字人视频接口轮询，或传 callback_url / 订阅 Webhook 接收 avatar-video.completed、avatar-video.failed 事件；重试时保持 Idempotency-Key 一致，不要自动重复提交。"
      },
      "fields": [
        {
          "name": "avatar_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "Owned avatar ID with training_status ready.",
            "zh": "training_status 为 ready 的本人数字人 ID。"
          },
          "required": true
        },
        {
          "name": "text",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Spoken text for platform TTS. Use with voice_id; omit when sending audio.",
            "zh": "平台 TTS 口播文本，最多 5,000 字符；需同时传 voice_id。使用音频时不要传。"
          },
          "required": false,
          "maxLength": 5000,
          "example": "Welcome to our lesson."
        },
        {
          "name": "voice_id",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Catalog voice ID or owned ready clone ID from list_voices. Required with text.",
            "zh": "list_voices 返回的目录声音或本人已就绪克隆的 ID，传 text 时必填。"
          },
          "required": false
        },
        {
          "name": "voice_version",
          "type": "integer",
          "location": "body",
          "description": {
            "en": "Voice version of voice_id: Voice I (1, default) or Voice II (2).",
            "zh": "voice_id 对应的声音版本：Voice I 为 1（默认），Voice II 为 2。"
          },
          "required": false,
          "enum": [
            1,
            2
          ],
          "default": 1
        },
        {
          "name": "locale",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Optional native locale for the voice.",
            "zh": "可选，声音的语言与地区代码。"
          },
          "required": false
        },
        {
          "name": "speed",
          "type": "number",
          "location": "body",
          "description": {
            "en": "TTS playback speed.",
            "zh": "TTS 语速，可选范围 0.5–2。"
          },
          "required": false,
          "min": 0.5,
          "max": 2
        },
        {
          "name": "audio_url",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Public http(s) speech audio URL (≤20 MB). The backend archives third-party audio to your owned storage first. Use this or asset_id instead of text.",
            "zh": "调用方提供的公网 http(s) 口播音频 URL。第三方音频会先归档到当前账号 OSS；私网地址、错误类型及超过 20MB 的音频会被拒绝。与 asset_id 二选一，替代 text。"
          },
          "required": false
        },
        {
          "name": "asset_id",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Owned audio asset ID returned by POST /v1/assets. Use this or audio_url instead of text.",
            "zh": "POST /v1/assets 返回的本人音频素材 ID。与 audio_url 二选一，替代 text。"
          },
          "required": false
        },
        {
          "name": "callback_url",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Optional public HTTPS URL notified once with avatar-video.completed or avatar-video.failed. Signed with the workspace callback secret (X-XPilot-Signature). Only set it when you run a receiving service.",
            "zh": "可选，仅本次请求生效的回调地址。须为公网 HTTPS（443 端口），最长 2,048 字符；内网、回环和 http 地址返回 400 invalid_webhook_url / unsafe_webhook_url。到达终态时 X Pilot 以 POST 发送 avatar-video.completed 或 avatar-video.failed 事件，用工作区回调签名密钥签名（开发者控制台 → Webhooks）。失败最多重试 6 次，投递记录可在控制台查看。"
          },
          "required": false,
          "maxLength": 2048
        },
        {
          "name": "callback_id",
          "type": "string",
          "location": "body",
          "description": {
            "en": "Optional reference echoed back as data.callback_id. Requires callback_url.",
            "zh": "可选，原样放在回调 data.callback_id 中返回的业务标识（如订单号）。字母、数字、_ . : -，最长 128 字符。需同时传 callback_url。"
          },
          "required": false,
          "maxLength": 128
        }
      ],
      "sample": {
        "data": {
          "id": "<video_id>",
          "avatar_id": "<avatar_id>",
          "status": "queued",
          "source": "text",
          "video_url": null,
          "format": "webm",
          "has_alpha": true,
          "duration_seconds": null,
          "width": null,
          "height": null,
          "cost_credits": null,
          "error": null,
          "created_at": 1791849600,
          "updated_at": 1791849600
        }
      },
      "status": 202,
      "idempotent": true,
      "paid": true,
      "examples": {
        "curl": "curl --fail-with-body --request POST \\\n  --url \"$XPILOT_API_BASE_URL/v1/avatars/%3Cavatar_id%3E/videos\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\" \\\n  --header 'Content-Type: application/json' \\\n  --header 'Idempotency-Key: lesson-request-001' \\\n  --data '{\"text\":\"Welcome to our lesson.\",\"voice_id\":\"<voice_id>\",\"voice_version\":1}'",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/avatars/%3Cavatar_id%3E/videos\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": \"lesson-request-001\",\n  },\n  body: JSON.stringify({\n  \"text\": \"Welcome to our lesson.\",\n  \"voice_id\": \"<voice_id>\",\n  \"voice_version\": 1\n}),\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"POST\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/avatars/%3Cavatar_id%3E/videos\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": \"lesson-request-001\",\n    },\n    json=json.loads(\"{\\\"text\\\":\\\"Welcome to our lesson.\\\",\\\"voice_id\\\":\\\"<voice_id>\\\",\\\"voice_version\\\":1}\"),\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "202": {
          "data": {
            "id": "<video_id>",
            "avatar_id": "<avatar_id>",
            "status": "queued",
            "source": "text",
            "video_url": null,
            "format": "webm",
            "has_alpha": true,
            "duration_seconds": null,
            "width": null,
            "height": null,
            "cost_credits": null,
            "error": null,
            "created_at": 1791849600,
            "updated_at": 1791849600
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "402": {
          "error": {
            "code": "insufficient_credits",
            "message": "The balance cannot cover the platform reserve, or the credential monthly budget is used up."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "409": {
          "error": {
            "code": "idempotency_conflict",
            "message": "The same idempotency key must use the exact same input. A conversation may already be busy."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    },
    {
      "id": "get-avatar-video",
      "group": "avatars",
      "method": "GET",
      "path": "/v1/avatars/{avatar_id}/videos/{video_id}",
      "scope": "avatars:read",
      "title": {
        "en": "get avatar video",
        "zh": "查询数字人视频"
      },
      "description": {
        "en": "Read an avatar video job: status (queued, processing, completed, failed), video_url (transparent WebM) once completed, duration_seconds, width, height and the settled cost_credits. Poll at least 5 seconds apart.",
        "zh": "查询数字人视频任务：status（queued、processing、completed、failed）、完成后的 video_url（透明 WebM）、duration_seconds、width、height 与结算后的 cost_credits。轮询间隔至少 5 秒。"
      },
      "fields": [
        {
          "name": "avatar_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "Owned avatar ID.",
            "zh": "本人数字人 ID。"
          },
          "required": true
        },
        {
          "name": "video_id",
          "type": "string",
          "location": "path",
          "description": {
            "en": "Video ID returned by create_avatar_video.",
            "zh": "合成数字人视频接口返回的视频 ID。"
          },
          "required": true
        }
      ],
      "sample": {
        "data": {
          "id": "<video_id>",
          "avatar_id": "<avatar_id>",
          "status": "completed",
          "source": "text",
          "video_url": "<webm_url>",
          "format": "webm",
          "has_alpha": true,
          "duration_seconds": 12.4,
          "width": 1080,
          "height": 1920,
          "cost_credits": 9.18,
          "error": null,
          "created_at": 1791849600,
          "updated_at": 1791849680
        }
      },
      "status": 200,
      "idempotent": false,
      "paid": false,
      "examples": {
        "curl": "curl --fail-with-body --request GET \\\n  --url \"$XPILOT_API_BASE_URL/v1/avatars/%3Cavatar_id%3E/videos/%3Cvideo_id%3E\" \\\n  --header \"Authorization: Bearer $XPILOT_API_KEY\"",
        "javascript": "const response = await fetch(process.env.XPILOT_API_BASE_URL + \"/v1/avatars/%3Cavatar_id%3E/videos/%3Cvideo_id%3E\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,\n  },\n});\nconst result = await response.json();\nif (!response.ok) throw new Error(JSON.stringify(result));\nconsole.log(result);",
        "python": "import os, json, requests\n\nresponse = requests.request(\n    \"GET\",\n    os.environ[\"XPILOT_API_BASE_URL\"] + \"/v1/avatars/%3Cavatar_id%3E/videos/%3Cvideo_id%3E\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"XPILOT_API_KEY\"],\n    },\n    timeout=70,\n)\nprint(response.status_code, response.text)\nresponse.raise_for_status()"
      },
      "responses": {
        "200": {
          "data": {
            "id": "<video_id>",
            "avatar_id": "<avatar_id>",
            "status": "completed",
            "source": "text",
            "video_url": "<webm_url>",
            "format": "webm",
            "has_alpha": true,
            "duration_seconds": 12.4,
            "width": 1080,
            "height": 1920,
            "cost_credits": 9.18,
            "error": null,
            "created_at": 1791849600,
            "updated_at": 1791849680
          }
        },
        "400": {
          "error": {
            "code": "invalid_parameter",
            "message": "Check parameter types, required fields and idempotency key."
          },
          "request_id": "<request_id>"
        },
        "401": {
          "error": {
            "code": "invalid_token",
            "message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
          },
          "request_id": "<request_id>"
        },
        "403": {
          "error": {
            "code": "insufficient_scope",
            "message": "Check the credential scope and resource ownership."
          },
          "request_id": "<request_id>"
        },
        "404": {
          "error": {
            "code": "not_found",
            "message": "Use an existing owned conversation, message, brand kit or render ID."
          },
          "request_id": "<request_id>"
        },
        "429": {
          "error": {
            "code": "rate_limited",
            "message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
            "retry_after": 60
          },
          "request_id": "<request_id>"
        }
      }
    }
  ],
  "errors": {
    "400": [
      "invalid_parameter",
      {
        "en": "Check parameter types, required fields and idempotency key.",
        "zh": "检查参数类型、必填字段和幂等键。"
      }
    ],
    "401": [
      "invalid_token",
      {
        "en": "Use an active scoped API Key. MCP OAuth tokens cannot call REST.",
        "zh": "使用有效 API Key；MCP OAuth 凭证不能调用 REST。"
      }
    ],
    "402": [
      "insufficient_credits",
      {
        "en": "The balance cannot cover the platform reserve, or the credential monthly budget is used up.",
        "zh": "余额不足以支付平台预扣，或凭证本月预算已用完。"
      }
    ],
    "403": [
      "insufficient_scope",
      {
        "en": "Check the credential scope and resource ownership.",
        "zh": "检查凭证权限及资源归属。"
      }
    ],
    "404": [
      "not_found",
      {
        "en": "Use an existing owned conversation, message, brand kit or render ID.",
        "zh": "使用存在且属于当前账号的 conversation、message、brand kit 或 render ID。"
      }
    ],
    "409": [
      "idempotency_conflict",
      {
        "en": "The same idempotency key must use the exact same input. A conversation may already be busy.",
        "zh": "同一幂等键必须使用相同输入，会话也可能正在运行。"
      }
    ],
    "429": [
      "rate_limited",
      {
        "en": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
        "zh": "触发凭证或工作区速率、并发限制，按 Retry-After 等待后重试。"
      }
    ]
  },
  "guide": {
    "overview": "Overview",
    "api": "API reference",
    "skill": "Skills",
    "mcp": "MCP",
    "agents": "For AI agents",
    "prompt": "Prompt guide",
    "back": "Back to app",
    "search": "Find a guide…",
    "empty": "No matching guides",
    "navigation": "Developer guides",
    "toc": "On this page",
    "copy": "Copy",
    "copied": "Copied",
    "failed": "Copy failed. Select and copy the text.",
    "view": "View SKILL.md",
    "download": "Download Skill",
    "title": "Build video into your workflow.",
    "subtitle": "Give your agent the lesson workflow. Use the API to create, refine, and preview videos with X Pilot.",
    "eyebrow": "X PILOT DEVELOPERS",
    "available": "Available",
    "planned": "Deployment required",
    "rest": "Developer API",
    "restDesc": "Build on the same project and generation workflow as the X Pilot web app.",
    "skillDesc": "Teach an agent to turn a teaching goal into a lesson, review the outline, and hand off the preview.",
    "mcpDesc": "Paste one URL into Claude, Cursor or Codex and sign in. No API key needed.",
    "start": "Choose your starting point",
    "resources": "Developer resources",
    "agentCopy": "Copy to your agent",
    "agentText": "Read {url}/llms.txt and the official xpilot-course-video Skill. Discover the configured MCP or scoped API connection, check credits and capacity, upload owned files via POST /v1/assets when needed, use stable idempotency keys, and poll list_messages. Default generation skips outline confirmation. Use require_outline_confirmation true only when the user should review the outline. When a message is paused, follow next_action: confirm_outline means show the outline and send outline_action=confirm (no prompt) or revise with a prompt; reply means answer with a prompt. MP4 export needs create_render. Do not invent deployment URLs.",
    "flow": "From brief to preview",
    "flowLabels": [
      "Lesson brief",
      "Outline review",
      "Generate & refine",
      "Preview handoff"
    ],
    "scope": "Skills encode the workflow. APIs execute it. A connected MCP server will expose tools to your agent.",
    "apiTitle": "Create and continue a video",
    "apiIntro": "API Keys, asynchronous tasks and idempotent retries. The API and MCP reuse the platform generation service and wallet.",
    "auth": "Authentication",
    "authText": "Create a API Key in the developer console. Set XPILOT_API_BASE_URL to the deployment's Node API root and XPILOT_API_KEY to the secret in your environment. OAuth tokens are bound to MCP.",
    "quick": "Quick start",
    "quickText": "Check credits/capacity, then POST /v1/video-agents/conversations with an Idempotency-Key. Save conversation_id, message_id and held cost_credits. Reuse the same key when retrying the same input.",
    "reference": "Endpoint reference",
    "filter": "Filter endpoints…",
    "purpose": "Purpose",
    "streaming": "Asynchronous messages & recovery",
    "streamingText": "Poll GET /v1/video-agents/conversations/{conversation_id}/messages every 5 seconds or subscribe to signed Webhooks. When a message is paused, next_action says what to send: confirm_outline (outline_action=confirm, or revise with a prompt) or reply (answer with a prompt). A disconnect does not mean completion. No SSE in this release.",
    "identity": "Use conversation_id for ownership and message_id for per-turn status. Lists do not return project_id. Times are Unix seconds.",
    "terminal": "A succeeded terminal task can provide a preview. A paused task requires input. MP4 export readiness is a separate state.",
    "errors": "Errors",
    "errorText": "401: expired/revoked credential. 403: scope/ownership. 402: credits/budget. 400 with param/allowed: invalid field or value. 409: idempotency conflict, conversation busy or avatar not ready. 429: RPM/concurrency; respect Retry-After. After a transport error, retry with the same Idempotency-Key.",
    "source": "Full REST contract",
    "skillTitle": "Video, audio and avatar workflows for your agent.",
    "skillIntro": "Three focused Skills with real API references. Install the workflow you need, then connect your account.",
    "library": "Official Skills",
    "official": "Official",
    "skillName": "Course & explainer video",
    "output": "Returns: project identity + observed state + actual preview URL",
    "install": "Install in your agent",
    "installText": "Choose a Skill and extract its folder into your agent skills directory, or use the command below. Review the files before installation.",
    "installHint": "Project installation · requires curl and unzip · restart your agent or refresh Skills after installation.",
    "example": "Try a lesson brief",
    "exampleText": "Use X Pilot to make a short English lesson on the Pythagorean theorem for 8th graders: open with the 3-4-5 knotted rope, state a² + b² = c², prove it with the area method, work two examples (legs 6 and 8; a 5 m ladder 3 m from the wall), and end with common mistakes. Show me the outline before generating.",
    "package": "What’s in the package",
    "packageText": "Each package includes SKILL.md and focused references (API, and for video a prompt guide and upload rules). No executable scripts are included.",
    "connection": "Before generating",
    "connectionText": "Install the Skill, then connect MCP using OAuth or configure a API Key. The console shows actual deployed endpoint URLs. Installation alone does not authorize generation.",
    "mcpTitle": "Connect X Pilot to your AI tools.",
    "mcpIntro": "Add X Pilot as a remote MCP connector in Claude, Cursor, Codex and other agents. Sign in once with OAuth, then create videos from chat. Usage is billed to your existing X Pilot credits.",
    "availability": "Current availability",
    "availabilityText": "Remote MCP is implemented. Deploy the backend and configure its public URLs, then copy the actual /mcp address from the console. Use Streamable HTTP with OAuth + S256 PKCE; authorizations are revocable.",
    "tools": "MCP tools",
    "toolsText": "tools/list exposes only the tools permitted by the credential. The agent handles stable retry keys; billing and capacity policy is enforced by the service.",
    "tool": "Tool",
    "map": "Capability",
    "toolNames": [
      "check_credits",
      "check_capacity",
      "list_conversations",
      "get_conversation",
      "create_conversation",
      "send_message",
      "list_messages",
      "stop_conversation",
      "delete_conversation",
      "list_renders",
      "create_render",
      "get_render",
      "delete_render",
      "list_brand_kits",
      "get_brand_kit",
      "create_brand_kit",
      "update_brand_kit",
      "delete_brand_kit",
      "list_assets",
      "list_voices",
      "get_voice",
      "generate_audio",
      "get_voice_clone_config",
      "clone_voice",
      "delete_voice",
      "list_avatars",
      "get_avatar",
      "create_avatar",
      "delete_avatar",
      "create_avatar_video",
      "get_avatar_video"
    ],
    "toolPurposes": [
      "Shared credit balance",
      "Rate and concurrency",
      "Owned conversations + cost_credits",
      "Conversation summary",
      "Create conversation and start first message",
      "Send message / outline action",
      "Poll message status",
      "Stop active message",
      "Delete conversation",
      "List MP4 renders",
      "Start MP4 export",
      "Poll render status",
      "Delete render",
      "List brand kits",
      "Get brand kit",
      "Create brand kit",
      "Update brand kit",
      "Delete brand kit",
      "Owned assets (upload via REST POST /v1/assets)",
      "Voice catalog",
      "Voice readiness",
      "MP3 synthesis (Voice I / II)",
      "Voice II consent statement and limits",
      "Clone Voice I, or Voice II with recorded consent",
      "Delete owned voice",
      "Owned avatars",
      "Avatar status",
      "Avatar creation",
      "Avatar deletion",
      "Talking avatar video (text or audio)",
      "Avatar video status"
    ],
    "path": "Available today",
    "pathText": "Install the Skill → connect MCP or configure API Key → check credits → optional POST /v1/assets → create_conversation. Default skips outline confirmation. Set require_outline_confirmation true only when the user should review the outline, then send_message with outline_action. Poll list_messages → deliver preview → create_render for MP4.",
    "agentsTitle": "Give your agent the right context.",
    "agentsIntro": "A compact integration index and a real Skill package, ready for on-demand loading.",
    "rules": "Integration rules",
    "ruleItems": [
      "Inspect available tools and authentication before making requests.",
      "Use only documented routes and returned identities.",
      "Creation starts a run; recover it before retrying.",
      "Review paused outlines and distinguish preview from MP4 export."
    ],
    "index": "Agent-readable index",
    "close": "Close source",
    "create": "Create and start first run",
    "continue": "Continue / edit / confirm outline",
    "replay": "Reconnect to existing stream",
    "status": "Read runtime state",
    "messages": "Read message history",
    "stop": "Stop current run",
    "list": "List projects (shared route)",
    "profile": "Read detail (shared route)",
    "docsStatus": "Documentation + distribution",
    "noEndpoints": "No matching endpoints",
    "mcpConnect": "Connect in four steps",
    "mcpUrlLabel": "X Pilot MCP URL",
    "mcpSteps": [
      {
        "title": "Open connector settings",
        "text": "In your AI tool, open the MCP or Connectors settings and choose to add a custom (remote) server."
      },
      {
        "title": "Paste the MCP URL",
        "text": "Name it X Pilot and paste the URL below. Transport: Streamable HTTP."
      },
      {
        "title": "Sign in with OAuth",
        "text": "Click Connect, sign in to X Pilot and approve the requested access. No API key is needed, and you can disconnect at any time from the developer console."
      },
      {
        "title": "Start creating",
        "text": "Ask for a video in plain language. Generation uses your existing credits, exactly like the web app."
      }
    ],
    "mcpExample": "Use X Pilot to make a 2-minute explainer video about the Pythagorean theorem for middle school students, then give me the preview link.",
    "mcpClientsTitle": "Client setup",
    "mcpClientsText": "Pick your AI tool for the exact place to add X Pilot and how to sign in.",
    "mcpClientNotes": [
      "Works in Claude on the web and desktop. Click Connect, then sign in and approve on the X Pilot page. On Team and Enterprise plans an owner adds the connector first.",
      "Click Add to Cursor or save this to mcp.json. Then open Settings → MCP and click Connect next to x-pilot to sign in.",
      "Run this once, then type /mcp inside Claude Code, choose x-pilot and Authenticate.",
      "The second command opens the browser to sign in. Restart Codex if the tools do not appear.",
      "Or add the same server to .vscode/mcp.json. VS Code asks you to sign in the first time the agent uses it.",
      "Then run /mcp auth x-pilot inside Gemini CLI to sign in."
    ],
    "addToCursor": "Add to Cursor",
    "mcpKey": "Connect with an API Key",
    "mcpKeyText": "Use an API Key when a client does not support OAuth, or for CI and servers. Create the key in the developer console and keep it in the XPILOT_API_KEY environment variable — never paste it into a config file or repository. The key's scopes and monthly budget still apply.",
    "mcpSecurity": "Access & billing",
    "mcpSecurityItems": [
      "The sign-in page shows the account and the access being granted. Tokens only work for the X Pilot MCP server; access tokens last one hour and refresh automatically.",
      "Disconnect a client any time from Developer console → Connected AI. Tasks already running continue.",
      "MCP uses the same wallet as the web app: credits are reserved when a task starts, released when it ends, and the actual usage is charged. A task never takes your balance below zero.",
      "The AI sees only the tools allowed by the access you approved. Upload files with REST POST /v1/assets."
    ],
    "mcpAgentText": "Connect the X Pilot remote MCP server at {mcp} (Streamable HTTP, OAuth with S256 PKCE, or Authorization: Bearer with an X Pilot API Key). Read {url}/llms.txt and the official xpilot-course-video Skill. Check credits and capacity, call create_conversation with a stable idempotency key, poll list_messages, follow next_action when a message is paused, deliver the preview and call create_render for MP4.",
    "plugin": "Plugin package",
    "pluginText": "One package with all three Skills and the X Pilot MCP connection (OAuth on first use). Use it where a client installs plugins; the Skill ZIPs below are plain skill folders and are not plugins.",
    "pluginDownload": "Download plugin",
    "installHintRepo": "Requires Node.js · installs into the current project; add -g for a user-level install · restart your agent or refresh Skills afterwards.",
    "pluginSteps": [
      {
        "title": "ChatGPT / Codex",
        "text": "Plugins → Create plugin → upload x-pilot.zip."
      },
      {
        "title": "Cursor",
        "text": "Extract into ~/.cursor/plugins/local/x-pilot (copy the files, not a symlink), then run Developer: Reload Window."
      }
    ],
    "webhooks": "Webhooks",
    "webhooksDesc": "Get a signed HTTPS callback when a video message, avatar or avatar video finishes.",
    "webhooksTitle": "Know the moment a video is ready.",
    "webhooksIntro": "Instead of polling, let X Pilot call your server when a video message completes, pauses for outline approval, fails or stops, and when an avatar or avatar video reaches its final state. Add callback_url to one request, or subscribe an endpoint for the whole account. Every delivery is signed and recorded in the developer console.",
    "whWays": "Two ways to receive",
    "whWayCols": [
      "",
      "Per-request callback_url",
      "Account endpoint"
    ],
    "whWayRows": [
      [
        "Set up",
        "Add callback_url to create conversation / send message / create avatar / create avatar video",
        "Developer console → Webhooks → Add endpoint"
      ],
      [
        "Scope",
        "Only the message, avatar or avatar video started by that request",
        "Every video message, avatar and avatar video created through the developer API"
      ],
      [
        "Signing secret",
        "One workspace callback secret (console → view / rotate)",
        "One secret per endpoint, shown once on creation"
      ],
      [
        "Best for",
        "Routing each job back to its own order or user",
        "One central receiver for everything"
      ]
    ],
    "whCallback": "Per-request callback",
    "whCallbackSteps": [
      {
        "title": "Expose an HTTPS receiver",
        "text": "Public HTTPS on port 443. Private, loopback and plain-HTTP addresses are rejected with invalid_webhook_url or unsafe_webhook_url (param: callback_url)."
      },
      {
        "title": "Pass callback_url when you start the job",
        "text": "Supported on POST /v1/video-agents/conversations, POST /v1/video-agents/conversations/{conversation_id}, POST /v1/avatars and POST /v1/avatars/{avatar_id}/videos, and on the MCP tools create_conversation, send_message, create_avatar and create_avatar_video. Optional callback_id (letters, digits, _ . : -, up to 128) is echoed back as data.callback_id."
      },
      {
        "title": "Store the callback signing secret",
        "text": "Open Developer console → Webhooks → Callback signing secret → View. Keep it in an environment variable such as XPILOT_WEBHOOK_SECRET."
      },
      {
        "title": "Verify, acknowledge, then process",
        "text": "Check X-XPilot-Signature, answer 2xx within 10 seconds, and handle the event asynchronously."
      }
    ],
    "whCallbackNote": "callback_url applies to the message, avatar or avatar video started by that request only. When an outline pause is confirmed with send message, pass callback_url again to be notified about the final video. The callback is part of the idempotency fingerprint: reusing an Idempotency-Key with a different callback returns idempotency_conflict. Avatar and avatar video events are detected by a background check about every 30 seconds, so they can arrive up to a minute after the state changes.",
    "whEvents": "Events",
    "whEventCols": [
      "Event",
      "When it is sent",
      "What to do"
    ],
    "whEventRows": [
      [
        "video-agent.paused",
        "The message needs input; next_action is confirm_outline or reply",
        "Show the outline or send outline_action=confirm"
      ],
      [
        "video-agent.completed",
        "The preview is ready",
        "Deliver preview_url, or call create render for an MP4"
      ],
      [
        "video-agent.failed",
        "Generation failed; error explains why",
        "Retry with a new Idempotency-Key or adjust the brief"
      ],
      [
        "video-agent.stopped",
        "The message was stopped",
        "No further action"
      ],
      [
        "avatar.completed",
        "Training finished and the preview sample (if requested) is ready",
        "Use the avatar; preview_image_url and preview_video_url are filled"
      ],
      [
        "avatar.failed",
        "Training or the preview sample failed; error and error_code explain why",
        "Fix the source and create a new avatar; X Pilot never retrains automatically"
      ],
      [
        "avatar-video.completed",
        "The transparent WebM is ready; cost_credits is settled",
        "Download video_url"
      ],
      [
        "avatar-video.failed",
        "Synthesis failed; error and error_code explain why",
        "Retry with a new Idempotency-Key"
      ],
      [
        "webhook.test",
        "Console → Send test",
        "Return 2xx to mark the endpoint verified"
      ]
    ],
    "whPayload": "Request format",
    "whPayloadText": "X Pilot sends POST with a JSON body. For video-agent events data mirrors the message you would get from list messages; avatar events carry avatar_id, status (same value as get avatar), training_status, preview_status, selectable, preview_image_url, preview_video_url, error and error_code; avatar-video events mirror get avatar video plus video_id. Per-request callbacks also include callback_id.",
    "whVerify": "Verify the signature",
    "whVerifyText": "X-XPilot-Signature is t=<unix seconds>,v1=<hex>. v1 = HMAC-SHA256(secret, t + \".\" + raw request body). Compute it over the raw bytes before JSON parsing, compare in constant time and reject timestamps older than 5 minutes.",
    "whDelivery": "Retries & delivery records",
    "whDeliveryItems": [
      "Any 2xx response within 10 seconds counts as delivered. Redirects, other status codes, timeouts and network errors are retried.",
      "Up to 6 attempts with growing back-off (about 30 s, 1, 2, 4 and 8 minutes). After the 6th failure the delivery is marked failed.",
      "The same event can arrive more than once — deduplicate by id / X-XPilot-Event-Id. Ordering is not guaranteed; use created_at and status.",
      "Developer console → Webhooks lists every delivery: event, source (per-request or endpoint), target, HTTP status, attempts, payload and time. Failed deliveries can be re-sent with one click.",
      "Polling GET /v1/video-agents/conversations/{conversation_id}/messages, GET /v1/avatars/{avatar_id} or GET /v1/avatars/{avatar_id}/videos/{video_id} remains the source of truth if your receiver was down."
    ],
    "whOpenConsole": "Open Webhooks in the console"
  },
  "mcpClients": [
    {
      "name": "Claude",
      "label": "Settings → Connectors → Add custom connector",
      "code": "Name  X Pilot\nURL   https://mcp.x-pilot.ai/mcp",
      "note": "Works in Claude on the web and desktop. Click Connect, then sign in and approve on the X Pilot page. On Team and Enterprise plans an owner adds the connector first."
    },
    {
      "name": "Cursor",
      "label": "~/.cursor/mcp.json",
      "code": "{\n  \"mcpServers\": {\n    \"x-pilot\": {\n      \"url\": \"https://mcp.x-pilot.ai/mcp\"\n    }\n  }\n}",
      "note": "Click Add to Cursor or save this to mcp.json. Then open Settings → MCP and click Connect next to x-pilot to sign in."
    },
    {
      "name": "Claude Code",
      "label": "Terminal",
      "code": "claude mcp add --transport http x-pilot https://mcp.x-pilot.ai/mcp",
      "note": "Run this once, then type /mcp inside Claude Code, choose x-pilot and Authenticate."
    },
    {
      "name": "Codex",
      "label": "Terminal",
      "code": "codex mcp add x-pilot --url https://mcp.x-pilot.ai/mcp\ncodex mcp login x-pilot",
      "note": "The second command opens the browser to sign in. Restart Codex if the tools do not appear."
    },
    {
      "name": "VS Code",
      "label": "Terminal",
      "code": "code --add-mcp '{\"name\":\"x-pilot\",\"type\":\"http\",\"url\":\"https://mcp.x-pilot.ai/mcp\"}'",
      "note": "Or add the same server to .vscode/mcp.json. VS Code asks you to sign in the first time the agent uses it."
    },
    {
      "name": "Gemini CLI",
      "label": "Terminal",
      "code": "gemini mcp add --transport http x-pilot https://mcp.x-pilot.ai/mcp",
      "note": "Then run /mcp auth x-pilot inside Gemini CLI to sign in."
    }
  ],
  "mcpKeyConfigs": [
    {
      "label": "Cursor · ~/.cursor/mcp.json",
      "code": "{\n  \"mcpServers\": {\n    \"x-pilot\": {\n      \"url\": \"https://mcp.x-pilot.ai/mcp\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${env:XPILOT_API_KEY}\"\n      }\n    }\n  }\n}"
    },
    {
      "label": "Codex · Terminal",
      "code": "codex mcp add x-pilot --url https://mcp.x-pilot.ai/mcp --bearer-token-env-var XPILOT_API_KEY"
    }
  ],
  "skillClients": [
    {
      "name": "Cursor",
      "directory": ".cursor/skills",
      "agent": "cursor"
    },
    {
      "name": "Claude Code",
      "directory": ".claude/skills",
      "agent": "claude-code"
    },
    {
      "name": "Codex",
      "directory": ".agents/skills",
      "agent": "codex"
    }
  ],
  "skills": [
    {
      "name": "xpilot-course-video",
      "description": "Create or revise educational and explainer videos with X Pilot, including the brief, style and duration choice, source uploads, optional outline review, asynchronous polling, preview handoff and MP4 export. Use when the user wants an X Pilot course, training, product or explainer video.",
      "source": "---\nname: xpilot-course-video\ndescription: Create or revise educational and explainer videos with X Pilot, including the brief, style and duration choice, source uploads, optional outline review, asynchronous polling, preview handoff and MP4 export. Use when the user wants an X Pilot course, training, product or explainer video.\n---\n\n# X Pilot course video\n\nTurn the user's goal into one video and return its real conversation_id, message state and preview.\n\n## 1. Connection\n\n- Prefer a connected X Pilot MCP server when its tools are present. For REST, read [references/api.md](references/api.md) and use `XPILOT_API_BASE_URL` and `XPILOT_API_KEY` from the environment.\n- Installing this Skill does not authorize an account. Never invent a server address and never ask for keys in chat.\n- With no authorized connection, prepare the brief and say clearly that generation has not started.\n\n## 2. Settle four things before generating\n\n| Item | Ask the user? | Maps to |\n|---|---|---|\n| Audience and goal | Yes | First lines of `prompt` |\n| Language | Yes, unless obvious | `video_config.target_language` (cannot change later) |\n| Length | Yes, unless obvious | `video_config.video_duration`: `short` 1–3 min, `deep` 3–10 min, `long` 10–20 min |\n| Style | No, default `auto` | `video_config.video_style` |\n\nOptional, only when the user wants them: `voice_id` (+ `voice_version`) from `list_voices`, `avatar_id` from `list_avatars` (must be `selectable: true`), `brand_kit_id` from `list_brand_kits`, `enable_initial_ai_video`. `video_config` is fixed at creation; a follow-up message cannot change it. Unknown fields or values fail with 400 and list the allowed values.\n\n## 3. Write the prompt\n\nRead [references/prompt-guide.md](references/prompt-guide.md) and pick the closest video type.\n\n- Use a `#` title and several `##` sections. Each section becomes roughly one scene; keep one video to about 12 scenes and split bigger topics into a series.\n- Write the real content: facts, numbers, formulas, dates and ordered steps. Do not name visual components (\"use a bar chart\"); the agent chooses visuals.\n- Do not invent statistics. If the user did not give a number, leave it out or ask.\n\n## 4. Upload source files\n\nRead [references/uploads.md](references/uploads.md).\n\n- Each `files` item uses either an owned `asset_id` or a public http(s) `url` supplied by the user. X Pilot archives third-party URLs to the user's storage before generation. REST multipart `POST /v1/assets` remains available for direct uploads; there is no MCP upload tool.\n- Put `files_role` inside each item: `\"primary\"` when that file is main content/visual material, `\"reference\"` when it is background only. Omit it for that item when the agent should decide.\n- Never search for, guess or invent file URLs, and never use private-network URLs.\n\n## 5. Outline mode\n\n- Default: omit `require_outline_confirmation`. The video is generated straight through. Do not send `outline_action`.\n- When the user asks to see the outline first, create with `require_outline_confirmation: true`. When the message shows `status: \"paused\"` and `next_action: \"confirm_outline\"`, show `outline` (title, scenes, estimated duration) to the user, then:\n  - user approves: `send_message` with `outline_action: \"confirm\"` and no prompt;\n  - user wants changes: `send_message` with `outline_action: \"revise\"` and the changes in `prompt`.\n- `next_action: \"reply\"` means the assistant asked a question. Relay it and send the user's answer as `prompt`.\n\n## 6. Submit, poll, deliver\n\n- Call `check_credits` and `check_capacity` first. Billing matches the X Pilot app: a reserve is held when the message starts (tell the user the `cost_credits` returned on create or send), released at the end, then actual usage is charged. Longer videos cost more; there is no per-message cap.\n- Generate one stable `idempotency_key` (UUID) per operation and reuse it only when retrying the exact same input.\n- Poll `list_messages` no faster than every 5 seconds and respect Retry-After. `status` is queued, running, stopping, paused, completed, failed or stopped; completed, paused, failed and stopped end that message. A timeout or disconnect is not completion.\n- Deliver only service URLs (`preview_video_url`), with conversation_id and message_id. A preview is not an MP4: call `create_render` (with its own `idempotency_key`) and poll `get_render` when the user needs a file. Report the returned `progress` (0–100) when present; if it is `null`, report the status without inventing a percentage.\n- To edit a finished video, `send_message` with a concrete instruction (\"shorten scene 2, add a worked example\").\n- Stop with `stop_conversation` only when the user asks; incurred cost may still be charged.\n\n## 7. Never\n\nTop up, enable automatic charging, expand permissions, or ask the user for scopes, budgets or retry keys. Never report failed or unfinished runs as done.\n",
      "files": [
        "SKILL.md",
        "references/api.md",
        "references/prompt-guide.md",
        "references/uploads.md"
      ]
    },
    {
      "name": "xpilot-audio",
      "description": "Find and preview X Pilot voices, produce spoken MP3 audio, and clone voices (Voice I instantly, or Voice II with the speaker's recorded consent) when the user requests narration, voiceover, text-to-speech, or a voice clone using X Pilot.",
      "source": "---\nname: xpilot-audio\ndescription: Find and preview X Pilot voices, produce spoken MP3 audio, and clone voices (Voice I instantly, or Voice II with the speaker's recorded consent) when the user requests narration, voiceover, text-to-speech, or a voice clone using X Pilot.\n---\n\nPrefer a connected X Pilot MCP client. For REST, read [references/api.md](references/api.md); use configured environment credentials, never ask for keys in chat.\n\n## Voice versions\n\n- Voice I (`voice_version=1`, the default) and Voice II (`voice_version=2`) are both available for synthesis and cloning. Always pass the `voice_version` that `list_voices` returned for the chosen voice; never substitute one version for the other or fall back silently when one fails.\n- Voice I clones instantly from one sample. Voice II clones are more natural personal voices that require the speaker's recorded consent and take a few minutes to prepare.\n- Video generation is separate: a video's `video_config.voice_id` may use either version returned by `list_voices`.\n\n## Workflow\n\n- Find voices with `list_voices` (`type=public` for the catalog, `type=private` for the user's own clones), filtered by language. Use a returned voice ID; never guess provider IDs or clone another person's voice.\n- Check readiness with `get_voice`. For pending or failed voices, explain the status instead of silently substituting another voice.\n- For a fixed audition clip, play `samples` from list or detail. There is no separate preview endpoint.\n- Call `generate_audio` with text, voice_id and optional locale and speed. Limit each request to 1,000 characters; split longer scripts on paragraph boundaries and label the clips. Do not promise one merged file.\n- To clone a Voice I voice, find an audio file with `list_assets` or upload one with REST multipart `POST /v1/assets` (field `files`; wav, mp3, m4a, aac, ogg or flac, ≤20 MB), then call `clone_voice` with that owned audio URL. Delete owned clones with `delete_voice`.\n- To clone a Voice II voice, only clone the speaker's own voice with their explicit agreement:\n  1. Call `get_voice_clone_config`. If `voice_ii_available` is false, explain that and offer Voice I only if the user agrees.\n  2. Ask for the speaker's real name and recording language (one of `locales`). Show the matching `consent_statement` with `{voice_talent_name}` and `{company_name}` filled in, and ask the speaker to record themselves reading it exactly.\n  3. Upload two separate owned files with `POST /v1/assets`: a 5–90 second voice sample and the consent recording (≤90 seconds), both from the same speaker.\n  4. Call `clone_voice` with `voice_version: 2`, `name`, `audio_url`, `voice_talent_name`, `locale`, `consent_audio_url` and `consent_accepted: true`. Set `consent_accepted` only after the speaker has explicitly agreed in this conversation; never fabricate or reuse someone else's consent recording.\n  5. Poll `get_voice` with `voice_version: 2` every few seconds until `state` is `trained` (`selectable: true`) or `failed`. A failed clone cannot be retried with the same inputs; delete it and record again.\n- Generate a stable UUID `idempotency_key` for each write and reuse it only when retrying the same input. Never ask the user for retry keys, budgets or limits. A pending or unknown result is not permission to resubmit with a new key.\n\n## Delivery\n\nReturn the real `audio_url`, format and voice used. A data URL contains the MP3 bytes; save or play it with the client's media tools. Never fabricate a hosted download URL or claim a clone finished before `get_voice` shows it ready.\n\nStandalone TTS follows the platform TTS policy with no separate developer surcharge. Video and avatar workflows bill differently.\n",
      "files": [
        "SKILL.md",
        "references/api.md"
      ]
    },
    {
      "name": "xpilot-avatar",
      "description": "Find, create and inspect X Pilot digital humans from uploaded images or training videos, and synthesize talking-presenter videos with them from text or audio, when the user asks for an X Pilot presenter or avatar video.",
      "source": "---\nname: xpilot-avatar\ndescription: Find, create and inspect X Pilot digital humans from uploaded images or training videos, and synthesize talking-presenter videos with them from text or audio, when the user asks for an X Pilot presenter or avatar video.\n---\n\nUse connected MCP first. For REST read [references/api.md](references/api.md). Installation does not authorize access; credentials stay in client settings or environment variables.\n\n## Source rules\n\n- Use a file in the user's own account: find it with `list_assets` or upload it with REST multipart `POST /v1/assets` (images ≤10 MB, video ≤60 MB), then pass its `asset_id`.\n- `image_url` / `video_url` may be a public http(s) URL supplied by the user. X Pilot downloads third-party content and archives it to that user's owned storage before training. Never search for, guess or invent a source URL, use private-network URLs, or use another person's likeness without authorization.\n\n## Workflow\n\n- List avatars with `list_avatars` and inspect `get_avatar` before choosing a presenter.\n- Create with `create_avatar`: name, avatar_type (`human_video` with a training video, or `virtual_character` with an image plus gender `Male` or `Female`), and the source from the rules above. Generate a stable UUID `idempotency_key` internally. Do not send a consent field or ask the user for API permissions or budgets.\n- Save the avatar ID and poll `get_avatar` about every five seconds, respecting Retry-After, until `status` is `ready` or `failed`. Use `stage` to describe progress and `error.message` to explain a failure. Report `generating`, `failed` and `migration_required` honestly; never recreate or retrain to hide a failure.\n- An avatar is usable in a video only when `selectable` is `true`. `training_status=ready` alone is not enough. Deliver the real preview URLs returned by the resource.\n- Delete owned avatars with `delete_avatar` only when the user asks.\n- If the user runs a receiving service and gives you its public HTTPS URL, pass it as `callback_url` (optional `callback_id`) on `create_avatar` or `create_avatar_video` to get an `avatar.completed` / `avatar.failed` or `avatar-video.completed` / `avatar-video.failed` event instead of polling. Never invent a callback URL.\n\n## Avatar video\n\n- For a standalone talking clip (no course video), call `create_avatar_video` on an avatar whose `training_status` is `ready`. Send exactly one speech source: `text` (≤5,000 characters) with a `voice_id` and its matching `voice_version` (Voice I or Voice II) from `list_voices`, or the user's own speech audio as `asset_id` / public `audio_url`. Speech is capped at 5 minutes.\n- It is paid per rendered second at the digital-human video rate; check credits first and tell the user before submitting long scripts. Generate a stable UUID `idempotency_key`; never resubmit to hide a failure.\n- Poll `get_avatar_video` at least five seconds apart until `status` is `completed` or `failed`. Deliver the returned `video_url` — a transparent-background WebM (VP9 with alpha) meant for compositing — and report `cost_credits` and errors honestly.\n\nDo not clone a voice or publish a video as a side effect. For a full video with this presenter, use the course video Skill and pass the avatar ID as `video_config.avatar_id`; the video request fails with 409 `avatar_not_ready` until the avatar is selectable.\n",
      "files": [
        "SKILL.md",
        "references/api.md"
      ]
    }
  ],
  "tools": [
    {
      "name": "check_credits",
      "scope": "account:read",
      "description": "Read the shared wallet balance in credits. Call before paid operations.",
      "inputSchema": {
        "type": "object",
        "properties": {},
        "required": [],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "check_capacity",
      "scope": "account:read",
      "description": "Read available concurrency slots and the remaining monthly budget of this credential. Call before create_conversation or send_message.",
      "inputSchema": {
        "type": "object",
        "properties": {},
        "required": [],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "list_conversations",
      "scope": "video_agents:read",
      "description": "List owned video conversations, newest first. status is one of queued, running, stopping, paused, completed, failed, stopped; next_action tells what a paused conversation needs. Times are Unix seconds.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "Items per page. Default 20."
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "description": "Items to skip. Default 0."
          }
        },
        "required": [],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "get_conversation",
      "scope": "video_agents:read",
      "description": "Read one conversation summary with status, next_action, cost_credits and preview_video_url. Use list_messages for per-message detail.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "conversation_id": {
            "type": "string",
            "minLength": 1,
            "description": "conversation_id returned by create_conversation or list_conversations."
          }
        },
        "required": [
          "conversation_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "create_conversation",
      "scope": "video_agents:write",
      "description": "Create a video conversation and start the first paid message immediately. Check credits and capacity first. Outline modes: by default (require_outline_confirmation omitted or false) the video is generated straight through without pausing. With require_outline_confirmation=true the message pauses with status=paused and next_action=confirm_outline; show the returned outline to the user, then call send_message with outline_action=confirm (no prompt needed) or outline_action=revise plus a prompt describing the change. Poll list_messages every 5 seconds or more; there is no streaming. Billing matches the app: the platform reserve is held, released at the end, then actual usage is charged (clamped to the balance). cost_credits is the held reserve until the message settles, then the actual charge.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "prompt": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20000,
            "description": "The lesson or video brief: audience, goal, then # title and ## sections with concrete facts, numbers, formulas, dates or ordered steps. Do not name visual components."
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[\\w.:-]{8,128}$",
            "description": "Retry key for this conversation. Generate a UUID once and reuse it only when retrying the same input; a different input with the same key is rejected. Never ask the user for it."
          },
          "require_outline_confirmation": {
            "type": "boolean",
            "description": "Default false (generate straight through). Set true only when the user wants to review the outline first."
          },
          "video_config": {
            "type": "object",
            "description": "Video settings applied when the conversation is created. They cannot be changed by send_message.",
            "properties": {
              "target_language": {
                "type": "string",
                "pattern": "^[A-Za-z]{2,3}([-_][A-Za-z0-9]{2,8})*$",
                "description": "Narration and subtitle language as an ISO 639-1 code or BCP-47 tag, for example en, zh or zh-CN. Default en. Decide before creating; changing it later means a new video."
              },
              "video_duration": {
                "type": "string",
                "enum": [
                  "short",
                  "deep",
                  "long"
                ],
                "description": "short: 1–3 minutes (default). deep: 3–10 minutes. long: 10–20 minutes."
              },
              "video_style": {
                "type": "string",
                "enum": [
                  "auto",
                  "clean_lecture",
                  "science_explainer",
                  "professional_training",
                  "product_showcase",
                  "cinematic_story"
                ],
                "description": "auto (default) lets the agent choose. clean_lecture: courses. science_explainer: science processes. professional_training: workplace training. product_showcase: product demos. cinematic_story: history and narrative."
              },
              "voice_id": {
                "type": "string",
                "description": "Narration voice id from list_voices. Default picks a voice for target_language."
              },
              "voice_version": {
                "type": "integer",
                "enum": [
                  1,
                  2
                ],
                "description": "Version of voice_id: 1 (Voice I, default) or 2. Requires voice_id."
              },
              "locale": {
                "type": "string",
                "description": "Optional native locale of voice_id, for example en-US. Requires voice_id."
              },
              "avatar_id": {
                "type": [
                  "string",
                  "integer"
                ],
                "description": "Presenter avatar id from list_avatars. The avatar must have selectable=true; otherwise create fails with avatar_not_ready."
              },
              "brand_kit_id": {
                "type": [
                  "string",
                  "integer"
                ],
                "description": "Brand kit id from list_brand_kits for colors, logo and tone."
              },
              "enable_initial_ai_video": {
                "type": "boolean",
                "description": "Generate an AI opening clip when the plan allows it. Default false."
              }
            },
            "additionalProperties": false
          },
          "files": {
            "type": "array",
            "maxItems": 5,
            "description": "Up to 5 attachments. Each item uses an owned asset_id or a caller-supplied public http(s) URL. Third-party URLs are archived to the caller’s storage before use.",
            "items": {
              "type": "object",
              "description": "An attachment with asset_id or a public url and its own role.",
              "properties": {
                "asset_id": {
                  "type": [
                    "string",
                    "integer"
                  ],
                  "description": "id returned by POST /v1/assets or list_assets."
                },
                "url": {
                  "type": "string",
                  "pattern": "^https?://",
                  "description": "Caller-supplied public http(s) URL. Third-party content is archived before use."
                },
                "name": {
                  "type": "string",
                  "maxLength": 255,
                  "description": "Optional display name; inferred from the source when omitted."
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "document",
                    "image",
                    "video"
                  ],
                  "description": "Optional; inferred from the file when omitted."
                },
                "files_role": {
                  "type": "string",
                  "enum": [
                    "primary",
                    "reference"
                  ],
                  "description": "How this file shapes the video: primary or reference. Omit to let the agent decide."
                }
              },
              "additionalProperties": false
            }
          },
          "callback_url": {
            "type": "string",
            "maxLength": 2048,
            "description": "Optional public HTTPS URL notified once when this message reaches completed, paused, failed or stopped. Signed with the workspace callback secret (X-XPilot-Signature). Only set it when the user runs a receiving service."
          },
          "callback_id": {
            "type": "string",
            "pattern": "^[\\w.:-]{1,128}$",
            "description": "Optional reference echoed back as data.callback_id. Requires callback_url."
          }
        },
        "required": [
          "prompt",
          "idempotency_key"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "send_message",
      "scope": "video_agents:write",
      "description": "Send a follow-up turn to a conversation that is not running. Paid. Use it to edit the video, answer the assistant (next_action=reply), or confirm/revise the outline (next_action=confirm_outline). Outline modes: by default (require_outline_confirmation omitted or false) the video is generated straight through without pausing. With require_outline_confirmation=true the message pauses with status=paused and next_action=confirm_outline; show the returned outline to the user, then call send_message with outline_action=confirm (no prompt needed) or outline_action=revise plus a prompt describing the change. video_config cannot be changed here.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "conversation_id": {
            "type": "string",
            "minLength": 1,
            "description": "conversation_id returned by create_conversation or list_conversations."
          },
          "prompt": {
            "type": "string",
            "maxLength": 20000,
            "description": "The instruction or answer. Required unless outline_action=confirm."
          },
          "outline_action": {
            "type": "string",
            "enum": [
              "confirm",
              "revise"
            ],
            "description": "Only when next_action=confirm_outline. confirm continues with the outline; revise needs a prompt describing the change."
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[\\w.:-]{8,128}$",
            "description": "Retry key for this message. Generate a UUID once and reuse it only when retrying the same input; a different input with the same key is rejected. Never ask the user for it."
          },
          "files": {
            "type": "array",
            "maxItems": 5,
            "description": "Up to 5 attachments. Each item uses an owned asset_id or a caller-supplied public http(s) URL. Third-party URLs are archived to the caller’s storage before use.",
            "items": {
              "type": "object",
              "description": "An attachment with asset_id or a public url and its own role.",
              "properties": {
                "asset_id": {
                  "type": [
                    "string",
                    "integer"
                  ],
                  "description": "id returned by POST /v1/assets or list_assets."
                },
                "url": {
                  "type": "string",
                  "pattern": "^https?://",
                  "description": "Caller-supplied public http(s) URL. Third-party content is archived before use."
                },
                "name": {
                  "type": "string",
                  "maxLength": 255,
                  "description": "Optional display name; inferred from the source when omitted."
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "document",
                    "image",
                    "video"
                  ],
                  "description": "Optional; inferred from the file when omitted."
                },
                "files_role": {
                  "type": "string",
                  "enum": [
                    "primary",
                    "reference"
                  ],
                  "description": "How this file shapes the video: primary or reference. Omit to let the agent decide."
                }
              },
              "additionalProperties": false
            }
          },
          "callback_url": {
            "type": "string",
            "maxLength": 2048,
            "description": "Optional public HTTPS URL notified once when this message reaches completed, paused, failed or stopped. Signed with the workspace callback secret (X-XPilot-Signature). Only set it when the user runs a receiving service."
          },
          "callback_id": {
            "type": "string",
            "pattern": "^[\\w.:-]{1,128}$",
            "description": "Optional reference echoed back as data.callback_id. Requires callback_url."
          }
        },
        "required": [
          "conversation_id",
          "idempotency_key"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "list_messages",
      "scope": "video_agents:read",
      "description": "List messages of a conversation in order. Poll this for status, next_action, outline (when an outline exists), cost_credits and preview_video_url. completed, paused, failed and stopped are terminal for that message.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "conversation_id": {
            "type": "string",
            "minLength": 1,
            "description": "conversation_id returned by create_conversation or list_conversations."
          }
        },
        "required": [
          "conversation_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "stop_conversation",
      "scope": "video_agents:write",
      "description": "Stop the running message. Poll list_messages until it is stopped; cost already incurred may still be charged.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "conversation_id": {
            "type": "string",
            "minLength": 1,
            "description": "conversation_id returned by create_conversation or list_conversations."
          }
        },
        "required": [
          "conversation_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "delete_conversation",
      "scope": "video_agents:write",
      "description": "Delete an owned conversation and stop any running message. Only when the user asks.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "conversation_id": {
            "type": "string",
            "minLength": 1,
            "description": "conversation_id returned by create_conversation or list_conversations."
          }
        },
        "required": [
          "conversation_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "list_renders",
      "scope": "video_agents:read",
      "description": "List MP4 exports of a conversation. status is queued, running, completed, failed or superseded. progress is a 0–100 percentage when known, otherwise null.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "conversation_id": {
            "type": "string",
            "minLength": 1,
            "description": "conversation_id returned by create_conversation or list_conversations."
          }
        },
        "required": [
          "conversation_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "create_render",
      "scope": "video_agents:write",
      "description": "Export an MP4 after preview_video_url is available. A preview is not an export. Poll get_render and report its 0–100 progress until completed, then return video_url.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "conversation_id": {
            "type": "string",
            "minLength": 1,
            "description": "conversation_id returned by create_conversation or list_conversations."
          },
          "message_id": {
            "type": "string",
            "minLength": 1,
            "description": "Message to export. Default: the latest message with a preview."
          },
          "force": {
            "type": "boolean",
            "description": "Re-export even when a completed export exists. Default false."
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[\\w.:-]{8,128}$",
            "description": "Retry key for this export. Generate a UUID once and reuse it only when retrying the same input; a different input with the same key is rejected. Never ask the user for it."
          }
        },
        "required": [
          "conversation_id",
          "idempotency_key"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "get_render",
      "scope": "video_agents:read",
      "description": "Read one export: status, progress (0–100 or null when unavailable), and video_url when completed.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "conversation_id": {
            "type": "string",
            "minLength": 1,
            "description": "conversation_id returned by create_conversation or list_conversations."
          },
          "render_id": {
            "type": "string",
            "minLength": 1,
            "description": "render_id returned by create_render or list_renders."
          }
        },
        "required": [
          "conversation_id",
          "render_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "delete_render",
      "scope": "video_agents:write",
      "description": "Delete an export record. Only when the user asks.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "conversation_id": {
            "type": "string",
            "minLength": 1,
            "description": "conversation_id returned by create_conversation or list_conversations."
          },
          "render_id": {
            "type": "string",
            "minLength": 1,
            "description": "render_id returned by create_render or list_renders."
          }
        },
        "required": [
          "conversation_id",
          "render_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "list_brand_kits",
      "scope": "brand_kits:read",
      "description": "List owned brand kits. Pass an id as video_config.brand_kit_id.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "Items per page. Default 20."
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "description": "Items to skip. Default 0."
          }
        },
        "required": [],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "get_brand_kit",
      "scope": "brand_kits:read",
      "description": "Read one owned brand kit.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "brand_kit_id": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Brand kit id returned by list_brand_kits or create_brand_kit."
          }
        },
        "required": [
          "brand_kit_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "create_brand_kit",
      "scope": "brand_kits:write",
      "description": "Create a brand kit for logos, theme colors and tone. Only when the user asks.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "brand_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Brand name shown in videos."
          },
          "brand_logo": {
            "type": "string",
            "maxLength": 2048,
            "description": "Logo URL, preferably one returned by POST /v1/assets."
          },
          "content_style_description": {
            "type": "string",
            "maxLength": 20000,
            "description": "Tone and style guidance for scripts, for example \"friendly, concise, avoid jargon\"."
          },
          "theme_color": {
            "type": "object",
            "description": "Theme colors. Each value is #rgb, #rrggbb, #rrggbbaa or rgb()/rgba(); null clears a color.",
            "properties": {
              "primary": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "primary color."
              },
              "secondary": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "secondary color."
              },
              "accent": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "accent color."
              },
              "background": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "background color."
              },
              "text": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "text color."
              },
              "textSecondary": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "textSecondary color."
              },
              "success": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "success color."
              },
              "warning": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "warning color."
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "error color."
              },
              "surface": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "surface color."
              },
              "surfaceLight": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "surfaceLight color."
              },
              "shadow": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "shadow color."
              }
            },
            "additionalProperties": false
          }
        },
        "required": [
          "brand_name"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": false,
        "openWorldHint": false
      }
    },
    {
      "name": "update_brand_kit",
      "scope": "brand_kits:write",
      "description": "Update fields of an owned brand kit. Omitted fields stay unchanged.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "brand_kit_id": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Brand kit id returned by list_brand_kits or create_brand_kit."
          },
          "brand_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Brand name shown in videos."
          },
          "brand_logo": {
            "type": "string",
            "maxLength": 2048,
            "description": "Logo URL, preferably one returned by POST /v1/assets."
          },
          "content_style_description": {
            "type": "string",
            "maxLength": 20000,
            "description": "Tone and style guidance for scripts, for example \"friendly, concise, avoid jargon\"."
          },
          "theme_color": {
            "type": "object",
            "description": "Theme colors. Each value is #rgb, #rrggbb, #rrggbbaa or rgb()/rgba(); null clears a color.",
            "properties": {
              "primary": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "primary color."
              },
              "secondary": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "secondary color."
              },
              "accent": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "accent color."
              },
              "background": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "background color."
              },
              "text": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "text color."
              },
              "textSecondary": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "textSecondary color."
              },
              "success": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "success color."
              },
              "warning": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "warning color."
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "error color."
              },
              "surface": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "surface color."
              },
              "surfaceLight": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "surfaceLight color."
              },
              "shadow": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 50,
                "description": "shadow color."
              }
            },
            "additionalProperties": false
          }
        },
        "required": [
          "brand_kit_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "delete_brand_kit",
      "scope": "brand_kits:write",
      "description": "Delete an owned brand kit. Only when the user asks.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "brand_kit_id": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Brand kit id returned by list_brand_kits or create_brand_kit."
          }
        },
        "required": [
          "brand_kit_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "list_assets",
      "scope": "assets:read",
      "description": "List files uploaded to this account, newest first. Upload new files with REST multipart POST /v1/assets (field files, up to 5 files); MCP cannot carry file bytes.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "Items per page. Default 20."
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "description": "Items to skip. Default 0."
          }
        },
        "required": [],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "list_avatars",
      "scope": "avatars:read",
      "description": "List owned presenter avatars and quota. Only avatars with selectable=true can be used as video_config.avatar_id.",
      "inputSchema": {
        "type": "object",
        "properties": {},
        "required": [],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "list_voices",
      "scope": "voices:read",
      "description": "Find public catalog voices or your own clones. Choose voice_id from the returned items; never guess provider identifiers. Use samples on list/detail items for fixed audition clips.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "voice_version": {
            "type": "integer",
            "description": "Voice I (1, default) or Voice II (2) for the public catalog. Private clone listings include both versions.",
            "enum": [
              1,
              2
            ]
          },
          "type": {
            "type": "string",
            "description": "public shared catalog or private owned clones.",
            "enum": [
              "public",
              "private"
            ]
          },
          "language": {
            "type": "string",
            "description": "Optional language filter aligned with the platform catalog."
          },
          "page": {
            "type": "integer",
            "description": "Page, starting at 1.",
            "minimum": 1
          },
          "limit": {
            "type": "integer",
            "description": "Items per page, up to 100.",
            "minimum": 1,
            "maximum": 100
          }
        },
        "required": [],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "get_voice",
      "scope": "voices:read",
      "description": "Read a public or owned voice, including readiness and samples. Private voices from another account are unavailable. Poll this after clone_voice: a Voice II clone moves from training to trained (selectable=true) or failed.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "voice_id": {
            "type": "string",
            "description": "Catalog or owned clone voice ID."
          },
          "voice_version": {
            "type": "integer",
            "description": "Voice version of the voice: Voice I (1, default) or Voice II (2).",
            "enum": [
              1,
              2
            ]
          }
        },
        "required": [
          "voice_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "generate_audio",
      "scope": "audio:write",
      "description": "Synthesize up to 1,000 characters with a catalog voice or one of your ready clones, in Voice I or Voice II. voice_version must match the voice. Returns MP3 audio_url (which may be a data URL). Uses platform TTS pricing; reuse the same idempotency_key for retries.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "description": "Spoken text.",
            "minLength": 1,
            "maxLength": 1000
          },
          "voice_id": {
            "type": "string",
            "description": "Catalog voice ID or owned ready clone ID."
          },
          "voice_version": {
            "type": "integer",
            "description": "Voice version of voice_id: Voice I (1, default) or Voice II (2). Never substitute one version for the other.",
            "enum": [
              1,
              2
            ]
          },
          "locale": {
            "type": "string",
            "description": "Speech locale. Voice II catalog voices accept their native or secondary locales; Voice II clones accept the locales they support."
          },
          "speed": {
            "type": "number",
            "description": "Playback speed.",
            "minimum": 0.5,
            "maximum": 2
          },
          "idempotency_key": {
            "type": "string",
            "description": "Retry key for this operation. Generate a UUID once and reuse it only when retrying the same input. Never ask the user for it.",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[\\w.:-]{8,128}$"
          }
        },
        "required": [
          "text",
          "voice_id",
          "idempotency_key"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "get_voice_clone_config",
      "scope": "voices:read",
      "description": "Read Voice II clone requirements before clone_voice with voice_version 2: availability, the authorized company_name, supported locales with the consent_statement the speaker must read aloud, and sample duration/size limits. When voice_ii_available is false (for example voice_ii_unavailable_reason clone_disabled), do not attempt a Voice II clone; offer Voice I instead.",
      "inputSchema": {
        "type": "object",
        "properties": {},
        "required": [],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "clone_voice",
      "scope": "audio:write",
      "description": "Clone a private voice from audio already uploaded to your account. Voice I (default) is an instant clone from one sample. Voice II needs the speaker's consent: call get_voice_clone_config, have the speaker record the consent_statement for the chosen locale with their name and company_name filled in, upload it separately, then pass voice_talent_name, locale, consent_audio_url and consent_accepted=true. Never fabricate consent or set consent_accepted without the speaker's explicit agreement. Voice II returns 403 voice_ii_unavailable while cloning is not open for the account. Poll get_voice until state is trained or failed. Reuse idempotency_key on retry.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Voice title.",
            "minLength": 1,
            "maxLength": 80
          },
          "audio_url": {
            "type": "string",
            "description": "Owned uploaded training audio URL. Voice II: 5–90 seconds of the same speaker."
          },
          "cover_image_url": {
            "type": "string",
            "description": "Optional owned uploaded cover image URL. Voice I only."
          },
          "voice_version": {
            "type": "integer",
            "description": "Voice I (1, default) or Voice II (2, consent-based personal voice).",
            "enum": [
              1,
              2
            ]
          },
          "voice_talent_name": {
            "type": "string",
            "description": "Voice II only: the speaker's name exactly as spoken in the consent recording.",
            "minLength": 1,
            "maxLength": 128
          },
          "locale": {
            "type": "string",
            "description": "Voice II only: recording locale from get_voice_clone_config, for example en-US or zh-CN.",
            "maxLength": 16
          },
          "consent_audio_url": {
            "type": "string",
            "description": "Voice II only: owned uploaded recording of the speaker reading the consent_statement for locale. Must be a separate file from audio_url, at most 90 seconds."
          },
          "consent_accepted": {
            "type": "boolean",
            "description": "Voice II only: true when the speaker has explicitly agreed that their voice may be cloned. Required for Voice II."
          },
          "idempotency_key": {
            "type": "string",
            "description": "Retry key for this operation. Generate a UUID once and reuse it only when retrying the same input. Never ask the user for it.",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[\\w.:-]{8,128}$"
          }
        },
        "required": [
          "name",
          "audio_url",
          "idempotency_key"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "delete_voice",
      "scope": "audio:write",
      "description": "Delete an owned cloned voice. Public catalog voices cannot be deleted. Reuse idempotency_key on retry.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "voice_id": {
            "type": "string",
            "description": "Owned clone voice ID."
          },
          "idempotency_key": {
            "type": "string",
            "description": "Retry key for this operation. Generate a UUID once and reuse it only when retrying the same input. Never ask the user for it.",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[\\w.:-]{8,128}$"
          }
        },
        "required": [
          "voice_id",
          "idempotency_key"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "get_avatar",
      "scope": "avatars:read",
      "description": "Read an owned avatar. status is the overall state (draft, generating, ready, failed, deleting, migration_required); training_status and preview_status track training and the preview sample; selectable must be true before video generation can use it; stage names the step in progress (queued, generating_training_video, provider_processing, generating_preview, deleting_provider_asset) or null when idle; error is {code, message} on failure. Training ready alone does not mean it can be used in a video.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "avatar_id": {
            "type": "string",
            "description": "Owned avatar ID."
          }
        },
        "required": [
          "avatar_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "create_avatar",
      "scope": "avatars:write",
      "description": "Create an avatar from an owned asset_id or a public http(s) image/video URL. Third-party URLs are downloaded and archived to the caller’s owned storage before training. Requires platform entitlement. Poll get_avatar for preview_image_url and preview_video_url, or receive avatar.completed / avatar.failed through callback_url or a webhook subscription. Reuse idempotency_key on retry; never retrain automatically.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Avatar name.",
            "minLength": 1,
            "maxLength": 80
          },
          "avatar_type": {
            "type": "string",
            "description": "Training source.",
            "enum": [
              "human_video",
              "virtual_character"
            ]
          },
          "asset_id": {
            "type": "string",
            "description": "Owned asset ID returned by POST /v1/assets. human_video uses a video asset; virtual_character uses an image asset."
          },
          "image_url": {
            "type": "string",
            "description": "Public http(s) image URL. The backend archives third-party content to your owned storage before training. Use this or asset_id for virtual_character."
          },
          "video_url": {
            "type": "string",
            "description": "Public http(s) training video URL. The backend archives third-party content to your owned storage before training. Use this or asset_id for human_video."
          },
          "gender": {
            "type": "string",
            "description": "Required for image avatars.",
            "enum": [
              "Male",
              "Female"
            ]
          },
          "callback_url": {
            "type": "string",
            "description": "Optional public HTTPS URL notified once with avatar.completed or avatar.failed. Signed with the workspace callback secret (X-XPilot-Signature). Only set it when you run a receiving service.",
            "maxLength": 2048
          },
          "callback_id": {
            "type": "string",
            "description": "Optional reference echoed back as data.callback_id. Requires callback_url.",
            "maxLength": 128,
            "pattern": "^[\\w.:-]{1,128}$"
          },
          "idempotency_key": {
            "type": "string",
            "description": "Retry key for this operation. Generate a UUID once and reuse it only when retrying the same input. Never ask the user for it.",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[\\w.:-]{8,128}$"
          }
        },
        "required": [
          "name",
          "avatar_type",
          "idempotency_key"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "delete_avatar",
      "scope": "avatars:write",
      "description": "Delete an owned avatar. Poll get_avatar or list_avatars until it disappears. Reuse idempotency_key on retry.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "avatar_id": {
            "type": "string",
            "description": "Owned avatar ID."
          },
          "idempotency_key": {
            "type": "string",
            "description": "Retry key for this operation. Generate a UUID once and reuse it only when retrying the same input. Never ask the user for it.",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[\\w.:-]{8,128}$"
          }
        },
        "required": [
          "avatar_id",
          "idempotency_key"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": true,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "create_avatar_video",
      "scope": "avatars:write",
      "description": "Synthesize a talking-presenter video with an owned avatar whose training_status is ready. Drive it with text + voice_id (platform TTS, Voice I or Voice II) or with your own speech audio (asset_id or a public http(s) audio_url) — exactly one source, up to 5 minutes of speech. Output is a transparent-background WebM (VP9 with alpha) for compositing. Billed per rendered second at the digital-human video rate; the balance must cover the estimate before dispatch. Poll get_avatar_video, or receive avatar-video.completed / avatar-video.failed through callback_url or a webhook subscription. Reuse idempotency_key on retry; never resubmit automatically.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "avatar_id": {
            "type": "string",
            "description": "Owned avatar ID with training_status ready."
          },
          "text": {
            "type": "string",
            "description": "Spoken text for platform TTS. Use with voice_id; omit when sending audio.",
            "minLength": 1,
            "maxLength": 5000
          },
          "voice_id": {
            "type": "string",
            "description": "Catalog voice ID or owned ready clone ID from list_voices. Required with text."
          },
          "voice_version": {
            "type": "integer",
            "description": "Voice version of voice_id: Voice I (1, default) or Voice II (2).",
            "enum": [
              1,
              2
            ]
          },
          "locale": {
            "type": "string",
            "description": "Optional native locale for the voice."
          },
          "speed": {
            "type": "number",
            "description": "TTS playback speed.",
            "minimum": 0.5,
            "maximum": 2
          },
          "audio_url": {
            "type": "string",
            "description": "Public http(s) speech audio URL (≤20 MB). The backend archives third-party audio to your owned storage first. Use this or asset_id instead of text."
          },
          "asset_id": {
            "type": "string",
            "description": "Owned audio asset ID returned by POST /v1/assets. Use this or audio_url instead of text."
          },
          "callback_url": {
            "type": "string",
            "description": "Optional public HTTPS URL notified once with avatar-video.completed or avatar-video.failed. Signed with the workspace callback secret (X-XPilot-Signature). Only set it when you run a receiving service.",
            "maxLength": 2048
          },
          "callback_id": {
            "type": "string",
            "description": "Optional reference echoed back as data.callback_id. Requires callback_url.",
            "maxLength": 128,
            "pattern": "^[\\w.:-]{1,128}$"
          },
          "idempotency_key": {
            "type": "string",
            "description": "Retry key for this operation. Generate a UUID once and reuse it only when retrying the same input. Never ask the user for it.",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[\\w.:-]{8,128}$"
          }
        },
        "required": [
          "avatar_id",
          "idempotency_key"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    },
    {
      "name": "get_avatar_video",
      "scope": "avatars:read",
      "description": "Read an avatar video job: status (queued, processing, completed, failed), video_url (transparent WebM) once completed, duration_seconds, width, height and the settled cost_credits. Poll at least 5 seconds apart.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "avatar_id": {
            "type": "string",
            "description": "Owned avatar ID."
          },
          "video_id": {
            "type": "string",
            "description": "Video ID returned by create_avatar_video."
          }
        },
        "required": [
          "avatar_id",
          "video_id"
        ],
        "additionalProperties": false
      },
      "annotations": {
        "readOnlyHint": true,
        "destructiveHint": false,
        "idempotentHint": true,
        "openWorldHint": false
      }
    }
  ]
}
