{"record":{"id":"e23f757f051ac272","repo":"yikart/AiToEarn","slug":"publish-id-upload-url","errorCode":null,"errorMessage":"初始化视频发布失败，缺少publish_id或upload_url","messagePattern":"初始化视频发布失败，缺少publish_id或upload_url","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.service.ts","lineNumber":224,"sourceCode":"      \n      // 添加视频封面时间戳，如果提供了\n      if (videoInfo.videoCoverTimestampMs) {\n        requestBody.post_info.video_cover_timestamp_ms = videoInfo.videoCoverTimestampMs;\n      }\n      \n      this.logger.debug('初始化视频发布请求:', JSON.stringify(requestBody));\n\n      const { data } = await firstValueFrom(\n        this.httpService.post(`${this.apiBaseUrl}/v2/post/publish/video/init/`, requestBody, {\n          headers: {\n            'Content-Type': 'application/json',\n            'Authorization': `Bearer ${accessToken}`\n          }\n        })\n      );\n      \n      if (!data.data.publish_id || !data.data.upload_url) {\n        throw new BadRequestException('初始化视频发布失败，缺少publish_id或upload_url');\n      }\n\n      return data.data;\n    } catch (error) {\n      this.logger.error('初始化TikTok视频发布失败:', error.response?.data || error.message);\n      throw new BadRequestException(`初始化视频发布失败: ${error.response?.data?.error?.message || error.message}`);\n    }\n  }\n\n  /**\n   * 上传视频文件\n   * @param accessToken 访问令牌\n   * @param videoBuffer 视频文件缓冲区\n   * @param initData 初始化返回的数据\n   * @returns 上传结果\n   */\n  async uploadVideo(\n    accessToken: string,","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.service.ts#L206-L242","documentation":"This BadRequestException is thrown inside TikTokService.initVideoPublish when TikTok's /v2/post/publish/video/init/ call returns HTTP 200 but the response body data.data lacks either publish_id or upload_url. It is a response-shape validation guard: the TikTok Content Posting API sometimes returns a success status with an incomplete payload (e.g. error object present at another level, or a different publish flow), and this code fails fast instead of returning unusable init data. Note this inner throw is itself caught by the surrounding catch block, which rewraps it as the message at error 422/423.","triggerScenarios":"initVideoPublish receives a 2xx response from POST ${apiBaseUrl}/v2/post/publish/video/init/ where data.data.publish_id is undefined/null/empty OR data.data.upload_url is undefined/null/empty — e.g. TikTok returned an error envelope with HTTP 200, returned only publish_id for PULL_FROM_URL-style flows, or the JSON structure changed (fields moved/renamed in a newer API version).","commonSituations":"Seen when TikTok silently degrades the response (draft mode apps, unpublished/unaudited privacy_level values like PUBLIC rejected for sandbox apps), when the response is data.error instead of data.data (so data.data is undefined and reading .publish_id on it throws or is falsy), after TikTok API version migrations that drop upload_url for certain sources, or when a proxy/gateway returns a 200 HTML/JSON page that doesn't match the expected schema.","solutions":["Log the full raw response body (JSON.stringify(data)) at this point to see what TikTok actually returned before assuming a missing field.","Check data.error / data.data.error for a TikTok error envelope returned with HTTP 200 and surface its message instead of proceeding.","Verify the app's publish scope (video.publish) is approved for production; sandbox apps may get success-with-limited payloads.","Confirm privacy_level and post_info values are valid for your app's audit status (invalid values can yield incomplete init responses).","If TikTok changed the response shape, update parsing to the current Content Posting API v2 schema (publish_id vs upload_url availability per source type) and pin the API version."],"exampleFix":"// before: reading nested fields that may not exist\nif (!data.data.publish_id || !data.data.upload_url) {\n  throw new BadRequestException('初始化视频发布失败，缺少publish_id或upload_url');\n}\n// after: guard against missing data envelope and surface TikTok error info\nconst initData = data?.data;\nif (!initData || !initData.publish_id || !initData.upload_url) {\n  const apiErr = data?.error ?? initData?.error;\n  throw new BadRequestException(\n    `初始化视频发布失败，缺少publish_id或upload_url: ${apiErr?.message ?? JSON.stringify(data).slice(0, 500)}`\n  );\n}","handlingStrategy":"type-guard","validationCode":"function isCompletePublishInit(res: any): res is { data: { data: { publish_id: string; upload_url: string } } } {\n  return !!res?.data?.data &&\n    typeof res.data.data.publish_id === 'string' && res.data.data.publish_id.length > 0 &&\n    typeof res.data.data.upload_url === 'string' && res.data.data.upload_url.length > 0;\n}","typeGuard":"function hasPublishInitData(v: unknown): v is { publish_id: string; upload_url: string } {\n  return typeof v === 'object' && v !== null &&\n    typeof (v as any).publish_id === 'string' && (v as any).publish_id.length > 0 &&\n    typeof (v as any).upload_url === 'string' && (v as any).upload_url.length > 0;\n}","tryCatchPattern":"try {\n  const initData = await tiktokService.initVideoPublish(accessToken, videoSize, videoInfo);\n  if (!hasPublishInitData(initData)) {\n    // HTTP 200 but incomplete payload: inspect raw response / TikTok error envelope before proceeding\n    throw new Error('init 返回不完整：缺少 publish_id 或 upload_url');\n  }\n} catch (e) {\n  logger.warn('initVideoPublish failed', e);\n  throw e;\n}","preventionTips":["Log the entire raw response body on init, not just assumed fields, so contract changes are visible immediately.","Check for a TikTok error envelope returned with HTTP 200 (data.error) before trusting data.data.","Pin/monitor the TikTok Content Posting API version you target and review changelogs for response-shape changes.","Use video.publish only with audit-approved privacy_level values valid for your app tier.","Treat a 200 response as untrusted input: validate publish_id/upload_url before persisting or using them."],"tags":["tiktok","response-validation","missing-field","api-contract","nestjs"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}