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-paginated?handle=username&amount=24Paginated results with cursor for bulk fetching

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 paginated endpoint is designed for bulk collection. It accepts an amount parameter to control how many Reels to fetch per page, and returns a cursor you can use to fetch the next page.

Fetching a Single Page

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

  const response = await fetch(
    `${BASE_URL}/v1/scrape/instagram/reels-paginated?${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
    pageSize = 24, // Reels per page
    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, pageSize, cursor);
    const reels = data.reels || [];

    allReels.push(...reels);
    cursor = data.cursor || 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, 24, 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
  • Empty cursor — 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 paginated endpoint (/reels-paginated) supports cursor-based navigation through a creator's full catalog — good for bulk collection.

How many Reels can I fetch per request?

The amount parameter on the paginated endpoint controls this. Typical values are 12 or 24. Higher values may increase response time.

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.