可灵动作控制 API 文档

ModelPort 视频接口

Kling 3.0 Motion Control

kling-3.0-motion-control · Kling · 可调用状态以模型目录为准

将一段视频中的动作迁移到人物图片上。

请求参数

参数可选值默认值
resolution720p / 1080p720p
aspect_ratioautoauto
generate_audio关闭关闭
n11

prompt 最多 4000 个字符。

动作控制

上传一张人物图和一段 3–30 秒的 MP4 视频,分别使用 reference_image 和 reference_video。支持 720p 和 1080p,画面方向跟随视频。

本站按动作视频的时长向上取整计费,并在报价时将参考视频上传至 Yir 核算上游成本。pricing.quantity 返回计费秒数。

参考素材

多素材参考

每次 2–2 份素材。

请求角色数量时长限制
reference_image1—
reference_video1—

上传后将返回的文件 ID 放入 references[].file_id,并用 references[].role 指定角色。

上传参考文件

调用示例

先设置 MODELPORT_API_KEY。cURL 示例使用 Bash 和 jq,Python 使用标准库,Node.js 示例保存为 .mjs 文件。示例会先获取报价,再提交生成任务。

上传所需的参考文件,并设置 MODELPORT_REFERENCE_IMAGE_ID、MODELPORT_REFERENCE_VIDEO_ID。

cURL (Bash)

cURL (Bash)

set -eu
: "${MODELPORT_API_KEY:?Set MODELPORT_API_KEY}"
base='https://api.apimodel.store'
request='{"model":"kling-3.0-motion-control","parameters":{"resolution":"720p","aspect_ratio":"auto","generate_audio":false,"n":1},"references":[{"role":"reference_image","file_id":"file_REPLACE_WITH_REFERENCE_IMAGE_ID"},{"role":"reference_video","file_id":"file_REPLACE_WITH_REFERENCE_VIDEO_ID"}]}'
request=$(printf '%s' "$request" | jq -ce --arg role 'reference_image' --arg id "${MODELPORT_REFERENCE_IMAGE_ID:?Set MODELPORT_REFERENCE_IMAGE_ID}" '(.references[] | select(.role == $role) | .file_id) = $id')
request=$(printf '%s' "$request" | jq -ce --arg role 'reference_video' --arg id "${MODELPORT_REFERENCE_VIDEO_ID:?Set MODELPORT_REFERENCE_VIDEO_ID}" '(.references[] | select(.role == $role) | .file_id) = $id')
request_id=${MODELPORT_REQUEST_ID:-modelport-example-$(date +%s)-$$}

quote=$(curl --fail-with-body --max-time 45 -sS 'https://api.apimodel.store/v1/quotes' \
  -H "Authorization: Bearer $MODELPORT_API_KEY" \
  -H "Content-Type: application/json" --data "$request")
quoted_cap=$(printf '%s' "$quote" | jq -er '.billing_price_usd | strings')
cap=${MODELPORT_MAX_PRICE_USD:-$quoted_cap}
printf 'Request ID: %s, maximum charge: %s USD\n' "$request_id" "$cap" >&2
payload=$(printf '%s' "$request" | jq -ce --arg cap "$cap" '. + {max_price_usd: $cap}')

job=$(curl --fail-with-body --max-time 45 -sS "$base/v1/jobs" \
  -H "Authorization: Bearer $MODELPORT_API_KEY" -H "Idempotency-Key: $request_id" \
  -H "Content-Type: application/json" --data "$payload")
job_id=$(printf '%s' "$job" | jq -er '.id | strings')
printf 'Job ID: %s\n' "$job_id" >&2

attempt=0
while [ "$attempt" -lt 90 ]; do
  status=$(printf '%s' "$job" | jq -er '.status')
  case "$status" in succeeded|failed) break ;; esac
  sleep 2
  job=$(curl --fail-with-body --max-time 45 -sS "$base/v1/jobs/$job_id" -H "Authorization: Bearer $MODELPORT_API_KEY")
  attempt=$((attempt + 1))
done
printf '%s\n' "$job"
Python

Python

import json
import os
import time
import uuid
from urllib.request import Request, urlopen

BASE = "https://api.apimodel.store"
KEY = os.environ["MODELPORT_API_KEY"]
request_id = os.getenv("MODELPORT_REQUEST_ID") or str(uuid.uuid4())
payload = json.loads("{\"model\":\"kling-3.0-motion-control\",\"parameters\":{\"resolution\":\"720p\",\"aspect_ratio\":\"auto\",\"generate_audio\":false,\"n\":1},\"references\":[{\"role\":\"reference_image\",\"file_id\":\"file_REPLACE_WITH_REFERENCE_IMAGE_ID\"},{\"role\":\"reference_video\",\"file_id\":\"file_REPLACE_WITH_REFERENCE_VIDEO_ID\"}]}")
for reference in payload["references"]:
    reference["file_id"] = os.environ[f'MODELPORT_{reference["role"].upper()}_ID']

def api(path, data=None, request_key=None):
    headers = {"Authorization": f"Bearer {KEY}"}
    if data is not None:
        headers["Content-Type"] = "application/json"
    if request_key:
        headers["Idempotency-Key"] = request_key
    body = json.dumps(data).encode("utf-8") if data is not None else None
    with urlopen(Request(BASE + path, data=body, headers=headers), timeout=45) as response:
        return json.load(response)

quote = api("/v1/quotes", payload)
cap = os.getenv("MODELPORT_MAX_PRICE_USD") or quote["billing_price_usd"]
print(f"Request ID: {request_id}, maximum charge: {cap} USD", flush=True)
job = api("/v1/jobs", {**payload, "max_price_usd": cap}, request_id)
job_id = job["id"]
print("Job ID:", job_id, flush=True)
for _ in range(90):
    if job["status"] in ("succeeded", "failed"):
        break
    time.sleep(2)
    job = api(f"/v1/jobs/{job_id}")
print(json.dumps(job, indent=2, ensure_ascii=False))
Node.js

Node.js

import { randomUUID } from "node:crypto";
import { setTimeout as sleep } from "node:timers/promises";

const base = "https://api.apimodel.store";
const key = process.env.MODELPORT_API_KEY;
if (!key) throw new Error("Set MODELPORT_API_KEY");
const requestId = process.env.MODELPORT_REQUEST_ID || randomUUID();
const payload = {
  "model": "kling-3.0-motion-control",
  "parameters": {
    "resolution": "720p",
    "aspect_ratio": "auto",
    "generate_audio": false,
    "n": 1
  },
  "references": [
    {
      "role": "reference_image",
      "file_id": "file_REPLACE_WITH_REFERENCE_IMAGE_ID"
    },
    {
      "role": "reference_video",
      "file_id": "file_REPLACE_WITH_REFERENCE_VIDEO_ID"
    }
  ]
};
for (const reference of payload.references) {
  const variable = `MODELPORT_${reference.role.toUpperCase()}_ID`;
  if (!process.env[variable]) throw new Error(`Set ${variable}`);
  reference.file_id = process.env[variable];
}

async function api(path, data, requestKey) {
  const headers = { Authorization: `Bearer ${key}` };
  if (data) headers["Content-Type"] = "application/json";
  if (requestKey) headers["Idempotency-Key"] = requestKey;
  const response = await fetch(base + path, {
    method: data ? "POST" : "GET", headers,
    body: data && JSON.stringify(data), signal: AbortSignal.timeout(45_000),
  });
  if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
  return response.json();
}

const quote = await api("/v1/quotes", payload);
const cap = process.env.MODELPORT_MAX_PRICE_USD || quote.billing_price_usd;
console.log(`Request ID: ${requestId}, maximum charge: ${cap} USD`);
let job = await api("/v1/jobs", { ...payload, max_price_usd: cap }, requestId);
const jobId = job.id;
console.log("Job ID:", jobId);
for (let attempt = 0; attempt < 90 && !["succeeded", "failed"].includes(job.status); attempt++) {
  await sleep(2_000);
  job = await api(`/v1/jobs/${jobId}`);
}
console.log(JSON.stringify(job, null, 2));

重试同一次提交时,保持请求参数一致,并用首次输出的值设置 MODELPORT_REQUEST_ID 和 MODELPORT_MAX_PRICE_USD。

提交 POST /v1/jobs 时复用相同请求,按报价填写 max_price_usd 并附唯一 Idempotency-Key。用任务 ID 查询结果和费用。

参考站对应文档 ↗