Wahlu API
Recipes

Post an Instagram Carousel

Upload images, finalise media processing, create a carousel post, and schedule it.

Post an Instagram carousel (multi-image post) using the full API upload flow: upload each image, finalise processing, create the content item, then schedule a publish run.

Step 1: Upload and finalise each image

For each slide: request an upload URL, upload bytes with PUT, then call PATCH status=ready_for_processing and wait until the media is completed.

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 processing failed for ${mediaId}`);
    }

    await new Promise((r) => setTimeout(r, 3000));
  }

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

async function uploadAndFinaliseImage(brandId, filename, fileBuffer) {
  // A) Create signed upload URL
  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();

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

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

  // D) Wait until media is completed
  return waitForMediaCompleted(brandId, upload.id);
}

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

for (const imagePath of imagePaths) {
  const fileBuffer = fs.readFileSync(imagePath);
  const mediaId = await uploadAndFinaliseImage(brandId, imagePath, fileBuffer);
  mediaIds.push(mediaId);
  console.log(`Completed: ${imagePath} → ${mediaId}`);
}

Step 2: Create the Instagram carousel content item

Set post_type to "GRID_POST" and pass all the media IDs in the media_ids array. The order of IDs determines the slide order. Instagram treats a grid post with multiple images as a carousel.

JavaScript
const contentItemRes = await fetch(
  `https://api.wahlu.com/v1/brands/${brandId}/content-items`,
  {
    method: "POST",
    headers: {
      Authorization: "Bearer wahlu_live_your_api_key_here",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Product Feature Carousel",
      instagram_settings: {
        description: "Swipe through our top 3 new features! Which one are you most excited about? #carousel #productupdate",
        media_ids: mediaIds,
        post_type: "GRID_POST",
      },
    }),
  }
);
const { data: contentItem } = await contentItemRes.json();
console.log(`Carousel content item created: ${contentItem.id}`);

Step 3: Schedule it

Schedule the carousel the same way you would any other content item.

JavaScript
// Get the Instagram integration ID
const integrationsRes = await fetch(
  `https://api.wahlu.com/v1/brands/${brandId}/integrations`,
  { headers: { Authorization: "Bearer wahlu_live_your_api_key_here" } }
);
const { data: integrations } = await integrationsRes.json();
const instagram = integrations.find(i => i.platform === "instagram");

// Schedule for tomorrow at 12pm UTC
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(12, 0, 0, 0);

const scheduleRes = await fetch(
  `https://api.wahlu.com/v1/brands/${brandId}/publish-runs`,
  {
    method: "POST",
    headers: {
      Authorization: "Bearer wahlu_live_your_api_key_here",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      content_item_id: contentItem.id,
      scheduled_at: tomorrow.toISOString(),
      integration_ids: [instagram.id],
    }),
  }
);
const { data: publishRun } = await scheduleRes.json();
console.log(`Carousel publish run scheduled for ${publishRun.scheduled_at}`);

Step 4: Verify publication status

JavaScript
const pubsRes = await fetch(
  `https://api.wahlu.com/v1/brands/${brandId}/publications?limit=20`,
  { headers: { Authorization: "Bearer wahlu_live_your_api_key_here" } }
);
const { data: publications } = await pubsRes.json();

const latestForPost = publications.find((p) => p.post_id === contentItem.id);
console.log(latestForPost?.status, latestForPost?.failure_reason);