Back to all posts

How to Scrape Instagram Channel Reels and Posts in 2026

Jonathan Geiger
Instagram APIReels ScrapingPosts ScrapingChannel DataInstagram Scraping

Need to pull every post or reel from an Instagram profile programmatically? Whether you're building an influencer analytics platform, monitoring competitor reels, or feeding training data into an AI model, scraping Instagram channels reliably in 2026 has become harder than it used to be.

In this guide, we'll walk through the two cleanest paths — one for the full posts feed (images, videos, carousels, reels) and one for reels-only — and explain how to handle pagination, rate limits, and the gotchas Instagram has added over the past year.

What Changed in 2026

Instagram's web feed used to be a simple HTML scrape. As of early 2026, three things have shifted:

  1. Logged-out profile pages serve a "soft error" SSR shell. The HTML loads with HTTP 200 but contains zero post data — only a pageID: "httpErrorPage" flag and a generic shell. Old scrapers that parsed window._sharedData returned empty arrays.
  2. /api/v1/users/web_profile_info/ blanket-rate-limits datacenter IPs. Both well-known commercial proxies and self-hosted scrapers get HTTP 429 on this endpoint, and it no longer matters which exit IP you rotate to.
  3. Search and explore endpoints require an authenticated session. Keyword search at /explore/search/keyword/ redirects to /accounts/login/ even with residential IPs.

The good news: the per-profile feed and clips endpoints (/api/v1/feed/user/<id>/ and /api/v1/clips/user/) still work for public profiles when you bootstrap a real LSD token through a proper session. That's what the SocialKit Instagram Channel Posts API and Channel Reels API do for you under the hood.

What You Can Extract

Both APIs return a normalized item shape so you don't have to branch by media type:

  • Identifiersid, shortcode, canonical URL
  • Typeimage, video, carousel, or reel
  • Engagementlikes, comments, views, plays
  • Content — full caption, duration, width, height, timestamp
  • Media URLsthumbnailUrl and direct videoUrl (signed CDN links)
  • Author — embedded author object (id, username, full name, verification, profile pic)
  • Paginationcursor token + hasMore boolean

The Easy Way: Two Endpoints, One Pattern

Rather than building a Puppeteer + residential-proxy + cookie-rotation pipeline yourself, the Instagram Channel Posts API and Instagram Channel Reels API handle all of that internally. Same auth, same pagination model, same response shape.

Posts vs Reels — When to Use Which

  • Use /instagram/channel-posts when you want the full feed in chronological order: images, carousels, video posts, and reels mixed together exactly the way Instagram displays them.
  • Use /instagram/channel-reels when you only care about short-form video. The response excludes static posts and carousels, so every item has a real view count and a video URL.

Both endpoints take the same query parameters (url, limit, cursor) and return the same item shape — pick whichever matches your use case.

Endpoints

# Full posts feed
https://api.socialkit.dev/instagram/channel-posts

# Reels-only feed
https://api.socialkit.dev/instagram/channel-reels

Parameters

ParameterTypeRequiredDescription
urlstringYesInstagram profile URL (e.g., https://www.instagram.com/natgeo)
access_keystringYesYour SocialKit access key
limitnumberNoItems per request, 1–100 (default 12)
cursorstringNoPagination cursor from a previous response

Example Request — Posts

GET https://api.socialkit.dev/instagram/channel-posts?access_key=<your-access-key>&url=https://www.instagram.com/natgeo&limit=12

Sample Response

{
  "success": true,
  "data": {
    "profileUrl": "https://www.instagram.com/natgeo",
    "username": "natgeo",
    "items": [
      {
        "id": "3890548995286221889",
        "shortcode": "DX-AbK9ScRB",
        "url": "https://www.instagram.com/p/DX-AbK9ScRB/",
        "type": "reel",
        "isVideo": true,
        "caption": "One trip to Italy is never enough...",
        "likes": 104690,
        "comments": 1438,
        "views": 4813905,
        "plays": 4813905,
        "duration": 73.32,
        "timestamp": 1778009597,
        "thumbnailUrl": "https://scontent-iad3-1.cdninstagram.com/...",
        "videoUrl": "https://scontent-iad3-1.cdninstagram.com/...",
        "width": 1216,
        "height": 2160,
        "productType": "clips",
        "author": {
          "id": "787132",
          "username": "natgeo",
          "fullName": "National Geographic",
          "isVerified": true,
          "profilePicUrl": "https://scontent-iad3-1.cdninstagram.com/..."
        }
      }
    ],
    "count": 12,
    "hasMore": true,
    "cursor": "3889718998899684009_25025320"
  }
}

Example Request — Reels

GET https://api.socialkit.dev/instagram/channel-reels?access_key=<your-access-key>&url=https://www.instagram.com/nasa&limit=12

The response shape is identical — only the items differ. Every entry has type: "reel", a real view count, and a video URL.

Code Examples

JavaScript / Node.js

const axios = require('axios');

async function getInstagramFeed(profileUrl, kind, accessKey, opts = {}) {
  const endpoint = kind === 'reels' ? 'channel-reels' : 'channel-posts';
  const response = await axios.get(`https://api.socialkit.dev/instagram/${endpoint}`, {
    params: {
      access_key: accessKey,
      url: profileUrl,
      limit: opts.limit || 50,
      ...(opts.cursor ? { cursor: opts.cursor } : {}),
    },
  });
  return response.data.data;
}

// Walk an entire profile's reels feed with pagination
async function walkAllReels(profileUrl, accessKey) {
  const all = [];
  let cursor = null;
  let page = 0;
  while (true) {
    const data = await getInstagramFeed(profileUrl, 'reels', accessKey, {
      limit: 50,
      cursor,
    });
    all.push(...data.items);
    page += 1;
    console.log(`Page ${page}: ${data.items.length} reels (running total: ${all.length})`);
    if (!data.hasMore || !data.cursor) break;
    cursor = data.cursor;
  }
  return all;
}

walkAllReels('https://www.instagram.com/nasa', 'your-access-key').then((reels) => {
  console.log(`Pulled ${reels.length} total reels`);
  const top = [...reels].sort((a, b) => b.views - a.views).slice(0, 5);
  top.forEach((r) =>
    console.log(`${r.views.toLocaleString()} views — ${r.url}`),
  );
});

Python

import requests

def get_instagram_feed(profile_url, kind, access_key, limit=50, cursor=None):
    endpoint = 'channel-reels' if kind == 'reels' else 'channel-posts'
    params = {
        'access_key': access_key,
        'url': profile_url,
        'limit': limit,
    }
    if cursor:
        params['cursor'] = cursor
    response = requests.get(
        f'https://api.socialkit.dev/instagram/{endpoint}',
        params=params,
        timeout=30,
    )
    response.raise_for_status()
    return response.json()['data']

def walk_all_posts(profile_url, access_key):
    items = []
    cursor = None
    while True:
        data = get_instagram_feed(profile_url, 'posts', access_key, limit=50, cursor=cursor)
        items.extend(data['items'])
        if not data['hasMore'] or not data.get('cursor'):
            break
        cursor = data['cursor']
    return items

posts = walk_all_posts('https://www.instagram.com/natgeo', 'your-access-key')
print(f'Total posts pulled: {len(posts)}')

cURL

# First page
curl "https://api.socialkit.dev/instagram/channel-posts?access_key=YOUR_KEY&url=https://www.instagram.com/natgeo&limit=12"

# Next page using cursor from previous response
curl "https://api.socialkit.dev/instagram/channel-posts?access_key=YOUR_KEY&url=https://www.instagram.com/natgeo&limit=12&cursor=3889718998899684009_25025320"

Use Cases

  • Influencer Vetting — Pull a creator's last 50 reels to verify the views-to-followers ratio before paying for a campaign. Bot-inflated accounts have followers but flat reel views.
  • Competitor Tracking — Run the posts endpoint nightly against 30 competitor accounts and surface new posts above a view threshold into a Slack channel.
  • Hook & Caption Mining — Sort reels by views, pull the top 1% across 50 creators in a niche, and feed the captions into an LLM to extract winning hook patterns.
  • Brand-Affiliate Compliance — Monitor every post your affiliates publish for branded mentions or off-brand content.
  • AI Training Data — Build labeled datasets of public Instagram reels with view counts as a quality signal for training video-understanding models.

Pagination — How It Works

Both endpoints return up to 100 items per request along with a cursor field. To get the next page, pass that cursor value back as a query parameter on your next request:

// Page 1
const page1 = await getInstagramFeed(url, 'posts', key, { limit: 50 });
// Page 2
const page2 = await getInstagramFeed(url, 'posts', key, { limit: 50, cursor: page1.cursor });
// ...keep going while data.hasMore === true

Cursors are opaque strings — don't try to parse them or build them yourself. They contain timing information that lets Instagram return consistent results even as the profile publishes new content during your pagination session.

When you've reached the end of the feed, hasMore flips to false and cursor becomes null.

Things to Watch Out For

Signed video URLs expire

The videoUrl and thumbnailUrl fields are signed Instagram CDN links. They expire after several hours. If you need to persist a video file, fetch the bytes immediately after the API call and store them in your own storage — don't store the signed URL itself.

Empty feeds for private accounts

Private accounts return count: 0 and hasMore: false. There's no workaround through the public API — you'd need to be a follower with login credentials, which neither endpoint supports.

View counts only on videos

Static images and carousels without video return views: 0. Use views > 0 to filter for video-style engagement, or check type === "reel" || type === "video".

Rate limits

Each request costs 1 credit per 20 results returned. A limit=12 call costs 1 credit. A limit=50 call costs 3 credits. Build credit budgeting into your bulk loops so you don't burn your monthly quota on a single profile with thousands of posts.

First call per username is slower

The first call for a previously-unseen username triggers a one-time profile resolution behind the scenes (~5-10 seconds). Subsequent calls — including paginated calls within the same session — return in ~1 second. If you're testing latency, run the same username twice.

Try It Free

You don't need to sign up to see the response shape. Use the Instagram Channel Posts Extractor and Instagram Channel Reels Extractor tools to pull a sample feed, inspect the JSON, and confirm the data matches what you need before integrating.

When you're ready to build, sign up for SocialKit to get 20 free credits — enough to pull ~400 posts or reels — without a credit card.

Conclusion

Scraping Instagram in 2026 means accepting that the easy paths (HTML scrapes, web_profile_info) are dead, and routing around them with proper session bootstrapping and the modern feed/clips endpoints. The Channel Posts API and Channel Reels API give you a one-call interface to do exactly that, with cursor pagination, normalized response shapes, and reasonable per-credit pricing.

Pick the right endpoint for your use case (posts for full feeds, reels for short-form), follow the cursor pagination pattern, and you can build influencer dashboards, content audit tools, or AI training pipelines without ever touching a Puppeteer cluster.

Ready to start? Check the Instagram Channel Posts API docs and Instagram Channel Reels API docs for full reference, or jump straight to the free extractor tools to see real responses against any public account.