jackwener/OpenCLI · error · CommandExecutionError
申请抖音上传地址失败: HTTP ${res.status} ${JSON.stringify(error ?? pay
Error message
申请抖音上传地址失败: HTTP ${res.status} ${JSON.stringify(error ?? payload)} What it means
applyVideoUploadInner throws this when the ApplyUploadInner endpoint returned a parseable JSON response, but the request failed at the API level: either the HTTP status is not 2xx or ResponseMetadata.Error is present. The full error object (or payload) is JSON-stringified into the message, so the server's Code/Message are visible.
Source
Thrown at clis/douyin/_shared/vod-upload.js:151
Action: 'ApplyUploadInner',
Version: '2020-11-19',
SpaceName: VOD_SPACE_NAME,
FileType: 'video',
IsInner: '1',
FileSize: String(fileSize),
});
const url = `${VOD_UPLOAD_HOST}?${params.toString()}`;
const res = await fetch(url, { headers: computeAws4Headers(url, credentials), signal: AbortSignal.timeout(30000) });
const text = await res.text();
let payload;
try {
payload = JSON.parse(text);
} catch {
throw new CommandExecutionError(`申请抖音上传地址失败,非 JSON 响应: HTTP ${res.status} ${text.slice(0, 300)}`);
}
const error = payload?.ResponseMetadata?.Error;
if (!res.ok || error) {
throw new CommandExecutionError(`申请抖音上传地址失败: HTTP ${res.status} ${JSON.stringify(error ?? payload)}`);
}
const uploadNode = payload?.Result?.InnerUploadAddress?.UploadNodes?.[0];
const storeInfo = uploadNode?.StoreInfos?.[0];
const videoId = payload?.Result?.Vid || uploadNode?.Vid;
const sessionKey = uploadNode?.SessionKey ?? storeInfo?.SessionKey ?? payload?.Result?.SessionKey;
if (!uploadNode?.UploadHost || !storeInfo?.StoreUri || !storeInfo?.Auth || !videoId || !sessionKey) {
throw new CommandExecutionError(`申请抖音上传地址响应缺少必要字段: ${JSON.stringify(payload).slice(0, 500)}`);
}
return {
video_id: videoId,
tos_upload_url: `https://${uploadNode.UploadHost}/${storeInfo.StoreUri}`,
auth: storeInfo.Auth,
session_key: sessionKey,
upload_header: uploadNode.UploadHeader ?? {},
user_id: credentials.user_id ?? '',
};
}
View on GitHub (pinned to 49907e53dc)
Solutions
- Read ResponseMetadata.Error.Code/Message in the error message — SignatureDoesNotMatch/ExpiredToken means refresh STS credentials and retry.
- Sync the system clock (NTP) if signature/timestamp errors appear.
- Refresh credentials via getUploadAuthV5Credentials before retrying when the token expired.
- Validate the FileSize argument passed in (must be a positive integer matching the real file).
- Retry with backoff for 5xx/throttling codes; reduce request rate if throttled.
Example fix
// before
await applyVideoUploadInner(fileSize, staleCredentials);
// after
if (isExpired(credentials, now)) {
credentials = await getUploadAuthV5Credentials(page); // refresh STS before applying
}
await applyVideoUploadInner(fileSize, credentials); Defensive patterns
Strategy: retry
Validate before calling
// check clock skew and token expiry before signing if (Math.abs(Date.now() - await ntpNow()) > 5 * 60 * 1000) syncClock(); if (credentials.expired_time * 1000 <= Date.now()) credentials = await getUploadAuthV5Credentials(page);
Type guard
null
Try / catch
try {
return await applyVideoUploadInner(fileSize, creds);
} catch (e) {
const m = e.message.match(/"Code":"([^"]+)"/);
if (m && /Expired|Signature|AuthAccess/i.test(m[1])) {
creds = await getUploadAuthV5Credentials(page); // refresh then retry once
return applyVideoUploadInner(fileSize, creds);
}
if (m && /Throttl|Limit/i.test(m[1])) { await sleep(5000); return retry(); }
throw e;
} Prevention
- Keep system clock NTP-synced (signature mismatches).
- Always use fresh STS credentials per upload session.
- Parse ResponseMetadata.Error.Code and branch on known codes (expiry vs throttle vs param).
- Rate-limit ApplyUploadInner calls to avoid throttling.
When it happens
Trigger: HTTP status >= 400 from the VOD endpoint, or a 200 response whose ResponseMetadata.Error is non-null — e.g. SignatureDoesNotMatch, InvalidParameter, quota exceeded, or auth expiry errors.
Common situations: Expired STS session token so AWS4 signing fails; wrong system clock causing signature timestamp mismatch; FileSize param rejected; upload quota exhausted for the account/space.
Related errors
- 获取视频分P信息失败: ${payload?.message ?? 'unknown'} (${payload?.cod
- 获取关注列表失败: ${payload.message} (${payload.code})
- Douyin API request failed (${method} ${url}): ${error instan
- 申请抖音上传地址失败,非 JSON 响应: HTTP ${res.status} ${text.slice(0, 300
- 提交抖音上传失败,非 JSON 响应: HTTP ${res.status} ${text.slice(0, 300)}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/dcbba1a327384eba.
Report an issue: GitHub.