Instagram Reels & Audio API Tutorial: Scrape Reels, Tracks & Comments

Reels are where Instagram's reach lives now, and that makes them the most interesting surface to analyze: which short videos a creator is publishing, which audio tracks are trending, and how people are reacting in the comments. The problem is that getting at this data is awkward. The official Meta Graph API doesn't expose arbitrary public Reels, and scraping the app yourself means dealing with logins, dynamic rendering, and constant breakage.

In this tutorial we'll use the Instagram Cheapest API on RapidAPI to do three things:

  1. Pull a creator's Reels with user_reels
  2. Find every Reel that uses a particular audio track with reels_audio
  3. Fetch the comments on a specific piece of media with media_comments

We'll do the main flow in Python with requests, then add a short Node.js snippet so you can drop this into a JavaScript backend too. Everything returns raw, real-time JSON.

Prerequisites

Every request uses two headers:

The base URL is https://instagram-cheapest.p.rapidapi.com and all endpoints are GET under /api/v1/instagram.

Step 0: A reusable client

import requests
import json

API_KEY = "YOUR_RAPIDAPI_KEY"
HOST = "instagram-cheapest.p.rapidapi.com"
BASE_URL = f"https://{HOST}/api/v1/instagram"

HEADERS = {
    "x-rapidapi-key": API_KEY,
    "x-rapidapi-host": HOST,
}


def get(path, params=None):
    resp = requests.get(f"{BASE_URL}/{path}", headers=HEADERS,
                        params=params, timeout=30)
    resp.raise_for_status()
    return resp.json()

Keep the timeout generous. This API serves live, uncached data, so calls take a real round trip to Instagram — don't set an aggressive 2-second timeout and wonder why requests fail.

A note on user IDs

Like the posts endpoint, the Reels endpoint identifies a creator by numeric user_id, not username. If you only have a handle, resolve it first. The userinfo endpoint (/api/v1/instagram/user/{username}) returns the profile, and the numeric ID is in that payload. There's also username_by_uid for going from an ID back to a handle. Print the profile JSON and pull the ID field you see there:

def get_user_id(username):
    profile = get(f"user/{username}")
    # Inspect the printed profile to confirm the exact key.
    return profile.get("id") or profile.get("pk")

From here on we work with that numeric ID.

Step 1: Fetch a creator's Reels with user_reels

user_reels takes user_id as a query parameter:

def get_reels(user_id):
    return get("user_reels", params={"user_id": user_id})


user_id = get_user_id("nike")
reels = get_reels(user_id)

# Always inspect the shape before iterating.
print(json.dumps(reels, indent=2)[:2000])

The response is raw JSON, so look at what comes back before assuming a structure. Reels responses are usually a collection of media objects — possibly at the top level, possibly nested under a key like items or data. Each Reel object typically carries a shortcode, a caption, a video URL or thumbnail, play/like/comment counts, and often a reference to the audio track it uses.

Iterate defensively and pull only fields you've confirmed exist:

def iter_reels(reels_response):
    items = reels_response if isinstance(reels_response, list) else (
        reels_response.get("items") or reels_response.get("data") or []
    )
    for reel in items:
        yield {
            "code": reel.get("code") or reel.get("shortcode"),
            "caption": reel.get("caption"),
            "plays": reel.get("play_count") or reel.get("view_count"),
            "likes": reel.get("like_count"),
            "comments": reel.get("comment_count"),
        }


for reel in iter_reels(reels):
    print(reel)

The point of using .get() everywhere and falling back across container keys is resilience: Instagram's payloads evolve, and you want your loop to keep running rather than crash on a renamed field.

Step 2: Find Reels by audio track with reels_audio

This is the fun one. Trending audio drives Reels distribution, so being able to list every Reel using a given track is genuinely useful for trend analysis. The reels_audio endpoint takes an audio_id query parameter:

def get_reels_by_audio(audio_id):
    return get("reels_audio", params={"audio_id": audio_id})

Where do you get an audio_id? In practice you discover it from a Reel you already fetched in Step 1 — many Reel objects include the audio/music metadata, and the track's numeric ID is in there. Inspect a Reel object to locate it, then feed it back in:

# Suppose you spotted an audio id while inspecting a reel object.
audio_id = "1234567890"  # replace with one you found in the Reels payload
audio_reels = get_reels_by_audio(audio_id)
print(json.dumps(audio_reels, indent=2)[:2000])

The response is, again, a collection of Reels — this time the ones sharing that track. Reuse the same iter_reels helper to walk it. This lets you answer questions like "how many creators are riding this sound?" or "what's the engagement spread on Reels using this track?"

Step 3: Pull engagement with media_comments

To go beyond counts and read actual audience reaction, use media_comments. Note this endpoint identifies media by its shortcode (code), not a numeric ID:

def get_comments(code):
    return get("media_comments", params={"code": code})


# Use a code you pulled from a reel or post earlier.
code = "CxAmPLe123"  # replace with a real shortcode from your data
comments = get_comments(code)
print(json.dumps(comments, indent=2)[:2000])

Comment payloads are typically a list of comment objects, each generally including the comment text, a like count, and some author info. As always, confirm the keys against your printed output:

def iter_comments(comments_response):
    items = comments_response if isinstance(comments_response, list) else (
        comments_response.get("comments")
        or comments_response.get("items")
        or comments_response.get("data") or []
    )
    for c in items:
        yield {
            "text": c.get("text"),
            "likes": c.get("comment_like_count") or c.get("like_count"),
        }


for c in iter_comments(comments):
    print(c)

Chaining these three endpoints — Reels → audio → comments — gives you a full engagement picture for a creator or a trend without ever touching a headless browser.

Error handling

Wrap calls so a bad shortcode, expired key, or rate limit doesn't take down your job:

def safe_get(path, params=None):
    try:
        resp = requests.get(f"{BASE_URL}/{path}", headers=HEADERS,
                            params=params, timeout=30)
        resp.raise_for_status()
        return resp.json()
    except requests.exceptions.HTTPError as e:
        code = e.response.status_code
        if code == 404:
            print(f"Not found: {path}")
        elif code in (401, 403):
            print("Auth error — check your x-rapidapi-key.")
        elif code == 429:
            print("Rate limited — back off or upgrade your tier.")
        else:
            print(f"HTTP {code}: {e}")
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")
    return None

Mind the rate limits when looping over many Reels or comment threads — the free Basic tier allows 1 request/second, so pace your calls accordingly.

The same flow in Node.js

If your stack is JavaScript, here's the user_reels call using the built-in fetch (Node 18+). The headers and query-param pattern are identical:

const HOST = "instagram-cheapest.p.rapidapi.com";
const API_KEY = "YOUR_RAPIDAPI_KEY";

async function getReels(userId) {
  const url = new URL(`https://${HOST}/api/v1/instagram/user_reels`);
  url.searchParams.set("user_id", userId);

  const resp = await fetch(url, {
    headers: {
      "x-rapidapi-key": API_KEY,
      "x-rapidapi-host": HOST,
    },
  });

  if (!resp.ok) {
    throw new Error(`Request failed: ${resp.status}`);
  }
  return resp.json();
}

getReels("13460080")
  .then((data) => console.log(JSON.stringify(data, null, 2)))
  .catch(console.error);

Same two headers, same query parameter, same raw JSON back. Swap the path for reels_audio?audio_id=... or media_comments?code=... to hit the other endpoints.

Bandwidth tip

Reels and comment payloads can get chunky. Every endpoint supports an optional fields parameter so you can request only the JSON fields you need and shrink the response. Each tier includes 10 GB/month (then $0.001/MB), so trimming payloads with fields is the easiest way to stay inside the allowance when you're pulling Reels in bulk.

A quick word on cost

Pricing here is per request and deliberately low: from $0.10 per 1,000 requests on the top Mega tier, $0.11 on Ultra, and $0.13 on Pro, with a free Basic tier (30 calls/month) for testing. Because a single trend analysis can chain many calls — Reels, then audio, then comments — that low per-request cost adds up in your favor. Pair it with the fields parameter to keep bandwidth in check and Reels analytics stays cheap even at volume.

Conclusion

You've now got a complete Reels workflow: list a creator's Reels with user_reels, pivot on a trending track with reels_audio, and read the room with media_comments — in both Python and Node.js. The recurring discipline is the same as with any raw-JSON API: print the response, confirm the field names, and iterate generically so your code survives Instagram's inevitable payload tweaks.

Start building with the free tier today:

Get the Instagram Cheapest API on RapidAPI →

Compliance note: this API returns public Instagram data only. You are responsible for complying with Instagram's terms and applicable privacy law (GDPR/CCPA). It is not affiliated with or endorsed by Meta/Instagram.

Start Building Today

Get 30 free requests per month on the Basic plan. No commitment required.

Get Started on RapidAPI