gitroomhq/postiz-app · error · BadBody

${error_message || 'Threads could not process the media'}

Error message

${error_message || 'Threads could not process the media'}

What it means

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.

Source

Thrown at libraries/nestjs-libraries/src/integrations/social/threads.provider.ts:185

      picture: picture || '',
      username: username,
    };
  }

  // Single, read-only status check of a media container - no loops and no
  // timers, so the post workflow can poll it with durable timers.
  private async checkContainerStatus(
    mediaContainerId: string,
    accessToken: string
  ): Promise<'FINISHED' | 'IN_PROGRESS' | 'PUBLISHED'> {
    const { status, error_message } = await (
      await this.fetch(
        `https://graph.threads.net/v1.0/${mediaContainerId}?fields=status,error_message&access_token=${accessToken}`
      )
    ).json();

    if (status === 'ERROR' || status === 'EXPIRED') {
      throw new BadBody(
        this.identifier,
        JSON.stringify({ status, error_message }),
        '{}',
        error_message || 'Threads could not process the media'
      );
    }

    if (status === 'FINISHED' || status === 'PUBLISHED') {
      return status;
    }

    return 'IN_PROGRESS';
  }

  private async checkLoaded(
    mediaContainerId: string,
    accessToken: string
  ): Promise<boolean> {

View on GitHub (pinned to 0f1647f749)

Solutions

  1. Inspect the logged error_message JSON - it names the exact Graph API rejection reason
  2. Re-encode media to Threads-supported formats (H.264 MP4, JPEG/PNG within size limits)
  3. Ensure the media URL is publicly reachable by Meta's servers
  4. Publish immediately after container creation to avoid EXPIRED
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-validate media against Threads limits
if (video.size > 100 * 1024 * 1024) throw new Error('Video exceeds Threads 100MB limit');

Type guard

const isTerminalContainerStatus = (s: string): s is 'ERROR' | 'EXPIRED' => s === 'ERROR' || s === 'EXPIRED';

Try / catch

try { await threads.publish(...); } catch (e) { if (e instanceof BadBody && e.error.includes('error_message')) surfaceToUser(e.error); throw e; }

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27). Data as JSON: /api/errors/dab53b9a9b366aca. Report an issue: GitHub.