{
  "openapi": "3.1.0",
  "info": {
    "title": "TikTok API",
    "version": "1.0.0",
    "description": "A TikTok scraper API for public TikTok data: account profiles with exact follower and like counts, the videos an account has posted, one video in full, keyword search, hashtag listings and video comments — as structured JSON, with cursor paging and a free URL parser."
  },
  "servers": [
    {
      "url": "https://api.fetchlayer.dev/tiktok"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API Key",
        "description": "Send your FetchLayer key as Authorization: Bearer ss-your-key."
      }
    }
  },
  "paths": {
    "/user-profile": {
      "post": {
        "operationId": "user-profile",
        "tags": [
          "Accounts"
        ],
        "summary": "Get a TikTok account profile",
        "description": "One public TikTok account’s profile, by username or profile URL. Returns the bio, avatar, verification and organization flags, region, the account creation date, the bio link, the secUid, and the full stats block. The counts are the exact published figures rather than the rounded ones shown on the page, so a follower count comes back as 1719888 rather than 1.7M — which is what makes week-to-week tracking possible at all. A private account is reported in notes rather than as an error. Billing: 1 credit.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "username": {
                    "type": "string",
                    "description": "A username with or without the leading @, or a profile URL."
                  },
                  "format": {
                    "type": "string",
                    "description": "json or markdown. Markdown returns a rendered summary instead of the structured record.",
                    "example": "json"
                  },
                  "timeoutMs": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Time limit for each individual page read, in milliseconds (max 60000). It bounds one page, not the whole request."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "username"
                ]
              },
              "examples": {
                "default": {
                  "value": {
                    "username": "@nasa"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "transport": {
                      "type": "string",
                      "description": "The upstream source. Always \"tiktok\"."
                    },
                    "notes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Caveats you can act on — a private account, comments turned off, a list that ran out early, paging that has gone as deep as it goes. Empty when there are none. An empty result always comes with a note explaining it, never as an unexplained empty array."
                    },
                    "pagesFetched": {
                      "type": "number",
                      "description": "Pages of results read, which is what the request costs: one credit per page. resolve-url reports 0 and is not billed."
                    },
                    "profile": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "TikTok’s numeric account id."
                        },
                        "uniqueId": {
                          "type": "string",
                          "description": "The @handle, without the @."
                        },
                        "nickname": {
                          "type": "string",
                          "description": "The display name."
                        },
                        "url": {
                          "type": "string",
                          "description": "Canonical profile URL."
                        },
                        "signature": {
                          "type": "string",
                          "description": "The bio text."
                        },
                        "avatarUrl": {
                          "type": "string",
                          "description": "Avatar image. Pass it to GET /media to re-fetch it after TikTok’s own signed link expires."
                        },
                        "verified": {
                          "type": "boolean",
                          "description": "Whether TikTok shows the verification badge."
                        },
                        "privateAccount": {
                          "type": "boolean",
                          "description": "Whether the account is private. A private account has no public posts, and notes says so."
                        },
                        "createdAt": {
                          "type": "string",
                          "description": "When the account was created, or null."
                        },
                        "region": {
                          "type": "string",
                          "description": "The region TikTok publishes for the account, or null."
                        },
                        "isOrganization": {
                          "type": "boolean",
                          "description": "Whether TikTok marks the account as an organization."
                        },
                        "bioLink": {
                          "type": "string",
                          "description": "The link in the bio, or null."
                        },
                        "secUid": {
                          "type": "string",
                          "description": "TikTok’s opaque secondary account id."
                        },
                        "stats": {
                          "type": "object",
                          "properties": {
                            "followerCount": {
                              "type": "number",
                              "description": "Followers — the exact figure, not the rounded one."
                            },
                            "followingCount": {
                              "type": "number",
                              "description": "Accounts this one follows."
                            },
                            "heartCount": {
                              "type": "number",
                              "description": "Total likes across the account’s videos."
                            },
                            "videoCount": {
                              "type": "number",
                              "description": "Public videos posted."
                            },
                            "friendCount": {
                              "type": "number",
                              "description": "Mutual follows."
                            }
                          },
                          "additionalProperties": false
                        }
                      },
                      "additionalProperties": false
                    }
                  },
                  "additionalProperties": false
                },
                "examples": {
                  "default": {
                    "value": {
                      "transport": "tiktok",
                      "notes": [],
                      "pagesFetched": 1,
                      "profile": {
                        "id": "7664638705177150477",
                        "uniqueId": "nasa",
                        "nickname": "NASA",
                        "url": "https://www.tiktok.com/@nasa",
                        "signature": "Making the seemingly impossible, possible.✨",
                        "avatarUrl": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/...",
                        "verified": true,
                        "privateAccount": false,
                        "createdAt": "2026-07-20T15:55:49.000Z",
                        "region": null,
                        "isOrganization": true,
                        "bioLink": null,
                        "secUid": "MS4wLjABAAAAU9BRVzC8oCaegVnia8IbqWhPb_-dbU7s00Y3wS1_Nx8g5RUaYvyXrpejgjdxTwd6",
                        "stats": {
                          "followerCount": 1719888,
                          "followingCount": 23,
                          "heartCount": 8962614,
                          "videoCount": 45,
                          "friendCount": 17
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing Bearer token"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/user-videos": {
      "post": {
        "operationId": "user-videos",
        "tags": [
          "Accounts"
        ],
        "summary": "List an account’s videos",
        "description": "The videos a public TikTok account has posted, newest first by default. The account's profile comes back with them, so listing an account's videos does not also need a profile call. Sort by latest, popular, oldest. Page with pages and cursor, up to 500 videos and 20 pages in one request. This is the route TikTok declines most often; the service retries internally before answering, so a 503 here means those retries all failed — wait before retrying rather than retrying at once. A private account or one with no public posts returns an explained empty result in notes. Billing: 1 credit per page read, reported as pagesFetched.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "username": {
                    "type": "string",
                    "description": "A username with or without the leading @, or a profile URL."
                  },
                  "limit": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Maximum videos returned, up to 500. Use -1 for the maximum.",
                    "example": "30"
                  },
                  "sort": {
                    "type": "string",
                    "description": "Order the account's videos: latest, popular, oldest.",
                    "example": "latest"
                  },
                  "pages": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Pages of results to read in one request, up to 20. Use -1 for the maximum. Each page read is one billable page, and the response reports how many were read as pagesFetched.",
                    "example": "1"
                  },
                  "cursor": {
                    "type": "string",
                    "description": "Continue from a previous response’s nextCursor. Cursors belong to the route and the query that produced them — do not carry one between routes."
                  },
                  "format": {
                    "type": "string",
                    "description": "json or markdown. Markdown returns a rendered summary instead of the structured record.",
                    "example": "json"
                  },
                  "timeoutMs": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Time limit for each individual page read, in milliseconds (max 60000). It bounds one page, not the whole request."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "username"
                ]
              },
              "examples": {
                "default": {
                  "value": {
                    "username": "@nasa",
                    "limit": 30,
                    "sort": "latest"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "transport": {
                      "type": "string",
                      "description": "The upstream source. Always \"tiktok\"."
                    },
                    "notes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Caveats you can act on — a private account, comments turned off, a list that ran out early, paging that has gone as deep as it goes. Empty when there are none. An empty result always comes with a note explaining it, never as an unexplained empty array."
                    },
                    "pagesFetched": {
                      "type": "number",
                      "description": "Pages of results read, which is what the request costs: one credit per page. resolve-url reports 0 and is not billed."
                    },
                    "profile": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "The account’s profile, in the same shape user-profile returns."
                    },
                    "sort": {
                      "type": "string",
                      "description": "The order the videos came back in."
                    },
                    "videos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true,
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Numeric video id, which video-detail and comments both accept."
                          },
                          "url": {
                            "type": "string",
                            "description": "Canonical link to the video. This is what you use to open or embed it."
                          },
                          "description": {
                            "type": "string",
                            "description": "The caption, or null when there is none."
                          },
                          "createdAt": {
                            "type": "string",
                            "description": "ISO 8601 timestamp of when it was posted, or null."
                          },
                          "durationSeconds": {
                            "type": "number",
                            "description": "Length in seconds, or null."
                          },
                          "isPhotoPost": {
                            "type": "boolean",
                            "description": "True for a photo carousel rather than a clip."
                          },
                          "coverUrl": {
                            "type": "string",
                            "description": "The cover frame. An image, so it re-fetches through GET /media normally."
                          },
                          "assets": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "additionalProperties": true,
                              "properties": {
                                "downloadUrl": {
                                  "type": "string",
                                  "description": "Pass it to GET /media to stream the bytes. It serves images — carousel frames and cover images. A clip's bytes cannot be fetched by anyone, so asking for one returns 404 (see the media endpoint)."
                                }
                              }
                            },
                            "description": "An ordered list, so a clip and a carousel are handled the same way: a clip yields one asset of kind \"video\", a carousel one of kind \"image\" per frame. Each carries url, width, height and downloadUrl."
                          },
                          "stats": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "playCount, diggCount, commentCount, shareCount, collectCount and repostCount. A null counter means TikTok did not publish that figure; a real zero comes back as 0."
                          },
                          "author": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "id, uniqueId, nickname, avatarUrl, signature, verified, privateAccount, profileUrl and secUid."
                          },
                          "music": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "id, title, authorName, durationSeconds, original, coverUrl, playUrl and url, or null."
                          },
                          "hashtags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Without the #, read from TikTok’s own annotations rather than parsed out of the caption."
                          },
                          "mentions": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Usernames without the @."
                          },
                          "isAd": {
                            "type": "boolean",
                            "description": "True when TikTok marks the post as promotional, or null when it says nothing."
                          },
                          "language": {
                            "type": "string",
                            "description": "The language TikTok assigned to the caption, or null."
                          }
                        }
                      },
                      "description": "The videos, in the requested order."
                    },
                    "videoCount": {
                      "type": "number",
                      "description": "How many videos are in this response."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether more results exist beyond what was returned."
                    },
                    "nextCursor": {
                      "type": "string",
                      "description": "Opaque value to pass back as cursor to continue. null when there is nothing further."
                    }
                  },
                  "additionalProperties": false
                },
                "examples": {
                  "default": {
                    "value": {
                      "transport": "tiktok",
                      "notes": [],
                      "pagesFetched": 1,
                      "profile": {
                        "id": "7664638705177150477",
                        "uniqueId": "nasa",
                        "nickname": "NASA",
                        "url": "https://www.tiktok.com/@nasa",
                        "signature": "Making the seemingly impossible, possible.✨",
                        "avatarUrl": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/...",
                        "verified": true,
                        "privateAccount": false,
                        "createdAt": "2026-07-20T15:55:49.000Z",
                        "region": null,
                        "isOrganization": true,
                        "bioLink": null,
                        "secUid": "MS4wLjABAAAAU9BRVzC8oCaegVnia8IbqWhPb_-dbU7s00Y3wS1_Nx8g5RUaYvyXrpejgjdxTwd6",
                        "stats": {
                          "followerCount": 1719888,
                          "followingCount": 23,
                          "heartCount": 8962614,
                          "videoCount": 45,
                          "friendCount": 17
                        }
                      },
                      "sort": "latest",
                      "videos": [
                        {
                          "id": "7686148096895569165",
                          "url": "https://www.tiktok.com/@nasa/video/7686148096895569165",
                          "description": "Turns out you can dance in space! 🪩🕺💫🛰️ Microgravity just unlocked some new moves.",
                          "createdAt": "2026-09-16T15:00:41.000Z",
                          "durationSeconds": 58,
                          "isPhotoPost": false,
                          "coverUrl": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/...",
                          "assets": [
                            {
                              "kind": "video",
                              "url": "https://v16-webapp-prime.us.tiktok.com/video/tos/useast5/...",
                              "width": 720,
                              "height": 1280,
                              "downloadUrl": "https://api.fetchlayer.dev/tiktok/media?url=..."
                            }
                          ],
                          "stats": {
                            "playCount": 27600,
                            "diggCount": 4367,
                            "commentCount": 173,
                            "shareCount": 104,
                            "collectCount": 273,
                            "repostCount": 0
                          },
                          "author": {
                            "id": "7664638705177150477",
                            "uniqueId": "nasa",
                            "nickname": "NASA",
                            "verified": true,
                            "privateAccount": false,
                            "profileUrl": "https://www.tiktok.com/@nasa"
                          },
                          "music": {
                            "id": "7686148224364694286",
                            "title": "original sound - NASA",
                            "authorName": "NASA",
                            "durationSeconds": 58,
                            "original": false,
                            "url": "https://www.tiktok.com/music/x-7686148224364694286"
                          },
                          "hashtags": [],
                          "mentions": [],
                          "isAd": false,
                          "language": "en"
                        }
                      ],
                      "videoCount": 30,
                      "hasMore": true,
                      "nextCursor": "1789330350443"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing Bearer token"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/video-detail": {
      "post": {
        "operationId": "video-detail",
        "tags": [
          "Videos"
        ],
        "summary": "Get one video in full",
        "description": "One TikTok video by numeric id or URL, in the same shape every other route returns videos in: caption, timestamp, duration, cover, assets, engagement stats, author, music, hashtags, mentions, ad flag and language. A shortened share link (vm.tiktok.com, vt.tiktok.com) carries nothing about its target and is rejected with a 400 — open it and pass the full URL, or run it through resolve-url first. Use the comments endpoint for the comments on it. Billing: 1 credit.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "video": {
                    "type": "string",
                    "description": "A video URL or a numeric video id. Shortened share links are rejected — resolve them first."
                  },
                  "format": {
                    "type": "string",
                    "description": "json or markdown. Markdown returns a rendered summary instead of the structured record.",
                    "example": "json"
                  },
                  "timeoutMs": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Time limit for each individual page read, in milliseconds (max 60000). It bounds one page, not the whole request."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "video"
                ]
              },
              "examples": {
                "default": {
                  "value": {
                    "video": "https://www.tiktok.com/@nasa/video/7686148096895569165"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "transport": {
                      "type": "string",
                      "description": "The upstream source. Always \"tiktok\"."
                    },
                    "notes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Caveats you can act on — a private account, comments turned off, a list that ran out early, paging that has gone as deep as it goes. Empty when there are none. An empty result always comes with a note explaining it, never as an unexplained empty array."
                    },
                    "pagesFetched": {
                      "type": "number",
                      "description": "Pages of results read, which is what the request costs: one credit per page. resolve-url reports 0 and is not billed."
                    },
                    "video": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "The video, in the shape below.",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Numeric video id, which video-detail and comments both accept."
                        },
                        "url": {
                          "type": "string",
                          "description": "Canonical link to the video. This is what you use to open or embed it."
                        },
                        "description": {
                          "type": "string",
                          "description": "The caption, or null when there is none."
                        },
                        "createdAt": {
                          "type": "string",
                          "description": "ISO 8601 timestamp of when it was posted, or null."
                        },
                        "durationSeconds": {
                          "type": "number",
                          "description": "Length in seconds, or null."
                        },
                        "isPhotoPost": {
                          "type": "boolean",
                          "description": "True for a photo carousel rather than a clip."
                        },
                        "coverUrl": {
                          "type": "string",
                          "description": "The cover frame. An image, so it re-fetches through GET /media normally."
                        },
                        "assets": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true,
                            "properties": {
                              "downloadUrl": {
                                "type": "string",
                                "description": "Pass it to GET /media to stream the bytes. It serves images — carousel frames and cover images. A clip's bytes cannot be fetched by anyone, so asking for one returns 404 (see the media endpoint)."
                              }
                            }
                          },
                          "description": "An ordered list, so a clip and a carousel are handled the same way: a clip yields one asset of kind \"video\", a carousel one of kind \"image\" per frame. Each carries url, width, height and downloadUrl."
                        },
                        "stats": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "playCount, diggCount, commentCount, shareCount, collectCount and repostCount. A null counter means TikTok did not publish that figure; a real zero comes back as 0."
                        },
                        "author": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "id, uniqueId, nickname, avatarUrl, signature, verified, privateAccount, profileUrl and secUid."
                        },
                        "music": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "id, title, authorName, durationSeconds, original, coverUrl, playUrl and url, or null."
                        },
                        "hashtags": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Without the #, read from TikTok’s own annotations rather than parsed out of the caption."
                        },
                        "mentions": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Usernames without the @."
                        },
                        "isAd": {
                          "type": "boolean",
                          "description": "True when TikTok marks the post as promotional, or null when it says nothing."
                        },
                        "language": {
                          "type": "string",
                          "description": "The language TikTok assigned to the caption, or null."
                        }
                      }
                    }
                  },
                  "additionalProperties": false
                },
                "examples": {
                  "default": {
                    "value": {
                      "transport": "tiktok",
                      "notes": [],
                      "pagesFetched": 1,
                      "video": {
                        "id": "7686148096895569165",
                        "url": "https://www.tiktok.com/@nasa/video/7686148096895569165",
                        "description": "Turns out you can dance in space! 🪩🕺💫🛰️ Microgravity just unlocked some new moves.",
                        "createdAt": "2026-09-16T15:00:41.000Z",
                        "durationSeconds": 58,
                        "isPhotoPost": false,
                        "coverUrl": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/...",
                        "assets": [
                          {
                            "kind": "video",
                            "url": "https://v16-webapp-prime.us.tiktok.com/video/tos/useast5/...",
                            "width": 720,
                            "height": 1280,
                            "downloadUrl": "https://api.fetchlayer.dev/tiktok/media?url=..."
                          }
                        ],
                        "stats": {
                          "playCount": 27600,
                          "diggCount": 4367,
                          "commentCount": 173,
                          "shareCount": 104,
                          "collectCount": 273,
                          "repostCount": 0
                        },
                        "author": {
                          "id": "7664638705177150477",
                          "uniqueId": "nasa",
                          "nickname": "NASA",
                          "verified": true,
                          "privateAccount": false,
                          "profileUrl": "https://www.tiktok.com/@nasa"
                        },
                        "music": {
                          "id": "7686148224364694286",
                          "title": "original sound - NASA",
                          "authorName": "NASA",
                          "durationSeconds": 58,
                          "original": false,
                          "url": "https://www.tiktok.com/music/x-7686148224364694286"
                        },
                        "hashtags": [],
                        "mentions": [],
                        "isAd": false,
                        "language": "en"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing Bearer token"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/comments": {
      "post": {
        "operationId": "comments",
        "tags": [
          "Videos"
        ],
        "summary": "Read a video’s comments",
        "description": "The public comments on a TikTok video, by id or URL, each with its text, timestamp, like and reply counts, whether it is pinned, whether the video's author wrote or liked it, and the commenter's profile. Two things to know. `replies` holds only the replies TikTok shows alongside a comment, which is usually none or a handful, while `replyCount` reports how many exist in total — a response where the two differ carries a note saying so. And `totalCount` is the figure TikTok publishes for the video, which can exceed what is readable through paging. Up to 500 comments and 20 pages in one request. A video with comments turned off returns an explained empty result rather than an error. Billing: 1 credit per page read.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "video": {
                    "type": "string",
                    "description": "A video URL or a numeric video id. Shortened share links are rejected — resolve them first."
                  },
                  "limit": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Maximum comments returned, up to 500. Use -1 for the maximum.",
                    "example": "30"
                  },
                  "pages": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Pages of results to read in one request, up to 20. Use -1 for the maximum. Each page read is one billable page, and the response reports how many were read as pagesFetched.",
                    "example": "1"
                  },
                  "cursor": {
                    "type": "string",
                    "description": "Continue from a previous response’s nextCursor. Cursors belong to the route and the query that produced them — do not carry one between routes."
                  },
                  "format": {
                    "type": "string",
                    "description": "json or markdown. Markdown returns a rendered summary instead of the structured record.",
                    "example": "json"
                  },
                  "timeoutMs": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Time limit for each individual page read, in milliseconds (max 60000). It bounds one page, not the whole request."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "video"
                ]
              },
              "examples": {
                "default": {
                  "value": {
                    "video": "7686148096895569165",
                    "limit": 30
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "transport": {
                      "type": "string",
                      "description": "The upstream source. Always \"tiktok\"."
                    },
                    "notes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Caveats you can act on — a private account, comments turned off, a list that ran out early, paging that has gone as deep as it goes. Empty when there are none. An empty result always comes with a note explaining it, never as an unexplained empty array."
                    },
                    "pagesFetched": {
                      "type": "number",
                      "description": "Pages of results read, which is what the request costs: one credit per page. resolve-url reports 0 and is not billed."
                    },
                    "videoId": {
                      "type": "string",
                      "description": "The video the comments belong to."
                    },
                    "comments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true,
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Numeric comment id."
                          },
                          "text": {
                            "type": "string",
                            "description": "The comment body."
                          },
                          "createdAt": {
                            "type": "string",
                            "description": "ISO 8601 timestamp, or null."
                          },
                          "likeCount": {
                            "type": "number",
                            "description": "Likes on the comment."
                          },
                          "replyCount": {
                            "type": "number",
                            "description": "How many replies exist in total — which can exceed what `replies` holds."
                          },
                          "isAuthor": {
                            "type": "boolean",
                            "description": "Whether the video’s author wrote it."
                          },
                          "likedByAuthor": {
                            "type": "boolean",
                            "description": "Whether the video’s author liked it."
                          },
                          "pinned": {
                            "type": "boolean",
                            "description": "Whether it is pinned to the top."
                          },
                          "author": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "id, uniqueId, nickname, avatarUrl, signature, verified, privateAccount, profileUrl and secUid. Fields TikTok does not publish on a commenter are null."
                          },
                          "replies": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "additionalProperties": true
                            },
                            "description": "Only the replies TikTok shows alongside the comment — usually none or a handful. Read replyCount for the true total."
                          }
                        }
                      },
                      "description": "The comments, in TikTok’s own order."
                    },
                    "commentCount": {
                      "type": "number",
                      "description": "How many comments are in this response."
                    },
                    "totalCount": {
                      "type": "number",
                      "description": "The total TikTok publishes for the video, which can exceed what paging can reach."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether more comments exist beyond what was returned."
                    },
                    "nextCursor": {
                      "type": "string",
                      "description": "Pass back as cursor to continue, or null."
                    }
                  },
                  "additionalProperties": false
                },
                "examples": {
                  "default": {
                    "value": {
                      "transport": "tiktok",
                      "notes": [],
                      "pagesFetched": 1,
                      "videoId": "7686148096895569165",
                      "comments": [
                        {
                          "id": "7686156652273713942",
                          "text": "Imagine trying to nod to the beat in zero gravity and accidentally rotating the rest of yourself 😂",
                          "createdAt": "2026-09-16T15:33:48.000Z",
                          "likeCount": 5,
                          "replyCount": 0,
                          "isAuthor": false,
                          "likedByAuthor": false,
                          "pinned": false,
                          "author": {
                            "id": "6992252002843853829",
                            "uniqueId": "newy963",
                            "nickname": "newy963",
                            "verified": null,
                            "profileUrl": "https://www.tiktok.com/@newy963"
                          },
                          "replies": []
                        }
                      ],
                      "commentCount": 30,
                      "totalCount": 183,
                      "hasMore": true,
                      "nextCursor": "30"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing Bearer token"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/video-transcript": {
      "post": {
        "operationId": "video-transcript",
        "tags": [
          "Videos"
        ],
        "summary": "Get a video’s transcript",
        "description": "The transcript TikTok publishes for a video, by id or URL, as one block of text plus time-coded segments. Coverage first, because it decides whether this route is worth building on: 68% of 185 videos measured across 14 languages had one. TikTok transcribes spoken audio for most videos but not all, and never for a video with no speech. A photo carousel is 0% by nature — there is no audio to transcribe. The other two thirds of the value is that the translations are free: a video posted in another language usually carries an English track alongside its original, both already timed. A real one, video 7643251685871062280, returns originalLanguage es-ES with an es-ES track of 58 segments (\"estas son las 7 noticias de ciencia más impactantes del día\") and an en-US track of the same 58 segments (\"here are the 7 most shocking science news of the day\") — one request, one credit, two languages. A video with no transcript is a normal 200, not an error: for video 7686148096895569165 it is `\"transcript\": {\"available\": false, \"originalLanguage\": null, \"durationSeconds\": 58, \"tracks\": []}`, with notes carrying \"No transcript is published for this video. TikTok transcribes spoken audio for most videos but not all, and never for a video with no speech.\" The duration is still filled in, and `pagesFetched` is still 1. Do not retry it. Track languages are BCP-47 in the conventional casing (language lowercase, script Titlecase, region UPPERCASE), so TikTok’s eng-US arrives as en-US and zho-Hans-CN as zh-Hans-CN. Compare them case-insensitively anyway, because upstreams are inconsistent about case. Billing: 1 credit.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "video": {
                    "type": "string",
                    "description": "A video URL or a numeric video id. Shortened share links are rejected — resolve them first."
                  },
                  "format": {
                    "type": "string",
                    "description": "json or markdown. Markdown returns a rendered summary instead of the structured record.",
                    "example": "json"
                  },
                  "timeoutMs": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Time limit for each individual page read, in milliseconds (max 60000). It bounds one page, not the whole request."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "video"
                ]
              },
              "examples": {
                "default": {
                  "value": {
                    "video": "7685115221458930957"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "transport": {
                      "type": "string",
                      "description": "The upstream source. Always \"tiktok\"."
                    },
                    "notes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Caveats you can act on — a private account, comments turned off, a list that ran out early, paging that has gone as deep as it goes. Empty when there are none. An empty result always comes with a note explaining it, never as an unexplained empty array."
                    },
                    "pagesFetched": {
                      "type": "number",
                      "description": "Pages of results read, which is what the request costs: one credit per page. resolve-url reports 0 and is not billed."
                    },
                    "videoId": {
                      "type": "string",
                      "description": "The video the transcript belongs to."
                    },
                    "videoUrl": {
                      "type": "string",
                      "description": "Canonical link to the video."
                    },
                    "transcript": {
                      "type": "object",
                      "properties": {
                        "available": {
                          "type": "boolean",
                          "description": "Whether a transcript came back. False is a normal 200, not a failure, and notes says why."
                        },
                        "originalLanguage": {
                          "type": "string",
                          "description": "BCP-47 tag for the language spoken in the video, or null when there is no transcript. Conventionally cased: language lowercase, script Titlecase, region UPPERCASE."
                        },
                        "durationSeconds": {
                          "type": "number",
                          "description": "Length of the video in seconds. Filled in even when available is false."
                        },
                        "tracks": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true,
                            "properties": {
                              "language": {
                                "type": "string",
                                "description": "BCP-47 tag for this track, e.g. en-US or es-ES. Compare case-insensitively — upstream casing is not consistent."
                              },
                              "isOriginalLanguage": {
                                "type": "boolean",
                                "description": "True on the track in the language actually spoken in the video. The translation alongside it is false."
                              },
                              "isAutoGenerated": {
                                "type": "boolean",
                                "description": "True when TikTok generated the captions rather than the account supplying them."
                              },
                              "source": {
                                "type": "string",
                                "description": "How the text was obtained — \"captions\" for a published caption track."
                              },
                              "text": {
                                "type": "string",
                                "description": "The whole track, one caption line per newline."
                              },
                              "segments": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "additionalProperties": true
                                },
                                "description": "Time-coded lines, each with start, end and text, in seconds. null rather than absent when a source carries no timings."
                              }
                            }
                          },
                          "description": "One entry per track. Empty when no transcript is published. A non-English video usually carries a free English track alongside its original, so expect two."
                        }
                      },
                      "additionalProperties": false
                    }
                  },
                  "additionalProperties": false
                },
                "examples": {
                  "default": {
                    "value": {
                      "transport": "tiktok",
                      "notes": [],
                      "pagesFetched": 1,
                      "videoId": "7685115221458930957",
                      "videoUrl": "https://www.tiktok.com/@_/video/7685115221458930957",
                      "transcript": {
                        "available": true,
                        "originalLanguage": "en-US",
                        "durationSeconds": 82,
                        "tracks": [
                          {
                            "language": "en-US",
                            "isOriginalLanguage": true,
                            "isAutoGenerated": false,
                            "source": "captions",
                            "text": "NASA's future depends on the people,\nmissions, and the networks that connect them.\nThis week had all three. Here's what's new in your NASA minute.\n…",
                            "segments": [
                              {
                                "start": 0.3,
                                "end": 2.14,
                                "text": "NASA's future depends on the people,"
                              },
                              {
                                "start": 2.141,
                                "end": 4.22,
                                "text": "missions, and the networks that connect them."
                              },
                              {
                                "start": 4.221,
                                "end": 7.88,
                                "text": "This week had all three. Here's what's new in your NASA minute."
                              }
                            ]
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing Bearer token"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/search-videos": {
      "post": {
        "operationId": "search-videos",
        "tags": [
          "Search & hashtags"
        ],
        "summary": "Search TikTok videos",
        "description": "Videos matching a search term, in TikTok's own ranking for it. There is no sort order and no date filter, because TikTok offers a public caller neither — passing one is a 400 naming the field rather than a parameter that is accepted and quietly ignored, which would hand you the wrong data with no indication why. Up to 500 videos and 20 pages in one request; when a list runs out before the requested number, notes says so and what was collected comes back. Billing: 1 credit per page read.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "What to search TikTok for."
                  },
                  "limit": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Maximum videos returned, up to 500. Use -1 for the maximum.",
                    "example": "30"
                  },
                  "pages": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Pages of results to read in one request, up to 20. Use -1 for the maximum. Each page read is one billable page, and the response reports how many were read as pagesFetched.",
                    "example": "1"
                  },
                  "cursor": {
                    "type": "string",
                    "description": "Continue from a previous response’s nextCursor. Cursors belong to the route and the query that produced them — do not carry one between routes."
                  },
                  "format": {
                    "type": "string",
                    "description": "json or markdown. Markdown returns a rendered summary instead of the structured record.",
                    "example": "json"
                  },
                  "timeoutMs": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Time limit for each individual page read, in milliseconds (max 60000). It bounds one page, not the whole request."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "query"
                ]
              },
              "examples": {
                "default": {
                  "value": {
                    "query": "space launch",
                    "limit": 30
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "transport": {
                      "type": "string",
                      "description": "The upstream source. Always \"tiktok\"."
                    },
                    "notes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Caveats you can act on — a private account, comments turned off, a list that ran out early, paging that has gone as deep as it goes. Empty when there are none. An empty result always comes with a note explaining it, never as an unexplained empty array."
                    },
                    "pagesFetched": {
                      "type": "number",
                      "description": "Pages of results read, which is what the request costs: one credit per page. resolve-url reports 0 and is not billed."
                    },
                    "query": {
                      "type": "string",
                      "description": "The term that was searched."
                    },
                    "videos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true,
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Numeric video id, which video-detail and comments both accept."
                          },
                          "url": {
                            "type": "string",
                            "description": "Canonical link to the video. This is what you use to open or embed it."
                          },
                          "description": {
                            "type": "string",
                            "description": "The caption, or null when there is none."
                          },
                          "createdAt": {
                            "type": "string",
                            "description": "ISO 8601 timestamp of when it was posted, or null."
                          },
                          "durationSeconds": {
                            "type": "number",
                            "description": "Length in seconds, or null."
                          },
                          "isPhotoPost": {
                            "type": "boolean",
                            "description": "True for a photo carousel rather than a clip."
                          },
                          "coverUrl": {
                            "type": "string",
                            "description": "The cover frame. An image, so it re-fetches through GET /media normally."
                          },
                          "assets": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "additionalProperties": true,
                              "properties": {
                                "downloadUrl": {
                                  "type": "string",
                                  "description": "Pass it to GET /media to stream the bytes. It serves images — carousel frames and cover images. A clip's bytes cannot be fetched by anyone, so asking for one returns 404 (see the media endpoint)."
                                }
                              }
                            },
                            "description": "An ordered list, so a clip and a carousel are handled the same way: a clip yields one asset of kind \"video\", a carousel one of kind \"image\" per frame. Each carries url, width, height and downloadUrl."
                          },
                          "stats": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "playCount, diggCount, commentCount, shareCount, collectCount and repostCount. A null counter means TikTok did not publish that figure; a real zero comes back as 0."
                          },
                          "author": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "id, uniqueId, nickname, avatarUrl, signature, verified, privateAccount, profileUrl and secUid."
                          },
                          "music": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "id, title, authorName, durationSeconds, original, coverUrl, playUrl and url, or null."
                          },
                          "hashtags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Without the #, read from TikTok’s own annotations rather than parsed out of the caption."
                          },
                          "mentions": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Usernames without the @."
                          },
                          "isAd": {
                            "type": "boolean",
                            "description": "True when TikTok marks the post as promotional, or null when it says nothing."
                          },
                          "language": {
                            "type": "string",
                            "description": "The language TikTok assigned to the caption, or null."
                          }
                        }
                      },
                      "description": "The matching videos, in TikTok’s ranking."
                    },
                    "videoCount": {
                      "type": "number",
                      "description": "How many videos are in this response."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether more results exist beyond what was returned."
                    },
                    "nextCursor": {
                      "type": "string",
                      "description": "Opaque value to pass back as cursor to continue. null when there is nothing further."
                    }
                  },
                  "additionalProperties": false
                },
                "examples": {
                  "default": {
                    "value": {
                      "transport": "tiktok",
                      "notes": [],
                      "pagesFetched": 1,
                      "query": "space launch",
                      "videos": [
                        {
                          "id": "7686148096895569165",
                          "url": "https://www.tiktok.com/@nasa/video/7686148096895569165",
                          "description": "Turns out you can dance in space! 🪩🕺💫🛰️ Microgravity just unlocked some new moves.",
                          "createdAt": "2026-09-16T15:00:41.000Z",
                          "durationSeconds": 58,
                          "isPhotoPost": false,
                          "coverUrl": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/...",
                          "assets": [
                            {
                              "kind": "video",
                              "url": "https://v16-webapp-prime.us.tiktok.com/video/tos/useast5/...",
                              "width": 720,
                              "height": 1280,
                              "downloadUrl": "https://api.fetchlayer.dev/tiktok/media?url=..."
                            }
                          ],
                          "stats": {
                            "playCount": 27600,
                            "diggCount": 4367,
                            "commentCount": 173,
                            "shareCount": 104,
                            "collectCount": 273,
                            "repostCount": 0
                          },
                          "author": {
                            "id": "7664638705177150477",
                            "uniqueId": "nasa",
                            "nickname": "NASA",
                            "verified": true,
                            "privateAccount": false,
                            "profileUrl": "https://www.tiktok.com/@nasa"
                          },
                          "music": {
                            "id": "7686148224364694286",
                            "title": "original sound - NASA",
                            "authorName": "NASA",
                            "durationSeconds": 58,
                            "original": false,
                            "url": "https://www.tiktok.com/music/x-7686148224364694286"
                          },
                          "hashtags": [],
                          "mentions": [],
                          "isAd": false,
                          "language": "en"
                        }
                      ],
                      "videoCount": 30,
                      "hasMore": true,
                      "nextCursor": "30"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing Bearer token"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/hashtag-videos": {
      "post": {
        "operationId": "hashtag-videos",
        "tags": [
          "Search & hashtags"
        ],
        "summary": "List videos under a hashtag",
        "description": "Videos posted under a hashtag, plus the hashtag itself — its id, name, url, description, cover image, view count and video count. The view count is the hashtag’s own total across TikTok, which is how you size a trend before deciding how much of it to read. Page with pages and cursor. A hashtag with no public videos returns an explained empty result in notes. Billing: 1 credit per page read.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hashtag": {
                    "type": "string",
                    "description": "A hashtag name with or without the leading #, or a hashtag URL."
                  },
                  "limit": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Maximum videos returned, up to 500. Use -1 for the maximum.",
                    "example": "30"
                  },
                  "pages": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Pages of results to read in one request, up to 20. Use -1 for the maximum. Each page read is one billable page, and the response reports how many were read as pagesFetched.",
                    "example": "1"
                  },
                  "cursor": {
                    "type": "string",
                    "description": "Continue from a previous response’s nextCursor. Cursors belong to the route and the query that produced them — do not carry one between routes."
                  },
                  "format": {
                    "type": "string",
                    "description": "json or markdown. Markdown returns a rendered summary instead of the structured record.",
                    "example": "json"
                  },
                  "timeoutMs": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Time limit for each individual page read, in milliseconds (max 60000). It bounds one page, not the whole request."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "hashtag"
                ]
              },
              "examples": {
                "default": {
                  "value": {
                    "hashtag": "nasa",
                    "limit": 30
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "transport": {
                      "type": "string",
                      "description": "The upstream source. Always \"tiktok\"."
                    },
                    "notes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Caveats you can act on — a private account, comments turned off, a list that ran out early, paging that has gone as deep as it goes. Empty when there are none. An empty result always comes with a note explaining it, never as an unexplained empty array."
                    },
                    "pagesFetched": {
                      "type": "number",
                      "description": "Pages of results read, which is what the request costs: one credit per page. resolve-url reports 0 and is not billed."
                    },
                    "hashtag": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "TikTok’s numeric hashtag id."
                        },
                        "name": {
                          "type": "string",
                          "description": "The tag, without the #."
                        },
                        "url": {
                          "type": "string",
                          "description": "Canonical hashtag URL."
                        },
                        "description": {
                          "type": "string",
                          "description": "The tag’s description, or null."
                        },
                        "coverUrl": {
                          "type": "string",
                          "description": "Cover image, or null."
                        },
                        "viewCount": {
                          "type": "number",
                          "description": "Total views across TikTok for the tag, or null when it publishes none."
                        },
                        "videoCount": {
                          "type": "number",
                          "description": "Videos TikTok reports under the tag, or null."
                        }
                      },
                      "additionalProperties": false
                    },
                    "videos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true,
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Numeric video id, which video-detail and comments both accept."
                          },
                          "url": {
                            "type": "string",
                            "description": "Canonical link to the video. This is what you use to open or embed it."
                          },
                          "description": {
                            "type": "string",
                            "description": "The caption, or null when there is none."
                          },
                          "createdAt": {
                            "type": "string",
                            "description": "ISO 8601 timestamp of when it was posted, or null."
                          },
                          "durationSeconds": {
                            "type": "number",
                            "description": "Length in seconds, or null."
                          },
                          "isPhotoPost": {
                            "type": "boolean",
                            "description": "True for a photo carousel rather than a clip."
                          },
                          "coverUrl": {
                            "type": "string",
                            "description": "The cover frame. An image, so it re-fetches through GET /media normally."
                          },
                          "assets": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "additionalProperties": true,
                              "properties": {
                                "downloadUrl": {
                                  "type": "string",
                                  "description": "Pass it to GET /media to stream the bytes. It serves images — carousel frames and cover images. A clip's bytes cannot be fetched by anyone, so asking for one returns 404 (see the media endpoint)."
                                }
                              }
                            },
                            "description": "An ordered list, so a clip and a carousel are handled the same way: a clip yields one asset of kind \"video\", a carousel one of kind \"image\" per frame. Each carries url, width, height and downloadUrl."
                          },
                          "stats": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "playCount, diggCount, commentCount, shareCount, collectCount and repostCount. A null counter means TikTok did not publish that figure; a real zero comes back as 0."
                          },
                          "author": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "id, uniqueId, nickname, avatarUrl, signature, verified, privateAccount, profileUrl and secUid."
                          },
                          "music": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "id, title, authorName, durationSeconds, original, coverUrl, playUrl and url, or null."
                          },
                          "hashtags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Without the #, read from TikTok’s own annotations rather than parsed out of the caption."
                          },
                          "mentions": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Usernames without the @."
                          },
                          "isAd": {
                            "type": "boolean",
                            "description": "True when TikTok marks the post as promotional, or null when it says nothing."
                          },
                          "language": {
                            "type": "string",
                            "description": "The language TikTok assigned to the caption, or null."
                          }
                        }
                      },
                      "description": "The videos posted under it."
                    },
                    "videoCount": {
                      "type": "number",
                      "description": "How many videos are in this response."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether more results exist beyond what was returned."
                    },
                    "nextCursor": {
                      "type": "string",
                      "description": "Opaque value to pass back as cursor to continue. null when there is nothing further."
                    }
                  },
                  "additionalProperties": false
                },
                "examples": {
                  "default": {
                    "value": {
                      "transport": "tiktok",
                      "notes": [],
                      "pagesFetched": 1,
                      "hashtag": {
                        "id": "3301",
                        "name": "nasa",
                        "url": "https://www.tiktok.com/tag/nasa",
                        "description": null,
                        "coverUrl": null,
                        "viewCount": 56600000000,
                        "videoCount": 0
                      },
                      "videos": [
                        {
                          "id": "7686148096895569165",
                          "url": "https://www.tiktok.com/@nasa/video/7686148096895569165",
                          "description": "Turns out you can dance in space! 🪩🕺💫🛰️ Microgravity just unlocked some new moves.",
                          "createdAt": "2026-09-16T15:00:41.000Z",
                          "durationSeconds": 58,
                          "isPhotoPost": false,
                          "coverUrl": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/...",
                          "assets": [
                            {
                              "kind": "video",
                              "url": "https://v16-webapp-prime.us.tiktok.com/video/tos/useast5/...",
                              "width": 720,
                              "height": 1280,
                              "downloadUrl": "https://api.fetchlayer.dev/tiktok/media?url=..."
                            }
                          ],
                          "stats": {
                            "playCount": 27600,
                            "diggCount": 4367,
                            "commentCount": 173,
                            "shareCount": 104,
                            "collectCount": 273,
                            "repostCount": 0
                          },
                          "author": {
                            "id": "7664638705177150477",
                            "uniqueId": "nasa",
                            "nickname": "NASA",
                            "verified": true,
                            "privateAccount": false,
                            "profileUrl": "https://www.tiktok.com/@nasa"
                          },
                          "music": {
                            "id": "7686148224364694286",
                            "title": "original sound - NASA",
                            "authorName": "NASA",
                            "durationSeconds": 58,
                            "original": false,
                            "url": "https://www.tiktok.com/music/x-7686148224364694286"
                          },
                          "hashtags": [],
                          "mentions": [],
                          "isAd": false,
                          "language": "en"
                        }
                      ],
                      "videoCount": 30,
                      "hasMore": true,
                      "nextCursor": "30"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing Bearer token"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/resolve-url": {
      "post": {
        "operationId": "resolve-url",
        "tags": [
          "Utilities"
        ],
        "summary": "Parse a TikTok URL (free)",
        "description": "What a TikTok link points to, as structured input for the other routes: whether it is a video, an account, a hashtag, a sound or a search, plus the id, username, tag or query it encodes. Makes no request to TikTok, so it costs no credit and reports pagesFetched 0. A shortened share link (vm.tiktok.com, vt.tiktok.com) carries no information about its target, so it comes back as kind \"unknown\" with a note saying to open it and pass the full URL — the routes that take a video reject a short link with a 400 for the same reason. Billing: free.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "Any TikTok link: a video, an account, a hashtag, a sound or a search."
                  },
                  "format": {
                    "type": "string",
                    "description": "json or markdown. Markdown returns a rendered summary instead of the structured record.",
                    "example": "json"
                  },
                  "timeoutMs": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Time limit for each individual page read, in milliseconds (max 60000). It bounds one page, not the whole request."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "url"
                ]
              },
              "examples": {
                "default": {
                  "value": {
                    "url": "https://www.tiktok.com/@nasa/video/7686148096895569165"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "transport": {
                      "type": "string",
                      "description": "The upstream source. Always \"tiktok\"."
                    },
                    "notes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Caveats you can act on — a private account, comments turned off, a list that ran out early, paging that has gone as deep as it goes. Empty when there are none. An empty result always comes with a note explaining it, never as an unexplained empty array."
                    },
                    "pagesFetched": {
                      "type": "number",
                      "description": "Pages of results read, which is what the request costs: one credit per page. resolve-url reports 0 and is not billed."
                    },
                    "kind": {
                      "type": "string",
                      "description": "One of video, user, hashtag, music, search or unknown."
                    },
                    "canonicalUrl": {
                      "type": "string",
                      "description": "The link in its canonical form, or null."
                    },
                    "videoId": {
                      "type": "string",
                      "description": "The video id, when the link points at one."
                    },
                    "username": {
                      "type": "string",
                      "description": "The account handle, when the link carries one."
                    },
                    "hashtagName": {
                      "type": "string",
                      "description": "The tag, when the link points at a hashtag."
                    },
                    "musicId": {
                      "type": "string",
                      "description": "The sound id, when the link points at a sound."
                    },
                    "query": {
                      "type": "string",
                      "description": "The search term, when the link is a search."
                    }
                  },
                  "additionalProperties": false
                },
                "examples": {
                  "default": {
                    "value": {
                      "transport": "tiktok",
                      "notes": [],
                      "pagesFetched": 0,
                      "kind": "video",
                      "canonicalUrl": "https://www.tiktok.com/@nasa/video/7686148096895569165",
                      "videoId": "7686148096895569165",
                      "username": "nasa",
                      "hashtagName": null,
                      "musicId": null,
                      "query": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing Bearer token"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    },
    "/media": {
      "get": {
        "operationId": "media",
        "tags": [
          "Utilities"
        ],
        "summary": "Stream an image or cover",
        "description": "Re-fetches one asset and streams it back, with Range requests supported. TikTok’s own asset URLs are signed and expire within hours, so every asset in a response carries a downloadUrl pointing here instead. Only TikTok’s own CDN hosts can be fetched; anything else is a 400. This route serves images: photo-post frames, cover frames and avatars. A clip’s url is returned on the video, and that is what you use to open or embed it. Billing: 1 credit.",
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "The asset URL, taken from an asset’s downloadUrl or from a coverUrl or avatarUrl. Must be a TikTok CDN host.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing Bearer token"
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ]
      }
    }
  }
}