{"record":{"id":"dcbba1a327384eba","repo":"jackwener/OpenCLI","slug":"http-res-status-json-stringify-er","errorCode":null,"errorMessage":"申请抖音上传地址失败: HTTP ${res.status} ${JSON.stringify(error ?? payload)}","messagePattern":"申请抖音上传地址失败: HTTP (.+?) (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/douyin/_shared/vod-upload.js","lineNumber":151,"sourceCode":"    Action: 'ApplyUploadInner',\n    Version: '2020-11-19',\n    SpaceName: VOD_SPACE_NAME,\n    FileType: 'video',\n    IsInner: '1',\n    FileSize: String(fileSize),\n  });\n  const url = `${VOD_UPLOAD_HOST}?${params.toString()}`;\n  const res = await fetch(url, { headers: computeAws4Headers(url, credentials), signal: AbortSignal.timeout(30000) });\n  const text = await res.text();\n  let payload;\n  try {\n    payload = JSON.parse(text);\n  } catch {\n    throw new CommandExecutionError(`申请抖音上传地址失败，非 JSON 响应: HTTP ${res.status} ${text.slice(0, 300)}`);\n  }\n  const error = payload?.ResponseMetadata?.Error;\n  if (!res.ok || error) {\n    throw new CommandExecutionError(`申请抖音上传地址失败: HTTP ${res.status} ${JSON.stringify(error ?? payload)}`);\n  }\n  const uploadNode = payload?.Result?.InnerUploadAddress?.UploadNodes?.[0];\n  const storeInfo = uploadNode?.StoreInfos?.[0];\n  const videoId = payload?.Result?.Vid || uploadNode?.Vid;\n  const sessionKey = uploadNode?.SessionKey ?? storeInfo?.SessionKey ?? payload?.Result?.SessionKey;\n  if (!uploadNode?.UploadHost || !storeInfo?.StoreUri || !storeInfo?.Auth || !videoId || !sessionKey) {\n    throw new CommandExecutionError(`申请抖音上传地址响应缺少必要字段: ${JSON.stringify(payload).slice(0, 500)}`);\n  }\n  return {\n    video_id: videoId,\n    tos_upload_url: `https://${uploadNode.UploadHost}/${storeInfo.StoreUri}`,\n    auth: storeInfo.Auth,\n    session_key: sessionKey,\n    upload_header: uploadNode.UploadHeader ?? {},\n    user_id: credentials.user_id ?? '',\n  };\n}\n","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/douyin/_shared/vod-upload.js#L133-L169","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait applyVideoUploadInner(fileSize, staleCredentials);\n// after\nif (isExpired(credentials, now)) {\n  credentials = await getUploadAuthV5Credentials(page); // refresh STS before applying\n}\nawait applyVideoUploadInner(fileSize, credentials);","handlingStrategy":"retry","validationCode":"// check clock skew and token expiry before signing\nif (Math.abs(Date.now() - await ntpNow()) > 5 * 60 * 1000) syncClock();\nif (credentials.expired_time * 1000 <= Date.now()) credentials = await getUploadAuthV5Credentials(page);","typeGuard":"null","tryCatchPattern":"try {\n  return await applyVideoUploadInner(fileSize, creds);\n} catch (e) {\n  const m = e.message.match(/\"Code\":\"([^\"]+)\"/);\n  if (m && /Expired|Signature|AuthAccess/i.test(m[1])) {\n    creds = await getUploadAuthV5Credentials(page); // refresh then retry once\n    return applyVideoUploadInner(fileSize, creds);\n  }\n  if (m && /Throttl|Limit/i.test(m[1])) { await sleep(5000); return retry(); }\n  throw e;\n}","preventionTips":["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."],"tags":["network","douyin","api-error","aws4-signature"],"backgroundTag":"api-request-rejected","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}