InstagramAnswer

Instagram hashtag API: recent and top posts for any tag

Meta's hashtag search caps you at 30 tags a week and hides usernames. Our API returns a hashtag's recent and top posts, 30 a call, with authors and likes.

5 min read

Short answer

  • Meta's Instagram API searches hashtags only through a Business or Creator account, at most 30 unique tags in 7 days, with recent posts from the last 24 hours.
  • Its hashtag results leave out the author's username, so you can't tell who posted without opening each link.
  • Our Instagram API returns a hashtag's recent or top posts, about 30 a call at 1 credit, with author, likes, comments and plays.
On this page

Yes, but Meta's version is narrow. The Instagram API's hashtag search runs from a Business or Creator account, allows 30 unique hashtags in any 7 days, returns recent posts from the last 24 hours only, and leaves out who posted them. Our Instagram API returns any hashtag's recent and top posts with the author, likes, comments and plays.

What Meta's hashtag search is for

The hashtag search runs from a Business or Creator account. It needs App Review for the Instagram Public Content Access feature and a token from a Facebook user approved for tasks on the connected Page. You look up a tag's ID, then read its top or recent media, up to 50 a page. Recent media covers posts from the last 24 hours, like counts drop out where the owner hid them, and the username field cannot be requested.

Hashtag posts with ToolzerHub

We make ToolzerHub. Our Instagram API returns public Instagram profiles, posts, reels and hashtags as JSON with one API key. /v1/instagram/hashtag/media takes hashtag and tab (recent or top) and costs 1 credit a call. Pass the response's next_max_id as max_id, and its next_page, for the next page.

bash
export TOOLZERHUB_API_KEY=your_key
curl -H "x-api-key: $TOOLZERHUB_API_KEY" \
  "https://api.toolzerhub.com/v1/instagram/hashtag/media?hashtag=sourdough&tab=top"

A reel from the top tab in data.medias, trimmed to the fields the script uses:

json
{
  "code": "DfU_vtxBJ1H",
  "taken_at": 1793813388,
  "product_type": "clips",
  "like_count": 801,
  "comment_count": 32,
  "play_count": 19122,
  "user": {"username": "connies_sweetest_things", "is_verified": true},
  "caption": {"text": "SOURDOUGH STARTER FROM SCRATCH | PART 1\n\nHave you ever wanted to make your own sourdough s"}
}

code builds the post link, instagram.com/p/<code>/. taken_at is Unix seconds. product_type is clips for a reel, carousel_container or carousel_item for a carousel and feed for a photo. Each post is the full media object, with more fields than shown.

Save a hashtag's posts to CSV

The script reads both tabs for each hashtag, keeps each post once and writes one row per post with author, kind, likes, comments and the first 500 characters of the caption. For each tag it prints how many minutes the newest 30 posts span, the median likes on each tab, and how many top posts are reels. Install requests (pip install requests) and save it as instagram_hashtag.py.

python
import csv
import os
import statistics
import sys
import time
from datetime import datetime, timezone

import requests

URL = "https://api.toolzerhub.com/v1/instagram/hashtag/media"
HEADERS = {"x-api-key": os.environ.get("TOOLZERHUB_API_KEY", "")}
FIELDS = ["hashtag", "tab", "code", "posted_at", "author", "verified", "kind", "likes", "comments", "caption", "url"]
KINDS = {"clips": "reel", "carousel_container": "carousel", "carousel_item": "carousel", "feed": "photo"}


def get_page(hashtag, tab, cursor, tries=3):
    # Retry rate limits, server errors and dropped connections.
    params = {"hashtag": hashtag, "tab": tab, **cursor}
    for attempt in range(tries):
        last = attempt == tries - 1
        try:
            r = requests.get(URL, headers=HEADERS, params=params, timeout=120)
        except requests.RequestException:
            if last:
                raise
            time.sleep(5 * (attempt + 1))
            continue
        if r.status_code in (429, 500, 502, 503, 504) and not last:
            time.sleep(5 * (attempt + 1))
            continue
        r.raise_for_status()
        return r.json()["data"]


def main(hashtags, pages="2", out="instagram_hashtag.csv"):
    with open(out, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        writer.writeheader()
        for tag in filter(None, (h.strip().lstrip("#") for h in hashtags.split(","))):
            summary = {}
            for tab in ("recent", "top"):
                posts, seen, cursor = [], set(), {}
                for _ in range(int(pages)):
                    data = get_page(tag, tab, cursor)
                    # The top tab can repeat a post within one page, so check each one.
                    for m in data.get("medias") or []:
                        if m["code"] not in seen:
                            seen.add(m["code"])
                            posts.append(m)
                    if not data.get("more_available"):
                        break
                    cursor = {"max_id": data.get("next_max_id"), "next_page": data.get("next_page")}
                for m in posts:
                    writer.writerow({
                        "hashtag": tag, "tab": tab, "code": m["code"],
                        "posted_at": datetime.fromtimestamp(m["taken_at"], timezone.utc).isoformat(),
                        "author": m["user"]["username"], "verified": m["user"].get("is_verified"),
                        "kind": KINDS.get(m.get("product_type"), m.get("product_type")),
                        "likes": m.get("like_count"), "comments": m.get("comment_count"),
                        "caption": ((m.get("caption") or {}).get("text") or "")[:500],
                        "url": f"https://www.instagram.com/p/{m['code']}/",
                    })
                summary[tab] = posts
            stamps = sorted((m["taken_at"] for m in summary["recent"]), reverse=True)[:30]
            minutes = (stamps[0] - stamps[-1]) / 60 if len(stamps) > 1 else 0
            top = summary["top"]
            now = time.time()
            med = {t: statistics.median([m.get("like_count") or 0 for m in p] or [0]) for t, p in summary.items()}
            age = statistics.median([(now - m["taken_at"]) / 86400 for m in top] or [0])
            print(f"#{tag}: newest 30 posts within {minutes:.0f} minutes, median {med['recent']:,.0f} likes; "
                  f"{len(top)} top posts, median {med['top']:,.0f} likes, "
                  f"{sum(m.get('product_type') == 'clips' for m in top)} reels, median age {age:.1f} days")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        sys.exit('usage: python instagram_hashtag.py "sourdough,marathon" [pages] [out.csv]')
    if not HEADERS["x-api-key"]:
        sys.exit("set TOOLZERHUB_API_KEY first")
    main(*sys.argv[1:])

What four hashtags showed

python instagram_hashtag.py "sourdough,marathon,skincare,vanlife" 2 made 16 calls in 22 seconds and saved 302 posts across both tabs.

Hashtag Newest 30 posts span Recent, median likes Top posts Top, median likes Reels in top Top, median age
#sourdough 27 minutes 2 30 1,900.5 28 14.8 days
#marathon 34 minutes 8.5 32 953.5 23 1.8 days
#skincare 5 minutes 0 30 762.5 28 1.1 days
#vanlife 43 minutes 1 30 1,131.5 24 2.9 days

Recent posts had a median of 0 to 8.5 likes, against 762.5 to 1,900.5 on the top tab. #skincare added 30 new posts in 5 minutes. The top tab is mostly reels, 103 of 122, and days old rather than minutes. Two pages gave 30 to 32 unique top posts per tag, so the top tab ends at about 30, while recent gave 30 to 60.

What it costs

A tag is 4 credits at two pages of each tab. The 100 free credits a month cover 25 tags at that depth; $19 buys 10,000 credits that never expire, and plans for higher volume cover tracking tags every hour. Set your key and the first hashtag is a CSV in under a minute.

For a creator's own posts, see Instagram API get user posts. For their reels, see the Instagram reels API, and for the reels that use a song, the Instagram music API.

We are not affiliated with Instagram or Meta.

Get this data from your own code

The Instagram API returns it as JSON with one x-api-key header. 100 free credits monthly.