Instagram Reels Competitor Monitor API in Python

Build a public Instagram Reels competitor monitor with keyword search, creator feeds, post details, comments, cursor pagination, and reproducible local scoring.

A dark monitoring dashboard comparing public Instagram Reels across creator lanes

A useful Reels competitor monitor does more than list the posts with the most plays. It needs a repeatable cohort, a fixed observation window, creator-level context, and a way to distinguish a one-off viral post from a format that a competitor uses consistently. This guide builds that workflow from public Instagram data with four endpoints in the Instagram API catalog.

I ran the calls on October 25, 2026 (UTC) with the keyword “AI tools.” The response fields below are observed data, not a promise that Instagram will keep every nested field. If you need a broader endpoint map, read the Instagram public data API guide.

Key takeaway

  • Use keyword Reels search to discover the market, then creator Reels to compare consistent output.
  • Store shortcode, capture time, and raw counts. Calculate ratios locally.
  • Use the returned pagination token or cursor exactly as documented for that endpoint.
  • Summarize comments in aggregate and avoid retaining commenter identifiers.

The observed response path

The first search response nested its Reels at data.items, returned a count, and supplied a top-level pagination_token. The first match was noisy for the intended research question, so keyword matching needs an explicit caption relevance filter before ranking. A result exposed a shortcode plus observed play and comment counts, which is enough to create a dated discovery row.

Evidence card showing an Instagram Reels keyword-search result The live search returned a creator handle, shortcode, plays, comments, and a pagination token.

Next, user-reels provided creator history plus page_info. Its forward cursor belongs in after, while keyword search uses pagination_token. Mixing those two pagination styles is a common integration bug.

Evidence card showing Instagram user Reels count and pagination cursor Creator history adds the baseline needed to distinguish a repeatable format from one outlier.

Finally, the comment request used sort_by: popular and returned its own token. Comments can reveal repeated questions and objections that are invisible in a play count, but the monitor should retain themes and aggregate counts rather than profiles.

Evidence card showing the Instagram comments response and privacy rule Comment themes can improve a content brief without building a database of individual commenters.

Install and create a strict client

python -m pip install requests
export SANDBASE_API_KEY="your_key_here"
import os, time, requests

BASE = "https://api.sandbase.ai/v1/api"
HEADERS = {"Authorization": f"Bearer {os.environ['SANDBASE_API_KEY']}",
           "Content-Type": "application/json"}

def call(model, payload, retries=2):
    for attempt in range(retries + 1):
        r = requests.post(f"{BASE}/{model}", headers=HEADERS,
                          json=payload, timeout=90)
        if r.status_code < 500 and r.status_code != 429:
            r.raise_for_status(); break
        if attempt == retries: r.raise_for_status()
        time.sleep(2 ** attempt)
    body = r.json()
    if body.get("status") != "completed":
        raise RuntimeError(body.get("error") or body.get("status"))
    try:
        return body["outputs"][0]["data"]
    except (KeyError, IndexError, TypeError) as exc:
        raise RuntimeError("Missing outputs[0].data") from exc

Discover Reels and follow the search token

def search_reels(keyword, pages=2):
    token, rows, seen = None, [], set()
    for _ in range(pages):
        payload = {"keyword": keyword}
        if token:
            payload["pagination_token"] = token
        page = call("instagram/v2/search-reels", payload)
        block = page.get("data") or {}
        for reel in block.get("items") or []:
            code = reel.get("code")
            if not code or code in seen:
                continue
            seen.add(code)
            caption = reel.get("caption") or {}
            user = caption.get("user") or {}
            rows.append({
                "code": code,
                "username": user.get("username"),
                "caption": caption.get("text"),
                "plays": reel.get("ig_play_count"),
                "likes": reel.get("like_count"),
                "comments": reel.get("comment_count"),
                "captured_at": "2026-10-25",
            })
        token = page.get("pagination_token")
        if not token: break
    return rows

The live second-page call worked when the original keyword and returned token were sent together. Always keep the discovery query with the cursor in job state; this also makes a saved run auditable.

Add the creator baseline

def creator_reels(username, pages=2):
    after, rows = None, []
    for _ in range(pages):
        payload = {"username": username, "first": 12}
        if after: payload["after"] = after
        page = call("instagram/v3/user-reels", payload)
        rows.extend(page.get("items") or page.get("data") or [])
        info = page.get("page_info") or {}
        if not info.get("has_next_page"): break
        after = info.get("end_cursor")
        if not after: raise RuntimeError("next page advertised without end_cursor")
    return rows

For each competitor, calculate the median rather than the mean because one viral Reel can distort the baseline. Keep at least the sample size and capture date beside the result.

from statistics import median

def performance_index(plays, creator_plays):
    clean = [x for x in creator_plays if isinstance(x, (int, float)) and x >= 0]
    baseline = median(clean) if clean else None
    return round(plays / baseline, 2) if baseline and plays is not None else None

An index of 2.0 means the Reel had twice the sampled creator median at capture time. It does not mean the format caused the difference.

Enrich only the shortlist

def inspect_reel(code):
    info = call("instagram/v2/post-info", {"code_or_url": code})
    comments = call("instagram/v2/post-comments", {
        "code_or_url": code, "sort_by": "popular"
    })
    raw = (comments.get("data") or {}).get("items") or []
    sample = [{"position": i, "text": item.get("text")}
              for i, item in enumerate(raw[:50]) if item.get("text")]
    return {"post": info.get("data"), "comment_sample": sample}

Do this after local ranking. Calling post info and comments for every discovery result increases latency and cost without improving the first filter. Keep the minimized text sample only for bounded theme extraction, then discard it after saving aggregate themes and counts; never persist the original comment objects or their user/profile fields.

Configuration reference

EndpointRequiredOptional fields usedPagination
instagram/v2/search-reelskeywordpagination_tokentoken returned by search
instagram/v3/user-reelsusernamefirst (up to 50), after, last, beforepage_info.end_cursor
instagram/v2/post-infocode_or_urlnonenone
instagram/v2/post-commentscode_or_urlsort_by, pagination_tokenreturned token

Production checklist

Use a competitor allowlist approved by the research owner. Cap pages and creators per run. Write append-only snapshots so count changes remain visible. Do not overwrite an old row with a newer count. Redact signed media URLs from logs because they expire and are not durable evidence. Alert on schema changes, not on every empty optional field.

Separate observation from interpretation in the output: “play count was 592,961 at capture” is an observation; “the hook caused the result” is a hypothesis. A useful weekly report shows top posts by performance index, recurring hooks and questions, sample sizes, and links for manual review.

Troubleshooting and FAQ

  • No username in a row: skip creator enrichment but keep the Reel discovery record.
  • Repeated posts across pages: deduplicate by shortcode.
  • A media URL expires: use the shortcode or public post URL as the stable reference.
  • Likes are missing: leave them null; do not substitute plays.
  • The creator baseline changes: expected; compare dated snapshots with the same sample rule.

Is this a private analytics replacement?

No. It observes public posts. Owned-account analytics, reach, retention, and conversion require Instagram’s authorized products and account permissions.

Can I rank competitors by engagement rate?

Only if the denominator is defined and available. Label a play-based or follower-based ratio precisely and preserve the raw counts.

Where can I verify current endpoint options?

Use the live Instagram API catalog. Availability and nested payload fields can change after this test.