> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sideshift.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Snapchat recent Spotlights

> Returns the creator's recent Spotlight snaps. Single page, no cursor: the response is
the account's whole available Spotlight inventory. 1 credit.




## OpenAPI

````yaml /openapi/scraper.yaml post /scrape/snapchat/posts
openapi: 3.0.4
info:
  title: SideShift Scraper API
  version: 1.1.0
  description: >
    Profiles, post listings, and single posts from TikTok, Instagram, YouTube,
    Facebook,

    Snapchat, X, and LinkedIn. One request shape, one response shape, one credit
    per call.


    ## Quickstart


    Every endpoint is a POST with a JSON body and your key in the `x-api-key`
    header.


    ```bash

    curl -X POST https://app.sideshift.app/api/v1/scrape/tiktok/posts \
      -H "x-api-key: scrape_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "username": "mrbeast" }'
    ```


    ```jsonc

    {
      "data": {
        "posts": [
          {
            "id": "7670566355153833246",
            "title": "The last one was hard 😴",
            "views": 25081146,
            "likes": 2543164,
            "comments": 85606,
            "shares": 47126,
            "bookmarks": 126013,
            "uploadedAt": 1785942939,          // unix seconds
            "postPage": "https://www.tiktok.com/@mrbeast/video/7670566355153833246",
            "creator": "mrbeast",
            "videoUrl": "https://…",           // direct media URL, short-lived
            "thumbnail": "https://…",
            "platform": "tiktok",
            "hashtags": []
          }
          // … the rest of the page
        ],
        "profile_pictures": { "mrbeast": "https://…" },
        "next_cursor": "1768430796798"       // pass back as "cursor" for page 2
      },
      "request_id": "req_k4XjoVoiYdzZ1ibg",
      "upstream_calls": 1,
      "meta": { "credits_charged": 1, "credits_remaining": 104987 }
    }

    ```


    ## 1. Authentication


    Create a key in the [Scraper dashboard](https://app.sideshift.app/scraper)
    under **Keys**.

    The full key is shown once. Send it in the `x-api-key` header on every
    request, and revoke

    it from the dashboard whenever you need to.


    Scraper keys start with `scrape_live_`. Integration API keys (`sk_live_*`,
    `sk_test_*`) are

    rejected with `401` on scraper routes, and scraper keys are rejected on
    integration routes.


    > **Call from your backend only.** Never embed a scraper key in client-side
    code.


    ## 2. Credits and billing


    One credit is one lookup. You buy a credit balance up front; there is no
    subscription. You

    are charged when we complete the work, and refunded when we can't.


    | Outcome | Charged |

    |---|---|

    | Successful scrape (each page of a listing counts as one) | 1 credit |

    | Confirmed not-found: the platform answered, the account or post isn't
    there (`404`) | Full rate for the resource: 1 credit, or 25 for audience |

    | TikTok audience demographics (`/scrape/tiktok/audience`) | 25 credits |

    | Rejected before we start work: bad input, bad key, empty balance,
    oversized body, over your rate limit (`400` `401` `402` `413` `429`) | Free
    |

    | Our problem or the platform's: errors and timeouts (`500` `502` `504`) |
    Free, auto-refunded |


    A full catalog costs `ceil(total_posts / page_size)` credits, one per page.
    A 300-video

    TikTok account is about 10.


    `meta.credits_charged` and `meta.credits_remaining` come back on every
    success. Error bodies

    have no `meta` block, so read the `X-Scraper-Credits-Charged` header to see
    what a billed

    error cost you. Refunds are automatic; you never have to claim one. Top up
    in the

    [Scraper dashboard](https://app.sideshift.app/scraper).


    ## 3. Resources


    | Resource | Path | Returns |

    |---|---|---|

    | **Profile** | `POST /scrape/{platform}/profile` | Display name, bio,
    follower/following counts, avatar, post count. |

    | **Posts** | `POST /scrape/{platform}/posts` | One page of the creator's
    recent posts, plus a `next_cursor` where the platform supports one. |

    | **Single Post** | `POST /scrape/{platform}/post` | One post, by URL. |

    | **Audience** | `POST /scrape/tiktok/audience` | TikTok audience
    distribution by country. 25 credits. |


    Platforms: `tiktok`, `instagram`, `youtube`, `facebook`, `snapchat`,
    `twitter` (X), `linkedin`.


    Each endpoint reads only the fields documented for it and ignores the rest,
    so a misspelled

    property is silently discarded rather than rejected. Bodies over 32 KB
    return

    `413 PAYLOAD_TOO_LARGE`. A `{platform}/{resource}` pair that does not exist,
    such as

    `audience` on any platform but TikTok, returns a plain HTTP `404` with an
    HTML body instead

    of the JSON error envelope, so guard your parsing.


    ## 4. Pagination, page size, and speed


    Same request, same response shape everywhere. What differs is how much each
    platform gives

    you per call:


    | Platform | Posts per page | More pages? | `videoUrl` in listings | Speed |

    |---|---|---|---|---|

    | TikTok | 30 | Yes, cursor | Yes | 2–4 s |

    | Instagram | 12 | Yes, cursor | Yes | 8–15 s |

    | YouTube | 30 | Yes, cursor | No; fetch the single post | 15–45 s |

    | Facebook | Up to 30 | Yes, cursor | Yes | 10–30 s |

    | Snapchat | Up to 30 | No; single page | Yes | 4–10 s |

    | X | 20 | Yes, cursor | Videos only | 3–8 s |

    | LinkedIn | 1–11 | No; single page | Videos only | 4–10 s |


    Listings return video-style content: reels on Instagram and Facebook, Shorts
    by default on

    YouTube (`contentType: video` switches to longform), Spotlights on Snapchat.


    Set your client timeout to 60 seconds, and 90 for YouTube and Facebook. Run
    scrapes from a

    background job, not inside a user request.


    ### Walking a full catalog


    Pass each response's `next_cursor` back as `cursor`. Stop on the cursor, not
    the page size:

    the first page can overshoot, and short pages turn up mid-walk.


    ```js

    const posts = [];

    let cursor;

    do {
      const res = await fetch("https://app.sideshift.app/api/v1/scrape/tiktok/posts", {
        method: "POST",
        headers: { "x-api-key": KEY, "Content-Type": "application/json" },
        body: JSON.stringify(cursor ? { username: "mrbeast", cursor } : { username: "mrbeast" }),
      });
      const { data } = await res.json();
      posts.push(...data.posts);
      cursor = data.next_cursor;
    } while (cursor);

    ```


    Cursors are opaque: pass one back verbatim, and never construct or derive
    one. A stale or

    unrecognised cursor is not an error, so you get a billed page that is not
    where you meant to

    be, and a loop that trusts it may never terminate.


    Snapchat and LinkedIn return a single page and never issue a cursor, so
    their listings are

    only what the platform exposes, not the account's full history. A creator
    with no retrievable

    posts returns `200` with an empty `posts` array, not a `404`, and is billed.


    New posts arrive at the front of page one. After the first backfill, poll
    page one on your

    schedule and keep what you haven't seen; re-fetch known posts only when you
    want updated

    metrics.


    ## 5. Reading the response


    Metrics are cumulative totals as of the moment you called. `uploadedAt` is
    unix seconds.

    `id` is the platform's own post id and is stable, which makes it the right
    key for storing

    and de-duplicating posts. On TikTok, Instagram, and Facebook, `videoUrl` and
    `thumbnail` are

    signed URLs that expire within hours: download the media when you receive
    it, and never

    store the URL. On TikTok photo posts, `videoUrl` points at the post's audio
    track rather

    than a video and nothing in the response flags it, so check the response
    `Content-Type`

    before treating the bytes as video.


    Two fields need care if you work across platforms:


    - `creator` is a lowercase handle on TikTok, Instagram, and LinkedIn,
    canonical case on X,
      and the account's **display name** on YouTube and Facebook (`Coca-Cola`, not `cocacola`).
      Join accounts on the identifier you requested, never on `creator`.
    - `postPage` on a single-post lookup echoes the URL you sent, so the same
    post can produce
      several different `postPage` values. Deduplicate on `id` plus `platform`, never on
      `postPage`.

    Single-post responses carry two keys that listings omit entirely,
    `transcript` and

    `topComments`, so read them defensively. Both are filled only when you ask
    for them with

    `include_transcript` or `include_comments`, and only on **TikTok, YouTube,
    and Facebook**;

    Instagram, Snapchat, X, and LinkedIn accept the flags and return `null`.
    Neither flag costs

    extra credits.


    Profile responses return `username`, `display_name`, and `follower_count` on
    all seven

    platforms. Everything else depends on what the source exposes, and
    unavailable fields are

    usually omitted rather than returned as `null`, so test for a usable value
    rather than for

    key presence.


    Field-level types, per-platform gaps, and the metrics that are structurally
    always `0` are

    in the `UnifiedPost` and `ScrapeProfileResponse` schemas below.


    ## 6. Errors and refunds


    Every error is JSON with an `error` code, a human-readable `message`, and a
    `request_id` to

    quote at support.


    | Code | What happened | What to do |

    |---|---|---|

    | `PROFILE_NOT_FOUND`<br>`POST_NOT_FOUND` | The platform confirmed it
    doesn't exist. Billed, because it was a real lookup. | Remove the identifier
    from your schedule. Retrying it costs a credit every time. |

    | `UPSTREAM_ERROR`<br>`UPSTREAM_TIMEOUT` | The platform failed or timed out.
    Refunded. | Retry with backoff: 30 s, then a few minutes. YouTube and
    Facebook throw these the most. |

    | `SCRAPER_RATE_LIMITED` | One of *your* limits is exhausted. The
    `X-Scraper-RateLimit-*` headers report the one that denied you. | Wait for
    `Retry-After`. If you hit this steadily, ask us to raise your limit. |

    | `SCRAPER_SYSTEM_BUSY` | *Our* capacity, nothing about your account.
    Refunded, and it does not consume your rate limit. | Wait for `Retry-After`
    and retry. |

    | `INSUFFICIENT_SCRAPER_CREDITS` | Balance is empty. Nothing charged. | Top
    up in the dashboard. `meta.credits_remaining` is on every success for
    alerting. |

    | `INVALID_INPUT` | The body failed validation. The `message` names the
    field and why. | Fix and resend. |


    The retry rule in one line: **retry refunded errors, never retry billed ones
    on a loop, and

    always honour `Retry-After`.**


    Not every `5xx` body is JSON. A request that outruns the roughly 120-second
    server ceiling

    is terminated by the gateway and returns a bare `504` with a `text/plain`
    body; its

    reservation is reclaimed within about ten minutes rather than at the moment
    it fails.


    ## 7. Rate limits


    New accounts start at 120 requests per minute across all endpoints. You may
    additionally have a

    per-endpoint limit. The account total is shared, so a request can be denied
    by the total

    while its own endpoint still has allowance. Both are token buckets: you can
    spend a minute's

    allowance in one burst, and it refills continuously rather than at a window
    edge.


    `X-Scraper-RateLimit-Limit`, `-Remaining`, and `-Reset` come back on every
    `200` and every

    `429`, reporting whichever limit is closest to being reached. Prefer
    `Retry-After` on a

    `429`: `-Reset` is computed when your request is admitted, so on a slow
    platform it can

    already be in the past by the time you read it.


    For production volume, contact support to raise your limits.
servers:
  - url: https://app.sideshift.app/api/v1
    description: Production
security:
  - apiKeyAuth: []
tags:
  - name: Profile
    description: >-
      Profile-level info for a creator (display name, bio, follower/following
      counts, avatar, post count).
  - name: Posts
    description: One page of a creator's recent posts.
  - name: Single Post
    description: One post, looked up by its URL.
  - name: Audience
    description: Audience location data for a creator.
paths:
  /scrape/snapchat/posts:
    post:
      tags:
        - Posts
      summary: Snapchat recent Spotlights
      description: >
        Returns the creator's recent Spotlight snaps. Single page, no cursor:
        the response is

        the account's whole available Spotlight inventory. 1 credit.
      operationId: scrapeSnapchatPosts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PostsRequest'
            example:
              username: nasa
      responses:
        '200':
          description: A single page of Spotlights. Can be empty.
          headers:
            X-Scraper-Credits-Charged:
              $ref: '#/components/headers/CreditsCharged'
            X-Scraper-Credits-Remaining:
              $ref: '#/components/headers/CreditsRemaining'
            X-Scraper-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-Scraper-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            X-Scraper-RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScrapePostsResponse'
              examples:
                sample:
                  $ref: '#/components/examples/SnapchatPostsExample'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '404':
          $ref: '#/components/responses/ProfileNotFound'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '502':
          $ref: '#/components/responses/UpstreamError'
        '504':
          $ref: '#/components/responses/UpstreamTimeout'
components:
  schemas:
    PostsRequest:
      type: object
      required:
        - username
      properties:
        username:
          type: string
          description: >-
            Creator handle (with or without a leading `@`), matching
            `@?[a-zA-Z0-9._-]{1,100}`. Facebook also accepts a numeric
            `profile_id`; YouTube accepts a `@handle` or `UC…` channel id;
            LinkedIn accepts a person slug or a `company/<slug>` identifier. A
            full profile URL is not accepted on any platform.
          example: mrbeast
        cursor:
          type: string
          description: >-
            Pagination cursor from a previous response's `next_cursor` — send it
            back verbatim and never construct one. Omit for the first page.
            Snapchat and LinkedIn return a single page and never issue one. A
            cursor the platform does not recognise is not an error; on Instagram
            it silently returns page 1 again, and you are billed for it.
    ScrapePostsResponse:
      type: object
      required:
        - data
        - request_id
        - upstream_calls
      properties:
        data:
          type: object
          required:
            - posts
            - profile_pictures
          properties:
            posts:
              type: array
              items:
                $ref: '#/components/schemas/UnifiedPost'
            profile_pictures:
              type: object
              additionalProperties:
                type: string
              description: >-
                Avatar URL keyed by the identifier you requested, which is not
                necessarily the same string as posts[].creator (on Facebook it
                never is). It holds one entry at most, so listing entries
                authored by someone else — Instagram collaborator posts, for
                example — have no avatar here. Can be an empty object.
            next_cursor:
              type: string
              description: >-
                Opaque pagination token, present only when more results exist.
                Pass it back verbatim as `cursor`; do not parse, construct or
                derive it. (TikTok currently returns an epoch-milliseconds
                value, but it is NOT uploadedAt * 1000 — it carries sub-second
                precision that uploadedAt cannot represent.) Omitted on the last
                page and on platforms that return a single page (Snapchat,
                LinkedIn).
        request_id:
          type: string
        upstream_calls:
          type: integer
          description: >-
            Number of upstream data-source calls this request generated (always
            1 — one page per call on every platform).
        meta:
          $ref: '#/components/schemas/CreditsMeta'
    UnifiedPost:
      type: object
      description: >-
        The platform-agnostic post shape. The posts listing returns the core
        fields only; the single-post endpoint additionally always returns
        transcript and topComments (null when not requested), plus contentType
        on Instagram and X. transcript and topComments are ABSENT from listing
        entries, not null.
      required:
        - id
        - platform
      properties:
        id:
          type: string
          description: >-
            Platform-native post id (Facebook reels canonicalised to the numeric
            reel id).
        title:
          type: string
          description: Caption / description text.
        creator:
          type: string
          description: >-
            Creator handle (no leading @) on TikTok, Instagram and LinkedIn, and
            a canonical-case handle on X. On YouTube and Facebook this is the
            channel or page DISPLAY NAME, which may contain spaces and
            punctuation (cocacola comes back as "Coca-Cola") and is not a
            handle; YouTube listing and single-post responses can also spell it
            differently for the same video. Key on `id`, not on this field.
        platform:
          type: string
          enum:
            - tiktok
            - instagram
            - youtube
            - facebook
            - snapchat
            - twitter
            - linkedin
        postPage:
          type: string
          description: >-
            Canonical URL of the post in `posts` listings. On the single-post
            endpoint this is the `url` you supplied, echoed back verbatim
            including any query string, alternate host, or missing scheme — so
            one post can yield several different values. Normalise it before
            using it as a join or dedupe key.
        views:
          type: integer
          description: >-
            0 when the platform does not expose it (never null). Always 0 on
            LinkedIn. Facebook listing values above ~10k are display-rounded;
            the single-post endpoint returns exact counts.
        likes:
          type: integer
        comments:
          type: integer
          description: >-
            Total comment count, not the comment text. On YouTube listings a
            count that could not be extracted is returned as 0, which is
            indistinguishable from a video with no comments.
        shares:
          type: integer
          description: >-
            0 when the platform does not expose it. Always 0 on Instagram,
            YouTube and LinkedIn.
        bookmarks:
          type: integer
          description: >-
            Saves / bookmarks. Always 0 on Instagram, YouTube, Facebook,
            Snapchat and LinkedIn.
        uploadedAt:
          type: integer
          nullable: true
          description: Unix seconds; null if unknown.
        uploadedAtFormatted:
          type: string
          description: >-
            ISO-8601 UTC of uploadedAt, e.g. 2026-06-15T02:43:21 (no trailing
            Z); empty if unknown.
        thumbnail:
          type: string
          description: >-
            Cover image URL. Reliably populated on TikTok, Instagram, Facebook,
            Snapchat and YouTube; empty on roughly a third of X posts
            (text-only). Signed and short-lived on TikTok/Instagram/Facebook;
            stable on YouTube, X and Snapchat.
        videoUrl:
          type: string
          description: >-
            Direct media URL. Always populated for
            TikTok/Instagram/Facebook/Snapchat. On X and LinkedIn it is
            populated only when the post carries video, and empty otherwise. For
            YouTube it is empty in posts listings and present only on the
            single-post (details) response, as a short-lived signed
            googlevideo.com URL. CAVEAT: TikTok photo (slideshow) posts have no
            video, and for those this field points at the post's AUDIO track
            (audio/mpeg or audio/mp4). That is roughly 5-8% of TikTok posts
            overall and up to about half for photo-heavy accounts, with no flag
            in the response to detect it — check the response Content-Type
            before treating the bytes as video.
        hashtags:
          type: array
          items:
            type: string
          description: Lowercased, leading '#' stripped (Unicode-aware).
        region:
          type: string
          nullable: true
          description: >-
            ISO 3166-1 alpha-2 country code TikTok attributes to THIS INDIVIDUAL
            VIDEO (in practice, the country it was uploaded from). Per-post, not
            a per-creator constant: a single creator's feed routinely mixes
            countries. It is not the creator's home country and not audience
            geography — use /scrape/tiktok/audience for audience distribution.
            TikTok only; null on all other platforms.
        contentType:
          type: string
          description: >-
            Present ONLY on single-post responses for Instagram ("reel") and X
            ("text" or "video"). Absent on every other platform and on all posts
            listings.
        transcript:
          type: string
          nullable: true
          description: >-
            SINGLE-POST RESPONSES ONLY — this key is absent entirely from posts
            listings, where a client reads undefined rather than null. On the
            single-post endpoint the key is always present: a string when
            include_transcript is true and the source supplies one, null
            otherwise. Instagram, Snapchat, X and LinkedIn never return a
            transcript.
        topComments:
          type: array
          nullable: true
          description: >-
            A top-weighted SAMPLE of the post's comments, returned by the
            single-post endpoint when include_comments is true (TikTok, YouTube,
            Facebook). SINGLE-POST RESPONSES ONLY — this key is absent entirely
            from posts listings. This is NOT the full thread: each platform has
            a fixed cap that does not grow with the post's comment count
            (YouTube exactly 20, Facebook exactly 10, TikTok up to 50 and
            typically 30-45), only top-level comments are returned, and there is
            no way to page beyond them — use `comments` for the true total.
            Entries come back in the platform's own comment-feed order, NOT
            sorted by likeCount; sort client-side if you need a ranking. `null`
            when not requested, unavailable, or the platform is
            Instagram/Snapchat/X/LinkedIn; `[]` when requested and there are no
            usable entries.
          items:
            $ref: '#/components/schemas/PostComment'
    CreditsMeta:
      type: object
      description: >-
        Per-request credit accounting (also surfaced in the X-Scraper-Credits-*
        response headers).
      properties:
        credits_charged:
          type: integer
        credits_remaining:
          type: integer
    ScrapeError:
      type: object
      required:
        - error
        - message
        - request_id
      properties:
        error:
          type: string
          enum:
            - INVALID_JSON
            - INVALID_INPUT
            - PAYLOAD_TOO_LARGE
            - UNAUTHORIZED
            - INSUFFICIENT_SCRAPER_CREDITS
            - PROFILE_NOT_FOUND
            - POST_NOT_FOUND
            - SCRAPER_RATE_LIMITED
            - SCRAPER_SYSTEM_BUSY
            - UPSTREAM_ERROR
            - UPSTREAM_TIMEOUT
            - SCRAPER_USAGE_FINALIZE_FAILED
            - INTERNAL_ERROR
        message:
          type: string
          description: Human-readable, SideShift-owned message.
        platform:
          type: string
          enum:
            - tiktok
            - instagram
            - youtube
            - facebook
            - snapchat
            - twitter
            - linkedin
        identifier:
          type: string
          description: The username or URL that was requested, when known.
        field:
          type: string
          description: Offending field on a validation error.
        reason:
          type: string
          description: Validation reason.
        request_id:
          type: string
    PostComment:
      type: object
      description: >-
        One entry in a post's `topComments` sample. The six keys are identical
        on every platform that supports comments; what the values are worth is
        not.
      properties:
        id:
          type: string
          description: >-
            Platform-native comment id (empty when the platform does not expose
            one).
        author:
          type: string
          description: >-
            Comment author — a handle (no leading @) on TikTok and YouTube, or a
            display name on Facebook.
        text:
          type: string
          description: >-
            Comment body. Entries with no readable body are omitted from the
            array.
        likeCount:
          type: integer
          description: >-
            Never null. ALWAYS 0 on Facebook, which exposes no per-comment
            engagement — never rank or filter Facebook comments on this. On
            YouTube it is the rounded display value, so 15000 means "15K", i.e.
            anywhere in [14500, 15500).
        replyCount:
          type: integer
          description: >-
            Replies to this comment. Counted in the post's `comments` total but
            not returned as entries. Never null. ALWAYS 0 on Facebook.
        timestamp:
          type: integer
          nullable: true
          description: Unix seconds; null if unknown.
  headers:
    CreditsCharged:
      schema:
        type: integer
      description: Credits debited for this request.
    CreditsRemaining:
      schema:
        type: integer
      description: Account credit balance after this request.
    RateLimitLimit:
      schema:
        type: integer
      description: >-
        Requests allowed per minute by the limit closest to being reached — your
        account total, or the per-endpoint limit for this endpoint if you have
        one.
    RateLimitRemaining:
      schema:
        type: integer
      description: >-
        Requests you can still make right now against that same limit. It is a
        token bucket, so this refills continuously at the per-minute rate rather
        than jumping back to the full limit at a window edge.
    RateLimitReset:
      schema:
        type: integer
      description: >-
        Unix seconds. Computed when the request is admitted, not when the
        response is written, so on a success it reads about a minute out MINUS
        the call's latency — on a slow platform it can already be in the past by
        the time you read it. On a 429, when the denied limit will have refilled
        enough to serve the request; prefer the `Retry-After` header, which says
        the same thing in seconds from now.
  examples:
    SnapchatPostsExample:
      summary: Snapchat posts — NASA, 7 Spotlights, single page
      value:
        data:
          posts:
            - id: W7_EDlXWTBiXAEEniNoMPwAAYeG9xcGd0ZWd3AZ14BVv0AZ14BQkrAAAAAQ
              title: Another Spotlight Snap brought to you by Snapchat
              creator: nasa
              platform: snapchat
              postPage: >-
                https://www.snapchat.com/spotlight/W7_EDlXWTBiXAEEniNoMPwAAYeG9xcGd0ZWd3AZ14BVv0AZ14BQkrAAAAAQ
              views: 23901
              likes: 2240
              comments: 114
              shares: 150
              bookmarks: 0
              uploadedAt: 1775835089
              uploadedAtFormatted: '2026-04-10T15:31:29'
              thumbnail: https://cf-st.sc-cdn.net/…/thumb
              videoUrl: https://cf-st.sc-cdn.net/…/video
              hashtags: []
              region: null
          profile_pictures:
            nasa: https://cf-st.sc-cdn.net/…/avatar
        request_id: req_4d5e6f7a8b9c0d1e
        upstream_calls: 1
        meta:
          credits_charged: 1
          credits_remaining: 9995
  responses:
    BadRequest:
      description: Invalid JSON or input. No credits are charged.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: INVALID_INPUT
            message: 'Invalid username: must match @?[a-zA-Z0-9._-]{1,100}'
            field: username
            reason: must match @?[a-zA-Z0-9._-]{1,100}
            request_id: req_8f3c9a2b1d4e6f70
    Unauthorized:
      description: >-
        Missing or invalid scraper key (or an Integration key was used). No
        credits are charged.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: UNAUTHORIZED
            message: >-
              Scraper routes require an independent scraper key from the Scraper
              API dashboard.
            request_id: req_8f3c9a2b1d4e6f70
    InsufficientCredits:
      description: >-
        Not enough scraper credits to run (or finalize) the request. No net
        credits are charged.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: INSUFFICIENT_SCRAPER_CREDITS
            message: Insufficient scraper credits
            platform: tiktok
            request_id: req_8f3c9a2b1d4e6f70
    ProfileNotFound:
      description: >-
        The profile is missing, private, restricted, or geo-blocked. Returned by
        the `profile`, `posts` and `audience` resources; the `post` resource
        returns `POST_NOT_FOUND` instead. Billed at the resource's full rate
        whenever the data source confirmed the lookup, which is the normal case
        — that is 1 credit for `profile`/`posts` and 25 for `audience`. When
        credits are refunded the `message` says so; a billed response does not
        mention credits at all, so read the `X-Scraper-Credits-Charged` header
        for the amount — there is no `meta` block on an error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: PROFILE_NOT_FOUND
            message: Profile not found or unavailable.
            platform: instagram
            identifier: someuser
            request_id: req_8f3c9a2b1d4e6f70
    PayloadTooLarge:
      description: Request body exceeds 32 KB. Rejected before any credit reservation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: PAYLOAD_TOO_LARGE
            message: Request body must be 32KB or smaller
            request_id: req_8f3c9a2b1d4e6f70
    RateLimited:
      description: >
        Two distinct conditions share this status, and the `error` code tells
        them apart.


        `SCRAPER_RATE_LIMITED` — one of YOUR limits is exhausted: either your
        account

        total or, if you have one, this endpoint's own per-minute limit. The

        X-Scraper-RateLimit-* headers report whichever one denied you. Spreading
        load

        across endpoints only helps if it was the per-endpoint limit that denied
        you —

        the account total is shared, so a request can be denied by it while this

        endpoint's own bucket still has tokens.


        `SCRAPER_SYSTEM_BUSY` — the scraping system is at capacity across all
        customers.

        Nothing about your account is wrong; retry after the `Retry-After`
        interval.


        Neither charges credits: no lookup is performed and the reservation is
        refunded

        in full. `SCRAPER_SYSTEM_BUSY` additionally does not consume your rate
        limit,

        whereas a `402` insufficient-balance block does.
      headers:
        Retry-After:
          description: Seconds to wait before retrying. Sent with both 429 codes.
          required: false
          schema:
            type: integer
            minimum: 1
            example: 3
        X-Scraper-RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        X-Scraper-RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        X-Scraper-RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: SCRAPER_RATE_LIMITED
            message: Scraper API rate limit exceeded
            platform: tiktok
            request_id: req_8f3c9a2b1d4e6f70
    ServerError:
      description: >
        An unexpected server error, or the usage could not be finalized after a
        successful

        scrape. When finalization fails, the reservation is refunded in full

        (`SCRAPER_USAGE_FINALIZE_FAILED`); a bare `INTERNAL_ERROR` reflects an
        unexpected

        fault and leaves no net credit change once the stranded reservation is
        reclaimed,

        which happens within about ten minutes rather than immediately.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: SCRAPER_USAGE_FINALIZE_FAILED
            message: Request could not be finalized. Your credits were refunded.
            platform: tiktok
            request_id: req_8f3c9a2b1d4e6f70
    UpstreamError:
      description: The data source failed. The reservation is refunded in full.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: UPSTREAM_ERROR
            message: Data source failed. Your credits were refunded.
            platform: youtube
            request_id: req_8f3c9a2b1d4e6f70
    UpstreamTimeout:
      description: >-
        The data source timed out. The reservation is refunded in full. A
        request that instead outruns the ~120 s platform function ceiling is
        terminated by the gateway before the API can respond, and returns a bare
        `504` with a `text/plain` body, no error code, and none of the
        `X-Scraper-*` headers; its reservation is reclaimed asynchronously
        within about ten minutes. Never assume a `504` body parses as JSON.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: UPSTREAM_TIMEOUT
            message: Data source timed out. Your credits were refunded.
            platform: facebook
            request_id: req_8f3c9a2b1d4e6f70
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Independent Scraper API key (`scrape_live_*`). Generate one in the
        Scraper dashboard.

````