---
title: "Search Instagram posts by keyword or topic: reels about a brand, phrase or subject, with view counts (1 credit a page)"
method: GET
path: "/instagram/search/reels"
tags: ["instagram"]
---

# Search Instagram posts by keyword or topic: reels about a brand, phrase or subject, with view counts (1 credit a page)

`GET /instagram/search/reels`

## Request

- Base URL: `https://www.socialcrawl.dev/v1`
- URL: `https://www.socialcrawl.dev/v1/instagram/search/reels`
- Auth: API key in header `x-api-key`

Searches Instagram for reels matching a keyword query. Returns matching reels with view counts (engagement.views, the play count), like counts, comment counts, and author info, sourced from the Instagram surface that still carries play counts. Each row carries post.ext.author_id (the creator's numeric user id) and post.author.username. `post.ext.author_followers` is null on a plain call, because Instagram stopped sending follower counts in its search payload in August 2026; `include=creator` fills it. Three optional params add what the search itself lacks. Each works alone, and they combine; without them the call is unchanged. `region` (ES, MX, DE, BR, KR or FR) localises the results. Instagram does not rank search by country, so the market's own name is added to your query (`fitness` becomes `fitness españa`), unless the query already names the market or is already written in the market's own script (Hangul for KR), which Instagram localises by itself. The response reports what ran in `data.region`: `{ requested, method: "localised_query", query_sent }`. `region` favours creators from that market but does not promise that every creator is from it; `country` does. `include=creator` adds each reel's creator card, read from the creator's About this account panel: `post.ext.author_country`, `author_followers`, `author_following`, `author_posts_count`, `author_public_email` and `author_public_phone`, plus the name, avatar and verified flag where the row lacks them. It labels rows and never drops, adds or reorders them. `author_country` is the country the creator declares on that panel, not where the reel was filmed. A null means Instagram does not publish one for that creator, and nothing is guessed. `country` keeps only the reels whose creator declares that market. It adds the creator card, and when `region` is not sent it localises the query for the same market. `data.country` reports `{ requested, kept, dropped_other, dropped_unknown }`: rows from another country, and rows whose creator publishes none, are dropped and counted. Topics about a place, such as travel or eSIMs, are often made by visitors, so the localised query finds fewer local creators there. A phrasing locals use helps (`esim móvil` rather than `esim`), and `country` keeps only the market's creators. `region` and `country` mean what they mean on TikTok search. Only the mechanism of `region` differs: TikTok searches from the market, while Instagram, which ignores where a search comes from, gets a localised query. Price: `region` costs nothing extra. The creator card costs 2 credits per creator looked up. Two reels by one creator are one lookup, a creator that cannot be found is refunded, and a creator looked up in the last 15 minutes is free. Time: `region` adds nothing. The creator card typically adds about 5 to 17 seconds on a fresh page (the lookups run 12 at a time), and little when the creators were looked up in the last 15 minutes. `data.hydration` reports the rows, lookups, cache hits, credits held and kept, and the time. On a `max_pages` walk each creator is looked up once, the report sums every page, and `from_earlier_pages` counts rows filled free from a creator already looked up earlier in the walk. A walk starts another page with the creator card only while at least 30 seconds of its time remain, so a `max_pages` walk with `include=creator` or `country` often stops after its first fresh page with `stopped: "time_budget"`; pages whose creators are cached go further. `data._warnings` carries `creator_partial` when some creators could not be read (they are refunded) and `creator_unavailable` when none could. With `country`, a page where no creator matches is `items: []` with the warning `country_no_match`, and its lookups are still billed. A page left empty only because the creator lookups failed costs nothing. `format=raw` cannot be combined with `include=creator` or `country`; that is a free 400. Note: engagement.shares carries the reel's share count on most rows; it is null where Instagram's search surface omits it, on date-filtered searches, and on pages served while that surface is unavailable. For a share count on every reel, pass a result URL to /v1/instagram/post/stats, or use /v1/instagram/profile/reels/full to get a whole creator's reels with share counts merged in. engagement.saves is null: saves are platform-private. Instagram exposes no numeric save metric on any surface, and it is never fabricated. Result ordering is Instagram's own relevance ranking and is not stable between calls: the same query re-run minutes later can return a different, overlapping set. Rank on engagement.views client-side rather than relying on result order, and deduplicate by post.id across pages. Note on date_posted: filtered searches are served by the only Instagram surface that offers a date filter, and that surface no longer carries play counts, so engagement.views is null when date_posted is set. If you need both a date window and view counts, omit date_posted and filter on published_at yourself. Pagination: forward `pagination.next_cursor` back as `cursor` and keep calling while `pagination.has_more` is true. The walk ends with `has_more: false` and an empty `items` list, charged 0 credits. Two things during a walk are normal and are not the end: a page can come back empty at 0 credits, when every reel on it was already returned earlier in the same walk or when Instagram answers that page with an empty result, and page sizes vary, so keep going while `has_more` is true and do not stop on the first short or empty page. Follow `next_cursor` in preference to requesting page numbers yourself: the cursor carries the reels already delivered, so repeats are dropped and a page with nothing new is not charged, while a bare `page=3` can return reels from an earlier page. A query with no matches returns an empty list rather than an error. A short keyword query such as `esim europa` usually pages with view counts on every page. A long phrase or a question often matches only on a surface that returns one page with view counts, so its later pages come back with engagement.views null. Note that a date-filtered search draws on a much smaller pool: expect roughly 10 reels per page and a walk that ends after a handful of pages, against 30 per page and far greater depth when date_posted is omitted.

## Query parameters

- `query` string, required
- `date_posted` 'last-week' | 'last-month' | 'last-year'
- `page` integer
- `region` 'ES' | 'MX' | 'DE' | 'BR' | 'KR' | 'FR'
- `include` 'creator'
- `country` 'ES' | 'MX' | 'DE' | 'BR' | 'KR' | 'FR'
- `relevance` 'score' | 'filter'
- `relevance_threshold` string
- `relevant_to` string
- `judgments` 'on' | 'off'
- `dry_run` '1'
- `fit` 'goal'
- `goal` string
- `fit_tokens` integer
- `label` string
- `exclude` string
- `brand` string
- `brand_description` string
- `offer` string
- `label_evidence` '1'
- `since` string
- `until` string
- `min_views` integer
- `max_age_days` integer
- `sort_rows` 'views'
- `max_pages` integer
- `seen` string
- `cursor` string

## Headers

- `Cache-Control` string
- `Idempotency-Key` string

## Response `200`

Successful response

- object
  - `success` boolean, required: Whether the request succeeded
  - `platform` string, required: Platform name
  - `endpoint` string, required: API endpoint path
  - `data` object, required: Platform-specific response data
    - `items` object[]: Array of canonical post wrappers ({ post, computed })
      - `post` object: Canonical Post object (unified across all platforms)
        - `id` string: Platform-specific post ID (always a string; numeric upstream IDs are stringified)
        - `url` string, nullable: Direct URL to the post on the source platform
        - `content` object: Post content fields (text, media, thumbnail, duration)
          - `text` string, nullable: Post caption, description, or text content
          - `media_urls` union: URL(s) of the primary media. Single string for video/photo posts; array of strings for carousels.
            - string
            - string[]
          - `thumbnail_url` string, nullable: URL to thumbnail image
          - `duration_seconds` integer, nullable: Video/clip duration in seconds (null for non-video posts)
        - `author` object: Subset of Author fields for the post creator
          - `username` string, nullable: Author username
          - `display_name` string, nullable: Author display name
          - `avatar_url` string, nullable: URL to author profile picture
          - `verified` boolean, nullable: Whether the author account is verified
        - `engagement` object: Engagement counts
          - `views` integer, nullable: View count (if available). For Instagram video/reels this is the play count: the headline 'Views' the IG app shows. Instagram removed per-post play counts from its public web pages in August 2026, so `GET /v1/instagram/post` now fills the count automatically from a second source within the same call: videos and reels keep a numeric `views` with no extra endpoint or workaround. Note: since mid-July 2026 Instagram's play count is Instagram-only. It no longer includes Facebook crosspost views (Meta-side change; the app UI changed identically). For combined reach, also fetch the Facebook crosspost via `/v1/facebook/post`.
          - `likes` integer, nullable: Like / reaction count
          - `comments` integer, nullable: Comment count. Note: on `GET /v1/instagram/post` this is Instagram's `edge_media_to_parent_comment.count` (top-level comments); the IG list endpoints report the mobile `comment_count` total (includes replies). Both land on this same field.
          - `shares` integer, nullable: Share / repost / retweet count. On Instagram this is `reshare_count` (the paper-plane Share / Send count) and it exists only on Instagram's mobile surface, so it is returned by `/v1/instagram/post/stats`, `/v1/instagram/profile/reels/full`, and `/v1/instagram/profile/posts/full`. The web-sourced Instagram list and search endpoints return `null` (never fabricated).
          - `saves` integer, nullable: Save / bookmark count. `null` on Instagram: the save count is platform-private and Instagram exposes no numeric save metric on any surface (never fabricated).
        - `flags` object: Boolean flags on the post
          - `nsfw` boolean, nullable: NSFW flag (null when platform does not surface)
          - `spoiler` boolean, nullable: Spoiler flag (null when platform does not surface)
          - `pinned` boolean, nullable: Pinned-to-profile flag (null when platform does not surface)
          - `deleted` boolean: Whether the post is tombstoned (always present)
          - `likes_hidden` boolean, nullable: Present and `true` when the creator has hidden the like count. `engagement.likes` is `null` in that case (never the platform's decoy preview number). Absent on normal posts.
          - `comments_hidden` boolean, nullable: Present and `true` when the upstream reports the comment count as hidden. `engagement.comments` is `null` in that case. Absent on normal posts.
          - `shares_hidden` boolean, nullable: Boolean at post.flags.shares_hidden
          - `views_hidden` boolean, nullable: Boolean at post.flags.views_hidden
          - `saves_hidden` boolean, nullable: Boolean at post.flags.saves_hidden
        - `published_at` union: Post creation timestamp as an ISO 8601 UTC string. When the upstream sent a Unix epoch it is converted here and the raw epoch is preserved under `post.ext.published_at_epoch` for one deprecation cycle.
          - string
          - integer
        - `ext` object, nullable: Platform-specific passthrough fields for cross-endpoint chains (present only on platforms that surface them)
          - `music_id` string, nullable: TikTok music/clip id (exact string): pass to `/v1/tiktok/song/videos?clipId=` to find videos using the same sound
          - `author_id` string, nullable: The creator's platform-native numeric user id. On TikTok search and list items, pass to `/v1/tiktok/profile?user_id=` for the creator's current follower count (survives username changes). On Instagram `search/reels` items it is present on every row from every serving source, accepted by `/v1/instagram/basic-profile?userId=`. On Facebook it appears when the upstream exposed a numeric actor id and no real handle.
          - `author_url` string, nullable: The posting account's profile URL, so a matching post can be followed to its account. Present on `/v1/facebook/search/posts` rows: the vanity or `/people/<name>/<id>/` URL when Facebook sends one, otherwise `https://www.facebook.com/profile.php?id=<numeric id>` built from the account id. Pass it as `url` to `/v1/facebook/profile` (or to `/v1/prism/profiles` with `platform: facebook`). Null when the row names neither an account URL nor a numeric account id. Absent on other endpoints.
          - `author_followers` integer, nullable: The creator's follower count as embedded in the search payload itself, when the search source happens to carry one. On Instagram `search/reels` it is null on a plain call, because Instagram stopped sending follower counts in its search payload in August 2026; send `include=creator` (or `country`) and it is filled from the creator's profile, 2 credits per creator looked up. It stays null when that lookup does not resolve.
          - `author_following` integer, nullable: Numeric at post.ext.author_following
          - `author_posts_count` integer, nullable: Numeric at post.ext.author_posts_count
          - `in_reply_to_id` string, nullable: String at post.ext.in_reply_to_id
          - `in_reply_to_username` string, nullable: String at post.ext.in_reply_to_username
          - `author_country` string, nullable: The creator's country as they declare it on Instagram's About this account panel, as a country name (for example `Spain`). It is not where the reel was filmed. Present only on `/v1/instagram/search/reels` when the request sends `include=creator` (or `country`), filled by looking the creator up. Null when Instagram does not publish a country for the creator or the lookup did not resolve, and nothing is guessed; absent on a plain call.
          - `author_public_email` string, nullable: The public contact email on the creator's profile. Present only on `/v1/instagram/search/reels` when the request sends `include=creator`, filled by looking the creator up. Null when the creator lists no public email or the lookup did not resolve; absent on a plain call.
          - `author_public_phone` string, nullable: The public contact phone number on the creator's profile. Present only on `/v1/instagram/search/reels` when the request sends `include=creator`, filled by looking the creator up. Null when the creator lists no public phone or the lookup did not resolve; absent on a plain call.
          - `download_count` integer, nullable: How many times the video has been saved to a device. TikTok only, and distinct from `engagement.saves`: a save keeps the video in a private collection on the platform, a download takes a copy off it, and TikTok counts the two separately. Present on TikTok post, search and post-list responses; absent everywhere else.
          - `region` string, nullable: TikTok only: the country TikTok registers the video to, as an ISO 3166-1 alpha-2 code (normally the creator's account country when they posted). It is not the viewer's country, not the `region` you requested, and not a language. On `/v1/tiktok/trending`, `/v1/tiktok/search`, `/v1/tiktok/search/top`, `/v1/tiktok/profile/videos` and `/v1/tiktok/search/hashtag` it is how you tell which rows are from a given country: filter on it when you need only that country. A row whose source omits the country stays null. The `region` request parameter still only sets the proxy.
          - `subreddit` string, nullable: Reddit subreddit name (search/list items): pass to `/v1/reddit/subreddit/details?subreddit=`
          - `title` string, nullable: String at post.ext.title
          - `selftext` string, nullable: String at post.ext.selftext
          - `upvote_ratio` integer, nullable: Reddit only: the share of a post's votes that are upvotes, as a fraction from 0 to 1 and usually close to 1 (six live rows measured 1, 1, 1, 0.86, 0.75, 1). Fractional despite the integer type this schema emits for every numeric leaf, the same caveat as `author.ext.average_rating`. Populated on `/v1/reddit/search` rows whose source carries it, and null everywhere else on Reddit including `/v1/reddit/post` and `/v1/reddit/subreddit`.
          - `flair` string, nullable: String at post.ext.flair
          - `content_language` string, nullable: The language the platform itself tags the post with, when it sends one: Reddit's own language tag on Reddit lanes, and TikTok's caption-language tag (for example `de`) on `/v1/tiktok/trending?feed=local`. Null when the platform could not tell (Reddit `und`, TikTok `un`, common on short or emoji-only captions); absent where the source sends no tag. Never inferred from the text by us: that is `post.computed.language`.
          - `type` string, nullable: String at post.ext.type
          - `content_type` string, nullable: Leaf at post.ext.content_type
          - `ticker_symbols` string[], nullable: Array at post.ext.ticker_symbols
          - `video_view_count` integer, nullable: Instagram's legacy 3-second-view count. Historical: Instagram stopped serving it in August 2026 (removed from its web post surface along with the web play count), so fresh `/v1/instagram/post` lookups no longer carry it and no source can restore it. Use `engagement.views` (the play count) instead.
          - `ig_play_count` integer, nullable: Instagram-only play count (`ig_play_count`). Since mid-July 2026 Instagram's headline play count (`engagement.views`) no longer includes Facebook crosspost views and equals this value; it is surfaced explicitly so you can tell the Instagram-only figure apart and detect any future re-divergence. For combined Instagram + Facebook reach, also fetch the Facebook crosspost via `/v1/facebook/post`. Present on `/v1/instagram/post/stats` and, since the August 2026 views fix, on `/v1/instagram/post` for video posts.
          - `repost_count` integer, nullable: Numeric at post.ext.repost_count
          - `media_type` string, nullable: Leaf at post.ext.media_type
          - `text_truncated` boolean, nullable: Boolean at post.ext.text_truncated
          - `ip_location` string, nullable: String at post.ext.ip_location
          - `carousel_count` integer, nullable: Numeric at post.ext.carousel_count
          - `remix_count` integer, nullable: Numeric at post.ext.remix_count
          - `audio_cluster_id` string, nullable: Instagram's audio cluster for a reel, as an exact digit string: the id Instagram uses to group different uploads of the same sound. Present on `/v1/instagram/audio/reels` rows and on `/v1/instagram/profile/reels` with `include=stats`; absent on a row when it is not available and on every other surface.
          - `facebook_likes` integer, nullable: Instagram only: the Facebook cross-post share that was EXCLUDED from `engagement.likes` on this row. A post shared to Facebook arrives from some sources with its Facebook likes already folded into the Instagram count, so the share is subtracted and published here, which keeps `engagement.likes` the Instagram-only figure instagram.com shows on the post. Add the two together for the combined number. Absent when the read carried no Facebook share, and absent on a post whose owner hides the like count, because nothing can be subtracted from a null. A response that excluded a share also carries a note in `data._warnings`.
          - `facebook_comments` integer, nullable: Instagram only: the Facebook cross-post share that was EXCLUDED from `engagement.comments` on this row, the comment counterpart of `post.ext.facebook_likes`. Each leaf appears only for the count it actually changed, so a post whose likes are hidden but whose comments were reconciled carries this one alone.
          - `instagram_views` integer, nullable: Instagram only: the Instagram-only play count of a video, the same figure on every Instagram endpoint. `engagement.views` keeps each endpoint's established meaning: on `/v1/instagram/profile/reels`, `/v1/instagram/audio/reels`, `/v1/instagram/search/reels` and `/v1/instagram/search/hashtag` it can include the post's Facebook cross-post views, while on `/v1/instagram/post` and `/v1/instagram/post/stats` it is Instagram-only. Read this leaf when you compare one post across endpoints. Absent on photos and when the source did not report the Instagram-only figure.
          - `facebook_views` integer, nullable: Instagram only: the Facebook cross-post part of a video's play count, the counterpart of `post.ext.instagram_views`. Present only when the source reported the Facebook part, or a combined total above the Instagram figure. Absent does not mean zero: some sources report the Instagram figure alone even for a cross-posted video.
          - `published_at_epoch` integer, nullable: Raw Unix epoch for `published_at` (seconds, or milliseconds when the upstream sent millis). Present only when the upstream sent a numeric epoch that was normalised to the ISO 8601 `published_at` string. Kept for one deprecation cycle for integrations pinned to the numeric form.
          - `usertags` string[], nullable: Array at post.ext.usertags
          - `coauthors` string[], nullable: Instagram collaborative posts (the native "Collab" feature). The full list of co-author accounts on the post, as `{ id, username, full_name, is_verified, profile_pic_url }`. A collab post has ONE producer and appears in every co-author's grid, so `post.author` is whichever account created it, which is not necessarily the profile you queried. The complete set of accounts on a post is `post.author.username` plus every `username` in this array. An empty array means Instagram reports the post as NOT a collab; the field is absent on surfaces that carry no co-author signal, including `/v1/instagram/post` (its web source ships the field permanently empty, so use `/v1/instagram/post/stats` for a single post).
          - `music` string, nullable: Leaf at post.ext.music
          - `sponsor_tags` string[], nullable: Array at post.ext.sponsor_tags
          - `sponsored_disclosed` boolean, nullable: Boolean at post.ext.sponsored_disclosed
          - `location` string, nullable: Leaf at post.ext.location
          - `media` object[], nullable: Array at post.ext.media
            - `type` string: Leaf at post.ext.media.type
            - `url` string, nullable: String at post.ext.media.url
            - `width` integer, nullable: Numeric at post.ext.media.width
            - `height` integer, nullable: Numeric at post.ext.media.height
          - `top_comments` object[], nullable: Array at post.ext.top_comments
            - `id` string, nullable: String at post.ext.top_comments.id
            - `text` string, nullable: String at post.ext.top_comments.text
            - `username` string, nullable: String at post.ext.top_comments.username
            - `likes` integer, nullable: Numeric at post.ext.top_comments.likes
            - `published_at` string, nullable: String at post.ext.top_comments.published_at
          - `hashtags` string[], nullable: Array at post.ext.hashtags
          - `mentions` string[], nullable: Array at post.ext.mentions
          - `quoted_post` string, nullable: Threads quote posts: the post this one quotes, as `{ id, url, author: { id, username }, text, media_urls, thumbnail_url }`. A quote post usually carries no caption or media of its own, and the text and video you see on the post's page belong to this attachment, by a different author. It is returned here rather than merged into `post.content`, so `content.*` always stays the outer post's own content and one account's media is never attributed to another. Absent on posts that are not quote posts.
          - `retweeted_post` string, nullable: Leaf at post.ext.retweeted_post
          - `quote_count` integer, nullable: Numeric at post.ext.quote_count
          - `all_media_urls` string[], nullable: Array at post.ext.all_media_urls
          - `reaction_counts` string[], nullable: Array at post.ext.reaction_counts
          - `share_urn` string, nullable: String at post.ext.share_urn
          - `post_type` string, nullable: String at post.ext.post_type
          - `activity_id` string, nullable: String at post.ext.activity_id
          - `published_at_precision` string, nullable: String at post.ext.published_at_precision
          - `author_urn` string, nullable: String at post.ext.author_urn
          - `author_headline` string, nullable: String at post.ext.author_headline
          - `author_type` string, nullable: String at post.ext.author_type
          - `is_repost_quote` boolean, nullable: Boolean at post.ext.is_repost_quote
          - `article` string, nullable: Leaf at post.ext.article
          - `reaction_type` string, nullable: String at post.ext.reaction_type
          - `download_media_urls` string[], nullable: Array at post.ext.download_media_urls
          - `tags` string[], nullable: Array at post.ext.tags
          - `categoryId` string, nullable: String at post.ext.categoryId
          - `categoryTitle` string, nullable: String at post.ext.categoryTitle
          - `topicCategories` string[], nullable: Array at post.ext.topicCategories
          - `duration` string, nullable: String at post.ext.duration
          - `license` string, nullable: String at post.ext.license
          - `madeForKids` boolean, nullable: Boolean at post.ext.madeForKids
          - `defaultAudioLanguage` string, nullable: String at post.ext.defaultAudioLanguage
          - `hasPaidProductPlacement` boolean, nullable: Boolean at post.ext.hasPaidProductPlacement
          - `caption` string, nullable: String at post.ext.caption
          - `position` integer, nullable: Numeric at post.ext.position
          - `playlistId` string, nullable: String at post.ext.playlistId
          - `videoOwnerChannelId` string, nullable: String at post.ext.videoOwnerChannelId
          - `videoPublishedAt` string, nullable: String at post.ext.videoPublishedAt
          - `description` string, nullable: String at post.ext.description
          - `default_language` string, nullable: String at post.ext.default_language
          - `localizations` string, nullable: Leaf at post.ext.localizations
          - `playlist_item_id` string, nullable: String at post.ext.playlist_item_id
          - `playlist_owner_channel_id` string, nullable: String at post.ext.playlist_owner_channel_id
          - `playlist_owner_title` string, nullable: String at post.ext.playlist_owner_title
          - `channel_id` string, nullable: String at post.ext.channel_id
          - `published_label` string, nullable: String at post.ext.published_label
          - `published_precision` string, nullable: Leaf at post.ext.published_precision
          - `title_maybe_translated` boolean, nullable: Boolean at post.ext.title_maybe_translated
          - `video_count` integer, nullable: Numeric at post.ext.video_count
          - `commerce` string, nullable: Leaf at post.ext.commerce
          - `on_screen_texts` string[], nullable: Array at post.ext.on_screen_texts
          - `topic_tag` string, nullable: String at post.ext.topic_tag
          - `reshare_count` integer, nullable: Numeric at post.ext.reshare_count
          - `dsp_ids` string, nullable: Leaf at post.ext.dsp_ids
          - `topic_tag_id` string, nullable: String at post.ext.topic_tag_id
          - `amazon_shop_lists` string[], nullable: Array at post.ext.amazon_shop_lists
          - `amazon_shop_trending_picks` string[], nullable: Array at post.ext.amazon_shop_trending_picks
          - `amazon_shop_curations` string[], nullable: Array at post.ext.amazon_shop_curations
          - `amazon_shop_socials` string[], nullable: Array at post.ext.amazon_shop_socials
          - `ad` string, nullable: Ad Library envelope (Facebook `adlibrary/ad` and `adlibrary/company/ads` items): page_id, currency, spend, reach_estimate, publisher_platforms, categories, targeted_or_reached_countries, cta_text, cta_type, link_url, is_active, end_date_iso, display_format, title, video_hd_url, video_sd_url. Absent on every non-ad surface.
          - `apple_music` string, nullable: Leaf at post.ext.apple_music
          - `feedback_id` string, nullable: String at post.ext.feedback_id
          - `question_id` string, nullable: Naver KnowledgeiN (`/v1/naver/kin/search`) only: the question's id (Naver's `docId`), read from the row's link. Kin returns one row per answer, so several rows can share it; send `group=question` for one row per question.
          - `answer_no` integer, nullable: Naver KnowledgeiN only: which answer of the question this row is (Naver's `answerNo`), read from the row's link.
          - `answer_nos` integer[], nullable: Naver KnowledgeiN with `group=question` only: every answer number of the question that was on the page, best-ranked first.
          - `published_at_est` string, nullable: Naver KnowledgeiN only: the question's date ESTIMATED from its id (`YYYY-MM-DD`, Korea time), never a published date, so `published_at` stays null. Absent for questions older than the calibration table.
          - `date_basis` string, nullable: How `post.ext.published_at_est` was derived. `kin_docid_estimate`: interpolated from the question id against question pages whose posted date was read.
          - `event` string, nullable: Leaf at post.ext.event
          - `trend` string, nullable: Trend-board envelope: what TikTok's own trend board says about the row. On `/v1/tiktok/hashtags/popular`: `rank`, `country_code`, `period_days`, `posts` and `views` for the window (not lifetime totals), `industry` / `industry_id` / `industry_label` for the first board the hashtag was read from (null for the overall board), `boards` listing EVERY board it ranks on as `{ industry, industry_id, industry_label, rank }` in board order (so a hashtag in the top 3 overall and on an industry board shows both), `popularity_curve` as `{ date, value }` with value 0 to 100 relative to the window's peak, and `top_creators`. On `/v1/tiktok/videos/popular`: `rank`, `country_code`, `period_days`, `order_by`, `period_views`, `organic_views`, `engagement_rate`, `six_second_view_through_rate`, `content_tags`. Absent on every non-board surface.
          - `updated_at` string, nullable: String at post.ext.updated_at
          - `alt_text` string, nullable: String at post.ext.alt_text
          - `board_name` string, nullable: String at post.ext.board_name
      - `computed` object: Server-computed analytics fields (consistent across platforms)
        - `engagement_rate` integer, nullable: Computed engagement rate (0..1). Null when the upstream lacks enough signal.
        - `language` string, nullable: ISO 639-1 language code detected from the post text. Null when undetectable.
        - `content_category` string, nullable: Keyword-classified content category (e.g. tech, food, gaming). Null when undetectable.
        - `estimated_reach` integer, nullable: Estimated reach based on views or follower count. Null when uncomputable.
        - `save_rate` integer, nullable: Numeric at computed.save_rate
        - `relevance` object, nullable: Nested object: computed.relevance
          - `p` integer: Numeric at computed.relevance.p
          - `sense` string: Leaf at computed.relevance.sense
          - `depth` integer: Numeric at computed.relevance.depth
          - `spam` integer: Numeric at computed.relevance.spam
        - `labels` string, nullable: Leaf at computed.labels
        - `labels_evidence` string, nullable: Leaf at computed.labels_evidence
    - `next_cursor` string, nullable: Opaque cursor for the next page. Pass it back as a query parameter on endpoints that support pagination. Present only when the upstream reports more results.
    - `total` integer, nullable: Total number of matching results, when the upstream provides a count. Omitted otherwise.
    - `dropped` integer, required: Number of upstream list items dropped because they could not be repaired to the endpoint schema. Valid list responses include 0.
    - `region` object: Present when the request sent `region`, or `country` without `region`. What the localisation did: Instagram does not rank search by country, so the market's own name is added to the query unless the query already names the market or is already written in the market's own script (Hangul for KR), which Instagram localises by itself.
      - `requested` string, required: The two-letter market code that was applied.
      - `method` 'localised_query', required: How the market was applied. `localised_query`: the market's local name was added to the query.
      - `query_sent` string, required: The query actually searched. Equal to your query when it already named the market.
    - `country` object: Present when the request sent `country`. Only reels whose creator declares that market on Instagram's About this account panel are kept; the rest are dropped and counted here. Kept rows keep their order.
      - `requested` string, required: The two-letter market code rows were kept for.
      - `kept` integer, required: Rows returned.
      - `dropped_other` integer, required: Rows dropped because the creator declares another country.
      - `dropped_unknown` integer, required: Rows dropped because the creator publishes no country, or because the creator lookup did not resolve. Nothing is guessed.
    - `hydration` object: Present when the request sent `include=creator` or `country`. What the creator card join did and cost. On a `max_pages` walk it sums every page walked.
      - `include` string: The token that switched the join on.
      - `implied_by` string: Present when you did not send `include` and another param switched the join on: `country`, which needs each creator's declared country and so looks every creator up. The join's credits (`extra_credits`) are then part of `credits_used`. Absent when you sent `include=creator` yourself.
      - `rows` integer: Rows the join looked at.
      - `looked_up` integer: Rows that needed a creator lookup.
      - `filled` integer: Rows that received at least one creator field.
      - `cached` integer: Of `filled`, rows filled from a creator looked up in the last 15 minutes, at no charge.
      - `unfilled` integer: Rows that needed a lookup and were not filled. Their credits are refunded.
      - `credits_held` integer: Credits held up front for the join, on top of the page's own price.
      - `extra_credits` integer: Of `credits_held`, the credits kept: 2 per creator filled from a fresh lookup. The rest was refunded.
      - `ms` integer: Time the join added, in milliseconds.
      - `from_earlier_pages` integer: `max_pages` walks only: rows filled free from a creator already looked up on an earlier page of the same walk. Not counted in `looked_up`, `filled` or `cached`. Absent when zero.
      - `cached_page` boolean: True when the whole page was served from cache, so the join cost nothing on this call.
    - `_warnings` string[]: Non-fatal notices about this response (field-map drift, clamped computed values). Advisory only: its presence never means the request failed. Omitted entirely when there is nothing to report, so treat absent as 'no warnings'.
  - `credits_used` integer, required: Number of credits consumed
  - `credits_remaining` integer, nullable, required: Current account balance, or null when this response could not establish one. Free cache hits always return null because they do not establish an authoritative post-request balance. An idempotency replay also returns null when its current-balance lookup fails. It is never a fabricated zero, so a zero here means the account really is empty. The authoritative reading is the one on a billed response, which comes straight from the deduction.
  - `request_id` string, required: Unique request identifier for support
  - `cached` boolean, required: Whether the response was served from cache
  - `idempotent_replay` boolean: True only when this response is an idempotency replay
  - `meta` object: Optional. Present only when this call completed a pattern that one other call does more cheaply or in one step, at most once per pattern per API key per day. Send the header `X-SocialCrawl-Hints: off` to never receive it.
    - `hint` object: One suggestion: what to call instead and why, with the price when the suggested call costs more than the calls it replaces.
      - `code` string: creator_card | batch_profiles | search_multi | incremental_sync | comment_labels | search_repeat
      - `message` string: One sentence for a person or an agent to read.
      - `method` string: GET or POST.
      - `path` string: The call the hint points to.
  - `pagination` object, required: Cursor state for this page. Present on every list response.
    - `next_cursor` string, nullable, required: Opaque token to send back as `cursor` for the next page, or null at end-of-list. Pass it back verbatim; never decode or trim it.
    - `has_more` boolean, required: Explicit stop signal. Prefer this over inspecting next_cursor or comparing against total.
    - `page_size` integer, required: Number of items in THIS page.
    - `stopped_at` 'since' | 'known_id' | 'end' | 'null', nullable: Present only when the request sent `since` or `stop_at_id`. Why the walk stopped: `since` (this page reached a post older than the date), `known_id` (this page reached the post you already hold), `end` (the list ran out first), or null (not yet, keep walking with `next_cursor`).

## Other responses

- `400`: Invalid request - missing or malformed parameters
- `401`: Unauthorized - missing or invalid API key
- `402`: Payment required - the account balance is too low, or the calling key has spent its own per-key credit limit
- `404`: Not found - the endpoint does not exist, or the requested resource was not found upstream
- `405`: Method not allowed - wrong HTTP verb for this endpoint
- `409`: Conflict - an idempotent request is still in flight, its completed body exceeded the replay limit, or the cohort resource is not in a state that allows this operation
- `410`: Gone - the discover run's retention passed and its rows were deleted; start a new run
- `413`: Payload too large: the JSON request body exceeds the 1 MB size limit and is rejected before parsing, or a single cohort result cannot fit beneath the 1 MB response-page ceiling
- `422`: Idempotency payload mismatch - this Idempotency-Key was already used with a different request payload; or a discover request with async=false is over what one call can answer (NEEDS_ASYNC)
- `429`: Rate or concurrency limit exceeded. `RATE_LIMITED`: more than 600 requests in a 1-minute sliding window on this API key (headers `X-RateLimit-Limit`/`Remaining`/`Reset`; `Retry-After` is seconds until the window resets). `CONCURRENCY_LIMIT`: more than 50 simultaneous in-flight requests (headers `X-Concurrency-Limit`/`Remaining`; short static `Retry-After`). Both are unbilled. Honor `Retry-After`, then back off with jitter. See /docs/rate-limits.
- `500`: Internal server error - an immediate refund is attempted; credits_used reports the committed net charge and exact compensation is queued if that refund cannot commit
- `502`: Upstream error - the platform returned an error; an immediate refund is attempted and credits_used reports the committed net charge
- `503`: Service unavailable - the request is unbilled when rejected before deduction, otherwise an immediate refund is attempted and credits_used reports the committed net charge
- `504`: Gateway timeout - the request was still running close to the platform time limit and is answered as UPSTREAM_ERROR, the same code as 502. An immediate refund is attempted and credits_used reports the committed net charge. When details.retry_with is set, retryable is false and there is no Retry-After

---

[API](https://skmtc.dev/socialcrawl/apis/socialcrawl-api.md) · [All operations](https://skmtc.dev/socialcrawl/apis/socialcrawl-api/llms.txt) · [OpenAPI document](https://skmtc.dev/socialcrawl/apis/socialcrawl-api/revisions/a0ad2511004a?raw)
