gitroomhq/postiz-app · error · BadBody

${handleError?.value || publish?.message || 'TikTok refused

Error message

${handleError?.value || publish?.message || 'TikTok refused the post'}

What it means

postPending's final fallback: the publish envelope has code != 0 / no share_id and the error was not classified as refresh-token or disconnect, so a BadBody 'TikTok refused the post' is thrown with TikTok's message.

Source

Thrown at libraries/nestjs-libraries/src/integrations/social/tiktok.business.provider.ts:712

      const asString = JSON.stringify(publish);
      const handleError = this.handleErrors(asString);
      if (handleError?.type === 'refresh-token') {
        throw new RefreshToken(
          'tiktok-business',
          asString,
          JSON.stringify(body),
          handleError.value
        );
      }
      if (handleError?.type === 'disconnect') {
        throw new Disconnect(
          'tiktok-business',
          asString,
          JSON.stringify(body),
          handleError.value
        );
      }
      throw new BadBody(
        'tiktok-business-error-upload',
        asString,
        Buffer.from(JSON.stringify(body)),
        handleError?.value || publish?.message || 'TikTok refused the post'
      );
    }

    // The publish is now irreversible on TikTok's side: return `pending` so the
    // workflow polls checkPostStatus instead of blocking (and possibly timing
    // out and re-posting) inside this activity.
    return [
      {
        id: firstPost.id,
        releaseURL: '',
        postId: '',
        status: 'pending',
        pendingData: { publishId: publish.data.share_id },
      },

View on GitHub (pinned to 0f1647f749)

Solutions

  1. Read the logged asString body - code/message identify the refusal reason
  2. Publish promptly after video upload so the video_id is still valid
  3. Verify the app has the scopes required for the chosen privacy level
  4. Check TikTok rate/quota limits
Defensive patterns

Strategy: try-catch

Validate before calling

// publish promptly after upload so video_id stays valid
if (Date.now() - upload.finishedAt > 60 * 60 * 1000) await reuploadVideo();

Type guard

const isRefusedEnvelope = (p: any) => p?.code !== 0 || !p?.data?.share_id;

Try / catch

try { await provider.postPending(...); } catch (e) { if (e instanceof BadBody && /refused the post/.test(e.message)) surfaceApiReason(e.error); throw e; }

Prevention

When it happens

Trigger: Business API rejects the direct post request itself: invalid video_id (expired upload), unsupported field combination, rate limit, or param validation error.

Common situations: Video uploaded but publish delayed past the video's validity window, wrong privacy option for the app's scopes, or hitting the daily post quota.

Related errors


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