{"record":{"id":"4d4d1f30e3e07316","repo":"yikart/AiToEarn","slug":"channelplatformresponseinvalid-4d4d1f","errorCode":"ChannelPlatformResponseInvalid","errorMessage":"ChannelPlatformResponseInvalid","messagePattern":"ChannelPlatformResponseInvalid","errorType":"error_code","errorClass":"AppException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/offline-qr/douyin-offline-qr.service.ts","lineNumber":33,"sourceCode":"  constructor(\n    private readonly publishRecordRepo: PublishRecordRepository,\n    private readonly materialGroupRepo: MaterialGroupRepository,\n    private readonly materialRepo: MaterialRepository,\n    private readonly douyinPublishProvider: DouyinPublishProvider,\n    private readonly mediaService: MediaService,\n  ) {}\n\n  async createPublish(dto: CreateDouyinOfflineQrPublishDto) {\n    await this.validateMaterial(dto.materialGroupId, dto.materialId)\n    const content = await this.prepareContent(dto.content)\n\n    const result = await this.douyinPublishProvider.createHandoffPublishResult({\n      content,\n      option: dto.option,\n    })\n    const dataOption = parseDouyinDataOption(result.dataOption)\n    if (!result.platformWorkId || !dataOption?.schema || !dataOption.shortLink || !dataOption.expiresAt) {\n      throw new AppException(ResponseCode.ChannelPlatformResponseInvalid, { platform: AccountType.Douyin })\n    }\n\n    const type = this.resolvePublishType(content)\n    const media = this.resolveRecordMedia(content, type)\n    const record = await this.publishRecordRepo.create({\n      userId: '',\n      accountId: '',\n      uid: '',\n      materialGroupId: dto.materialGroupId,\n      materialId: dto.materialId,\n      accountType: AccountType.Douyin,\n      type,\n      status: PublishStatus.WaitingForUserAction,\n      title: content.title,\n      desc: content.body,\n      topics: parseTopicsFromBody(content.body),\n      publishTime: new Date(),\n      source: dto.source ?? PublishRecordSource.OfflineQr,","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/offline-qr/douyin-offline-qr.service.ts#L15-L51","documentation":"ChannelPlatformResponseInvalid is thrown by DouyinOfflineQrService.createPublish when the handoff publish result from the Douyin publish provider is incomplete: platformWorkId, dataOption.schema, dataOption.shortLink, or dataOption.expiresAt is missing after parseDouyinDataOption. The offline-QR flow requires all four fields to build a scannable, expiring QR share record, so any missing piece invalidates the whole response.","triggerScenarios":"Calling createPublish on the offline-QR service when the underlying Douyin share handoff (getShareid/generateShareSchema chain) yields a result whose dataOption cannot be parsed or lacks schema/shortLink/expiresAt — e.g. the share-id request failed upstream, or the schema URL generation returned an empty/short form.","commonSituations":"Douyin platform returning degraded share-id data; app credentials valid but share schema permission missing so schema generation is skipped; changes in Douyin's dataOption format breaking parseDouyinDataOption; transient upstream failure cached and surfaced here.","solutions":["Log the raw provider result and check which field is missing (platformWorkId vs schema vs shortLink vs expiresAt) to target the real failure.","Verify the Douyin app has share/schema permissions and valid client credentials so generateShareSchema produces a full schema URL.","Confirm parseDouyinDataOption still matches the current Douyin dataOption format (update after platform API changes).","Retry the createPublish call; transient upstream share-id failures often succeed on a second attempt."],"exampleFix":"// before\nconst result = await provider.createHandoffPublishResult(...) // dataOption lacks expiresAt -> throws\n// after\n// ensure upstream returns full schema with expiry\nconst schema = await douyinService.generateShareSchema({ shareId, private_status: 0 })\n// provider now returns dataOption.schema/shortLink/expiresAt -> createPublish succeeds","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isValidHandoffResult(r: { platformWorkId?: string; dataOption?: unknown } | null)\n  : r is { platformWorkId: string; dataOption: { schema: string; shortLink: string; expiresAt: unknown } } {\n  const d = parseDouyinDataOption(r?.dataOption)\n  return !!r?.platformWorkId && !!d?.schema && !!d?.shortLink && d?.expiresAt != null\n}","tryCatchPattern":"try {\n  const record = await offlineQrService.createPublish(dto)\n} catch (err) {\n  if (err instanceof AppException && err.code === ResponseCode.ChannelPlatformResponseInvalid) {\n    // log raw provider result for which field was missing, retry once before surfacing\n  } else throw err\n}","preventionTips":["Verify the Douyin app's share/schema permissions so full dataOption is always returned","Keep parseDouyinDataOption in sync with current Douyin dataOption format","Log raw provider results on failure to identify the missing field quickly","Preflight the share-id/ticket chain before starting offline-QR publish flows"],"tags":["douyin","offline-qr","response-validation","third-party-api"],"backgroundTag":"invalid-platform-response","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}