Wahlu API
Recipes

Post a TikTok Carousel

Upload images via API, wait for completed status, create a TikTok carousel, and schedule it.

Post a TikTok photo carousel using API-only media upload. This flow is: upload files, finalise processing, create a TikTok carousel content item, then schedule a publish run.

Step 1: Upload images and wait for completed status

JavaScript
const apiKey = "wahlu_live_your_api_key_here";
const headers = {
  Authorization: `Bearer ${apiKey}`,
  "Content-Type": "application/json",
};

async function waitForMediaCompleted(brandId, mediaId, timeoutMs = 120000) {
  const started = Date.now();
  while (Date.now() - started < timeoutMs) {
    const mediaRes = await fetch(
      `https://api.wahlu.com/v1/brands/${brandId}/media/${mediaId}`,
      { headers: { Authorization: `Bearer ${apiKey}` } }
    );
    const { data: media } = await mediaRes.json();

    if (media.status === "completed") return media.id;
    if (media.status === "failed") throw new Error(`Media failed: ${mediaId}`);
    await new Promise((r) => setTimeout(r, 3000));
  }

  throw new Error(`Timed out waiting for media ${mediaId}`);
}

async function uploadAndFinaliseImage(brandId, filename, fileBuffer) {
  const uploadRes = await fetch(
    `https://api.wahlu.com/v1/brands/${brandId}/media/upload-url`,
    {
      method: "POST",
      headers,
      body: JSON.stringify({
        filename,
        content_type: "image/jpeg",
        size: fileBuffer.length,
        source: "upload",
      }),
    }
  );
  const { data: upload } = await uploadRes.json();

  await fetch(upload.upload_url, {
    method: "PUT",
    headers: { "Content-Type": "image/jpeg" },
    body: fileBuffer,
  });

  await fetch(
    `https://api.wahlu.com/v1/brands/${brandId}/media/${upload.id}`,
    {
      method: "PATCH",
      headers,
      body: JSON.stringify({ status: "ready_for_processing" }),
    }
  );

  return waitForMediaCompleted(brandId, upload.id);
}

const slidePaths = ["slide1.jpg", "slide2.jpg", "slide3.jpg", "slide4.jpg"];
const mediaIds = [];

for (const slidePath of slidePaths) {
  const fileBuffer = fs.readFileSync(slidePath);
  const mediaId = await uploadAndFinaliseImage(brandId, slidePath, fileBuffer);
  mediaIds.push(mediaId);
}

Step 2: Create a TikTok carousel post

JavaScript
const contentItemRes = await fetch(
  `https://api.wahlu.com/v1/brands/${brandId}/content-items`,
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      name: "Coding Education Carousel",
      tiktok_settings: {
        description: "4 practical coding lessons for beginners. #coding #learncode",
        media_ids: mediaIds,
        post_type: "CAROUSEL",
        privacy_level: "PUBLIC_TO_EVERYONE",
        allow_comment: true,
        allow_duet: true,
        allow_stitch: true,
        auto_add_music: false,
        is_commercial_content: false,
        is_aigc: false,
      },
    }),
  }
);
const { data: contentItem } = await contentItemRes.json();
console.log(`TikTok carousel content item created: ${contentItem.id}`);

Step 3: Schedule for TikTok integration

JavaScript
const integrationsRes = await fetch(
  `https://api.wahlu.com/v1/brands/${brandId}/integrations`,
  { headers: { Authorization: `Bearer ${apiKey}` } }
);
const { data: integrations } = await integrationsRes.json();
const tiktok = integrations.find((i) => i.platform === "tiktok");
if (!tiktok) throw new Error("No TikTok integration found for this brand");

const scheduleAt = new Date(Date.now() + 60 * 1000).toISOString(); // 1 min from now
const scheduleRes = await fetch(
  `https://api.wahlu.com/v1/brands/${brandId}/publish-runs`,
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      content_item_id: contentItem.id,
      scheduled_at: scheduleAt,
      integration_ids: [tiktok.id],
      approval_status: "approved",
    }),
  }
);
const { data: publishRun } = await scheduleRes.json();
console.log(`Scheduled: ${publishRun.id} at ${publishRun.scheduled_at}`);

Step 4: Check publication result

JavaScript
const pubsRes = await fetch(
  `https://api.wahlu.com/v1/brands/${brandId}/publications?limit=20`,
  { headers: { Authorization: `Bearer ${apiKey}` } }
);
const { data: publications } = await pubsRes.json();
const publication = publications.find((p) => p.post_id === contentItem.id && p.platform === "tiktok");

console.log(publication?.status);        // processing | published | failed
console.log(publication?.failure_reason); // null when successful