API Integration Guide

视频生成开放接口 · 对接文档

测试环境 快速联调信息

以下为测试渠道信息,仅用于对接自测;正式渠道请以我方单独下发为准。

测试请求地址
https://mfapi.mailxiuyun.com 复制
测试渠道 ID
CH_26954D42995D444C 复制
测试证书(RSA 公钥)
1

开始之前

接入前我方会向你提供:

  1. 请求地址
  2. 渠道号(也叫渠道标识),形如 CH_xxxxxxxx,是你在系统中的唯一身份;
  3. RSA 公钥证书(一个 .pem 文件),用于对每一次请求做签名。

接入测试请使用上方提供的凭证进行联调,对接成功后切入生产环境之前再向我方索取正式参数,测试参数不可用于生产。

切生产环境之前,还需准备以下两点:

  • IP加白:把你方生产端IP发给我们加白,测试环境无需IP白名单,生产环境必须加白IP,如有负载均衡,请上报全部IP
  • 充值视频时长余量:切生产环境之前需联系我方加余量,否则无法调用。
2

请求鉴权

除健康检查外,所有接口都需要在请求头里带上三个字段。每次请求都要重新计算,不能复用。

请求头说明
X-Channel-Id你的渠道号,例如 CH_xxxxxxxx
X-Timestamp当前时间的 Unix 时间戳,单位为秒
X-Channel-Sign签名,见下方生成方式

另外提交任务时需要带 Content-Type: application/json

签名怎么算

规则很简单,一句话概括:用我方给你的公钥,加密「渠道号 + 时间戳」这串文本,再做 Base64 编码,就是签名。

  1. 取当前时间戳(秒),比如 1752480000,这个值同时用于 X-Timestamp
  2. 把渠道号和时间戳直接拼接成一个字符串,中间没有任何分隔符。例如渠道号 CH_abc123 + 时间戳 1752480000,拼出来就是 CH_abc1231752480000
  3. 用公钥对这个字符串做 RSA 加密,加密方案为 RSA/ECB/PKCS1Padding(即 PKCS#1 v1.5)。
  4. 对加密结果做 Base64 编码,得到的字符串放进 X-Channel-Sign
⚠️
三个要点:① 时间戳有效窗口默认 ±300 秒,请确保调用服务器时间准确(建议开启 NTP 同步),否则会被判为过期;② 拼接顺序固定「渠道号在前、时间戳在后」,顺序错了签名过不了;③ 每次请求都要用当前时间戳重新生成签名,签名不可缓存复用。

Python 示例(pycryptodome)

pythonimport time, base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

CHANNEL_ID = "CH_abc123"                 # 我方提供的渠道号
PUBLIC_KEY_PATH = "channel_public.pem"   # 我方提供的公钥证书路径


def build_auth_headers():
    ts = str(int(time.time()))
    plain = (CHANNEL_ID + ts).encode("utf-8")

    with open(PUBLIC_KEY_PATH, "rb") as f:
        public_key = RSA.import_key(f.read())

    cipher = PKCS1_v1_5.new(public_key)
    sign = base64.b64encode(cipher.encrypt(plain)).decode("utf-8")

    return {
        "X-Channel-Id": CHANNEL_ID,
        "X-Timestamp": ts,
        "X-Channel-Sign": sign,
    }
💡
依赖安装:pip install pycryptodome

Java 示例

javaimport javax.crypto.Cipher;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class ChannelSign {

    // 我方提供的渠道号与公钥证书路径
    static final String CHANNEL_ID = "CH_abc123";
    static final String PUBLIC_KEY_PATH = "channel_public.pem";

    // 生成鉴权请求头:X-Channel-Id / X-Timestamp / X-Channel-Sign
    public static Map<String, String> authHeaders() throws Exception {
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        String sign = sign(CHANNEL_ID, timestamp, PUBLIC_KEY_PATH);

        Map<String, String> headers = new HashMap<>();
        headers.put("X-Channel-Id", CHANNEL_ID);
        headers.put("X-Timestamp", timestamp);
        headers.put("X-Channel-Sign", sign);
        return headers;
    }

    // 用公钥加密「渠道号 + 时间戳」,Base64 后即为签名
    public static String sign(String channelId, String timestamp, String publicKeyPath) throws Exception {
        String pem = new String(Files.readAllBytes(Paths.get(publicKeyPath)));
        String base64 = pem.replace("-----BEGIN PUBLIC KEY-----", "")
                           .replace("-----END PUBLIC KEY-----", "")
                           .replaceAll("\\s", "");
        byte[] keyBytes = Base64.getDecoder().decode(base64);
        PublicKey publicKey = KeyFactory.getInstance("RSA")
                .generatePublic(new X509EncodedKeySpec(keyBytes));

        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encrypted = cipher.doFinal((channelId + timestamp).getBytes("UTF-8"));
        return Base64.getEncoder().encodeToString(encrypted);
    }
}
3

接口一:提交视频生成任务

POSThttps://<接口域名>/ttv/jobs

提交成功后接口立即返回任务 ID,视频在后台生成。请求体为 JSON

请求参数

参数类型必填说明
textstring视频文案内容,100~5000 字
ratenumber语速倍率,仅支持 0.8 / 1.0 / 1.2 / 1.5 四档
subtitleboolean是否显示字幕,true 开启 / false 关闭
bgmModestring背景音乐开关:llm 开启(系统智能选曲)/ none 关闭
bgmVolumenumber背景音乐音量倍率,取值 01.0,推荐 0.7(即 70%)
voicestring音色代号,取值见下方「开放音色」表
clientJobIdstring你自己的业务单号,用于幂等:相同值重复提交返回同一任务,不会重复出片
  • 语速请严格使用四个挡位之一,传其他值可能不生效。
  • 背景音乐音量0.7 表示压到原音量的 70%,日常推荐 0.7 左右,你也可以按需要设一个自己的默认值。

开放音色

voice 参数请从下表选择,直接填「音色代号」列的值即可。

A 组 代号为数字
内部音色名音色代号
沉稳青叔 603006
情感姐姐 602003
青年解说 502005
温柔女声 502001
知心暖男 603005
亲和女声 602005
随和沉稳 603003
活力女声 502003
阳光男孩 502006
客服姐姐 502004
阅读男声 101004
新闻女声 101011
磁性男声 101021
自然轻快 101030
B 组 代号为英文
内部音色名音色代号
温柔女声 Serena
阳光男孩 Ethan
暴躁女声 Vivian
乖巧女声 Mia
新闻主持 Neil
讲解讲师 Elias
粤语阿强 Rocky
四川辣妹 Sunny

请求示例

json{
  "text": "近日,我国新能源产业再传捷报。据最新数据显示,今年上半年新能源汽车销量同比增长超过35%。",
  "rate": 1.0,
  "subtitle": true,
  "bgmMode": "llm",
  "bgmVolume": 0.7,
  "voice": "603006",
  "clientJobId": "biz-20260714-0001"
}

响应示例

json{
  "code": 0,
  "ok": true,
  "msg": "操作成功",
  "data": {
    "jobId": "ttv-3f9a1c2b-1752480000",
    "status": "queued",
    "clientJobId": "biz-20260714-0001"
  }
}

data.jobId 是这条任务的唯一标识,后续查询进度都以它为准,请务必保存。statusqueued 表示已进入排队。

4

接口二:查询任务状态

GEThttps://<接口域名>/ttv/jobs/{jobId}

把提交时拿到的 jobId 拼到路径上,请求头同样带上三件套鉴权字段。

响应字段

json{
  "code": 0,
  "ok": true,
  "data": {
    "jobId": "ttv-3f9a1c2b-1752480000",
    "clientJobId": "biz-20260714-0001",
    "status": "succeeded",
    "progress": "完成",
    "progressPct": 100,
    "ossUrl": "https://oss.example.com/output/xxx.mp4",
    "title": "视频标题",
    "duration": 25.6,
    "error": null,
    "createdAt": 1752480000,
    "finishedAt": 1752480180
  }
}
字段说明
status任务状态,取值见下
progress当前阶段的中文描述,如「配音」「合成」
progressPct进度百分比,0~100
ossUrl成片下载地址,仅在 status=succeeded 时有值,链接有效期5分钟
title系统自动生成的视频标题
duration成片时长(秒)
error失败原因,仅在 status=failed 时有值
createdAt / finishedAt创建、完成时间(Unix 秒)

状态说明

status含义
queued排队中
running生成中
uploading上传成片中
succeeded已完成,可从 ossUrl 下载
failed失败,原因见 error

正常流转为:queuedrunninguploadingsucceeded;中途出错则转为 failed

轮询建议

  • 任务提交后,请由你方主动轮询查询接口获取进度,我方不会主动推送结果。
  • 轮询不要过于频繁,建议每 10 秒查询一次,直到状态变为 succeededfailed;请求过密可能触发限流。
  • 一条视频的生成通常在数十秒到十几分钟之间,与文案长度、素材匹配等因素相关。
  • 查询进度是获取任务结果的唯一方式,请在提交后持续轮询,直至拿到终态。

视频地址说明

  • 视频生成成功后,链接地址有效期为5分钟
  • 请在视频生成成功后5分钟内通过我方提供的视频地址下载视频到你方资产库,超时视频将丢失。
5

错误处理

鉴权或参数有问题时,接口会返回对应的 HTTP 状态码,响应体形如:

json{ "code": 401, "ok": false, "msg": "RSA签名验证失败", "data": null }
HTTP提示排查方向
401缺少渠道标识 / 渠道不存在 / 渠道已禁用检查 X-Channel-Id 是否正确、渠道是否启用
401缺少时间戳 / 时间戳已过期检查服务器时间,确保与标准时间偏差在 300 秒内
401缺少签名 / RSA签名验证失败 / RSA解密失败检查签名的拼接顺序、加密方案、公钥是否用对
403IP不在白名单用白名单内的服务器 IP 调用,或联系我方调整
403余量小于10分钟,请及时充值视频时长余量不足,联系我方充值
业务成功时 code0oktrue;判断成功与否请以 ok 字段为准。
6

完整调用示例(Python)

下面是一段可直接参考的端到端示例:提交任务,然后轮询到出片完成。

pythonimport time, base64, json, requests
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

BASE_URL = "https://<接口域名>"
CHANNEL_ID = "CH_abc123"
PUBLIC_KEY_PATH = "channel_public.pem"

with open(PUBLIC_KEY_PATH, "rb") as f:
    PUBLIC_KEY = RSA.import_key(f.read())


def auth_headers():
    ts = str(int(time.time()))
    cipher = PKCS1_v1_5.new(PUBLIC_KEY)
    sign = base64.b64encode(cipher.encrypt((CHANNEL_ID + ts).encode())).decode()
    return {
        "X-Channel-Id": CHANNEL_ID,
        "X-Timestamp": ts,
        "X-Channel-Sign": sign,
    }


# 1. 提交任务
body = {
    "text": "近日,我国新能源产业再传捷报,上半年新能源汽车销量同比增长超过35%。",
    "rate": 1.0,
    "subtitle": True,
    "bgmMode": "llm",
    "bgmVolume": 0.7,
    "voice": "603006",
    "clientJobId": "biz-20260714-0001",
}
headers = auth_headers()
headers["Content-Type"] = "application/json"

resp = requests.post(f"{BASE_URL}/ttv/jobs", data=json.dumps(body), headers=headers, timeout=30)
job_id = resp.json()["data"]["jobId"]
print("任务已提交:", job_id)

# 2. 主动轮询进度(建议间隔 10 秒,不要过于频繁)
while True:
    time.sleep(10)
    r = requests.get(f"{BASE_URL}/ttv/jobs/{job_id}", headers=auth_headers(), timeout=30)
    data = r.json()["data"]
    print(f"[{data['progressPct']}%] {data['status']} - {data.get('progress')}")
    if data["status"] == "succeeded":
        print("出片完成,下载地址:", data["ossUrl"])
        break
    if data["status"] == "failed":
        print("出片失败:", data.get("error"))
        break