yikart/AiToEarn · warning · ThreadsPlatformException
ChannelPlatformResponseInvalid
ChannelPlatformResponseInvalid
Error message
Failed to fetch permalink for ${objectId} What it means
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.
Source
Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/threads/threads-publish.provider.ts:404
} satisfies ThreadsPublishDataOption),
}
}
private async fetchPermalink(
objectId: string,
accessToken: string,
): Promise<string> {
try {
const publishedPost = await this.threadsService.getPublishedPost(
objectId,
accessToken,
'permalink',
)
if (publishedPost.permalink)
return publishedPost.permalink
}
catch (err) {
this.logger.warn(err, `Failed to fetch permalink for ${objectId}`)
}
throw ThreadsPlatformException.validation({
code: ResponseCode.ChannelPlatformResponseInvalid,
category: PlatformErrorCategory.PlatformUnavailable,
context: {
endpoint: 'fetchPermalink',
platformWorkId: objectId,
},
})
}
private buildPostText(body: string | undefined): string {
return (stripTopicsFromBody(body) ?? '').trim()
}
private buildPost(body: string | undefined): { text: string, topicTag?: string } {
const topics = parseTopicsFromBody(body)
return {View on GitHub (pinned to d3aa8bea5b)
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
Example fix
// before if (publishedPost.permalink) return publishedPost.permalink // after if (publishedPost.permalink) return publishedPost.permalink await new Promise(r => setTimeout(r, 3000)) // Threads may need time before permalink is available const retried = await this.fetchPublishedPost(objectId, 'permalink') if (retried?.permalink) return retried.permalink
Defensive patterns
Strategy: retry
Validate before calling
// before fetching permalink, confirm the media is published
const status = await fetch(`https://graph.threads.net/v1/${objectId}?fields=status&access_token=${token}`).then(r => r.json());
if (status?.status !== 'FINISHED') await delay(5000); Type guard
function hasPermalink(p: unknown): p is { permalink: string } {
return typeof p === 'object' && p !== null && 'permalink' in p && typeof (p as any).permalink === 'string' && (p as any).permalink.length > 0;
} Try / catch
try {
const url = await provider.permalink(objectId);
} catch (e) {
// Threads may not expose permalink immediately: retry with backoff, then degrade gracefully
await delay(3000);
url = await provider.permalink(objectId).catch(() => null);
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Querying permalink immediately after publish before Threads indexes the post; deleted/archived posts; token scope changes after a user re-authorizes with fewer permissions.
Related errors
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/e4da4b3b9a00a74a.
Report an issue: GitHub.