Instagram Stories API — An Account’s Active Stories
Documentation menu
FetchLayer FetchLayer API

Read an account’s active stories

The stories a public Instagram account has up right now, oldest first: every frame with its media, posting and expiry time, attached links and re-shared posts, plus the account’s profile. Use it to catch stories before they expire. Pass a username or profile URL.

POST /user-stories Accounts
https://api.fetchlayer.dev/instagram/user-stories
OpenAPI JSON

Headers

Authorization string required

Bearer token — your FetchLayer API key.

Parameters

username string optional

A username with or without the @, or a profile URL. Give this or userId.

userId string optional

The account’s numeric id (profile.id), as a string of digits. Give this or username. An id names the account even after it changes its username; an id that belongs to no account is a 404. Name the account with exactly one of username and userId; both or neither is a 400.

include string[] optional

Optional: ["native"] fills profile.native, the account’s full profile record under Instagram’s own field names (see user-profile). It adds about a second with username, and usually nothing with userId. Any other value is a 400.

format string optional

json or markdown, lowercase; any other value is a 400. Over MCP, markdown returns the result rendered as text; the REST API accepts it and returns JSON. (default: json)

timeoutMs integer optional

Time limit for the whole request, in milliseconds (1000–120000); a value outside that range is a 400. When it runs out, a paged request returns what it has with nextCursor, billed for the pages it returned; a request with nothing to return yet fails with a 503 you can retry, marked with the response header X-Timeout-Source: caller. Left out, the request runs as long as it needs. A call usually takes 2–5 seconds, under a second when there is no active story, and occasionally up to about 30. Under heavy load it can answer 503; retry after a short wait.

Response Fields

user-stories

Read an account’s active stories

transport string

The data source: always the platform id, "instagram".

notes string[]

Caveats you can act on — a ceiling Instagram imposes, a counter it withholds, a list that stopped early. Several real limits are reported only here, so read them. An empty result always comes with a note explaining it, never as an unexplained empty array.

pagesFetched number

Pages of results fetched, which is what the request costs: one credit per page. resolve-url reports 0 and is not billed.

restricted boolean

True for an account Instagram restricts — typically an age-restricted brand such as alcohol or cannabis — and false for every other. Such an account answers 200, not 404, with less detail. Its stories come back the same way as any other account’s.

minimumAge integer | null

The minimum age Instagram states for a restricted account, or null when it states none (and on every other account).

username string

The account the stories belong to.

profile object

The account’s profile in the user-profile shape, including the exact stats.postCount (the same rule as user-profile).

profile.native object | null

Only with include ["native"]: the account’s full profile record as Instagram publishes it, field for field under Instagram’s own field names — account_type (1 personal, 2 business, 3 creator), category, public_email, public_phone_number, the address fields, hd_profile_pic_url_info, bio_links with link_id, fbid_v2 and more, around 240 fields. Every id is an exact string; an empty string means the account has not published that detail. Null when not available this time (the note says so, and nativeUnavailableReason says why); absent without the include.

profile.nativeUnavailableReason object | null

Only with include ["native"]: null when native is present, otherwise {code, retryable} saying why it is null: time-limit (timeoutMs left too little time; allow more), temporarily-unavailable (asking again usually returns it). Naming the account by userId makes the record faster to get.

latestStoryAt string | null

When the account last posted a story (ISO 8601), or null when it has none. With a complete list it equals the newest frame’s takenAt.

stories object[] | null

The active frames, oldest first, normally current to the second. Empty when the account has no active story; null for a private account. Each comes with a note, and both are ordinary answers that cost 1 credit, not errors. On rare occasions the very newest frame is not in the list yet: the note says the most recent story may not be included, and a later call picks it up.

stories[].id string

Instagram’s exact id for the frame, as a string (a 64-bit number; keep it as a string). A link built from it is exact: https://www.instagram.com/stories/<username>/<id>/.

stories[].url string | null

Link to the frame on Instagram.

stories[].kind string

image or video.

stories[].takenAt string | null

When the frame was posted (ISO 8601).

stories[].expiresAt string | null

When Instagram will take the frame down (ISO 8601), normally 24 hours after takenAt.

stories[].width integer | null

Pixel width.

stories[].height integer | null

Pixel height.

stories[].durationSeconds number | null

A video frame’s length in seconds; null for an image.

stories[].hasAudio boolean | null

Whether a video frame has sound; null for an image.

stories[].thumbnailUrl string | null

Preview image, or null.

stories[].assets object[]

Every rendition — video first, then images, largest first — each with kind, url, width, height and a downloadUrl that keeps working after Instagram’s own link expires.

stories[].links object[]

Links the account attached to the frame, each with its url, unwrapped to the real destination.

stories[].sharedPosts object[]

Posts the account re-shared into the frame, each with its shortcode and url — pass either to post-detail.

storyCount number

How many frames are in this response.

Request

curl -X POST "https://api.fetchlayer.dev/instagram/user-stories" \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"username":"@nasa"}'

Response

{
  "transport": "instagram",
  "notes": [],
  "pagesFetched": 1,
  "restricted": false,
  "minimumAge": null,
  "username": "nasa",
  "profile": {
    "id": "528817151",
    "username": "nasa",
    "url": "https://www.instagram.com/nasa/",
    "fullName": "NASA",
    "verified": true,
    "privateAccount": false,
    "hasActiveStory": true,
    "latestStoryAt": "2026-10-12T21:47:37.000Z",
    "country": null,
    "dateJoined": null,
    "stats": {
      "followerCount": 104352683,
      "followingCount": 91,
      "postCount": null
    }
  },
  "latestStoryAt": "2026-10-12T21:47:37.000Z",
  "stories": [
    {
      "id": "4006121587703428119",
      "url": "https://www.instagram.com/stories/nasa/4006121587703428119/",
      "kind": "video",
      "takenAt": "2026-10-12T21:47:37.000Z",
      "expiresAt": "2026-10-13T21:47:37.000Z",
      "width": 720,
      "height": 1280,
      "durationSeconds": 14.8,
      "hasAudio": true,
      "thumbnailUrl": "https://scontent-fra5-1.cdninstagram.com/v/t51.82787-15/...",
      "assets": [
        {
          "kind": "video",
          "url": "https://scontent-fra5-1.cdninstagram.com/o1/v/t2/f2/m86/...",
          "width": 720,
          "height": 1280,
          "downloadUrl": "https://api.fetchlayer.dev/instagram/media?url=..."
        },
        {
          "kind": "image",
          "url": "https://scontent-fra5-1.cdninstagram.com/v/t51.82787-15/...",
          "width": 720,
          "height": 1280,
          "downloadUrl": "https://api.fetchlayer.dev/instagram/media?url=..."
        }
      ],
      "links": [
        {
          "url": "https://www.nasa.gov/ways-to-watch"
        }
      ],
      "sharedPosts": []
    }
  ],
  "storyCount": 1
}