{"record":{"id":"e4da4b3b9a00a74a","repo":"yikart/AiToEarn","slug":"channelplatformresponseinvalid-e4da4b","errorCode":"ChannelPlatformResponseInvalid","errorMessage":"Failed to fetch permalink for ${objectId}","messagePattern":"Failed to fetch permalink for (.+?)","errorType":"exception","errorClass":"ThreadsPlatformException","httpStatus":null,"severity":"warning","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/threads/threads-publish.provider.ts","lineNumber":404,"sourceCode":"      } satisfies ThreadsPublishDataOption),\n    }\n  }\n\n  private async fetchPermalink(\n    objectId: string,\n    accessToken: string,\n  ): Promise<string> {\n    try {\n      const publishedPost = await this.threadsService.getPublishedPost(\n        objectId,\n        accessToken,\n        'permalink',\n      )\n      if (publishedPost.permalink)\n        return publishedPost.permalink\n    }\n    catch (err) {\n      this.logger.warn(err, `Failed to fetch permalink for ${objectId}`)\n    }\n    throw ThreadsPlatformException.validation({\n      code: ResponseCode.ChannelPlatformResponseInvalid,\n      category: PlatformErrorCategory.PlatformUnavailable,\n      context: {\n        endpoint: 'fetchPermalink',\n        platformWorkId: objectId,\n      },\n    })\n  }\n\n  private buildPostText(body: string | undefined): string {\n    return (stripTopicsFromBody(body) ?? '').trim()\n  }\n\n  private buildPost(body: string | undefined): { text: string, topicTag?: string } {\n    const topics = parseTopicsFromBody(body)\n    return {","sourceCodeStart":386,"sourceCodeEnd":422,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/threads/threads-publish.provider.ts#L386-L422","documentation":"fetchPermalink in threads-publish.provider.ts calls the Threads Graph API 'permalink' field for a published media object. If the API call throws, or returns a post without a permalink, it logs a warning and throws ThreadsPlatformException.validation(ChannelPlatformResponseInvalid, PlatformUnavailable) — i.e., Threads did not return a usable permalink for the published post.","triggerScenarios":"permalink() is called shortly after publishing (media not yet finished processing server-side), the objectId is invalid or belongs to another container, the access token lacks permissions/is expired, or Threads returns an incomplete response without a permalink field.","commonSituations":"Querying permalink immediately after publish before Threads indexes the post; deleted/archived posts; token scope changes after a user re-authorizes with fewer permissions.","solutions":["Wait and retry: add a short delay/backoff after publish before requesting the permalink, since Threads may take time to expose it","Verify the objectId is a published Threads media id and the token has threads_basic/read permissions","Call the Graph API directly (curl with the same token) to confirm whether permalink is returned and inspect the raw error","Treat the failure as non-fatal in the caller if a permalink is optional"],"exampleFix":"// before\nif (publishedPost.permalink)\n  return publishedPost.permalink\n// after\nif (publishedPost.permalink)\n  return publishedPost.permalink\nawait new Promise(r => setTimeout(r, 3000)) // Threads may need time before permalink is available\nconst retried = await this.fetchPublishedPost(objectId, 'permalink')\nif (retried?.permalink) return retried.permalink","handlingStrategy":"retry","validationCode":"// before fetching permalink, confirm the media is published\nconst status = await fetch(`https://graph.threads.net/v1/${objectId}?fields=status&access_token=${token}`).then(r => r.json());\nif (status?.status !== 'FINISHED') await delay(5000);","typeGuard":"function hasPermalink(p: unknown): p is { permalink: string } {\n  return typeof p === 'object' && p !== null && 'permalink' in p && typeof (p as any).permalink === 'string' && (p as any).permalink.length > 0;\n}","tryCatchPattern":"try {\n  const url = await provider.permalink(objectId);\n} catch (e) {\n  // Threads may not expose permalink immediately: retry with backoff, then degrade gracefully\n  await delay(3000);\n  url = await provider.permalink(objectId).catch(() => null);\n}","preventionTips":["Delay permalink fetch a few seconds after publish","Verify tokens have threads_basic/read permissions","Check media status is FINISHED before querying permalink","Cache permalinks once fetched to reduce API calls"],"tags":["threads","graph-api","permalink","async-propagation"],"backgroundTag":"platform-api-response-invalid","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}