{"record":{"id":"dab53b9a9b366aca","repo":"gitroomhq/postiz-app","slug":"error-message-threads-could-not-process-the","errorCode":null,"errorMessage":"${error_message || 'Threads could not process the media'}","messagePattern":"\\$\\{error_message \\|\\| 'Threads could not process the media'\\}","errorType":"exception","errorClass":"BadBody","httpStatus":null,"severity":"error","filePath":"libraries/nestjs-libraries/src/integrations/social/threads.provider.ts","lineNumber":185,"sourceCode":"      picture: picture || '',\n      username: username,\n    };\n  }\n\n  // Single, read-only status check of a media container - no loops and no\n  // timers, so the post workflow can poll it with durable timers.\n  private async checkContainerStatus(\n    mediaContainerId: string,\n    accessToken: string\n  ): Promise<'FINISHED' | 'IN_PROGRESS' | 'PUBLISHED'> {\n    const { status, error_message } = await (\n      await this.fetch(\n        `https://graph.threads.net/v1.0/${mediaContainerId}?fields=status,error_message&access_token=${accessToken}`\n      )\n    ).json();\n\n    if (status === 'ERROR' || status === 'EXPIRED') {\n      throw new BadBody(\n        this.identifier,\n        JSON.stringify({ status, error_message }),\n        '{}',\n        error_message || 'Threads could not process the media'\n      );\n    }\n\n    if (status === 'FINISHED' || status === 'PUBLISHED') {\n      return status;\n    }\n\n    return 'IN_PROGRESS';\n  }\n\n  private async checkLoaded(\n    mediaContainerId: string,\n    accessToken: string\n  ): Promise<boolean> {","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/gitroomhq/postiz-app/blob/0f1647f7491a217d43eb5ae7a480484bdf0aff3e/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts#L167-L203","documentation":"When publishing to Threads, media is staged in a container and polled via checkContainerStatus. If the Graph API reports status ERROR or EXPIRED, a BadBody is thrown with Threads' own error_message (or a generic fallback), meaning Threads rejected or timed out processing the media.","triggerScenarios":"Container status query returns status=ERROR (invalid/corrupt media, unsupported codec, aspect-ratio violation) or status=EXPIRED (the container was never published within its validity window).","commonSituations":"Uploading HEVC videos or images over Threads' size limits, wrong media_url that Threads cannot fetch, waiting too long between container creation and publish so the container expires.","solutions":["Inspect the logged error_message JSON - it names the exact Graph API rejection reason","Re-encode media to Threads-supported formats (H.264 MP4, JPEG/PNG within size limits)","Ensure the media URL is publicly reachable by Meta's servers","Publish immediately after container creation to avoid EXPIRED"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// pre-validate media against Threads limits\nif (video.size > 100 * 1024 * 1024) throw new Error('Video exceeds Threads 100MB limit');","typeGuard":"const isTerminalContainerStatus = (s: string): s is 'ERROR' | 'EXPIRED' => s === 'ERROR' || s === 'EXPIRED';","tryCatchPattern":"try { await threads.publish(...); } catch (e) { if (e instanceof BadBody && e.error.includes('error_message')) surfaceToUser(e.error); throw e; }","preventionTips":["Use JPEG/PNG images and H.264 MP4 within Threads limits","Publish immediately after container creation","Keep media URLs publicly accessible to Meta crawlers"],"tags":["threads","meta-graph","media-processing","container-status"],"backgroundTag":"social-media-container-error","analyzedSha":"0f1647f7491a217d43eb5ae7a480484bdf0aff3e","analyzedAt":"2026-08-27T12:09:55.020Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}