# SocialCrawl API: instagram endpoints # Base URL: https://www.socialcrawl.dev # Auth: x-api-key header # Full docs: https://www.socialcrawl.dev/docs/instagram ## GET /v1/instagram/profile Get Instagram user profile Credit cost: 1 (standard) Parameters: - handle (required): Instagram username without the @ symbol - trim (optional, boolean): Set to true to skip the recent-posts read. The profile comes back faster, at the same price, with `author.last_post_at` and `computed.engagement_rate` null. - contact_email (optional, enum: 1): Optional. When 1, also returns author.ext.contact_email: when the bio lists two or more email addresses, the one the creator gives for contacting them personally, copied verbatim from the bio. Null when there is one address or none, when every address belongs to a manager, agency or brand, or when the choice was not confident. author.ext.public_email is unchanged. No extra credits. Worth sending: - trim=true: Same price, lighter payload: drops the bulky raw fields. curl -G "https://www.socialcrawl.dev/v1/instagram/profile" \ --data-urlencode "handle=instagram" \ --data-urlencode "trim=true" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/profile/about Get Instagram account transparency details Credit cost: 1 credit per call, with or without `include=verified_at`. A call that fails, a handle that does not exist and a refused parameter cost nothing. Parameters: - handle (required): Instagram username without the @ symbol - include (optional, string): Send `verified_at` to add `author.ext.verified_at`, the month Instagram verified the account ("April 2024"). Same price. Any other value is a free 400. curl -G "https://www.socialcrawl.dev/v1/instagram/profile/about" \ --data-urlencode "handle=instagram" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/profile/country Get the country an Instagram account declares, by user id Credit cost: 1 credit per call. A refused username or a missing `user_id` costs nothing. Parameters: - user_id (optional, string): The numeric Instagram account id, for example `post.ext.author_id` from a search row or `author.id` from /v1/instagram/profile/about. A username is refused at no charge. - Constraint: one of user_id (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/profile/country" \ --data-urlencode "user_id=25025320" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/profile/posts List Instagram user posts Credit cost: 1-29 (metered): 1 credit per page. include=audio holds 1 more per row, up to 12, and keeps it only for each video whose sound came back, refunding the rest, so a page costs at most 13 credits and a page with no videos costs 1. include=tags holds 1 more per row, up to 12, and keeps it only for each post whose tagged accounts came back, refunding the rest; with both tokens a page costs at most 25 credits. exclude_pinned and recent_days do not change the price of a page. The default labels (sponsored, intent, niche) are free. label=mention, quality, ai_style, ai_written, injection, or label=intent with offer=, holds 4 extra credits and refunds down to 1 credit per started 25 posts that were newly judged: posts already labelled are free, a page where nothing could be judged refunds the whole extra, and a cached page is free. With `max_pages`, `max_items` or `max_credits`, each page walked is billed at this price (a cached page is free), and `max_credits` caps the total. Parameters: - handle (required): Instagram username without the @ symbol - since (optional, string): Only posts published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Older posts are left off the page, and the page that reaches one ends the walk: `next_cursor` is not returned and `pagination.stopped_at` is `since`. Pinned posts sit out of date order and never end the walk. The page still costs what a page costs, so a daily poll pays for the pages it walks and no more. - stop_at_id (optional, string): The id (`post.id`) or URL (`post.url`) of the newest post you already hold. The page stops just before it: that post and everything after it are left off, `next_cursor` is not returned, and `pagination.stopped_at` is `known_id`. A pinned post never counts as the stop point. If it is not on this page the page is returned in full with its cursor, so keep walking. `pagination.stopped_at` is `end` when the list ran out first and `null` while there is more to walk. A post counts as pinned when `post.flags.pinned` is true or when it is older than a post below it on the page, so a pinned post that also sits in date order is never the stop point on a page that marks pins, and can be on a page whose `post.flags.pinned` is null. - next_max_id (optional, string): Cursor to get next page of results. - trim (optional, boolean): Set to true to request the lighter upstream record. That record is the narrow width described above, so the page is normally served from the second source with every field. A trimmed page served as it is omits `post.ext.coauthors`, and its `post.flags.pinned` is null, as it is on any page called without `exclude_pinned`. The price is the same either way. - exclude_pinned (optional, boolean): Set to true to leave out the posts the owner pinned to the top of the profile. The pins are read from a second source alongside the page, at the same price, and the rows kept carry `post.flags.pinned: false`. A row whose pin state is unknown (`post.flags.pinned: null`), such as a row that pin read did not reach, a trimmed row, or a page from a source that cannot mark pins, is kept, and `data._warnings` says so. Without this parameter `post.flags.pinned` is not read and is null on rows from the first source, except on a page reached with a cursor, whose rows are `false` either way because a pinned post is served on the first page only. - recent_days (optional, integer): Keep only posts published in the last N days (1 to 3650). Older posts are removed from the page, pinned ones included, and once a page reaches posts older than the window `next_cursor` is not returned, so a walk back to a date window stops on its own. The page still costs what a page costs. - include (optional, string): `audio` adds `post.ext.music_id` and `post.ext.music` to video rows from a second lookup: 1 extra credit per video filled, refunded when the sound could not be read. Photos and carousels are never looked up. `tags` adds `post.ext.usertags`, the usernames tagged in the post, to every row from a second lookup: 1 extra credit per post that names someone, refunded for a post that tags nobody or whose lookup failed. Both tokens can be combined (`include=audio,tags`). Adds a few seconds; read `data.hydration` for the credits held and kept and the time. - label (optional, string): Optional CSV of SocialCrawl labels to add to every post. Without this param every page already carries sponsored, intent and niche, free; label= adds the labels you name to them (the defaults keep running). sponsored, intent and niche are free when asked for too; mention, quality, ai_style, intent with offer=, and injection add 1 credit per started 25 newly judged posts. judgments=off (or label=none) turns the default labels off. sponsored: is the post a paid or gifted promotion (p, 0 to 1), did it carry a disclosure marker such as #ad or 광고 (disclosed), is it likely paid with no marker (undisclosed), and which of the accounts it mentions does it promote (brand, or null). These are signals to review, never a finding. mention (needs brand=): is the post about that brand rather than something that shares its name (about_brand, 0 to 1), how it feels about the brand on five levels (sentiment_level 0 to 4 and sentiment_score 0 to 1, null when the post is not about the brand), is it sarcastic, which aspect it talks about (taste_or_quality, price_or_value, availability_or_delivery, health_or_safety, advertising_or_campaign, customer_service, none), and did the author buy or use it (first_hand). intent: what the author is mainly doing (label: asking_for_recommendation, comparing_options, switching_away, complaining, promoting, news_or_discussion, other, or null when unsure, with confidence), whether they read as a potential buyer rather than a seller (buyer, seller), how pressing the need is (urgency, 0 to 3), and, when you pass offer=, whether your offer would plausibly help them (fits_offer). niche: which of the 33 niches of the published taxonomy sc-niche-v1 the caption belongs to, or personal_no_niche, or other (label, confidence, taxonomy), with label null when the caption is too thin to tell or the pick is unsure. It reads the caption only, not the video. quality: how much checkable detail the caption carries (fact_density 0 to 3), whether it mainly asks for likes, replies, shares, follows or tags (engagement_bait, 0 to 1), whether it is written to provoke anger as a way to get engagement (rage_bait, 0 to 1; about the writing, never the side taken), whether it only repeats someone else's news or view (secondhand, 0 to 1), and what the post is mainly doing (post_aim: inform, opinion, sell, entertain, provoke, other). exclude=engagement_bait drops posts whose engagement_bait is 0.8 or higher. ai_style: writing patterns common in AI-generated text, judged on the text alone. It does not say who wrote the post, and a person who writes in this style is flagged too. Six patterns are judged, each a probability (0 to 1): binary_contrast ("This is not X. It's Y."), faux_insight ("Here's what nobody tells you" and throat-clearing openers), colon_reveal ("The best part: it learns."), profound_kicker (ends on an aphorism or mic-drop line), puffery ("marks a pivotal moment", "highlighting the importance of") and rhetorical_setup ("What if I told you", a question the post answers itself, "Not a X. Not a Y. A Z."). Four are counted in the text: slop_words (words and phrases such as delve, leverage, robust, game changer, at the end of the day), em_dashes, emoji_bullets (lines that start with an emoji or a symbol bullet) and one_line_paragraphs (paragraphs that are one short line). pattern_count is how many patterns are present (a judged pattern at 0.8 or higher, slop_words 1 or more, em_dashes 2 or more, emoji_bullets 2 or more, one_line_paragraphs 5 or more) and markers lists their names, comma-separated. A post with less than about eight words of text is only counted, with every judged pattern 0. The judged patterns read the first 400 and the last 150 characters of a long post, and the counts read the whole post. exclude=ai_style drops posts with 2 or more patterns. ai_written: how likely the post was written by an AI model, from SocialCrawl's own text classifier, reading the opening of the post (about 1,000 characters): p (0 to 1, calibrated), level (likely (p 0.95 or higher), possible (p 0.71 or higher) or unlikely) and model (the model name; a new name means a new scale). A post under 8 words is too short to read: p null, level too_short. It is a probability, not proof: a person can be flagged and an AI-written post can pass, and the shorter the post the less certain it is. exclude=ai_written drops likely posts. injection flags text that addresses an AI system and tries to direct it (flagged, p); it never drops or rewrites a row. A post that could not be judged carries labels: null. Posts already labelled for anyone are free, and so is a cached page. data.labels reports what was judged and billed. - exclude (optional, string): Optional CSV (engagement_bait, ai_style, ai_written). engagement_bait, only with label=quality, drops posts whose engagement_bait probability is 0.8 or higher. ai_style, only with label=ai_style, drops posts that show 2 or more of its writing patterns (pattern_count 2 or higher). ai_written, only with label=ai_written, drops posts whose level is likely (p 0.95 or higher), or whose p reaches your ai_written_threshold; a post too short to read is never dropped. Dropped posts are listed in data.labels.dropped_ids. A post that could not be judged is never dropped. - ai_written_threshold (optional, string): Optional, only with label=ai_written and exclude=ai_written. The p (0 to 1) at which exclude=ai_written drops a post. Default 0.95 (level likely). 0.71 also drops possible posts: more AI posts go, and more posts by people with them. - brand (optional, string): Required by label=mention, ignored otherwise. The brand or product the posts are judged against, up to 300 characters, for example brand=Buldak. Without it label=mention is skipped with the warning label_mention_needs_brand and is not billed. - brand_description (optional, string): Optional, with label=mention. One plain sentence saying what the brand is, for example brand_description=Samyang's spicy instant noodle brand. Use it when the name is also an ordinary word or another company's name. - offer (optional, string): Optional, with label=intent. One or two plain sentences saying what you sell, up to 300 characters, for example offer=A web design agency that builds websites for small businesses. Adds fits_offer to every post; without it fits_offer is null. - judgments (optional, enum: on | off): Optional, on (the default) or off. By default every row gains free SocialCrawl judgments (computed.labels, and computed.relevance on search endpoints), reported in data.labels (mode default) and data.relevance (origin default), each with a status (complete, partial or skipped) and pending: the rows still being judged when the page was sent, which carry null now and are filled on your next call or cached read. Default judgments never add credits, never change an existing field, and never drop or reorder a row. off returns the page exactly as before, with none of those keys. label=none does the same. - dry_run (optional, enum: 1): Optional. When 1, return a cost preview for this labelled or relevance-filtered request without fetching the page or judging any row. data.estimate reports rows_expected, rows_cached, label_credits_min, label_credits_max and base_credits. 0 credits charged. - label_evidence (optional, enum: 1): Optional, only with label=. When 1, every labelled row also carries computed.labels_evidence. = { quote, sentence_index }: the sentence in the row that most clearly shows the label, copied verbatim. Absent or null when no single sentence shows it. - fit (optional, enum: goal): Optional. When goal, keep the rows and fields needed for the goal you pass in goal= (plus any that are uncertain, and the first and last), and replace the rest with a stub. data.held_back lists the held ids and a recall id that re-reads the full page from cache at no extra charge. Without this param the page is unchanged. - goal (optional, string): Required by fit=goal. What you are trying to do, in your own words, up to 300 characters. - fit_tokens (optional, integer): Optional, only with fit=goal. Soft cap on how much of the page to keep, in tokens. Uncertain blocks and the first and last block are kept even if they exceed it. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. Worth sending: - label=sponsored: Marks each post that is paid or sponsored, at no extra credits; intent and niche are free too. - since=2026-09-01: Returns only posts after this date, so a daily poll pays only for the pages with new posts. - trim=true: Same price, lighter payload: drops the bulky raw fields. curl -G "https://www.socialcrawl.dev/v1/instagram/profile/posts" \ --data-urlencode "handle=instagram" \ --data-urlencode "label=sponsored" \ --data-urlencode "since=2026-09-01" \ --data-urlencode "trim=true" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/post Get Instagram post details Credit cost: 1 (standard) Parameters: - url (required): Full URL of the Instagram post. A share link copied from the app (`instagram.com/share/...`) works too; a share link to a profile is refused at no charge with the handle to send, and a dead one is a free 404. - region (optional, string): 2 letter country code to set the proxy in - trim (optional, boolean): Set to true to get a trimmed response - download_media (optional, boolean): Set to true to also download the video/images and get back durable hosted media URLs under `data.post.ext.download_media_urls` (`[{ post_id, cdn_url, type, cached }]`). Use these for archiving; the raw `media_urls` are short-lived signed CDN links that expire. Adds a few seconds of latency while the media is fetched. Not available on login-gated posts (age-restricted 18+ and similar), which are served by a source that cannot host durable copies: the post and its media still come back in full, and the response carries a `data._warnings` note telling you to read `data.post.content.media_urls` directly. Worth sending: - trim=true: Same price, lighter payload: drops the bulky raw fields. curl -G "https://www.socialcrawl.dev/v1/instagram/post" \ --data-urlencode "url=https://www.instagram.com/p/DcCH2ZygIiP/" \ --data-urlencode "trim=true" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/post/comments List Instagram post comments Credit cost: 5-19 (metered): 5 credits per page, with the default labels (sentiment, question, purchase_intent, complaint) included free. label=spam, toxic or low_quality holds 4 extra credits and refunds down to 1 credit per started 25 comments that were newly judged: comments already labelled are free, a page where nothing could be judged refunds the whole extra, and a cached page is free. With `max_pages`, `max_items` or `max_credits`, each page walked is billed at this price (a cached page is free), and `max_credits` caps the total. Parameters: - url (required): URL of the Instagram post (a /p/, /reel/, /reels/, or /tv/ link). A share link copied from the app (`instagram.com/share/...`) works too; a share link to a profile is refused at no charge with the handle to send, and a dead one is a free 404. - sort (optional, enum: top | recent): Ordering: `top` (default) ranks by Instagram's popularity order (most-liked first); `recent` returns newest-first. Use `recent` when you are paging through a whole thread: Instagram ranks only the most-liked head, so a `top` walk starts repeating comments once you page past it. - cursor (optional, string): Pagination cursor. Use the `next_cursor` from the previous response to fetch the next page. - safe_url (optional, boolean): When true, returns URL-safe profile picture links suitable for embedding. - scan_pages (optional, integer): Optional, 1 to 3, default 1. With 2 or 3, the call reads that many pages, drops comments repeated across pages, and returns them all sorted by likes (sort=top) or newest first (sort=recent). Use it when the first page of a top-sorted thread is low-like comments. Costs 5 credits for each page that added comments; a page of repeats is not billed. data.scan reports the pages read and billed and whether the thread ended. - label (optional, string): Optional CSV of SocialCrawl labels to add to every comment: sentiment, spam, question, purchase_intent, complaint, toxic, low_quality, injection. Without this param every page already carries sentiment, question, purchase_intent and complaint, free; label= adds the labels you name to them (the defaults keep running). Each comment gains computed.labels with probabilities (0 to 1), so you choose the cut-off; a comment that could not be judged carries labels: null. sentiment, question, purchase_intent and complaint are free when asked for too; spam, toxic, low_quality and injection add 1 credit per started 25 newly judged comments (comments already labelled for anyone are free, and so is a cached page). injection flags text that addresses an AI system and tries to direct it (flagged, p); it never drops or rewrites a row. judgments=off (or label=none) turns the default labels off. data.labels reports what was judged and billed. - exclude (optional, string): Optional CSV (spam, low_quality), only with the same preset in label. Drops comments whose probability is 0.8 or higher and lists their ids in data.labels.dropped_ids. A comment that could not be judged is never dropped. - judgments (optional, enum: on | off): Optional, on (the default) or off. By default every row gains free SocialCrawl judgments (computed.labels, and computed.relevance on search endpoints), reported in data.labels (mode default) and data.relevance (origin default), each with a status (complete, partial or skipped) and pending: the rows still being judged when the page was sent, which carry null now and are filled on your next call or cached read. Default judgments never add credits, never change an existing field, and never drop or reorder a row. off returns the page exactly as before, with none of those keys. label=none does the same. - dry_run (optional, enum: 1): Optional. When 1, return a cost preview for this labelled or relevance-filtered request without fetching the page or judging any row. data.estimate reports rows_expected, rows_cached, label_credits_min, label_credits_max and base_credits. 0 credits charged. - label_evidence (optional, enum: 1): Optional, only with label=. When 1, every labelled row also carries computed.labels_evidence. = { quote, sentence_index }: the sentence in the row that most clearly shows the label, copied verbatim. Absent or null when no single sentence shows it. - fit (optional, enum: goal): Optional. When goal, keep the rows and fields needed for the goal you pass in goal= (plus any that are uncertain, and the first and last), and replace the rest with a stub. data.held_back lists the held ids and a recall id that re-reads the full page from cache at no extra charge. Without this param the page is unchanged. - goal (optional, string): Required by fit=goal. What you are trying to do, in your own words, up to 300 characters. - fit_tokens (optional, integer): Optional, only with fit=goal. Soft cap on how much of the page to keep, in tokens. Uncertain blocks and the first and last block are kept even if they exceed it. - since (optional, string): Only rows published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Rows older than it are left off the page, and the page that reaches one ends the walk: `next_cursor` is not returned and `pagination.stopped_at` is `since`. Sending it sets the newest-first sort shown in this endpoint's sort parameter (any other sort value is a free 400). The page still costs what a page costs, so you pay for the pages you walk and no more. A row with no date is kept. `published_after`, `start_date`, `from_date`, `publish_time` (`yesterday`, `this-week`, `this-month`, `last-3-months`, `last-6-months`) and `max_age_days` are accepted and applied as `since`. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. Worth sending: - label=purchase_intent: Marks each comment that shows buying intent, at no extra credits; sentiment, question and complaint are free too. curl -G "https://www.socialcrawl.dev/v1/instagram/post/comments" \ --data-urlencode "url=https://www.instagram.com/p/DXidPIVDU6M/" \ --data-urlencode "label=purchase_intent" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/comment Look up one Instagram comment by URL or id Credit cost: 5-15 (metered): 5 credits for a standard lookup, 15 with `deep_scan=true`, which keeps paging the thread when the comment is not in the first pages. Reach for deep_scan only after a standard lookup comes back not-found Parameters: - comment_url (optional, string): An Instagram comment permalink: `https://www.instagram.com/p/{shortcode}/c/{commentId}/` (or a reply permalink `.../c/{parent}/r/{reply}/`, best-effort). Mutually exclusive with `post_url`+`comment_id`. - post_url (optional, string): The post URL (a `/p/`, `/reel/`, `/reels/`, or `/tv/` link; an app share link `instagram.com/share/...` works too). Combine with `comment_id`, or with `author_username`/`text_contains` for a search. - comment_id (optional, string): The target comment's numeric id (`pk`). Requires `post_url`. - author_username (optional, string): Return up to `max` comments authored by this username (no comment id needed). Mutually exclusive with `text_contains` and any comment id. - text_contains (optional, string): Return up to `max` comments whose text contains this snippet (case-insensitive). Mutually exclusive with `author_username` and any comment id. - deep_scan (optional, boolean): Widen the scan budget for deeply-buried comments (raises the per-chain page ceiling and deadline). Bills 15 credits instead of 5. - position_hint (optional, string): Opaque token from a prior lookup's `lookup.position_hint`. Passing it back replays just the last-known sort chain first, making a re-check of an already-found comment cheap. - max (optional, integer): For `author_username`/`text_contains` search: max matches to return (1-20, default 5). curl "https://www.socialcrawl.dev/v1/instagram/comment" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/basic-profile Get Instagram basic profile Credit cost: 1 (standard) Parameters: - userId (optional, string): Instagram numeric user ID curl "https://www.socialcrawl.dev/v1/instagram/basic-profile" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/profile/reels List Instagram user reels Credit cost: 1-18 (metered): 1 credit per page. include=stats adds 1 credit a page. include=audio holds 1 more per row, up to 12, and keeps it only for each video whose sound came back, refunding the rest; with include=stats every reel already carries its sound, so the audio join costs nothing more. A page costs at most 14 credits. exclude_pinned and recent_days do not change the price of a page. sort=most_viewed costs the same 1 credit a page. The default labels (sponsored, intent, niche) are free. label=mention, quality, ai_style, ai_written, injection, or label=intent with offer=, holds 4 extra credits and refunds down to 1 credit per started 25 posts that were newly judged: posts already labelled are free, a page where nothing could be judged refunds the whole extra, and a cached page is free. With `max_pages`, `max_items` or `max_credits`, each page walked is billed at this price (a cached page is free), and `max_credits` caps the total. Parameters: - since (optional, string): Only reels published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Older reels are left off the page, and the page that reaches one ends the walk: `next_cursor` is not returned and `pagination.stopped_at` is `since`. Pinned reels sit out of date order and never end the walk. The page still costs what a page costs, so a daily poll pays for the pages it walks and no more. - stop_at_id (optional, string): The id (`post.id`) or URL (`post.url`) of the newest reel you already hold. The page stops just before it: that reel and everything after it are left off, `next_cursor` is not returned, and `pagination.stopped_at` is `known_id`. A pinned reel never counts as the stop point. If it is not on this page the page is returned in full with its cursor, so keep walking. `pagination.stopped_at` is `end` when the list ran out first and `null` while there is more to walk. - user_id (optional, string): Instagram user id. Use this for faster response times. - handle (optional, string): Instagram username without the @ symbol - max_id (optional, string): Max id to get more reels. Get 'max_id' from previous response. - trim (optional, boolean): Set to true to request the lighter upstream record: a smaller, faster page that keeps the engagement, the author, the caption, the timestamp and the reel URL. What it drops is `post.content.media_urls` (the video file), `post.content.duration_seconds`, `post.ext.coauthors` and `post.flags.pinned`. The cover image is returned as `post.content.thumbnail_url` either way. Leave it unset when you need the video URL or the duration. - exclude_pinned (optional, boolean): Set to true to leave out the posts the owner pinned to the top of the profile. A row whose pin state is unknown, which is every row with trim=true, is kept. - recent_days (optional, integer): Keep only posts published in the last N days (1 to 3650). Older posts are removed from the page, pinned ones included, and once a page reaches posts older than the window `next_cursor` is not returned, so a walk back to a date window stops on its own. The page still costs what a page costs. - include (optional, string): `stats` returns the full stat line on every reel for 1 extra credit a page: `engagement.shares`, `engagement.saves` and `post.ext.repost_count` beside views, likes, comments, the remix counter, `post.ext.music_id` for original sounds and licensed music alike, and `post.ext.audio_cluster_id`, Instagram's audio cluster for the reel. It is served by a second source, so a cursor from a request without it cannot continue a walk with it; start from page 1. `audio` adds `post.ext.music_id` and `post.ext.music` to reels still missing them from a second lookup: 1 extra credit per reel filled, refunded when the sound could not be read. Both tokens can be combined (`include=audio,stats`). - sort (optional, enum: most_viewed): Send `most_viewed` for the creator's reels in Instagram's most viewed order (highest plays first) instead of newest first. Same price. Its cursors only continue a `sort=most_viewed` walk. Cannot be combined with `include`, `trim`, `recent_days`, `since` or `stop_at_id`. - label (optional, string): Optional CSV of SocialCrawl labels to add to every post. Without this param every page already carries sponsored, intent and niche, free; label= adds the labels you name to them (the defaults keep running). sponsored, intent and niche are free when asked for too; mention, quality, ai_style, intent with offer=, and injection add 1 credit per started 25 newly judged posts. judgments=off (or label=none) turns the default labels off. sponsored: is the post a paid or gifted promotion (p, 0 to 1), did it carry a disclosure marker such as #ad or 광고 (disclosed), is it likely paid with no marker (undisclosed), and which of the accounts it mentions does it promote (brand, or null). These are signals to review, never a finding. mention (needs brand=): is the post about that brand rather than something that shares its name (about_brand, 0 to 1), how it feels about the brand on five levels (sentiment_level 0 to 4 and sentiment_score 0 to 1, null when the post is not about the brand), is it sarcastic, which aspect it talks about (taste_or_quality, price_or_value, availability_or_delivery, health_or_safety, advertising_or_campaign, customer_service, none), and did the author buy or use it (first_hand). intent: what the author is mainly doing (label: asking_for_recommendation, comparing_options, switching_away, complaining, promoting, news_or_discussion, other, or null when unsure, with confidence), whether they read as a potential buyer rather than a seller (buyer, seller), how pressing the need is (urgency, 0 to 3), and, when you pass offer=, whether your offer would plausibly help them (fits_offer). niche: which of the 33 niches of the published taxonomy sc-niche-v1 the caption belongs to, or personal_no_niche, or other (label, confidence, taxonomy), with label null when the caption is too thin to tell or the pick is unsure. It reads the caption only, not the video. quality: how much checkable detail the caption carries (fact_density 0 to 3), whether it mainly asks for likes, replies, shares, follows or tags (engagement_bait, 0 to 1), whether it is written to provoke anger as a way to get engagement (rage_bait, 0 to 1; about the writing, never the side taken), whether it only repeats someone else's news or view (secondhand, 0 to 1), and what the post is mainly doing (post_aim: inform, opinion, sell, entertain, provoke, other). exclude=engagement_bait drops posts whose engagement_bait is 0.8 or higher. ai_style: writing patterns common in AI-generated text, judged on the text alone. It does not say who wrote the post, and a person who writes in this style is flagged too. Six patterns are judged, each a probability (0 to 1): binary_contrast ("This is not X. It's Y."), faux_insight ("Here's what nobody tells you" and throat-clearing openers), colon_reveal ("The best part: it learns."), profound_kicker (ends on an aphorism or mic-drop line), puffery ("marks a pivotal moment", "highlighting the importance of") and rhetorical_setup ("What if I told you", a question the post answers itself, "Not a X. Not a Y. A Z."). Four are counted in the text: slop_words (words and phrases such as delve, leverage, robust, game changer, at the end of the day), em_dashes, emoji_bullets (lines that start with an emoji or a symbol bullet) and one_line_paragraphs (paragraphs that are one short line). pattern_count is how many patterns are present (a judged pattern at 0.8 or higher, slop_words 1 or more, em_dashes 2 or more, emoji_bullets 2 or more, one_line_paragraphs 5 or more) and markers lists their names, comma-separated. A post with less than about eight words of text is only counted, with every judged pattern 0. The judged patterns read the first 400 and the last 150 characters of a long post, and the counts read the whole post. exclude=ai_style drops posts with 2 or more patterns. ai_written: how likely the post was written by an AI model, from SocialCrawl's own text classifier, reading the opening of the post (about 1,000 characters): p (0 to 1, calibrated), level (likely (p 0.95 or higher), possible (p 0.71 or higher) or unlikely) and model (the model name; a new name means a new scale). A post under 8 words is too short to read: p null, level too_short. It is a probability, not proof: a person can be flagged and an AI-written post can pass, and the shorter the post the less certain it is. exclude=ai_written drops likely posts. injection flags text that addresses an AI system and tries to direct it (flagged, p); it never drops or rewrites a row. A post that could not be judged carries labels: null. Posts already labelled for anyone are free, and so is a cached page. data.labels reports what was judged and billed. - exclude (optional, string): Optional CSV (engagement_bait, ai_style, ai_written). engagement_bait, only with label=quality, drops posts whose engagement_bait probability is 0.8 or higher. ai_style, only with label=ai_style, drops posts that show 2 or more of its writing patterns (pattern_count 2 or higher). ai_written, only with label=ai_written, drops posts whose level is likely (p 0.95 or higher), or whose p reaches your ai_written_threshold; a post too short to read is never dropped. Dropped posts are listed in data.labels.dropped_ids. A post that could not be judged is never dropped. - ai_written_threshold (optional, string): Optional, only with label=ai_written and exclude=ai_written. The p (0 to 1) at which exclude=ai_written drops a post. Default 0.95 (level likely). 0.71 also drops possible posts: more AI posts go, and more posts by people with them. - brand (optional, string): Required by label=mention, ignored otherwise. The brand or product the posts are judged against, up to 300 characters, for example brand=Buldak. Without it label=mention is skipped with the warning label_mention_needs_brand and is not billed. - brand_description (optional, string): Optional, with label=mention. One plain sentence saying what the brand is, for example brand_description=Samyang's spicy instant noodle brand. Use it when the name is also an ordinary word or another company's name. - offer (optional, string): Optional, with label=intent. One or two plain sentences saying what you sell, up to 300 characters, for example offer=A web design agency that builds websites for small businesses. Adds fits_offer to every post; without it fits_offer is null. - judgments (optional, enum: on | off): Optional, on (the default) or off. By default every row gains free SocialCrawl judgments (computed.labels, and computed.relevance on search endpoints), reported in data.labels (mode default) and data.relevance (origin default), each with a status (complete, partial or skipped) and pending: the rows still being judged when the page was sent, which carry null now and are filled on your next call or cached read. Default judgments never add credits, never change an existing field, and never drop or reorder a row. off returns the page exactly as before, with none of those keys. label=none does the same. - dry_run (optional, enum: 1): Optional. When 1, return a cost preview for this labelled or relevance-filtered request without fetching the page or judging any row. data.estimate reports rows_expected, rows_cached, label_credits_min, label_credits_max and base_credits. 0 credits charged. - label_evidence (optional, enum: 1): Optional, only with label=. When 1, every labelled row also carries computed.labels_evidence. = { quote, sentence_index }: the sentence in the row that most clearly shows the label, copied verbatim. Absent or null when no single sentence shows it. - fit (optional, enum: goal): Optional. When goal, keep the rows and fields needed for the goal you pass in goal= (plus any that are uncertain, and the first and last), and replace the rest with a stub. data.held_back lists the held ids and a recall id that re-reads the full page from cache at no extra charge. Without this param the page is unchanged. - goal (optional, string): Required by fit=goal. What you are trying to do, in your own words, up to 300 characters. - fit_tokens (optional, integer): Optional, only with fit=goal. Soft cap on how much of the page to keep, in tokens. Uncertain blocks and the first and last block are kept even if they exceed it. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. - Constraint: one of user_id, handle (at least one required) Worth sending: - label=sponsored: Marks each post that is paid or sponsored, at no extra credits; intent and niche are free too. - since=2026-09-01: Returns only posts after this date, so a daily poll pays only for the pages with new posts. - trim=true: Same price, lighter payload: drops the bulky raw fields. curl -G "https://www.socialcrawl.dev/v1/instagram/profile/reels" \ --data-urlencode "handle=mrbeast" \ --data-urlencode "label=sponsored" \ --data-urlencode "since=2026-09-01" \ --data-urlencode "trim=true" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/highlights List Instagram story highlights Credit cost: 1 (standard) Parameters: - user_id (optional, string): Instagram user id. Use for faster response times. - handle (optional, string): Instagram username without the @ symbol - since (optional, string): Only rows published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Rows older than it are left off each page. This list is not in date order, so the walk does not stop at the date: keep following `next_cursor`, and each page costs what a page costs even when every row on it is filtered out. A row with no date is kept. `published_after`, `start_date`, `from_date`, `publish_time` (`yesterday`, `this-week`, `this-month`, `last-3-months`, `last-6-months`) and `max_age_days` are accepted and applied as `since`. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. - Constraint: one of user_id, handle (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/highlights" \ --data-urlencode "handle=instagram" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/highlight/detail Get Instagram highlight detail Credit cost: 1 (standard) Parameters: - id (optional, string): Instagram highlight ID: the numeric id, with or without the `highlight:` prefix. Get it from `/v1/instagram/highlights`. - since (optional, string): Only rows published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Rows older than it are left off each page. This list is not in date order, so the walk does not stop at the date: keep following `next_cursor`, and each page costs what a page costs even when every row on it is filtered out. A row with no date is kept. `published_after`, `start_date`, `from_date`, `publish_time` (`yesterday`, `this-week`, `this-month`, `last-3-months`, `last-6-months`) and `max_age_days` are accepted and applied as `since`. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. curl "https://www.socialcrawl.dev/v1/instagram/highlight/detail" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/search/reels Search Instagram posts by keyword or topic: reels about a brand, phrase or subject, with view counts (1 credit a page) Credit cost: 1-69 (metered): 1 credit per page on a plain call or with `region`; `include=creator` or `country` makes it up to 61 (1 plus 2 per creator looked up). `region` costs nothing extra. `include=country` makes it up to 31: 1 extra credit per creator whose declared country came back, refunded for a creator who publishes none or cannot be found. `include=creator` adds 2 credits per creator looked up: it holds 60 extra credits for a full page of 30 and refunds every creator it did not look up or could not find. Two reels by one creator are one lookup, a creator looked up in the last 15 minutes is free, and on a `max_pages` walk each creator is looked up and billed once. `country` runs the same creator lookup, because it needs each creator's declared country, at the same price, charged for every creator looked up, kept or not; `data.hydration.implied_by` says so on the response. `include=creator,country` is priced as `include=creator` alone, because the creator card already carries the country. Relevance against your query (by default, or with relevance=) is free. Adding relevant_to= holds 4 extra credits and refunds down to 1 credit per started 25 rows that were newly judged: rows already judged for the same topic are free, a page where nothing could be judged refunds the whole extra, and a cached page is free. The default labels (sponsored, intent, niche) are free. label=mention, quality, ai_style, ai_written, injection, or label=intent with offer=, holds 4 extra credits and refunds down to 1 credit per started 25 posts that were newly judged: posts already labelled are free, a page where nothing could be judged refunds the whole extra, and a cached page is free. With `max_pages`, `max_items` or `max_credits`, each page walked is billed at this price (a cached page is free), and `max_credits` caps the total. With `seen`, a page's price falls with its share of rows you already received under that id; credits for joined data (`include=`) are never discounted. Parameters: - query (required): Search keyword or phrase to find Instagram reels - date_posted (optional, enum: last-week | last-month | last-year): Date posted - page (optional, integer): The page number to return. - region (optional, enum: ES | MX | DE | BR | KR | FR | ZA | NG | KE | AE | SA): Two-letter market code (ES, MX, DE, BR, KR, FR, ZA, NG, KE, AE, SA) to localise the results. Instagram does not rank search by country, so the market's own name is appended to your query (`fitness` with `ES` searches `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 query actually searched is returned in `data.region.query_sent`. It favours creators from the market but does not promise that every creator is from it; send `country` for that. For a topic about a place, such as eSIMs or travel, a phrasing locals use works better (`esim móvil` rather than `esim`). Same price as a plain call. Only these markets are supported; any other code is refused with the list, at no charge. A typo is a free 400. - include (optional, string): Send `creator` to add 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 creator's name, avatar and verified flag where the row lacks them. `author_country` is the country the creator declares there, not where the reel was filmed; it is null when Instagram does not publish one, and nothing is guessed. 2 extra credits per creator looked up; two reels by one creator cost one lookup, a creator that cannot be found is refunded, and a creator looked up in the last 15 minutes is free. Send `country` for the declared country alone (`post.ext.author_country`, the same value): 1 extra credit per creator whose country came back, refunded for a creator who publishes none or cannot be found, and free for a creator looked up in the last 30 days, with no counts or contact. `creator,country` is the same call as `creator`. A typo is a free 400. - country (optional, enum: ES | MX | DE | BR | KR | FR | ZA | NG | KE | AE | SA): Two-letter market code (ES, MX, DE, BR, KR, FR, ZA, NG, KE, AE, SA) to keep only reels whose creator is based in that market. Costs up to 61 credits a page, not 1: 1 credit plus 2 per creator looked up, kept or not, because the country is read from each creator's own profile (`post.ext.author_country`). It therefore adds the creator card (`include=creator`, reported as `data.hydration.implied_by: "country"`) and, when `region` is not sent, localises the query for the same market. To localise without the lookups, send `region` alone (1 credit). Rows from another country, or whose creator publishes none, are dropped and counted in `data.country` (`kept`, `dropped_other`, `dropped_unknown`); kept rows keep their order. When no row matches, the page is `items: []` with the warning `country_no_match`, and the lookups are still billed. A page left empty only because the creator lookups failed costs nothing. Works with `max_pages`. A typo is a free 400. - relevance (optional, enum: score | filter): Optional. Without this param every row already carries computed.relevance against your query, free ({ p, sense, depth, spam }: is this row about what your query means, or a different thing that shares its words?), and nothing is dropped or reordered. score asks for it explicitly and waits for every row; filter also drops the rows that are not about your query, and lists their ids in data.relevance.dropped_ids. Your query is the topic; nothing to configure. A row that could not be judged is never dropped and carries relevance: null. Pagination is unchanged, so a filtered page can hold fewer rows. Free with your query as the topic; with relevant_to it adds 1 credit per started 25 newly judged rows (rows already judged for the same topic are free, and so is a cached page). - relevance_threshold (optional, string): Optional, only with relevance. The probability (0 to 1) a row must reach to be kept by relevance=filter. Default 0.5. Lower keeps more rows, higher keeps fewer. - relevant_to (optional, string): Optional, only with relevance. Up to 200 characters describing what you mean, used as the topic instead of the query. Use it when the query is ambiguous, for example query=cleopatra with relevant_to=Cleopatra, the IGT slot game. - judgments (optional, enum: on | off): Optional, on (the default) or off. By default every row gains free SocialCrawl judgments (computed.labels, and computed.relevance on search endpoints), reported in data.labels (mode default) and data.relevance (origin default), each with a status (complete, partial or skipped) and pending: the rows still being judged when the page was sent, which carry null now and are filled on your next call or cached read. Default judgments never add credits, never change an existing field, and never drop or reorder a row. off returns the page exactly as before, with none of those keys. label=none does the same. - dry_run (optional, enum: 1): Optional. When 1, return a cost preview for this labelled or relevance-filtered request without fetching the page or judging any row. data.estimate reports rows_expected, rows_cached, label_credits_min, label_credits_max and base_credits. 0 credits charged. - fit (optional, enum: goal): Optional. When goal, keep the rows and fields needed for the goal you pass in goal= (plus any that are uncertain, and the first and last), and replace the rest with a stub. data.held_back lists the held ids and a recall id that re-reads the full page from cache at no extra charge. Without this param the page is unchanged. - goal (optional, string): Required by fit=goal. What you are trying to do, in your own words, up to 300 characters. - fit_tokens (optional, integer): Optional, only with fit=goal. Soft cap on how much of the page to keep, in tokens. Uncertain blocks and the first and last block are kept even if they exceed it. - label (optional, string): Optional CSV of SocialCrawl labels to add to every post. Without this param every page already carries sponsored, intent and niche, free; label= adds the labels you name to them (the defaults keep running). sponsored, intent and niche are free when asked for too; mention, quality, ai_style, intent with offer=, and injection add 1 credit per started 25 newly judged posts. judgments=off (or label=none) turns the default labels off. sponsored: is the post a paid or gifted promotion (p, 0 to 1), did it carry a disclosure marker such as #ad or 광고 (disclosed), is it likely paid with no marker (undisclosed), and which of the accounts it mentions does it promote (brand, or null). These are signals to review, never a finding. mention (needs brand=): is the post about that brand rather than something that shares its name (about_brand, 0 to 1), how it feels about the brand on five levels (sentiment_level 0 to 4 and sentiment_score 0 to 1, null when the post is not about the brand), is it sarcastic, which aspect it talks about (taste_or_quality, price_or_value, availability_or_delivery, health_or_safety, advertising_or_campaign, customer_service, none), and did the author buy or use it (first_hand). intent: what the author is mainly doing (label: asking_for_recommendation, comparing_options, switching_away, complaining, promoting, news_or_discussion, other, or null when unsure, with confidence), whether they read as a potential buyer rather than a seller (buyer, seller), how pressing the need is (urgency, 0 to 3), and, when you pass offer=, whether your offer would plausibly help them (fits_offer). niche: which of the 33 niches of the published taxonomy sc-niche-v1 the caption belongs to, or personal_no_niche, or other (label, confidence, taxonomy), with label null when the caption is too thin to tell or the pick is unsure. It reads the caption only, not the video. quality: how much checkable detail the caption carries (fact_density 0 to 3), whether it mainly asks for likes, replies, shares, follows or tags (engagement_bait, 0 to 1), whether it is written to provoke anger as a way to get engagement (rage_bait, 0 to 1; about the writing, never the side taken), whether it only repeats someone else's news or view (secondhand, 0 to 1), and what the post is mainly doing (post_aim: inform, opinion, sell, entertain, provoke, other). exclude=engagement_bait drops posts whose engagement_bait is 0.8 or higher. ai_style: writing patterns common in AI-generated text, judged on the text alone. It does not say who wrote the post, and a person who writes in this style is flagged too. Six patterns are judged, each a probability (0 to 1): binary_contrast ("This is not X. It's Y."), faux_insight ("Here's what nobody tells you" and throat-clearing openers), colon_reveal ("The best part: it learns."), profound_kicker (ends on an aphorism or mic-drop line), puffery ("marks a pivotal moment", "highlighting the importance of") and rhetorical_setup ("What if I told you", a question the post answers itself, "Not a X. Not a Y. A Z."). Four are counted in the text: slop_words (words and phrases such as delve, leverage, robust, game changer, at the end of the day), em_dashes, emoji_bullets (lines that start with an emoji or a symbol bullet) and one_line_paragraphs (paragraphs that are one short line). pattern_count is how many patterns are present (a judged pattern at 0.8 or higher, slop_words 1 or more, em_dashes 2 or more, emoji_bullets 2 or more, one_line_paragraphs 5 or more) and markers lists their names, comma-separated. A post with less than about eight words of text is only counted, with every judged pattern 0. The judged patterns read the first 400 and the last 150 characters of a long post, and the counts read the whole post. exclude=ai_style drops posts with 2 or more patterns. ai_written: how likely the post was written by an AI model, from SocialCrawl's own text classifier, reading the opening of the post (about 1,000 characters): p (0 to 1, calibrated), level (likely (p 0.95 or higher), possible (p 0.71 or higher) or unlikely) and model (the model name; a new name means a new scale). A post under 8 words is too short to read: p null, level too_short. It is a probability, not proof: a person can be flagged and an AI-written post can pass, and the shorter the post the less certain it is. exclude=ai_written drops likely posts. injection flags text that addresses an AI system and tries to direct it (flagged, p); it never drops or rewrites a row. A post that could not be judged carries labels: null. Posts already labelled for anyone are free, and so is a cached page. data.labels reports what was judged and billed. - exclude (optional, string): Optional CSV (engagement_bait, ai_style, ai_written). engagement_bait, only with label=quality, drops posts whose engagement_bait probability is 0.8 or higher. ai_style, only with label=ai_style, drops posts that show 2 or more of its writing patterns (pattern_count 2 or higher). ai_written, only with label=ai_written, drops posts whose level is likely (p 0.95 or higher), or whose p reaches your ai_written_threshold; a post too short to read is never dropped. Dropped posts are listed in data.labels.dropped_ids. A post that could not be judged is never dropped. - ai_written_threshold (optional, string): Optional, only with label=ai_written and exclude=ai_written. The p (0 to 1) at which exclude=ai_written drops a post. Default 0.95 (level likely). 0.71 also drops possible posts: more AI posts go, and more posts by people with them. - brand (optional, string): Required by label=mention, ignored otherwise. The brand or product the posts are judged against, up to 300 characters, for example brand=Buldak. Without it label=mention is skipped with the warning label_mention_needs_brand and is not billed. - brand_description (optional, string): Optional, with label=mention. One plain sentence saying what the brand is, for example brand_description=Samyang's spicy instant noodle brand. Use it when the name is also an ordinary word or another company's name. - offer (optional, string): Optional, with label=intent. One or two plain sentences saying what you sell, up to 300 characters, for example offer=A web design agency that builds websites for small businesses. Adds fits_offer to every post; without it fits_offer is null. - label_evidence (optional, enum: 1): Optional, only with label=. When 1, every labelled row also carries computed.labels_evidence. = { quote, sentence_index }: the sentence in the row that most clearly shows the label, copied verbatim. Absent or null when no single sentence shows it. - since (optional, string): Only rows published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Rows older than it are left off each page. This list is not in date order, so the walk does not stop at the date: keep following `next_cursor`, and each page costs what a page costs even when every row on it is filtered out. A row with no date is kept. `published_after`, `start_date`, `from_date`, `publish_time` (`yesterday`, `this-week`, `this-month`, `last-3-months`, `last-6-months`) and `max_age_days` are accepted and applied as `since`. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. - min_views (optional, integer): Keep only rows with at least this many views (`post.engagement.views`). A row whose view count is unknown is discarded, so every returned row meets the floor. Filtering runs on our side after each page is fetched, so every page walked is billed as usual; `data.walk.discarded` counts what was removed and why. Each page is its own entry in `GET /v1/credits/transactions`, under the page's request id listed in `data.walk.request_ids`. - max_age_days (optional, integer): Keep only rows published within this many days (`post.published_at`), 1 to 3650. A row with no date is discarded. Filtering runs on our side after each page is fetched, so every page walked is billed as usual; `data.walk.discarded` counts what was removed and why. Each page is its own entry in `GET /v1/credits/transactions`, under the page's request id listed in `data.walk.request_ids`. - sort_rows (optional, enum: views): `views`: return the kept rows ordered by view count, highest first, across every page walked. Rows without a view count go last. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them (after any filter). Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows (after any filter), then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. - seen (optional, string): An id you choose (1 to 64 letters, digits, `.`, `_` or `-`). Rows your account already received under the same `seen` id are removed from the page, and the page price falls with the share of repeats: page credits x new rows / rows on the page, rounded up, so a page of nothing but repeats is free. Only the page's own price is discounted: credits for joined data (`include=`) are charged only for rows you have not received, and are never discounted. The memory lasts 24 hours from the last call that used the id, holds row ids only, and is private to your account. Use one id across a set of related searches; `data.walk.repeats` counts what was removed. Worth sending: - label=sponsored: Marks each post that is paid or sponsored, at no extra credits; intent and niche are free too. - relevance=filter: Drops rows that are not about your query, at no extra credits; relevance=score keeps them and adds a score. - date_posted=last-week: Only posts from this time window. curl -G "https://www.socialcrawl.dev/v1/instagram/search/reels" \ --data-urlencode "query=workout routine" \ --data-urlencode "label=sponsored" \ --data-urlencode "relevance=filter" \ --data-urlencode "date_posted=last-week" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/media/transcript Get Instagram media transcript Credit cost: 10 (premium) Parameters: - url (required): Full URL of the Instagram video or reel (a `/reel/`, `/p/` or `/tv/` link to a video post). Photo posts have no audio to transcribe. A share link copied from the app (`instagram.com/share/...`) works too; a share link to a profile is refused at no charge with the handle to send, and a dead one is a free 404. curl -G "https://www.socialcrawl.dev/v1/instagram/media/transcript" \ --data-urlencode "url=https://www.instagram.com/reel/DHsD6HGqJhp/" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/media/screen-text Get Instagram post on-screen text Credit cost: 5-10 (metered): 5 credits a call, the same for a photo, a reel's cover or a carousel page of 10 slides. 10 credits with `frames=all` on a reel; when the post has no video to read, or the video cannot be read and the cover is read instead, the call costs 5 and the response says so in `warnings`. Refunded when the post cannot be found, when nothing on it could be read, or when a parameter does not fit the post (a `slide_offset` past the last slide, a reel over 180 seconds with `frames=all`). Parameters: - url (required): Full URL of the Instagram post, reel or video (a `/p/`, `/reel/`, `/reels/` or `/tv/` link). A share link copied from the app (`instagram.com/share/...`) works too; a share link to a profile is refused at no charge with the handle to send, and a dead one is a free 404. - frames (optional, enum: cover | all): What to read on a reel or video. `cover` (the default) reads the cover frame for 5 credits. `all` reads the whole video, up to 180 seconds, for 10 credits, and adds `subtitles`, `timeline` and `duration_seconds`. Has no effect on photos and carousels, which are read as usual for 5 credits. - slide_offset (optional, integer): Carousels only: the 0-based index of the first slide to read, 10 slides a call. Default 0. `slide_offset=10` reads slides 11 to 20. Each image in `images` keeps its index in the whole carousel, and `next_slide_offset` gives the value for the next call. curl -G "https://www.socialcrawl.dev/v1/instagram/media/screen-text" \ --data-urlencode "url=https://www.instagram.com/p/DdEqEl5Dl-S/" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/user/embed Get Instagram user embed HTML Credit cost: 1 (standard) Parameters: - handle (required): Instagram username without the @ symbol curl -G "https://www.socialcrawl.dev/v1/instagram/user/embed" \ --data-urlencode "handle=instagram" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/audio/reels List Instagram reels using an audio track Credit cost: 1 (standard) Parameters: - audio_id (required): Instagram audio ID: the numeric id from an instagram.com/reels/audio/{audio_id}/ URL - cursor (optional, string): Pagination cursor. Use the `next_cursor` from the previous response to fetch the next page. - since (optional, string): Only rows published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Rows older than it are left off each page. This list is not in date order, so the walk does not stop at the date: keep following `next_cursor`, and each page costs what a page costs even when every row on it is filtered out. A row with no date is kept. `published_after`, `start_date`, `from_date`, `publish_time` (`yesterday`, `this-week`, `this-month`, `last-3-months`, `last-6-months`) and `max_age_days` are accepted and applied as `since`. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. curl -G "https://www.socialcrawl.dev/v1/instagram/audio/reels" \ --data-urlencode "audio_id=1392969992841787" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/search/hashtag Feed of a known hashtag, 1 credit a page, every ranking paginates (for keyword or topic discovery use instagram/search/reels) Credit cost: 1-31 (metered): 1 credit per page, on every `type`. `max_items` and `max_pages` walk more pages in one call at 1 credit each, and `max_credits` caps the total. A page Instagram returns empty is not charged. `include=country` holds 30 more credits a page and keeps 1 per creator whose declared country came back, refunding a creator who publishes none or cannot be found, so a page with it costs 1 to 31. With `max_pages`, `max_items` or `max_credits`, each page walked is billed at this price (a cached page is free), and `max_credits` caps the total. With `seen`, a page's price falls with its share of rows you already received under that id; credits for joined data (`include=`) are never discounted. Parameters: - hashtag (required): The hashtag to search for. The leading # is optional. - type (optional, enum: top | recent | clips): Ranking of the returned posts: `top` (default), `recent` (newest first), or `clips` (reels only). Every ranking paginates. - cursor (optional, string): Pagination cursor. Use the `next_cursor` from the previous response and send the same `type` again. - safe_url (optional, boolean): When true, returns URL-safe media links suitable for embedding. - include (optional, enum: country): Send `country` to add each post's creator country as `post.ext.author_country`: the country the creator declares on Instagram's About this account panel, not where the post was filmed, and null when the creator publishes none. Every row carries the creator's numeric id as `post.ext.author_id`, which is what it is read by. 1 extra credit per creator whose country came back; two posts by one creator cost one lookup, a creator looked up in the last 30 days is free, and a creator who publishes none or cannot be found is refunded. A typo is a free 400. - since (optional, string): Only rows published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Rows older than it are left off the page. With `type=recent` the pages run from newest to oldest (the posts inside one page are not in date order), so the first page with no post on or after the date ends the walk. It comes back empty,`next_cursor` is not returned and `pagination.stopped_at` is `since`, and you pay for the pages you walk and no more. Other `type` values are not in date order, so there the walk does not stop at the date and each page costs what a page costs even when every row on it is filtered out. A row with no date is kept. `published_after`, `start_date`, `from_date`, `publish_time` (`yesterday`, `this-week`, `this-month`, `last-3-months`, `last-6-months`) and `max_age_days` are accepted and applied as `since`. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. - min_views (optional, integer): Keep only rows with at least this many views (`post.engagement.views`). A row whose view count is unknown is discarded, so every returned row meets the floor. Filtering runs on our side after each page is fetched, so every page walked is billed as usual; `data.walk.discarded` counts what was removed and why. Each page is its own entry in `GET /v1/credits/transactions`, under the page's request id listed in `data.walk.request_ids`. - max_age_days (optional, integer): Keep only rows published within this many days (`post.published_at`), 1 to 3650. A row with no date is discarded. Filtering runs on our side after each page is fetched, so every page walked is billed as usual; `data.walk.discarded` counts what was removed and why. Each page is its own entry in `GET /v1/credits/transactions`, under the page's request id listed in `data.walk.request_ids`. - sort_rows (optional, enum: views): `views`: return the kept rows ordered by view count, highest first, across every page walked. Rows without a view count go last. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them (after any filter). Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows (after any filter), then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. - seen (optional, string): An id you choose (1 to 64 letters, digits, `.`, `_` or `-`). Rows your account already received under the same `seen` id are removed from the page, and the page price falls with the share of repeats: page credits x new rows / rows on the page, rounded up, so a page of nothing but repeats is free. Only the page's own price is discounted: credits for joined data (`include=`) are charged only for rows you have not received, and are never discounted. The memory lasts 24 hours from the last call that used the id, holds row ids only, and is private to your account. Use one id across a set of related searches; `data.walk.repeats` counts what was removed. curl -G "https://www.socialcrawl.dev/v1/instagram/search/hashtag" \ --data-urlencode "hashtag=makeup" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/search/profiles Search Instagram profiles by keyword Credit cost: 1-25 (metered): 1 credit for the search page. With `include=about`, 1 plus 2 credits per row filled, held at the top 8 (17) unless you send `limit`, refunded down to the rows that actually received an About-panel leaf. With `max_pages`, `max_items` or `max_credits`, each page walked is billed at this price (a cached page is free), and `max_credits` caps the total. Parameters: - query (required): Keyword or phrase matched against account usernames and display names, as Instagram's own account search does. - cursor (optional, string): Pagination cursor. Pass the `next_cursor` from the previous response to get the next page; treat it as an opaque token. - include (optional, enum: about): Send `about` to fill `author.ext.country`, join month and public contact from the About panel, 2 extra credits a row filled, never more than 12 rows. A typo is a free 400. - limit (optional, integer): Cap the rows served, and with `include=about` the credits held. 1 to 12. Default 8 when the token is present so the hold is 17, not 25. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. curl -G "https://www.socialcrawl.dev/v1/instagram/search/profiles" \ --data-urlencode "query=yoga" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/reels/trending Get trending Instagram reels Credit cost: 5 (advanced) Parameters: - since (optional, string): Only rows published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Rows older than it are left off each page. This list is not in date order, so the walk does not stop at the date: keep following `next_cursor`, and each page costs what a page costs even when every row on it is filtered out. A row with no date is kept. `published_after`, `start_date`, `from_date`, `publish_time` (`yesterday`, `this-week`, `this-month`, `last-3-months`, `last-6-months`) and `max_age_days` are accepted and applied as `since`. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. curl "https://www.socialcrawl.dev/v1/instagram/reels/trending" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/followers List Instagram followers Credit cost: 5-10 (metered): 5 credits a page. Up to three reads are merged into one page, typically about 50 accounts, and a page can carry fewer. With `coverage=full`, up to 10 credits a page, settled down to 5 when the plain walk covered the whole page. A page that fails is refunded. With `query`, 5 credits for its one window, and nothing when no account matches. With `max_pages`, `max_items` or `max_credits`, each page walked is billed at this price (a cached page is free), and `max_credits` caps the total. Parameters: - handle (optional, string): Instagram username without the @ symbol. - user_id (optional, string): Instagram numeric user ID. Use this for faster responses. - cursor (optional, string): Pagination cursor. Use the `next_cursor` from the previous response to fetch the next page. - coverage (optional, enum: full): Send `full` to walk a merged list: accounts are added from several reads of the list and no account appears twice inside a page, until the rows reach the profile's count in `data.total`. On the pages one source covered on its own a handful of accounts can repeat across consecutive pages; wherever that source cannot cover the list the merged walk runs instead, and it never returns an account it has already given you. Requires `handle`. 10 credits a page instead of 5, settled down to 5 on any page a single read covered in full. Up to three reads are merged into one page, typically about 50 accounts, so a full list costs at most roughly the account count divided by 50, times 10 credits: on 13/09/2026 a following list of 2,652 took 54 pages and a follower list of 2,360 took 59, because the later pages of a follower walk add fewer new accounts. A page usually takes about 8 seconds and can take up to about 45, and a `503` is retried with the same cursor. A cursor from this mode only continues this mode. - query (optional, string): A username or name to look for in the list. Returns the accounts whose username or display name contains it (ignoring case and accents) in one window with no cursor, at the price of a page, and nothing is charged when no account matches. Cannot be combined with `cursor` or `coverage`. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. - Constraint: one of handle, user_id (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/followers" \ --data-urlencode "handle=mrbeast" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/following List Instagram following Credit cost: 5-10 (metered): 5 credits a page. Up to three reads are merged into one page, typically about 50 accounts, and a page can carry fewer. With `coverage=full`, up to 10 credits a page, settled down to 5 when the plain walk covered the whole page. A page that fails is refunded. With `query`, 5 credits for its one window, and nothing when no account matches. With `max_pages`, `max_items` or `max_credits`, each page walked is billed at this price (a cached page is free), and `max_credits` caps the total. Parameters: - handle (optional, string): Instagram username without the @ symbol. - user_id (optional, string): Instagram numeric user ID. Use this for faster responses. - cursor (optional, string): Pagination cursor. Use the `next_cursor` from the previous response to fetch the next page. - coverage (optional, enum: full): Send `full` to walk a merged list: accounts are added from several reads of the list and no account appears twice inside a page, until the rows reach the profile's count in `data.total`. On the pages one source covered on its own a handful of accounts can repeat across consecutive pages; wherever that source cannot cover the list the merged walk runs instead, and it never returns an account it has already given you. Requires `handle`. 10 credits a page instead of 5, settled down to 5 on any page a single read covered in full. Up to three reads are merged into one page, typically about 50 accounts, so a full list costs at most roughly the account count divided by 50, times 10 credits: on 13/09/2026 a following list of 2,652 took 54 pages and a follower list of 2,360 took 59, because the later pages of a follower walk add fewer new accounts. A page usually takes about 8 seconds and can take up to about 45, and a `503` is retried with the same cursor. A cursor from this mode only continues this mode. - query (optional, string): A username or name to look for in the list. Returns the accounts whose username or display name contains it (ignoring case and accents) in one window with no cursor, at the price of a page, and nothing is charged when no account matches. Cannot be combined with `cursor` or `coverage`. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. - Constraint: one of handle, user_id (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/following" \ --data-urlencode "handle=mrbeast" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/similar List similar Instagram accounts Credit cost: 5-85 (metered): 5 credits for the list. include=profile holds 1 credit per row and keeps only the rows filled from a fresh lookup; the hydrated list is its top 20 accounts by default (at most 25 credits) and `limit=80` buys the full roster (at most 85). Rows served from cache are free, unfilled rows are refunded, and a repeat within the cache window costs 0. The list and the per-account lookups are cached for 15 minutes. Parameters: - handle (optional, string): Instagram username without the @ symbol. - user_id (optional, string): Instagram numeric user ID. Use this for faster responses. - include (optional, enum: profile): Set to `profile` (one token only) to fill `author.followers`, `author.following` and `author.bio` on every row in this one call, plus `author.posts_count` where the lookup has an exact total. The hydrated list is its top 20 accounts unless you pass `limit` (1 to 80). Holds 1 credit per row and keeps only the rows filled from a fresh lookup; rows already in cache and rows that could not be filled are refunded, so the most a call can cost is 5 + rows (25 by default, 85 with `limit=80`) and the usual bill is lower. Adds 3 to 6 seconds for 20 rows (never more than 12) and about four times that for 80, and nothing when the accounts are already cached; read `data.hydration` for the rows, the credits held and kept, and the time. - limit (optional, integer): Take the top N accounts of the list (1 to 80). With `include=profile` the list defaults to its top 20 (at most 25 credits) and `limit` caps both the rows and the extra credits: `limit=80&include=profile` buys the full roster for at most 85 credits. Without `include=profile` the full list is returned unless you cap it. - Constraint: one of handle, user_id (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/similar" \ --data-urlencode "handle=mrbeast" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/post/likers List Instagram post likers Credit cost: 5 (advanced) Parameters: - url (required): URL of the Instagram post (a /p/ or /reel/ link). A share link copied from the app (`instagram.com/share/...`) works too; a share link to a profile is refused at no charge with the handle to send, and a dead one is a free 404. - safe_url (optional, boolean): When true, returns URL-safe profile picture links suitable for embedding. curl -G "https://www.socialcrawl.dev/v1/instagram/post/likers" \ --data-urlencode "url=https://www.instagram.com/p/CnpPou9hWqq/" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/post/stats Get Instagram post stats including the share count Credit cost: 5-9 (metered): 5 credits for the post. include=saves holds 4 more and keeps them only when it fills a save count the plain call lacked, refunding them otherwise, so a hydrated call is at most 9 credits; on a reel whose save count the plain call already carries, and on any photo or carousel, it settles at 5. Post lookups are cached for 10 minutes. Parameters: - url (required): URL of the Instagram post (a /p/, /reel/, or /tv/ link). A share link copied from the app (`instagram.com/share/...`) works too; a share link to a profile is refused at no charge with the handle to send, and a dead one is a free 404. - safe_url (optional, boolean): When true, returns URL-safe media links suitable for embedding. - include (optional, enum: saves): Set to `saves` (one token only) to ask a third source for a reel's save count (`post.engagement.saves`) and repost count (`post.ext.repost_count`) when the plain call returned without them. 4 extra credits, kept only when a save count comes back; a repeat within the cache window is 0. Photos and carousels are never looked up, because Instagram publishes no save count on them. Adds 3 to 6 seconds when a lookup runs; read `data.hydration` for the credits held and kept and the time. curl -G "https://www.socialcrawl.dev/v1/instagram/post/stats" \ --data-urlencode "url=https://www.instagram.com/p/CnpPou9hWqq/" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/tagged List posts an Instagram user is tagged in Credit cost: 5 (advanced) Parameters: - handle (optional, string): Instagram username without the @ symbol. - user_id (optional, string): Instagram numeric user ID. Use this for faster responses. - cursor (optional, string): Pagination cursor. Use the `next_cursor` from the previous response to fetch the next page. - safe_url (optional, boolean): When true, returns URL-safe media links suitable for embedding. - since (optional, string): Only rows published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Rows older than it are left off each page. This list is not in date order, so the walk does not stop at the date: keep following `next_cursor`, and each page costs what a page costs even when every row on it is filtered out. A row with no date is kept. `published_after`, `start_date`, `from_date`, `publish_time` (`yesterday`, `this-week`, `this-month`, `last-3-months`, `last-6-months`) and `max_age_days` are accepted and applied as `since`. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. - Constraint: one of handle, user_id (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/tagged" \ --data-urlencode "handle=mrbeast" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/location/posts List recent posts at an Instagram location Credit cost: 5-68 (metered): 5 credits per page. `include=country` holds 63 more credits a page and keeps 1 per creator whose declared country came back, refunding a creator who publishes none or cannot be found, so a page with it costs 5 to 68. A page that fails is refunded in full and looks no creator up. With `max_pages`, `max_items` or `max_credits`, each page walked is billed at this price (a cached page is free), and `max_credits` caps the total. Parameters: - location_id (required): Instagram location id. Use location.pk from /v1/instagram/search/location. - cursor (optional, string): Pagination cursor. Use the `next_cursor` from the previous response to fetch the next, older page. - safe_url (optional, boolean): When true, returns URL-safe media links suitable for embedding. - include (optional, enum: country): Send `country` to add each post's creator country as `post.ext.author_country`: the country the creator declares on Instagram's About this account panel, not the place the post is tagged at, and null when the creator publishes none. Every row carries the creator's numeric id as `post.ext.author_id`, which is what it is read by. 1 extra credit per creator whose country came back; two posts by one creator cost one lookup, a creator looked up in the last 30 days is free, and a creator who publishes none or cannot be found is refunded. A typo is a free 400. - since (optional, string): Only rows published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Rows older than it are left off each page. This list is not in date order, so the walk does not stop at the date: keep following `next_cursor`, and each page costs what a page costs even when every row on it is filtered out. A row with no date is kept. `published_after`, `start_date`, `from_date`, `publish_time` (`yesterday`, `this-week`, `this-month`, `last-3-months`, `last-6-months`) and `max_age_days` are accepted and applied as `since`. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. curl -G "https://www.socialcrawl.dev/v1/instagram/location/posts" \ --data-urlencode "location_id=331004901" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/engagement Get Instagram engagement statistics Credit cost: 5 (advanced) Parameters: - handle (required): Instagram username without the @ symbol. curl -G "https://www.socialcrawl.dev/v1/instagram/engagement" \ --data-urlencode "handle=mrbeast" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/search/location Search Instagram locations Credit cost: 5 (advanced) Parameters: - query (required): Search keyword or phrase to find Instagram locations. curl -G "https://www.socialcrawl.dev/v1/instagram/search/location" \ --data-urlencode "query=Paris" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/search Search Instagram accounts, hashtags, and places Credit cost: 1 (standard) Parameters: - query (required): An account, hashtag or place name as typed in Instagram's search box (a name, not a topic sentence). For posts about a topic use instagram/search/reels. curl -G "https://www.socialcrawl.dev/v1/instagram/search" \ --data-urlencode "query=nike" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/search/popular Search popular Instagram posts Credit cost: 1-13 (metered): 1 credit for the page. include=engagement holds 1 credit per row, at most 12 (or `limit`), and keeps only the rows filled from a fresh lookup, so a full page is at most 13 credits; rows served from cache are free, unfilled rows are refunded, and a repeat of the same call within the cache window costs 0. Search pages are cached for 2 minutes and the per-post lookups for 10 minutes. With `max_pages`, `max_items` or `max_credits`, each page walked is billed at this price (a cached page is free), and `max_credits` caps the total. Parameters: - query (required): Keyword or topic to find popular Instagram posts. - cursor (optional, string): Cursor from the previous response to fetch the next page. - include (optional, enum: engagement): Set to `engagement` (one token only) to fill `post.engagement.likes`, `.comments`, `.shares`, `.views`, `post.published_at` and `post.content.duration_seconds` on every row in this one call. Holds 1 credit per row (at most 12, or `limit`) and keeps only the rows filled from a fresh lookup; rows already in cache and rows that could not be filled are refunded, so the most a call can cost is 1 + rows and the usual bill is lower. Adds 2 to 9 seconds on a fresh page (never more than 12) and nothing when the rows are already cached; read `data.hydration` for the rows, the credits held and kept, and the time. - limit (optional, integer): Take the top N rows of the page (1 to 12) after the search has run. With `include=engagement` it also caps the extra credits at N: `limit=5&include=engagement` costs at most 6. It is not a page size: `next_cursor` still advances past the full page, so rows beyond N on this page are not returned by the next page. - since (optional, string): Only rows published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Rows older than it are left off each page. This list is not in date order, so the walk does not stop at the date: keep following `next_cursor`, and each page costs what a page costs even when every row on it is filtered out. A row with no date is kept. `published_after`, `start_date`, `from_date`, `publish_time` (`yesterday`, `this-week`, `this-month`, `last-3-months`, `last-6-months`) and `max_age_days` are accepted and applied as `since`. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. curl -G "https://www.socialcrawl.dev/v1/instagram/search/popular" \ --data-urlencode "query=basketball" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/post/comment/replies List replies under an Instagram comment Credit cost: 1-5 (metered): 1 credit per page, with the default labels (sentiment, question, purchase_intent, complaint) included free. label=spam, toxic or low_quality holds 4 extra credits and refunds down to 1 credit per started 25 comments that were newly judged: comments already labelled are free, a page where nothing could be judged refunds the whole extra, and a cached page is free. With `max_pages`, `max_items` or `max_credits`, each page walked is billed at this price (a cached page is free), and `max_credits` caps the total. Parameters: - url (required): URL of the Instagram post that holds the parent comment. A share link copied from the app (`instagram.com/share/...`) works too; a share link to a profile is refused at no charge with the handle to send, and a dead one is a free 404. - comment_id (required): Id of the parent comment whose replies to list. - cursor (optional, string): Cursor from the previous response to fetch the next page. - label (optional, string): Optional CSV of SocialCrawl labels to add to every comment: sentiment, spam, question, purchase_intent, complaint, toxic, low_quality, injection. Without this param every page already carries sentiment, question, purchase_intent and complaint, free; label= adds the labels you name to them (the defaults keep running). Each comment gains computed.labels with probabilities (0 to 1), so you choose the cut-off; a comment that could not be judged carries labels: null. sentiment, question, purchase_intent and complaint are free when asked for too; spam, toxic, low_quality and injection add 1 credit per started 25 newly judged comments (comments already labelled for anyone are free, and so is a cached page). injection flags text that addresses an AI system and tries to direct it (flagged, p); it never drops or rewrites a row. judgments=off (or label=none) turns the default labels off. data.labels reports what was judged and billed. - exclude (optional, string): Optional CSV (spam, low_quality), only with the same preset in label. Drops comments whose probability is 0.8 or higher and lists their ids in data.labels.dropped_ids. A comment that could not be judged is never dropped. - judgments (optional, enum: on | off): Optional, on (the default) or off. By default every row gains free SocialCrawl judgments (computed.labels, and computed.relevance on search endpoints), reported in data.labels (mode default) and data.relevance (origin default), each with a status (complete, partial or skipped) and pending: the rows still being judged when the page was sent, which carry null now and are filled on your next call or cached read. Default judgments never add credits, never change an existing field, and never drop or reorder a row. off returns the page exactly as before, with none of those keys. label=none does the same. - dry_run (optional, enum: 1): Optional. When 1, return a cost preview for this labelled or relevance-filtered request without fetching the page or judging any row. data.estimate reports rows_expected, rows_cached, label_credits_min, label_credits_max and base_credits. 0 credits charged. - label_evidence (optional, enum: 1): Optional, only with label=. When 1, every labelled row also carries computed.labels_evidence. = { quote, sentence_index }: the sentence in the row that most clearly shows the label, copied verbatim. Absent or null when no single sentence shows it. - fit (optional, enum: goal): Optional. When goal, keep the rows and fields needed for the goal you pass in goal= (plus any that are uncertain, and the first and last), and replace the rest with a stub. data.held_back lists the held ids and a recall id that re-reads the full page from cache at no extra charge. Without this param the page is unchanged. - goal (optional, string): Required by fit=goal. What you are trying to do, in your own words, up to 300 characters. - fit_tokens (optional, integer): Optional, only with fit=goal. Soft cap on how much of the page to keep, in tokens. Uncertain blocks and the first and last block are kept even if they exceed it. - since (optional, string): Only rows published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Rows older than it are left off each page. This list is not in date order, so the walk does not stop at the date: keep following `next_cursor`, and each page costs what a page costs even when every row on it is filtered out. A row with no date is kept. `published_after`, `start_date`, `from_date`, `publish_time` (`yesterday`, `this-week`, `this-month`, `last-3-months`, `last-6-months`) and `max_age_days` are accepted and applied as `since`. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. Worth sending: - label=purchase_intent: Marks each comment that shows buying intent, at no extra credits; sentiment, question and complaint are free too. curl -G "https://www.socialcrawl.dev/v1/instagram/post/comment/replies" \ --data-urlencode "url=https://www.instagram.com/reel/C8rKmYvsrck" \ --data-urlencode "comment_id=18038110327814211" \ --data-urlencode "label=purchase_intent" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/username-suggestions Get Instagram username suggestions Credit cost: 5 (advanced) Parameters: - query (required): Keyword to seed the username suggestions. curl -G "https://www.socialcrawl.dev/v1/instagram/username-suggestions" \ --data-urlencode "query=mrbeast" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/search/music Search Instagram music Credit cost: 5 (advanced) Parameters: - query (required): Search keyword or phrase to find Instagram audio tracks. - cursor (optional, string): Pagination cursor. Use the `next_cursor` from the previous response to fetch the next page. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. curl -G "https://www.socialcrawl.dev/v1/instagram/search/music" \ --data-urlencode "query=beyonce" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/stories List an Instagram user's active stories Credit cost: 5 (advanced) Parameters: - handle (optional, string): Instagram username without the @ symbol. - user_id (optional, string): Instagram numeric user ID. Use this for faster responses. - safe_url (optional, boolean): Optional and usually ignored: most responses carry ordinary media URLs whether or not you send it. Same price either way. - since (optional, string): Only rows published on or after this date: YYYY-MM-DD (midnight UTC) or an ISO 8601 timestamp. Rows older than it are left off each page. This list is not in date order, so the walk does not stop at the date: keep following `next_cursor`, and each page costs what a page costs even when every row on it is filtered out. A row with no date is kept. `published_after`, `start_date`, `from_date`, `publish_time` (`yesterday`, `this-week`, `this-month`, `last-3-months`, `last-6-months`) and `max_age_days` are accepted and applied as `since`. - until (optional, string): Only rows published on or before this date: YYYY-MM-DD (that whole UTC day is included) or an ISO 8601 timestamp. Newer rows are left off each page and the walk does not stop, so older pages follow. A row with no date is kept. `published_before`, `end_date` and `to_date` are accepted and applied as `until`. - Constraint: one of handle, user_id (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/stories" \ --data-urlencode "handle=instagram" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/story/download Download a single Instagram story Credit cost: 5 (advanced) Parameters: - user_id (optional, string): Instagram numeric user ID of the story's author. - story_id (optional, string): ID of the individual story to download. - safe_url (optional, boolean): When true, returns URL-safe media links suitable for embedding. - Constraint: one of user_id (at least one required) - Constraint: one of story_id (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/story/download" \ --data-urlencode "user_id=25025320" \ --data-urlencode "story_id=3926693776893966474" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/music/trending List trending Instagram music Credit cost: 5 (advanced) curl "https://www.socialcrawl.dev/v1/instagram/music/trending" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/hashtag Get an Instagram hashtag's size: its exact media count Credit cost: 1 (standard) Parameters: - hashtag (optional, string): Hashtag name, with or without the leading # symbol. - Constraint: one of hashtag (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/hashtag" \ --data-urlencode "hashtag=streetfood" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/location Get an Instagram location's profile Credit cost: 1 (standard) Parameters: - location_id (optional, string): Instagram location id: the numeric `location.pk` from /v1/instagram/search/location, or the number in an instagram.com/explore/locations/{location_id}/ URL. - Constraint: one of location_id (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/location" \ --data-urlencode "location_id=103912118089363" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/comment/likers List the accounts that liked an Instagram comment Credit cost: 1 (standard) Parameters: - comment_id (optional, string): Numeric id of the Instagram comment. - url (optional, string): Permalink of the comment: https://www.instagram.com/p/{shortcode}/c/{comment_id}/ (a reply permalink names the reply). - cursor (optional, string): Pagination cursor. Use the `next_cursor` from the previous response to fetch the next page. - max_pages (optional, integer): 1 to 50 (default 1, or 50 when `max_items` or `max_credits` is sent). Walk up to this many pages in one call and return the rows from all of them. Each page walked is billed exactly as one call to this endpoint (a cached page is free) and counts against your rate limit; its charge and any refund are recorded under the page's own request id, listed in `data.walk.request_ids`. The walk stops early at the last page, or when the request's time budget runs low; `data.walk.stopped` says why (`end`, `max_pages`, `max_items`, `max_credits`, `time_budget`, `page_error`) and `pagination.next_cursor` continues from where it stopped. - max_items (optional, integer): 1 to 5000. Get more in one call: keep walking pages until the response holds at least this many rows, then stop with `data.walk.stopped: "max_items"`. The last page is returned whole (it was billed), so `data.items` can hold up to one page more than this, and `pagination.next_cursor` resumes right after it without skipping a row. Walks up to 50 pages unless `max_pages` says fewer; each page is billed as one call (a cached page is free). Add `max_credits` to cap the spend. A long walk can stop on the time budget first (`"time_budget"`): send the returned cursor with the same params to continue. - max_credits (optional, integer): 1 to 100000. The most this call may spend, across every page it walks. A page starts only while the credits already spent plus that page's price (or, when higher, the most any page of this walk has cost) still fit, so the call never goes past it; the walk then stops with `data.walk.stopped: "max_credits"` and the cursor to resume. Sent alone, it walks as far as the budget goes (up to 50 pages). A value below the price of one page is a free 400 whose `error.details.page_credits` names that price. Credits a row join (`include=`) adds are known only once a page has run, so page 1 is checked against its own price. - Constraint: one of comment_id, url (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/comment/likers" \ --data-urlencode "comment_id=18133095883781285" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/profile/full Instagram profile, recent posts, and computed analytics in one call. Credit cost: 5 (override; tier standard) Parameters: - handle (optional, string): Instagram username or handle, with or without a leading @. One of the identity params is required. - posts (optional, integer): How many of the fetched recent posts to return and compute the metrics over (1-100, default 25). One call reads one page, which on Instagram is 12 posts, so a larger value returns that page and a `_warnings` note; pass posts_cursor back as cursor for the next page. - cursor (optional, string): Pass a prior response's posts_cursor to read the next page of posts. Each call is a separate 5-credit call whose metrics cover that page only. - include (optional, string): CSV subset of posts,computed (default both). include=computed omits the raw posts[] array from the response to save payload. The metrics are computed from the same fetched posts either way, so they do not change. - Constraint: one of handle (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/profile/full" \ --data-urlencode "handle=mrbeast" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/profile/reels/full Instagram reels with views, likes, comments, and per-reel share counts where available, in one call. Credit cost: 5-25 (metered): 5 credits per upstream page of about 12 reels. With no `limit` that is one page and a flat 5 credits. With `limit` set the composite walks `ceil(limit / 12)` pages - 3 for limit=30, 5 for the 50 maximum, so 25 credits at the top - holds that many up front and refunds every page it did not need. One partial refund sits inside the per-page price: a shares leg that fails outright refunds 4 of the 5 credits for that page, leaving the 1-credit list price. A shares leg that succeeds with low coverage is still the full 5, because the second source was queried either way. A `user_id` call is priced exactly like a `handle` call Parameters: - handle (optional, string): Instagram username (with or without a leading @). Required unless user_id is given. - user_id (optional, string): Numeric Instagram user id. Alternative to handle: the account's username is looked up from the id first, so a user_id call returns the same items, share counts and price as a handle call. An id that matches no account returns 404 and is not charged. - cursor (optional, string): Opaque cursor from a prior response's next_cursor to page deeper. - limit (optional, integer): Return up to this many items in one call (1-50). The endpoint pages the underlying source server-side until it has collected this many (or runs out), and bills per upstream page consumed (5 credits/page). Omit for a single page. - Constraint: one of handle, user_id (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/profile/reels/full" \ --data-urlencode "handle=sid.and.listen" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/profile/posts/full Instagram posts with views, likes, comments, and per-post share counts where available, in one call. Credit cost: 5-25 (metered): 5 credits per upstream page of about 12 posts. With no `limit` that is one page and a flat 5 credits. With `limit` set the composite walks `ceil(limit / 12)` pages - 3 for limit=30, 5 for the 50 maximum, so 25 credits at the top - holds that many up front and refunds every page it did not need. One partial refund sits inside the per-page price: a shares leg that fails outright refunds 4 of the 5 credits for that page, leaving the 1-credit list price. A shares leg that succeeds with low coverage is still the full 5, because the second source was queried either way. A `user_id` call is priced exactly like a `handle` call Parameters: - handle (optional, string): Instagram username (with or without a leading @). Required unless user_id is given. - user_id (optional, string): Numeric Instagram user id. Alternative to handle: the account's username is looked up from the id first, so a user_id call returns the same items, share counts and price as a handle call. An id that matches no account returns 404 and is not charged. - cursor (optional, string): Opaque cursor from a prior response's next_cursor to page deeper. - limit (optional, integer): Return up to this many items in one call (1-50). The endpoint pages the underlying source server-side until it has collected this many (or runs out), and bills per upstream page consumed (5 credits/page). Omit for a single page. - Constraint: one of handle, user_id (at least one required) curl -G "https://www.socialcrawl.dev/v1/instagram/profile/posts/full" \ --data-urlencode "handle=sid.and.listen" \ -H "x-api-key: YOUR_API_KEY" ## GET /v1/instagram/search/mentions Search Instagram posts that tag or mention an account Credit cost: 0-6 (metered): Metered, 0-6 credits: each call reads one page of /v1/instagram/tagged (5 credits) and /v1/instagram/search/reels (1 credit), each billed at that endpoint's own price exactly as a direct call is. A page that found nothing or failed costs nothing, and a page served from cache is free. A page is billed when it returned rows, even if none of them carried the mention; data.legs shows each page's charge and data.excluded what was left out. Parameters: - handle (required): The account's username, with or without the @, or its profile URL. - cursor (optional, string): data.next_cursor from the previous response, sent back verbatim for the next page. - since (optional, string): Only posts published on or after this date, YYYY-MM-DD (UTC). - until (optional, string): Only posts published on or before this date, YYYY-MM-DD (UTC); that whole day is included. - include_self (optional, boolean): When true, also keep posts by the account itself. Default false. curl -G "https://www.socialcrawl.dev/v1/instagram/search/mentions" \ --data-urlencode "handle=myntra" \ -H "x-api-key: YOUR_API_KEY"