Back to Blog
General

Instagram Reels Paginated API: Bulk-Fetch Creator Content at Scale

May 29, 2026
8 min read
S
By SociaVault Team
instagramreelsapicontent scraping

Instagram Reels Paginated API: Bulk-Fetch Creator Content at Scale

TL;DR: The SociaVault Instagram Reels API has two endpoints: a standard one that returns the latest batch of Reels, and a paginated one that lets you bulk-fetch a creator's entire Reels catalog using cursor-based pagination. This guide covers both, with working Node.js code for content audits, competitor analysis, and UGC collection.

If you've ever needed to pull all the Reels from a specific Instagram creator — for a content audit, competitor analysis, or UGC collection — you know the pain. Instagram's official API is heavily restricted. Scraping the frontend is fragile and slow. And manually downloading content is not a real option at scale.

The SociaVault Instagram Reels API solves this with two endpoints: a standard endpoint for the latest Reels, and a paginated endpoint that uses cursor-based navigation to walk through a creator's entire catalog. This guide covers both, with practical Node.js examples you can run today.


The Two Reels Endpoints

EndpointPathWhat it returns
Standard Reels/v1/scrape/instagram/reels?handle=usernameLatest batch of Reels (typically 12-24)
Paginated Reels/v1/scrape/instagram/reels?handle=username&max_id=MAX_IDNext page of Reels via paging_info.max_id

Both endpoints require your API key in the x-api-key header.

When to use which:

  • Use the standard endpoint when you need a quick snapshot of a creator's recent content — for example, checking what a competitor posted this week.
  • Use the paginated endpoint when you need to bulk-fetch a large number of Reels — for content audits, historical analysis, or building a comprehensive dataset.

Standard Reels Endpoint

The standard endpoint is the simplest way to get a creator's recent Reels. It returns the latest batch without requiring pagination.

const fetch = require("node-fetch"); // or use native fetch in Node 18+

const API_KEY = "your_api_key_here";
const BASE_URL = "https://api.sociavault.com";

async function getRecentReels(handle) {
  const response = await fetch(
    `${BASE_URL}/v1/scrape/instagram/reels?handle=${encodeURIComponent(handle)}`,
    {
      headers: { "x-api-key": API_KEY },
    },
  );

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  return response.json();
}

// Example usage
const data = await getRecentReels("natgeo");
console.log(`Found ${data.reels.length} recent Reels`);
data.reels.forEach((reel) => {
  console.log(
    `- ${reel.id}: ${reel.play_count?.toLocaleString()} plays | ${reel.like_count?.toLocaleString()} likes`,
  );
});

Sample response:

{
  "handle": "natgeo",
  "reels": [
    {
      "id": "CxYZ1234abcd",
      "shortcode": "CxYZ1234abcd",
      "url": "https://www.instagram.com/reel/CxYZ1234abcd/",
      "thumbnail_url": "https://scontent.xx.fbcdn.net/v/...",
      "caption": "The Amazon rainforest at dawn. 🌿 #nature #wildlife",
      "play_count": 2847392,
      "like_count": 184920,
      "comment_count": 1203,
      "duration_seconds": 28,
      "posted_at": "2026-05-10T14:22:00Z",
      "hashtags": ["nature", "wildlife"],
      "mentions": []
    }
  ],
  "cursor": "QVFDd3..."
}

Paginated Reels Endpoint

The separate /reels-paginated endpoint has been removed. Bulk collection now uses the standard /instagram/reels endpoint: each response includes paging_info.max_id, which you send back as max_id to fetch the next page while paging_info.more_available is true.

Fetching a Single Page

async function getReelsPaginated(handle, cursor = null) {
  const params = new URLSearchParams({ handle });
  if (cursor) params.set("max_id", cursor);

  const response = await fetch(
    `${BASE_URL}/v1/scrape/instagram/reels?${params}`,
    {
      headers: { "x-api-key": API_KEY },
    },
  );

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  return response.json();
}

Bulk-Fetching All Reels from a Creator

Here's the full implementation for walking through all pages of a creator's Reels catalog:

async function getAllReels(handle, options = {}) {
  const {
    maxReels = 500, // Stop after this many Reels
    delayMs = 800, // Delay between requests (be respectful)
    onProgress = null, // Optional progress callback
  } = options;

  const allReels = [];
  let cursor = null;
  let page = 0;

  console.log(`Fetching Reels for @${handle}...`);

  do {
    const { data } = await getReelsPaginated(handle, cursor);
    const items = Array.isArray(data?.items)
      ? data.items
      : Object.values(data?.items || {});
    const reels = items.map((item) => item.media).filter(Boolean);

    allReels.push(...reels);
    cursor = data?.paging_info?.more_available ? data.paging_info.max_id : null;
    page++;

    if (onProgress) {
      onProgress({ page, total: allReels.length, hasMore: !!cursor });
    }

    console.log(
      `Page ${page}: fetched ${reels.length} Reels (total: ${allReels.length})`,
    );

    if (!cursor || allReels.length >= maxReels) break;

    // Respect rate limits
    await new Promise((resolve) => setTimeout(resolve, delayMs));
  } while (cursor);

  return allReels.slice(0, maxReels);
}

// Fetch up to 200 Reels from a creator
const reels = await getAllReels("natgeo", { maxReels: 200 });
console.log(`Collected ${reels.length} Reels total`);

Use Case 1: Content Audit

A content audit analyzes a creator's Reels catalog to identify what's working. Here's a script that fetches all Reels and produces a performance summary:

async function contentAudit(handle) {
  const reels = await getAllReels(handle, { maxReels: 300 });

  if (reels.length === 0) {
    console.log("No Reels found.");
    return;
  }

  // Sort by play count
  const byPlays = [...reels].sort(
    (a, b) => (b.play_count || 0) - (a.play_count || 0),
  );

  // Calculate averages
  const avgPlays =
    reels.reduce((sum, r) => sum + (r.play_count || 0), 0) / reels.length;
  const avgLikes =
    reels.reduce((sum, r) => sum + (r.like_count || 0), 0) / reels.length;
  const avgComments =
    reels.reduce((sum, r) => sum + (r.comment_count || 0), 0) / reels.length;

  // Find most common hashtags
  const hashtagCounts = {};
  reels.forEach((r) => {
    (r.hashtags || []).forEach((tag) => {
      hashtagCounts[tag] = (hashtagCounts[tag] || 0) + 1;
    });
  });
  const topHashtags = Object.entries(hashtagCounts)
    .sort((a, b) => b[1] - a[1])
    .slice(0, 10);

  console.log(`\n=== Content Audit: @${handle} ===`);
  console.log(`Total Reels analyzed: ${reels.length}`);
  console.log(
    `Avg plays: ${avgPlays.toLocaleString("en-US", { maximumFractionDigits: 0 })}`,
  );
  console.log(
    `Avg likes: ${avgLikes.toLocaleString("en-US", { maximumFractionDigits: 0 })}`,
  );
  console.log(
    `Avg comments: ${avgComments.toLocaleString("en-US", { maximumFractionDigits: 0 })}`,
  );

  console.log(`\nTop 5 Reels by plays:`);
  byPlays.slice(0, 5).forEach((r, i) => {
    console.log(
      `  ${i + 1}. ${r.play_count?.toLocaleString()} plays — ${r.url}`,
    );
  });

  console.log(`\nTop hashtags:`);
  topHashtags.forEach(([tag, count]) => {
    console.log(`  #${tag}: used in ${count} Reels`);
  });
}

await contentAudit("natgeo");

Use Case 2: Competitor Analysis

Compare two creators' Reels performance side by side:

async function compareCreators(handle1, handle2, reelCount = 50) {
  const [reels1, reels2] = await Promise.all([
    getAllReels(handle1, { maxReels: reelCount }),
    getAllReels(handle2, { maxReels: reelCount }),
  ]);

  const stats = (reels) => ({
    count: reels.length,
    avgPlays: reels.reduce((s, r) => s + (r.play_count || 0), 0) / reels.length,
    avgLikes: reels.reduce((s, r) => s + (r.like_count || 0), 0) / reels.length,
    avgDuration:
      reels.reduce((s, r) => s + (r.duration_seconds || 0), 0) / reels.length,
    postingFrequency:
      reels.length > 1
        ? (new Date(reels[0].posted_at) -
            new Date(reels[reels.length - 1].posted_at)) /
          reels.length /
          86400000
        : null,
  });

  const s1 = stats(reels1);
  const s2 = stats(reels2);

  console.log(
    `\n${"Metric".padEnd(25)} ${"@" + handle1.padEnd(20)} ${"@" + handle2}`,
  );
  console.log("-".repeat(70));
  console.log(
    `${"Reels analyzed".padEnd(25)} ${s1.count.toString().padEnd(20)} ${s2.count}`,
  );
  console.log(
    `${"Avg plays".padEnd(25)} ${s1.avgPlays.toFixed(0).padEnd(20)} ${s2.avgPlays.toFixed(0)}`,
  );
  console.log(
    `${"Avg likes".padEnd(25)} ${s1.avgLikes.toFixed(0).padEnd(20)} ${s2.avgLikes.toFixed(0)}`,
  );
  console.log(
    `${"Avg duration (s)".padEnd(25)} ${s1.avgDuration.toFixed(1).padEnd(20)} ${s2.avgDuration.toFixed(1)}`,
  );
}

await compareCreators("natgeo", "bbcearth", 50);

Use Case 3: UGC Collection

Brands collecting user-generated content use the paginated endpoint to pull all Reels from a list of creators who have tagged or mentioned them:

async function collectUGC(creatorHandles, outputFile = "ugc.json") {
  const fs = require("fs");
  const allContent = [];

  for (const handle of creatorHandles) {
    console.log(`Collecting from @${handle}...`);
    try {
      const reels = await getAllReels(handle, { maxReels: 100 });
      allContent.push({ handle, reels, collectedAt: new Date().toISOString() });
    } catch (err) {
      console.error(`Failed for @${handle}: ${err.message}`);
    }
    // Delay between creators
    await new Promise((r) => setTimeout(r, 1000));
  }

  fs.writeFileSync(outputFile, JSON.stringify(allContent, null, 2));
  console.log(
    `\nSaved ${allContent.length} creators' content to ${outputFile}`,
  );
  return allContent;
}

const creators = ["creator1", "creator2", "creator3"];
await collectUGC(creators);

Error Handling

async function safeGetReels(handle, cursor = null) {
  try {
    return await getReelsPaginated(handle, cursor);
  } catch (err) {
    if (err.message.includes("429")) {
      console.log("Rate limit hit — waiting 60 seconds...");
      await new Promise((r) => setTimeout(r, 60000));
      return safeGetReels(handle, cursor); // Retry once
    }
    if (err.message.includes("404")) {
      throw new Error(`Creator @${handle} not found or account is private`);
    }
    throw err;
  }
}

Key things to handle:

  • 429 Rate Limited — back off and retry
  • 404 Not Found — account doesn't exist or is private
  • more_available is false — you've reached the end of the catalog
  • Network timeouts — retry with exponential backoff

Frequently Asked Questions

What's the difference between the standard and paginated endpoints?

The standard endpoint (/reels) returns the latest batch of Reels in a single request — good for quick checks. The separate /reels-paginated endpoint is no longer available, so for bulk collection you call /reels again with max_id set to the previous page's paging_info.max_id until paging_info.more_available is false.

How many Reels can I fetch per request?

You can't set it: /instagram/reels has no amount parameter. Each request returns one page, and you keep paging with max_id to get more.

Does this work for private accounts?

No. The API only accesses publicly visible content. Private accounts require the user to approve followers, and their content is not accessible.

How far back does the Reels history go?

The API can walk back through a creator's full Reels catalog as long as Instagram's pagination supports it. For very prolific creators, this can be hundreds of Reels going back years.

Can I fetch Reels from multiple creators in parallel?

Yes, but be mindful of rate limits. Running 2-3 creators in parallel is generally fine. Running 20+ simultaneously will likely trigger rate limiting. Use Promise.all for small batches and sequential processing for large lists.

What data is returned for each Reel?

Each Reel object includes: ID, shortcode, URL, thumbnail URL, caption, play count, like count, comment count, duration, posted date, hashtags, and mentions.


Get started with a free account at sociavault.com — 50 free credits, no credit card required.

Found this helpful?

Share it with others who might benefit

Ready to Try SociaVault?

Start extracting social media data with our powerful API. No credit card required.