> ## 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.

# TikTok — sound info

> One sound's metadata — title, artist, artwork, duration, usage count. Platform field names are preserved inside `data`. Billing: 1 SideShift credit per completed lookup.



## OpenAPI

````yaml /openapi/scraper.yaml post /scrape/tiktok/sound
openapi: 3.0.4
info:
  title: SideShift Scraper API
  version: 1.3.0
  description: >
    One scraper API with canonical /scrape/{platform}/{resource} paths.
    Normalized

    profile, posts, post, and TikTok audience resources remain stable; focused
    TikTok

    and Instagram operations preserve platform fields when their schemas are
    genuinely

    different. Completed lookups cost one credit except TikTok audience, which
    costs 25.


    ## 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`.


    Normalized profile, posts, post, and audience endpoints read their
    documented fields and

    ignore the rest. Focused TikTok and Instagram operations reject unknown or
    misspelled

    fields with the JSON error envelope before billing. Bodies over 32 KB return

    `413 PAYLOAD_TOO_LARGE`. Unknown TikTok or Instagram operations return a
    JSON

    `ENDPOINT_NOT_FOUND` response; paths outside the documented platform
    namespaces may return

    the framework's standard HTTP `404`.


    ## 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.


    ### A retry wrapper you can copy


    The wrapper below is the retry rule as code: it honours `Retry-After` on a
    `429`, backs

    off with jitter on refunded `5xx` errors, and gives up immediately on
    everything else.

    Route every call through it and both kinds of transient failure stop
    reaching your code.


    ```js

    async function scrape(url, body, attempts = 5) {
      for (let attempt = 1; ; attempt++) {
        const res = await fetch(url, {
          method: "POST",
          headers: { "x-api-key": KEY, "Content-Type": "application/json" },
          body: JSON.stringify(body),
        });
        if (res.ok) return (await res.json()).data;

        // 4xx other than 429 is deterministic (bad input, bad key, billed not-found):
        // retrying repeats the same answer, and on a 404 it repeats the same charge.
        const retriable = res.status === 429 || res.status >= 500;
        if (!retriable || attempt === attempts) throw new Error(await res.text());

        // A 429 says exactly when to come back. Refunded 5xx errors don't, so back off:
        // 30 s, 60 s, 2 min, 4 min. Jitter keeps parallel workers from retrying in step.
        const retryAfter = Number(res.headers.get("Retry-After"));
        const waitMs = retryAfter > 0
          ? retryAfter * 1000
          : Math.min(30_000 * 2 ** (attempt - 1), 300_000);
        await new Promise((resolve) => setTimeout(resolve, waitMs + Math.random() * 1000));
      }
    }


    const profile = await scrape(
      "https://app.sideshift.app/api/v1/scrape/tiktok/profile",
      { username: "mrbeast" },
    );

    ```


    It slots straight into the catalog walk in section 4: swap the raw `fetch`
    for

    `scrape(...)` and the loop rides out rate limits and platform hiccups
    unattended.


    ## 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.


    In almost all cases your limit is exactly what you get: stay under your rpm
    and your

    requests are admitted. The exception is the moment the scraping system as a
    whole is

    saturated across all customers, when a request can be turned away with

    `SCRAPER_SYSTEM_BUSY` even though your own buckets still have tokens. That
    answer is

    free, refunded, and carries a `Retry-After`; the wrapper in section 6
    absorbs it without

    any extra code. Treat it as something your client retries automatically, not
    an outage.


    For production volume, contact support to raise your limits.
servers:
  - url: https://app.sideshift.app/api/v1
    description: Production
security: []
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.
  - name: TikTok · Creator
    description: TikTok · Creator focused platform operations.
  - name: TikTok · Sound
    description: TikTok · Sound focused platform operations.
  - name: TikTok · Hashtag
    description: TikTok · Hashtag focused platform operations.
  - name: TikTok · Playlist
    description: TikTok · Playlist focused platform operations.
  - name: TikTok · Collection
    description: TikTok · Collection focused platform operations.
  - name: TikTok · Discovery
    description: TikTok · Discovery focused platform operations.
  - name: TikTok · Comments
    description: TikTok · Comments focused platform operations.
  - name: TikTok · Media
    description: TikTok · Media focused platform operations.
  - name: TikTok · Ads
    description: TikTok · Ads focused platform operations.
  - name: Instagram · Profile
    description: Instagram · Profile focused platform operations.
  - name: Instagram · Posts
    description: Instagram · Posts focused platform operations.
  - name: Instagram · Comments
    description: Instagram · Comments focused platform operations.
  - name: Instagram · Discovery
    description: Instagram · Discovery focused platform operations.
  - name: Instagram · Audio
    description: Instagram · Audio focused platform operations.
  - name: Instagram · Stories
    description: Instagram · Stories focused platform operations.
paths:
  /scrape/tiktok/sound:
    post:
      tags:
        - TikTok · Sound
      summary: TikTok — sound info
      description: >-
        One sound's metadata — title, artist, artwork, duration, usage count.
        Platform field names are preserved inside `data`. Billing: 1 SideShift
        credit per completed lookup.
      operationId: scrapeTiktokSound
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlatformTiktokSoundRequest'
            example:
              clipId: '7002634556977908485'
      responses:
        '200':
          description: Completed lookup. 1 SideShift credit.
          headers:
            X-Scraper-Credits-Charged:
              description: Credits debited for this completed lookup (normally 1).
              schema:
                type: integer
                example: 1
            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/PlatformTiktokSoundResponse'
              examples:
                live:
                  summary: Live data captured 2026-08-07
                  description: >-
                    The data object comes from a successful live request using
                    the documented input. Arrays are shortened to representative
                    items, long strings are trimmed, contact fields are
                    redacted, and transient URL query strings are removed. The
                    SideShift envelope values are representative.
                  value:
                    data:
                      status_code: 0
                      status_msg: ''
                      music_info:
                        album: null
                        author: 🇺🇸
                        duration: 6
                        id: '7002634556977908485'
                        id_str: '7002634556977908485'
                        is_original: true
                        is_original_sound: true
                        mid: '7002634556977908485'
                        owner_nickname: 🇺🇸
                        title: original sound - duyoungin
                        user_count: 982
                        cover_large:
                          height: null
                          uri: >-
                            https://p19-common-sign.tiktokcdn.com/tos-maliva-avt-0068/7350346788798218286~tplv-tiktokx-cropcenter:720:720.webp
                          url_prefix: null
                          width: null
                          url_list:
                            - >-
                              https://p19-common-sign.tiktokcdn.com/tos-maliva-avt-0068/7350346788798218286~tplv-tiktokx-cropcenter:720:720.webp
                        cover_medium:
                          height: null
                          uri: >-
                            https://p19-common-sign.tiktokcdn.com/tos-maliva-avt-0068/7350346788798218286~tplv-tiktokx-cropcenter:720:720.webp
                          url_prefix: null
                          width: null
                          url_list:
                            - >-
                              https://p19-common-sign.tiktokcdn.com/tos-maliva-avt-0068/7350346788798218286~tplv-tiktokx-cropcenter:720:720.webp
                        cover_thumb:
                          height: null
                          uri: >-
                            https://p19-common-sign.tiktokcdn.com/tos-maliva-avt-0068/7350346788798218286~tplv-tiktokx-cropcenter:720:720.webp
                          url_prefix: null
                          width: null
                          url_list:
                            - >-
                              https://p19-common-sign.tiktokcdn.com/tos-maliva-avt-0068/7350346788798218286~tplv-tiktokx-cropcenter:720:720.webp
                        play_url:
                          height: null
                          uri: >-
                            https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7002634676770999045.mp3
                          url_prefix: null
                          width: null
                          url_list:
                            - >-
                              https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7002634676770999045.mp3
                      rec_list: []
                      similar_music: []
                      similar_music_ids: []
                    request_id: req_8f3c9a2b1d4e6f70
                    upstream_calls: 1
                    meta:
                      credits_charged: 1
                      credits_remaining: 9998
        '400':
          $ref: '#/components/responses/PlatformOperationBadRequest'
        '401':
          $ref: '#/components/responses/PlatformOperationUnauthorized'
        '402':
          $ref: '#/components/responses/PlatformOperationInsufficientCredits'
        '404':
          $ref: '#/components/responses/PlatformOperationNotFound'
        '413':
          $ref: '#/components/responses/PlatformOperationPayloadTooLarge'
        '429':
          $ref: '#/components/responses/PlatformOperationRateLimited'
        '500':
          $ref: '#/components/responses/PlatformOperationServerError'
        '502':
          $ref: '#/components/responses/PlatformOperationDataSourceError'
        '504':
          $ref: '#/components/responses/PlatformOperationDataSourceTimeout'
components:
  schemas:
    PlatformTiktokSoundRequest:
      type: object
      additionalProperties: false
      properties:
        clipId:
          type: string
          description: >-
            Sound id, or a full tiktok.com/music/… URL. Accepted alias group:
            clipId or music.
        music:
          type: string
          description: >-
            Sound id, or a full tiktok.com/music/… URL. Accepted alias group:
            clipId or music.
      anyOf:
        - required:
            - clipId
        - required:
            - music
    PlatformTiktokSoundResponse:
      type: object
      required:
        - data
        - request_id
        - upstream_calls
        - meta
      properties:
        data:
          $ref: '#/components/schemas/PlatformTiktokSoundData'
        request_id:
          type: string
          description: SideShift request id. Include it in support requests.
        upstream_calls:
          type: integer
          enum:
            - 1
          description: Number of data-source lookups performed.
        meta:
          $ref: '#/components/schemas/CreditsMeta'
    PlatformTiktokSoundData:
      type: object
      description: TikTok platform data payload. Field names and nesting are preserved.
      additionalProperties: true
      properties:
        music_info:
          type: object
          additionalProperties: true
        rec_list:
          type: array
          items: {}
        similar_music:
          type: array
          items: {}
        similar_music_ids:
          type: array
          items: {}
        status_code:
          type: number
          description: Platform status code. 0 is success.
        status_msg:
          type: string
          description: Platform status message.
    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
            - RESOURCE_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
  headers:
    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.
    CreditsCharged:
      schema:
        type: integer
      description: Credits debited for this request.
  responses:
    PlatformOperationBadRequest:
      description: Invalid JSON or request fields. No credits are charged.
      headers:
        X-Scraper-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Scraper-Credits-Remaining:
          $ref: '#/components/headers/CreditsRemaining'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: INVALID_INPUT
            message: Invalid request body.
            request_id: req_8f3c9a2b1d4e6f70
    PlatformOperationUnauthorized:
      description: Missing or invalid SideShift scraper key. No credits are charged.
      headers:
        X-Scraper-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Scraper-Credits-Remaining:
          $ref: '#/components/headers/CreditsRemaining'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: UNAUTHORIZED
            message: Invalid scraper API key.
            request_id: req_8f3c9a2b1d4e6f70
    PlatformOperationInsufficientCredits:
      description: Insufficient scraper credits. No credits are charged.
      headers:
        X-Scraper-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Scraper-Credits-Remaining:
          $ref: '#/components/headers/CreditsRemaining'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: INSUFFICIENT_SCRAPER_CREDITS
            message: Insufficient scraper credits.
            request_id: req_8f3c9a2b1d4e6f70
    PlatformOperationNotFound:
      description: >-
        The requested public resource was not found or is unavailable. A
        completed lookup is billed at the endpoint rate.
      headers:
        X-Scraper-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Scraper-Credits-Remaining:
          $ref: '#/components/headers/CreditsRemaining'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: RESOURCE_NOT_FOUND
            message: Requested resource not found or unavailable.
            request_id: req_8f3c9a2b1d4e6f70
    PlatformOperationPayloadTooLarge:
      description: Request body exceeds 32 KB. No credits are charged.
      headers:
        X-Scraper-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Scraper-Credits-Remaining:
          $ref: '#/components/headers/CreditsRemaining'
      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
    PlatformOperationRateLimited:
      description: >-
        Rate limit or system-capacity limit. No credits are charged; honor
        Retry-After.
      headers:
        X-Scraper-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Scraper-Credits-Remaining:
          $ref: '#/components/headers/CreditsRemaining'
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
            minimum: 1
        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.
            request_id: req_8f3c9a2b1d4e6f70
    PlatformOperationServerError:
      description: Unexpected SideShift error. Credits are refunded.
      headers:
        X-Scraper-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Scraper-Credits-Remaining:
          $ref: '#/components/headers/CreditsRemaining'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: INTERNAL_ERROR
            message: Request failed. Your credits were refunded.
            request_id: req_8f3c9a2b1d4e6f70
    PlatformOperationDataSourceError:
      description: The data source failed. Credits are refunded.
      headers:
        X-Scraper-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Scraper-Credits-Remaining:
          $ref: '#/components/headers/CreditsRemaining'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: UPSTREAM_ERROR
            message: Data source failed. Your credits were refunded.
            request_id: req_8f3c9a2b1d4e6f70
    PlatformOperationDataSourceTimeout:
      description: The data source timed out. Credits are refunded.
      headers:
        X-Scraper-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Scraper-Credits-Remaining:
          $ref: '#/components/headers/CreditsRemaining'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ScrapeError'
          example:
            error: UPSTREAM_TIMEOUT
            message: Data source timed out. Your credits were refunded.
            request_id: req_8f3c9a2b1d4e6f70

````