Seedance_V3原生(SC)官方 SDK(AK/SK)接入
已有火山方舟 SDK 或按火山 V4 规范签名的客户端,可不改代码接入本站素材库与真人认证(/sc/*):把 SDK 的 endpoint 指向本站,AK 与 SK 均填平台令牌即可。
适用范围:素材库与真人认证的全部接口(SC 素材库/真人认证)。视频生成任务接口(/v3/video/tasks、/v1/videos)使用 Bearer 鉴权,不支持 AK/SK,见 SC 生成视频。
SDK 配置
| 配置项 | 值 |
|---|---|
| endpoint / base_url | $URL/sc(含 http:// 或 https://,末尾不加斜杠) |
| AK(Access Key) | 平台令牌(令牌页展示的完整 sk- 值) |
| SK(Secret Key) | 平台令牌(与 AK 同一个值) |
| region | cn-beijing |
| service | ark |
火山 Go SDK 使用 WithEndpoint 设置本站地址;SDK 会自动完成请求签名。
签名协议
与火山 V4 签名协议一致。自行实现签名的客户端须满足:
X-Date:UTC 时间yyyyMMdd'T'HHmmss'Z'格式,与服务端时钟偏差不超过 5 分钟;X-Content-Sha256:最终发送的请求体原始字节的 SHA-256 小写 hex(GET 请求对空体计算),且必须参与签名(签名后不要重新序列化请求体);Authorization:
HMAC-SHA256 Credential={AK}/{yyyyMMdd}/cn-beijing/ark/request, SignedHeaders=content-type;host;x-content-sha256;x-date, Signature={hex}- 密钥派生:
HMAC(SK, 日期) → HMAC(·, cn-beijing) → HMAC(·, ark) → HMAC(·, "request"),再对 StringToSign 签名; - CanonicalRequest 由请求方法、路径、规范化的查询串(按 key 排序、严格百分号编码)、规范化的签名头、
SignedHeaders与请求体哈希组成。
签名校验失败返回 SignatureDoesNotMatch;时钟偏差超限返回 RequestTimeTooSkewed。验签通过后,令牌有效性、账号状态、IP 限制等校验与 Bearer 方式完全一致。
Python 手动签名
不依赖 SDK、仅用标准库实现的通用签名函数,覆盖素材 RPC 与真人认证 REST(GET/POST):
import hmac, hashlib, json, time
from urllib.request import Request, urlopen
KEY = "sk-..." # 平台令牌,AK=SK 同一个值
HOST = "你的网关地址"
def pct(s):
safe = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~")
return "".join(c if c in safe else "".join("%%%02X" % b for b in c.encode()) for c in s)
def signed_request(method, path, query="", body_obj=None):
body = b"" if body_obj is None else json.dumps(body_obj).encode()
t = time.gmtime()
xdate = time.strftime("%Y%m%dT%H%M%SZ", t)
short = time.strftime("%Y%m%d", t)
payload_hash = hashlib.sha256(body).hexdigest()
sh = ["content-type", "host", "x-content-sha256", "x-date"]
ch = (f"content-type:application/json\nhost:{HOST}\n"
f"x-content-sha256:{payload_hash}\nx-date:{xdate}\n")
q = {}
for kv in query.split("&"):
if kv:
k, _, v = kv.partition("=")
q.setdefault(k, []).append(v)
cq = "&".join(f"{pct(k)}={pct(v)}" for k in sorted(q) for v in sorted(q[k]))
cr = "\n".join([method, path, cq, ch, ";".join(sh), payload_hash])
scope = f"{short}/cn-beijing/ark/request"
sts = "\n".join(["HMAC-SHA256", xdate, scope,
hashlib.sha256(cr.encode()).hexdigest()])
k = KEY.encode()
for e in (short, "cn-beijing", "ark", "request"):
k = hmac.new(k, e.encode(), hashlib.sha256).digest()
sig = hmac.new(k, sts.encode(), hashlib.sha256).hexdigest()
url = f"https://{HOST}{path}" + (f"?{query}" if query else "")
req = Request(url, data=body if method == "POST" else None, method=method,
headers={"Content-Type": "application/json", "X-Date": xdate,
"X-Content-Sha256": payload_hash, "Host": HOST,
"Authorization": f"HMAC-SHA256 Credential={KEY}/{scope}, "
f"SignedHeaders={';'.join(sh)}, Signature={sig}"})
return json.load(urlopen(req, timeout=30))
def rpc(action, body):
return signed_request("POST", "/sc/", f"Action={action}&Version=2024-01-01", body)素材组操作
# 创建素材组
r = rpc("CreateAssetGroup", {"Name": "我的素材组", "GroupType": "AIGC"})
group_id = r["Result"]["Id"]
# 查询 / 更新
rpc("GetAssetGroup", {"Id": group_id})
rpc("UpdateAssetGroup", {"Id": group_id, "Description": "品牌宣传素材"})
# 删除
rpc("DeleteAssetGroup", {"Id": group_id})素材操作
# 上传素材(异步:返回 Id 后需轮询到 Active)
r = rpc("CreateAsset", {
"GroupId": group_id,
"URL": "https://example.com/photo.jpg", # 公网可达
"AssetType": "Image", # Image / Video / Audio
"Name": "参考图",
})
asset_id = r["Result"]["Id"]
# 轮询处理状态
import time
while True:
r = rpc("GetAsset", {"Id": asset_id})["Result"]
if r["Status"] != "Processing":
break
time.sleep(3)
assert r["Status"] == "Active", r.get("Error")
asset_url = r["URL"] # 引用 URL(约 12 小时有效),供任务 content 引用
# 更新名称 / 删除
rpc("UpdateAsset", {"Id": asset_id, "Name": "新名称"})
rpc("DeleteAsset", {"Id": asset_id})列表与分页
# 页码式
r = rpc("ListAssets", {"Filter": {"GroupType": "AIGC"}, "PageNumber": 1, "PageSize": 10})
items, total = r["Result"]["Items"], r["Result"]["TotalCount"]
# 游标式:MaxResults 起步,携带 NextToken 续页,直到响应无 NextToken
token, all_items = None, []
while True:
body = {"Filter": {"GroupType": "AIGC"}, "MaxResults": 20}
if token:
body["NextToken"] = token
r = rpc("ListAssets", body)["Result"]
all_items += r["Items"]
token = r.get("NextToken")
if not token:
breakListAssetGroups 用法相同(Filter 不含 Statuses)。
真人认证
REST 与 RPC 两种方式二选一,凭据不通用。
REST 方式(verification_id):
# 创建会话:保存 verification_id,在浏览器打开 h5_url 完成人脸认证
r = signed_request("POST", "/sc/api/seedance/face-verifications",
"", {"return_url": "https://your.site/back"})
verification_id, h5_url = r["verification_id"], r["h5_url"]
# 查询结果:status=verified 后使用 group_id(LivenessFace 素材组)
r = signed_request("GET", f"/sc/api/seedance/face-verifications/{verification_id}")
if r["status"] == "verified":
liveness_group_id = r["group_id"]RPC 方式(BytedToken):
# 创建会话(CallbackURL 必填,认证完成回调携带 resultCode=10000)
r = rpc("CreateVisualValidateSession", {"CallbackURL": "https://your.site/callback"})
byted_token, h5_link = r["Result"]["BytedToken"], r["Result"]["H5Link"]
# 查询结果:认证通过后 Result 返回 GroupId
r = rpc("GetVisualValidateResult", {"BytedToken": byted_token})
liveness_group_id = r["Result"].get("GroupId") # 未完成时可能为空,稍后再查认证通过后拿到的 LivenessFace 素材组即可用于 CreateAsset 上传真真人像素材;浏览器跳回 return_url / 收到回调不代表成功,以查询接口为准。
错误处理
错误统一为 { "ResponseMetadata": { "Error": { "Code", "Message" } } } 信封(REST 真人认证为 {"error": {...}})。常见 Code:
| Code | 说明 |
|---|---|
SignatureDoesNotMatch | 签名不符:确认 AK/SK 均填平台令牌、对最终发送的原始请求体签名 |
RequestTimeTooSkewed | X-Date 与服务器时钟偏差超 5 分钟,校准本机时间 |
InvalidParameter.DownloadFailed | 素材 URL 不可公网访问 |
AccessDenied | 操作了不属于自己的资源(素材按客户隔离) |
GroupIdRequired | 上传素材必须携带本人 GroupId |
接口的 Action 列表、参数与分页说明见 SC 素材库/真人认证。
