22 KiB
即梦 API · Golang 对接最小参考
0. 全局须知(对接前必读)
- 生成任务消耗 token(仅超级会员限时可用);token 包独立购买,与即梦网页端积分不通用。
- 任务提交按能力策略预扣 token;成功按实际用量结算(多退少补);失败全额返还。
- 产物链接有效期为 24 小时,生成后请在 24h 内下载保存,逾期失效。
- 限流:任务提交 10 qps(用户级),任务查询 20 qps(用户级)。
- 所有接口均为
POST,Content-Type: application/json。
1. 鉴权(AK/SK 签名)★必须完整实现
调用方持一对 AccessKey(AK) / SecretKey(SK)。SK 只在创建时明文返回一次,服务端不提供二次查询,自行妥善保存。
1.1 请求头(8 个字段全部必填)
| Header | 必填 | 说明 |
|---|---|---|
X-Agent-Access-Key |
是 | AK,服务端签发字符串 |
X-Agent-Timestamp |
是 | 请求发起 Unix 时间戳(秒)。服务端允许 ±300 秒偏差,超出视为重放 |
X-Agent-Nonce |
是 | 本次随机串(推荐 UUID)。同一 AK+Nonce 在时间窗口内去重,防重放 |
X-Agent-Body-SHA256 |
是 | HTTP Body 的 SHA-256 摘要(小写十六进制);空 Body 也要传空字符串的 SHA-256 |
X-Agent-Signature-Method |
是 | 固定 HMAC-SHA256 |
X-Agent-Version |
是 | 固定 v1 |
X-Agent-Signature |
是 | 签名结果,Base64URL 编码(不含 padding),长度 = HMAC-SHA256 输出 32 字节 |
Content-Type |
是 | 固定 application/json(该字段参与签名) |
1.2 签名算法
按顺序拼接 8 个字段,字段间用换行符 \n 分隔构造 StringToSign,再用 SK 做 HMAC-SHA256:
StringToSign = METHOD + "\n"
+ PATH + "\n"
+ CANONICAL_QUERY + "\n"
+ CONTENT_TYPE + "\n"
+ SHA256(RequestBody) + "\n"
+ TIMESTAMP + "\n"
+ NONCE + "\n"
+ ACCESS_KEY
Signature = Base64URL( HMAC-SHA256(SK, StringToSign) )
各字段规则:
METHOD:HTTP 方法全大写,本 API 均为POST。PATH:请求路径含前缀,如/agent_openapi/v1/video/submit。CANONICAL_QUERY:query string 按参数名 ASCII 升序排序,每个 key/value 分别按 RFC3986 做 URL Encode(空格编码为%20,~保留不编码),最后用&连接;无 query 时为空字符串。CONTENT_TYPE:与请求头完全一致,即application/json。SHA256(RequestBody):Body 的小写 SHA-256 十六进制,与X-Agent-Body-SHA256头一致。TIMESTAMP / NONCE / ACCESS_KEY:与对应请求头完全一致。
1.3 Golang 签名实现(标准库,无第三方依赖)
package main
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
const (
host = "https://jimeng.jianying.com"
path = "/agent_openapi/v1/video/submit"
method = "POST"
contentType = "application/json"
accessKey = "your_access_key"
secretKey = "your_secret_key"
)
func main() {
body := []byte(`{"capability_key":"pippit_avatar_marketing_agent","action_key":"generate_video","run_id":"biz-avatar-005","avatar_marketing_input":{"product_name":"【高级美容护肤品拍一发三】虾青素抗皱面霜紧致修护补水保湿身体","product_image_url_list":["https://lf3-static.bytednsdoc.com/obj/eden-cn/upseh7fkuhm/product1.jpeg"],"model_image_url_list":["https://lf3-static.bytednsdoc.com/obj/eden-cn/upseh7fkuhm/model.jpeg"]}}`)
query := url.Values{}
bodySHA256 := sha256Hex(body)
canonicalQuery := canonicalQueryString(query)
timestamp := fmt.Sprintf("%d", time.Now().Unix())
nonce, err := randomNonce()
if err != nil {
panic(err)
}
stringToSign := method + "\n" +
path + "\n" +
canonicalQuery + "\n" +
contentType + "\n" +
bodySHA256 + "\n" +
timestamp + "\n" +
nonce + "\n" +
accessKey
signature := hmacSHA256Base64URL(secretKey, stringToSign)
requestURL := host + path
if canonicalQuery != "" {
requestURL += "?" + canonicalQuery
}
req, err := http.NewRequest(method, requestURL, bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", contentType)
req.Header.Set("X-Agent-Access-Key", accessKey)
req.Header.Set("X-Agent-Timestamp", timestamp)
req.Header.Set("X-Agent-Nonce", nonce)
req.Header.Set("X-Agent-Body-SHA256", bodySHA256)
req.Header.Set("X-Agent-Signature-Method", "HMAC-SHA256")
req.Header.Set("X-Agent-Version", "v1")
req.Header.Set("X-Agent-Signature", signature)
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
rspBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println("status:", resp.StatusCode)
fmt.Println(string(rspBody))
}
func canonicalQueryString(query url.Values) string {
keys := make([]string, 0, len(query))
for key := range query {
keys = append(keys, key)
}
sort.Strings(keys)
parts := make([]string, 0)
for _, key := range keys {
values := append([]string(nil), query[key]...)
sort.Strings(values)
for _, value := range values {
parts = append(parts, urlEncode(key)+"="+urlEncode(value))
}
}
return strings.Join(parts, "&")
}
func urlEncode(value string) string {
encoded := url.QueryEscape(value)
encoded = strings.ReplaceAll(encoded, "+", "%20")
encoded = strings.ReplaceAll(encoded, "%7E", "~")
return encoded
}
func sha256Hex(data []byte) string {
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:])
}
func hmacSHA256Base64URL(secret, payload string) string {
mac := hmac.New(sha256.New, []byte(secret))
_, _ = mac.Write([]byte(payload))
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
}
func randomNonce() (string, error) {
buf := make([]byte, 16)
_, err := rand.Read(buf)
if err != nil {
return "", err
}
return hex.EncodeToString(buf), nil
}
1.4 常见签名失败原因(报错 10006 时逐条排查)
- 本地时钟偏差超过 300 秒。
X-Agent-Body-SHA256不是小写十六进制。- Base64URL 带了
=padding。 - 多个同名 query 参数未分别 encode 后再字典序拼接。
- 计算时用的
Content-Type与实际发送的不一致。 - 同一 AK 的 nonce 在 300 秒窗口内重复。
2. 通用请求 / 响应结构
2.1 提交任务请求体通参
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
capability_key |
string | 是 | 能力标识,见「能力对照」 |
action_key |
string | 是 | 具体动作标识,须与 capability_key 匹配 |
run_id |
string | 否 | 业务侧幂等键。同一 AK 下重复提交同一 run_id 返回同一任务;未传由系统生成 |
input |
object | 是 | 能力相关业务入参,不同能力结构不同(见各能力章节) |
注:营销视频/剧情营销 使用字段名
avatar_marketing_input而非input;短剧与短片创作使用input。
2.2 通用响应结构
{ "code": "0", "message": "success", "request_id": "20260708xxxxxxxx", "data": { ... } }
| 字段 | 类型 | 说明 |
|---|---|---|
code |
string/int64 | 业务错误码,0 表示成功;非零见「错误码」 |
message |
string | 错误信息或成功描述 |
request_id |
string | 请求追踪 ID(LogID),排障必须提供该字段 |
data |
object | 业务数据,不同接口结构不同 |
提交接口 data 通常含:task_id(后续查询主键)、run_id、thread_id(短剧会话 ID,链路串联依赖)、status(一般为 QUEUED)、capability_key、action_key、created_at(Unix 秒)。
2.3 任务状态机
| status | 含义 |
|---|---|
CREATED |
已创建,等待入队 |
QUEUED |
已入队,等待调度 |
RUNNING |
Agent 任务执行中 |
SUCCESS |
成功终态,从 data 读取产物 |
FAILED |
失败终态,err_code/err_msg 给出原因 |
CANCELED |
已取消终态 |
2.4 计费字段(query 返回 data.usage)
| 字段 | 说明 |
|---|---|
withhold_token |
预扣 Token(执行时冻结) |
actual_token |
实际结算 Token(成功终态生效) |
refund_token |
返还 Token(失败或结算预扣差额返还) |
withhold_quota / actual_quota |
短片/剧情营销额外返回:视频=预估/实际总时长,图片=张数 |
3. 接口清单
Base:https://jimeng.jianying.com,全部 POST + 鉴权头。
| 用途 | 接口 | 能力 |
|---|---|---|
| 短剧提交 | /agent_openapi/v1/novel/submit |
pippit_novel_agent |
| 短剧查询 | /agent_openapi/v1/novel/query |
pippit_novel_agent |
| 视频提交 | /agent_openapi/v1/video/submit |
带货营销 / 剧情营销 / 短片创作 |
| 视频查询 | /agent_openapi/v1/video/query |
带货营销 / 剧情营销 / 短片创作 |
4. 能力对照(capability_key / action_key)
| 业务能力 | capability_key | 支持的 action_key |
|---|---|---|
| 短剧/漫剧 | pippit_novel_agent |
script_analysis(剧本解析)、narration_design(旁白改编·可选)、character_generate(角色生成)、scene_generate(场景生成)、storyboard_design(分镜设计)、shot_video_generate(分镜短片 Seedance2.0)、shot_video_generate_fast(Seedance2.0 fast)、shot_video_compose(成片合成) |
| 带货营销视频 | pippit_avatar_marketing_agent |
generate_video(另有 avatar_marketing_agent_v2 待开放) |
| 剧情营销视频 | pippit_story_marketing_agent |
generate_video |
| 短片创作 | pippit_video_part_agent |
generate_video |
5. 短剧 Agent(novel,多阶段流水线)
短剧是一串串联的阶段任务,用 thread_id(会话)+ asset_id(资产)把链路串起来:
script_analysis ─→ narration_design(可选) ─→ character_generate / scene_generate ─→ storyboard_design ─→ shot_video_generate(_fast) ─→ shot_video_compose
5.1 提交任务 POST /agent_openapi/v1/novel/submit
Body:
{
"capability_key": "pippit_novel_agent",
"action_key": "<各阶段动作>",
"run_id": "biz-novel-003",
"input": { ... }
}
input 字段(是否必填取决于 action_key):
| 字段 | 类型 | 必填时机 | 说明 |
|---|---|---|---|
thread_id |
string | 除 script_analysis 外均需回填 |
剧本分析返回的会话 ID |
asset_id |
string | 除 script_analysis 外均需回填 |
narration_design/character_generate/scene_generate 传 OverviewAssetID(蓝图资产);storyboard_design/shot_video_generate/shot_video_generate_fast/shot_video_compose 传 StoryboardAssetID(分镜资产) |
visual_style |
string | 是(script_analysis) | 视觉风格,如 "2D,国风,平涂"、"3D,CG动画,写实都市"、"真人写实,电影风格,冷色调" |
video_ratio |
string | 是(script_analysis) | "16:9" / "9:16" |
file_url |
string | 是(script_analysis) | 剧本文件 URL,需公网可访问 |
file_type |
string | 是(script_analysis) | "txt" / "docx" |
file_name |
string | 是(script_analysis) | 剧本文件名 |
shot_ids |
string[] | 是(shot_video_generate / _fast) | 分镜脚本编码列表,如 ["S1","S2"] |
enable_watermark |
bool | 否 | 是否加水印,默认 false |
5.2 查询任务 POST /agent_openapi/v1/novel/query
请求 Body:task_id 或 run_id 二选一。
响应关键字段(data):
status/usage/thread_id/start_at/elapsed_seconds:同通用结构。novel_data.overview_asset_id:蓝图资产 ID,后续角色/场景/分镜/视频链路的关键入口。novel_data.resp_data:JSON 字符串,需再次反序列化才能读取内部字段。
resp_data 二次反序列化后的关键字段(对接必须会用):
| 字段 | 说明 |
|---|---|
OverviewAssetID |
蓝图资产 ID → 旁白/角色/场景阶段回填为 asset_id |
StoryboardAssetID |
分镜资产 ID(在 StoryboardBriefs[] 内)→ 分镜/短片/成片阶段回填为 asset_id |
Settings.VideoRatio / Settings.VisualStyle |
比例与风格(与提交一致) |
EpisodeAssets[] |
每集:EpisodeID、EpisodeAssetID、StoryboardAssetID、CharacterAssetIDs、SceneAssetIDs |
CharacterAssets[] |
角色:CharacterID/Name、CharacterAssetID、IsMainCharacter |
SceneAssets[] |
场景:SceneAssetID、EpisodeAssetIDs |
ScriptAssetID / CoreElement |
剧本资产与核心要素 |
链路取
asset_id的规则:narration_design、character_generate、scene_generate取 OverviewAssetID;storyboard_design、shot_video_generate(_fast)、shot_video_compose取对应集的 StoryboardAssetID。
6. 视频类能力(video:带货营销 / 剧情营销 / 短片创作)
三类共用 POST /agent_openapi/v1/video/submit 与 /agent_openapi/v1/video/query,仅 capability_key 与入参对象不同。
6.1 带货营销 pippit_avatar_marketing_agent
Body 使用 avatar_marketing_input:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
prompt |
string | 否 | 自定义营销 prompt |
product_name |
string | 是 | 商品名称,用于文案/素材语义理解 |
product_image_url_list |
array | 是 | 商品图 1~3 张 |
model_image_url_list |
array | 否 | 模特图,最多 1 张 |
enable_watermark |
bool | 否 | 默认无水印 |
6.2 剧情营销 pippit_story_marketing_agent
Body 使用 avatar_marketing_input:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
prompt |
string | 否 | 自定义剧情营销 prompt |
model |
string | 否 | 默认 seedance20_fast;可选 seedance25 / seedance20 / seedance20_fast / seedance20_mini |
duration |
int | 否 | 秒,默认 15s;seedance25 最长 30s |
product_name |
string | 是 | 商品名称 |
product_image_url_list |
array | 是 | 商品图 1~9 张 |
model_image_url_list |
array | 否 | 模特图,最多 1 张 |
enable_watermark |
bool | 否 | 默认无水印 |
6.3 短片创作 pippit_video_part_agent
Body 使用 input:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
prompt |
string | 是 | 提示词 |
image_url_list |
string[] | 否 | 参考图,最多 9 张,公网可访问 |
video_url_list |
string[] | 否 | 参考视频,最多 3 条;seedance25 总时长 ≤30s,其他 ≤15s |
audio_url_list |
string[] | 否 | 参考音频,最多 3 条;时长限制同上 |
model |
string | 否 | 默认 seedance20_fast,可选同剧情营销 |
duration |
int32 | 否 | ≥5s,默认 15s;seedance25 最大 30s,其他 15s |
ratio |
string | 否 | 21:9 / 16:9 / 4:3 / 1:1 / 3:4 / 9:16 |
resolution |
string | 否 | 默认 720P;seedance25:480/720/1080;seedance20:480/720/1080/4K;fast/mini:480/720 |
seed |
int32 | 否 | 随机种子,不传由下游生成 |
enable_watermark |
bool | 否 | 默认 false |
6.4 图片通用规格(所有引用图片,带货营销 ≤3 张,剧情/短片 ≤9 张)
- 格式:JPEG、PNG、WebP、BMP、TIFF、GIF、HEIC、HEIF。
- Seedance 2.0:宽高比
(0.4, 2.5)不含边界;宽高各(300, 6000)px 不含边界。 - Seedance 2.5:宽高比
[0.4, 2.5]含边界;宽高各[300, 6000]px 含边界。 - 单张 < 30 MB,URL 需公网可访问。
6.5 查询任务 POST /agent_openapi/v1/video/query
请求 Body:task_id 或 run_id 二选一。
响应关键字段(data):
| 字段 | 说明 |
|---|---|
status |
状态机取值 |
usage.* |
计费明细(短片/剧情营销含 withhold_quota / actual_quota) |
video_artifact.url |
成品视频下载 URL(24h 有效) |
video_artifact.cover_url |
封面图 |
video_artifact.duration |
成片时长(秒) |
video_artifact.width / height |
成片像素 |
start_at / elapsed_seconds |
时间信息 |
err_code / err_msg |
终态 FAILED 时才有 |
7. 错误码(对接需处理的分段与关键码)
分段:1xxxx=参数/鉴权/账号风控(可校对重试);2xxxx=业务规则(配额/审核/下游终态);5xxxx=系统内部(稍后重试)。
| code | 含义 | 建议 |
|---|---|---|
0 |
成功 | — |
10001 |
参数非法 | 校验请求体字段 |
10002 |
AK 不存在 | 确认 AK,重新签发 |
10003 |
AK 被禁用/冻结 | 联系管理员 |
10004 |
未授权/UserID 缺失 | 确认 AK 已开通 |
10005 |
未知能力/动作 | 核对 capability_key/action_key |
10006 |
签名校验失败 | 见 §1.4 逐项排查 |
10007 |
身份权限不满足 | 确认账号具备能力权限(如即梦 Ultra 会员) |
10008 |
请求频率超限 | 降并发(提交 10qps / 查询 20qps) |
10020 |
账号风控拦截 | 联系管理员,提供 request_id |
20001 |
任务不存在 | 核对 task_id / run_id |
20002 |
Token 余额不足/预扣失败 | 充值或稍后重试 |
20004 |
计费失败 | 稍后重试,持续失败给 request_id |
20010~20016 |
输入/输出音频/文本/图片/视频审核未通过 | 更换合规素材 / 调整 prompt,可重试 |
20020 |
下游任务执行失败 | 确定性终态;重试仍失败给 request_id |
20021 |
下游任务已取消 | 确定性终态 |
50000 |
系统内部错误 | 稍后重试 |
50001 |
下游任务轮询超时 | 稍后重试 |
8. Golang 对接骨架(提交 → 轮询)
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"yourmod/signer" // §1.3 的签名包
)
const (
submitVideoPath = "/agent_openapi/v1/video/submit"
queryVideoPath = "/agent_openapi/v1/video/query"
)
type Client struct {
signer *signer.Config
hc *http.Client
}
func NewClient(ak, sk string) *Client {
return &Client{
signer: &signer.Config{AccessKey: ak, SecretKey: sk, Host: "https://jimeng.jianying.com"},
hc: &http.Client{Timeout: 30 * time.Second},
}
}
// post 统一发送带签名的 POST 请求
func (c *Client) post(path string, body any, out any) error {
b, err := json.Marshal(body)
if err != nil {
return err
}
hdrs, err := c.signer.Headers(&signer.Request{
Method: "POST",
Path: path,
Body: b,
ContentType: "application/json",
})
if err != nil {
return err
}
req, err := http.NewRequest("POST", c.signer.Host+path, bytes.NewReader(b))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
for k, v := range hdrs {
req.Header.Set(k, v)
}
resp, err := c.hc.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
rb, _ := io.ReadAll(resp.Body)
if err := json.Unmarshal(rb, out); err != nil {
return fmt.Errorf("decode resp: %w, body=%s", err, string(rb))
}
return nil
}
// ---- 提交:带货营销视频 ----
type SubmitResp struct {
Code string `json:"code"`
Message string `json:"message"`
Data struct {
TaskID string `json:"task_id"`
RunID string `json:"run_id"`
ThreadID string `json:"thread_id"`
Status string `json:"status"`
} `json:"data"`
}
func (c *Client) SubmitMarketingVideo(productName string, productImages []string) (*SubmitResp, error) {
req := map[string]any{
"capability_key": "pippit_avatar_marketing_agent",
"action_key": "generate_video",
"run_id": fmt.Sprintf("biz-%d", time.Now().UnixNano()),
"avatar_marketing_input": map[string]any{
"product_name": productName,
"product_image_url_list": productImages,
"model_image_url_list": []string{},
"enable_watermark": false,
},
}
var out SubmitResp
if err := c.post(submitVideoPath, req, &out); err != nil {
return nil, err
}
if out.Code != "0" {
return nil, fmt.Errorf("submit failed code=%s msg=%s", out.Code, out.Message)
}
return &out, nil
}
// ---- 查询 + 轮询 ----
type QueryVideoResp struct {
Code string `json:"code"`
Message string `json:"message"`
Data struct {
Status string `json:"status"`
ErrCode string `json:"err_code"`
ErrMsg string `json:"err_msg"`
VideoArtifact struct {
URL string `json:"url"`
CoverURL string `json:"cover_url"`
Duration float64 `json:"duration"`
Width int64 `json:"width"`
Height int64 `json:"height"`
} `json:"video_artifact"`
} `json:"data"`
}
// PollVideo 提交后轮询直到终态;SUCCESS 返回下载 URL,FAILED 返回错误
func (c *Client) PollVideo(taskID, runID string, timeout time.Duration) (string, error) {
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
var out QueryVideoResp
body := map[string]string{}
if taskID != "" {
body["task_id"] = taskID
} else {
body["run_id"] = runID
}
if err := c.post(queryVideoPath, body, &out); err != nil {
return "", err
}
switch out.Data.Status {
case "SUCCESS":
return out.Data.VideoArtifact.URL, nil
case "FAILED", "CANCELED":
return "", fmt.Errorf("task %s err=%s msg=%s", out.Data.Status, out.Data.ErrCode, out.Data.ErrMsg)
}
time.Sleep(3 * time.Second)
}
return "", fmt.Errorf("timeout after %v", timeout)
}
8.1 对接注意点(代码里务必处理)
- 时钟同步:本地时间偏差 >300s 会直接
10006;部署前校准服务器时钟。 - 幂等:
run_id自行生成(建议biz-<毫秒/纳秒>或业务单号);重复提交同 run_id 返回同一任务。 - 产物时效:
video_artifact.url/novel_data里的下载 URL 24h 过期,成功终态后立即下载落库。 - 轮询:提交返回
QUEUED后开始轮询 query,间隔建议 3~5s,注意查询接口限流 20qps。 - 短剧多阶段:每阶段是独立 submit+query,从上一阶段
resp_data里取OverviewAssetID/StoryboardAssetID作为下一阶段asset_id,thread_id全程回填。 - 图片规格:提交前校验宽高比、尺寸、格式,避免
10001参数非法。 - 排障:所有报错都把
request_id记录下来,供反馈排查。