crawlora-tiktok-mobile 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5780bf9916c767a09b772e3a065a5fffbfe0c4f5f6327373dcd9fe1faf93f6d3
4
+ data.tar.gz: 8d1df4092b14f72a86fcc4238f8bd22ede07e4f2c24ecab1f78e2ef68d8f2fad
5
+ SHA512:
6
+ metadata.gz: 4ab30d22251d8a664ec30f2b265bc2638d3e1d8c22678d2539853c8164263a148d803ff632f91b8cf128a5973e6bb87ca67fa89d7b7ba10b73dab13939af075f
7
+ data.tar.gz: cfc332c1486593e1469bc41ef6bec748dc79fc313562c57981b8135bd0c2ab66d3ffe238873feb25f92a52ab1c7d4e876c8453527c8d3917cf52b77b499496ab
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial focused TikTok Mobile Ruby client for Crawlora's hosted API.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Crawlora
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # Crawlora TikTok Mobile API client for Ruby (`crawlora-tiktok-mobile`)
2
+
3
+ This Ruby gem calls Crawlora's hosted TikTok Mobile API at `https://api.crawlora.net/api/v1`. It exposes 63 endpoints covering mobile feed, search, video, creator, music, LIVE, and place endpoints; it does not call or scrape TikTok Mobile directly. Requests require your Crawlora API key and use your account's service plan.
4
+
5
+ ## Install
6
+
7
+ ```ruby
8
+ gem "crawlora-tiktok-mobile"
9
+ ```
10
+
11
+ Create an account at [crawlora.net](https://crawlora.net/signup?utm_source=rubygems&utm_medium=referral&utm_campaign=platform-clients&utm_content=tiktok-mobile-ruby-signup), open the [Crawlora console](https://crawlora.net/app?utm_source=rubygems&utm_medium=referral&utm_campaign=platform-clients&utm_content=tiktok-mobile-ruby-console) to get an API key, and set `CRAWLORA_API_KEY` before running the client:
12
+
13
+ ```ruby
14
+ require "json"
15
+ require "crawlora/tiktok-mobile"
16
+
17
+ client = Crawlora::TiktokMobile::Client.new
18
+ result = client.request("tiktok-mobile-search", JSON.parse("{\"keyword\": \"sample\"}"))
19
+ puts result
20
+ client.close
21
+ ```
22
+
23
+ Use an operation-specific method for normal calls. `request(operation_id, params = {}, response_type: :auto)` is available for every operation. `response_type: :text` returns raw response text. This gem includes 63 API operations.
24
+
25
+ ```ruby
26
+ client = Crawlora::TiktokMobile::Client.new(api_key: ENV.fetch("CRAWLORA_API_KEY"), timeout: 30)
27
+ # client.<operation_method>(<endpoint parameters>)
28
+ client.close
29
+ ```
30
+
31
+ Client options include `api_key`, `base_url`, and `timeout`. Ruby stdlib provides the HTTP and JSON support.
32
+
33
+ ## Supported TikTok Mobile API endpoints
34
+
35
+ Browse every route included in this gem. The [endpoint reference](https://github.com/Crawlora-org/crawlora-tiktok-mobile/blob/main/docs/usage.md) includes parameter and response details.
36
+
37
+ | Method | Endpoint | API operation | Description |
38
+ | --- | --- | --- | --- |
39
+ | `GET` | `/tiktok/mobile/anchor/list` | `tiktok-mobile-anchor-list` | Anchor catalogue used by the video composer |
40
+ | `GET` | `/tiktok/mobile/collection/detail` | `tiktok-mobile-collection-detail` | Get TikTok collection detail on the mobile API |
41
+ | `GET` | `/tiktok/mobile/collection/list` | `tiktok-mobile-collection-list` | List TikTok collections on the mobile API |
42
+ | `GET` | `/tiktok/mobile/collection/videos` | `tiktok-mobile-collection-videos` | Get videos in a TikTok collection on the mobile API |
43
+ | `GET` | `/tiktok/mobile/comment/replies` | `tiktok-mobile-comment-replies` | Retrieve replies to a TikTok comment |
44
+ | `GET` | `/tiktok/mobile/comments` | `tiktok-mobile-comments` | Retrieve comments for a TikTok video |
45
+ | `GET` | `/tiktok/mobile/feed` | `tiktok-mobile-feed` | Retrieve the TikTok mobile For You feed |
46
+ | `GET` | `/tiktok/mobile/feed/offline` | `tiktok-mobile-feed-offline` | Fetch the app's cached-pull offline feed (protobuf) decoded without a schema |
47
+ | `GET` | `/tiktok/mobile/feed/post` | `tiktok-mobile-feed-post` | Fetch the app's primary feed call (POST, protobuf) decoded without a schema |
48
+ | `GET` | `/tiktok/mobile/hashtag` | `tiktok-mobile-hashtag` | Retrieve a TikTok hashtag |
49
+ | `GET` | `/tiktok/mobile/hashtag/posts` | `tiktok-mobile-hashtag-posts` | Retrieve videos for a TikTok hashtag |
50
+ | `GET` | `/tiktok/mobile/hashtags` | `tiktok-mobile-hashtags` | Search TikTok hashtags |
51
+ | `GET` | `/tiktok/mobile/live/effects` | `tiktok-mobile-live-effects` | Retrieve TikTok LIVE effects |
52
+ | `GET` | `/tiktok/mobile/live/gifts` | `tiktok-mobile-live-gifts` | Retrieve TikTok LIVE gifts |
53
+ | `GET` | `/tiktok/mobile/live/hashtags` | `tiktok-mobile-live-hashtags` | LIVE category/game reference catalogue (hashtags, live-studio/voice/third-party tags, game tags) |
54
+ | `GET` | `/tiktok/mobile/live/room` | `tiktok-mobile-live-room` | LIVE room detail: status, viewers, owner, stream URLs, cover |
55
+ | `GET` | `/tiktok/mobile/live/room-by-user` | `tiktok-mobile-live-room-by-user` | LIVE room by user: the room a user is currently streaming |
56
+ | `GET` | `/tiktok/mobile/live/search` | `tiktok-mobile-live-search` | Search TikTok LIVE streams |
57
+ | `GET` | `/tiktok/mobile/live/tabs` | `tiktok-mobile-live-tabs` | Fetch the LIVE tab catalogue |
58
+ | `GET` | `/tiktok/mobile/music/beats` | `tiktok-mobile-music-beats` | Retrieve TikTok music beats |
59
+ | `GET` | `/tiktok/mobile/music/chart` | `tiktok-mobile-music-chart` | List a regional TikTok sound chart |
60
+ | `GET` | `/tiktok/mobile/music/choices` | `tiktok-mobile-music-choices` | Recommended sounds for video creation |
61
+ | `GET` | `/tiktok/mobile/music/collection-list` | `tiktok-mobile-music-collection-list` | Music collection list (paged browse) |
62
+ | `GET` | `/tiktok/mobile/music/collections` | `tiktok-mobile-music-collections` | Retrieve TikTok music collections |
63
+ | `GET` | `/tiktok/mobile/music/detail` | `tiktok-mobile-music-detail` | Retrieve one TikTok sound |
64
+ | `GET` | `/tiktok/mobile/music/pick` | `tiktok-mobile-music-pick` | Retrieve TikTok sound chart picks |
65
+ | `GET` | `/tiktok/mobile/music/posts` | `tiktok-mobile-music-posts` | Retrieve videos for a TikTok sound |
66
+ | `GET` | `/tiktok/mobile/music/related` | `tiktok-mobile-music-related` | Retrieve sounds related to a TikTok video |
67
+ | `GET` | `/tiktok/mobile/music/trending` | `tiktok-mobile-music-trending` | Retrieve TikTok trending sounds |
68
+ | `GET` | `/tiktok/mobile/mv/videos` | `tiktok-mobile-mv-videos` | MV channel feed: the app's music-video tab, paged |
69
+ | `GET` | `/tiktok/mobile/place/detail` | `tiktok-mobile-place-detail` | Place (POI) detail from the mobile app's Places surface |
70
+ | `GET` | `/tiktok/mobile/place/info` | `tiktok-mobile-place-info` | Compact place (POI) record: address, coordinates, rating, hours, counters |
71
+ | `GET` | `/tiktok/mobile/place/media` | `tiktok-mobile-place-media` | List a TikTok place's mixed media |
72
+ | `GET` | `/tiktok/mobile/place/photos` | `tiktok-mobile-place-photos` | Place photo album with per-photo attribution |
73
+ | `GET` | `/tiktok/mobile/place/reviews` | `tiktok-mobile-place-reviews` | List a TikTok place's reviews |
74
+ | `GET` | `/tiktok/mobile/place/videos` | `tiktok-mobile-place-videos` | Videos posted at one place (paged) |
75
+ | `GET` | `/tiktok/mobile/playlist/detail` | `tiktok-mobile-playlist-detail` | Retrieve one TikTok playlist |
76
+ | `GET` | `/tiktok/mobile/playlist/list` | `tiktok-mobile-playlist-list` | List a creator's TikTok playlists |
77
+ | `GET` | `/tiktok/mobile/playlist/videos` | `tiktok-mobile-playlist-videos` | List a playlist's videos |
78
+ | `GET` | `/tiktok/mobile/search` | `tiktok-mobile-search` | Search TikTok videos on the mobile API |
79
+ | `GET` | `/tiktok/mobile/search/effects` | `tiktok-mobile-search-effects` | Search TikTok effects/stickers by keyword |
80
+ | `GET` | `/tiktok/mobile/search/hashtags` | `tiktok-mobile-search-hashtags` | Hashtag suggestions for a keyword (hashtag typeahead) |
81
+ | `GET` | `/tiktok/mobile/search/music` | `tiktok-mobile-search-music` | Search TikTok sounds on the mobile API |
82
+ | `GET` | `/tiktok/mobile/search/photos` | `tiktok-mobile-search-photos` | Search the mobile app's photo-post tab |
83
+ | `GET` | `/tiktok/mobile/search/place` | `tiktok-mobile-search-place` | Search TikTok places |
84
+ | `GET` | `/tiktok/mobile/search/pre-info` | `tiktok-mobile-search-pre-info` | Pre-search info token for a search session |
85
+ | `GET` | `/tiktok/mobile/search/stream` | `tiktok-mobile-search-stream` | Run the app's primary (streaming) general search |
86
+ | `GET` | `/tiktok/mobile/search/sug` | `tiktok-mobile-search-sug` | Retrieve TikTok search suggestions |
87
+ | `GET` | `/tiktok/mobile/search/tabs` | `tiktok-mobile-search-tabs` | Search tabs available for a keyword |
88
+ | `GET` | `/tiktok/mobile/search/user` | `tiktok-mobile-search-user` | Search TikTok users on the mobile API |
89
+ | `GET` | `/tiktok/mobile/search/videos` | `tiktok-mobile-search-videos` | Search TikTok videos on the mobile API (video tab) |
90
+ | `GET` | `/tiktok/mobile/translation/description` | `tiktok-mobile-translation-description` | Translate one TikTok video caption |
91
+ | `GET` | `/tiktok/mobile/trending/categories` | `tiktok-mobile-trending-categories` | Retrieve TikTok trending categories |
92
+ | `GET` | `/tiktok/mobile/trending/effects` | `tiktok-mobile-trending-effects` | Retrieve TikTok trending effects |
93
+ | `GET` | `/tiktok/mobile/user/followers` | `tiktok-mobile-user-followers` | Retrieve the followers of a TikTok creator |
94
+ | `GET` | `/tiktok/mobile/user/following` | `tiktok-mobile-user-following` | Retrieve the accounts a TikTok creator follows |
95
+ | `GET` | `/tiktok/mobile/user/original-effects` | `tiktok-mobile-user-original-effects` | List a creator's original TikTok effects |
96
+ | `GET` | `/tiktok/mobile/user/original-music` | `tiktok-mobile-user-original-music` | List a creator's original TikTok sounds |
97
+ | `GET` | `/tiktok/mobile/user/posts` | `tiktok-mobile-user-posts` | Retrieve the videos posted by a TikTok creator |
98
+ | `GET` | `/tiktok/mobile/user/profile` | `tiktok-mobile-user-profile` | Retrieve a TikTok creator profile |
99
+ | `GET` | `/tiktok/mobile/user/recommendations` | `tiktok-mobile-user-recommendations` | Recommended creators on the mobile app's Following page |
100
+ | `GET` | `/tiktok/mobile/video/detail` | `tiktok-mobile-video-detail` | Retrieve one TikTok video |
101
+ | `GET` | `/tiktok/mobile/video/subtitles` | `tiktok-mobile-video-subtitles` | Video captions translated into a target language |
102
+
103
+ See [Crawlora](https://crawlora.net/?utm_source=rubygems&utm_medium=referral&utm_campaign=platform-clients&utm_content=tiktok-mobile-ruby-homepage), the [API documentation](https://crawlora.net/docs?utm_source=rubygems&utm_medium=referral&utm_campaign=platform-clients&utm_content=tiktok-mobile-ruby-api-docs), and [the package repository](https://github.com/Crawlora-org/crawlora-tiktok-mobile) for account setup, the API operation reference, and release history.
@@ -0,0 +1,448 @@
1
+ require "json"
2
+ require "net/http"
3
+ require "uri"
4
+
5
+ module Crawlora
6
+ module TiktokMobile
7
+ module Errors
8
+ class Error < StandardError
9
+ attr_reader :status, :operation_id, :body
10
+
11
+ def initialize(message, status: nil, operation_id: nil, body: nil)
12
+ super(message)
13
+ @status, @operation_id, @body = status, operation_id, body
14
+ end
15
+ end
16
+ class ClientError < Error; end
17
+ class ServerError < Error; end
18
+ class NetworkError < Error; end
19
+ end
20
+
21
+ OPERATIONS = JSON.parse(<<~'JSON').freeze
22
+ {"tiktok-mobile-anchor-list": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-anchor-list", "method": "GET", "params": [], "path": "/tiktok/mobile/anchor/list", "pathParams": [], "produces": ["application/json"], "queryParams": [], "security": ["ApiKeyAuth"]}, "tiktok-mobile-collection-detail": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-collection-detail", "method": "GET", "params": [{"description": "Collection id (`item_archive_id` from the collection list)", "in": "query", "name": "collection_id", "required": true, "type": "string", "x-example": "7394627756635573022"}], "path": "/tiktok/mobile/collection/detail", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "collection_id", "required": true, "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-collection-list": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-collection-list", "method": "GET", "params": [{"description": "Creator's numeric user id", "in": "query", "name": "user_id", "required": true, "type": "string", "x-example": "107955"}, {"default": 0, "description": "Pagination cursor (server token)", "in": "query", "name": "cursor", "type": "integer"}, {"default": 10, "description": "Collections per page (max 30)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/collection/list", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "user_id", "required": true, "type": "string"}, {"in": "query", "name": "cursor", "type": "integer"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-collection-videos": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-collection-videos", "method": "GET", "params": [{"description": "Collection id (`item_archive_id` from the collection list)", "in": "query", "name": "collection_id", "required": true, "type": "string", "x-example": "7394627756635573022"}, {"default": 0, "description": "Paging cursor; use the previous page's max_cursor", "in": "query", "name": "cursor", "type": "integer"}, {"default": 10, "description": "Videos per page (max 30; the server serves about 30)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/collection/videos", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "collection_id", "required": true, "type": "string"}, {"in": "query", "name": "cursor", "type": "integer"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-comment-replies": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-comment-replies", "method": "GET", "params": [{"description": "Video id the comment belongs to", "in": "query", "name": "aweme_id", "required": true, "type": "string", "x-example": "7680168962218511636"}, {"description": "Comment id to list replies for", "in": "query", "name": "comment_id", "required": true, "type": "string", "x-example": "7680523330713240340"}, {"default": 0, "description": "Pagination cursor", "in": "query", "name": "cursor", "type": "integer"}, {"default": 20, "description": "Replies per page (max 50)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/comment/replies", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "aweme_id", "required": true, "type": "string"}, {"in": "query", "name": "comment_id", "required": true, "type": "string"}, {"in": "query", "name": "cursor", "type": "integer"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-comments": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-comments", "method": "GET", "params": [{"description": "Video id to list comments for", "in": "query", "name": "aweme_id", "required": true, "type": "string", "x-example": "7681236055831416084"}, {"default": 0, "description": "Pagination cursor", "in": "query", "name": "cursor", "type": "integer"}, {"default": 20, "description": "Comments per page (max 50)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/comments", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "aweme_id", "required": true, "type": "string"}, {"in": "query", "name": "cursor", "type": "integer"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-feed": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-feed", "method": "GET", "params": [{"default": 0, "description": "Videos to request (0 = app default, max 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Pagination cursor", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/feed", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-feed-offline": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-feed-offline", "method": "GET", "params": [{"description": "Pull type forwarded to the app route (measured 0 and 11; default 11)", "in": "query", "name": "pull_type", "type": "integer", "x-example": 11}, {"description": "Page size, bounded at 50 (upstream default when omitted)", "in": "query", "name": "count", "type": "integer", "x-example": 10}], "path": "/tiktok/mobile/feed/offline", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "pull_type", "type": "integer"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-feed-post": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-feed-post", "method": "GET", "params": [], "path": "/tiktok/mobile/feed/post", "pathParams": [], "produces": ["application/json"], "queryParams": [], "security": ["ApiKeyAuth"]}, "tiktok-mobile-hashtag": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-hashtag", "method": "GET", "params": [{"description": "Hashtag name without the leading #", "in": "query", "name": "name", "required": true, "type": "string", "x-example": "readingvlog"}], "path": "/tiktok/mobile/hashtag", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "name", "required": true, "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-hashtag-posts": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-hashtag-posts", "method": "GET", "params": [{"description": "Hashtag name without the leading #", "in": "query", "name": "name", "required": true, "type": "string", "x-example": "readingvlog"}, {"default": "fresh", "description": "Listing: fresh (newest) or popular (hot)", "enum": ["fresh", "popular"], "in": "query", "name": "sort", "type": "string"}, {"default": 20, "description": "Videos per page (max 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Pagination cursor", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/hashtag/posts", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "name", "required": true, "type": "string"}, {"enum": ["fresh", "popular"], "in": "query", "name": "sort", "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-hashtags": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-hashtags", "method": "GET", "params": [{"description": "Keyword to search hashtags for", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "reading"}, {"default": 20, "description": "Results per page (max 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Pagination cursor", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/hashtags", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-live-effects": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-live-effects", "method": "GET", "params": [], "path": "/tiktok/mobile/live/effects", "pathParams": [], "produces": ["application/json"], "queryParams": [], "security": ["ApiKeyAuth"]}, "tiktok-mobile-live-gifts": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-live-gifts", "method": "GET", "params": [], "path": "/tiktok/mobile/live/gifts", "pathParams": [], "produces": ["application/json"], "queryParams": [], "security": ["ApiKeyAuth"]}, "tiktok-mobile-live-hashtags": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-live-hashtags", "method": "GET", "params": [], "path": "/tiktok/mobile/live/hashtags", "pathParams": [], "produces": ["application/json"], "queryParams": [], "security": ["ApiKeyAuth"]}, "tiktok-mobile-live-room": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-live-room", "method": "GET", "params": [{"description": "Numeric LIVE room id from /api/v1/tiktok/mobile/live/search (data[].lives.author.room_id)", "in": "query", "name": "room_id", "required": true, "type": "string", "x-example": "7699174209096059678"}], "path": "/tiktok/mobile/live/room", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "room_id", "required": true, "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-live-room-by-user": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-live-room-by-user", "method": "GET", "params": [{"description": "Numeric TikTok user id (e.g. the owner id from a profile, video author or live search row)", "in": "query", "name": "user_id", "required": true, "type": "string", "x-example": "6707748031095751686"}], "path": "/tiktok/mobile/live/room-by-user", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "user_id", "required": true, "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-live-search": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-live-search", "method": "GET", "params": [{"description": "Search keyword", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "lofi"}, {"default": 0, "description": "Results to request (0 = app default, max 50)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/live/search", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-live-tabs": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-live-tabs", "method": "GET", "params": [], "path": "/tiktok/mobile/live/tabs", "pathParams": [], "produces": ["application/json"], "queryParams": [], "security": ["ApiKeyAuth"]}, "tiktok-mobile-music-beats": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-music-beats", "method": "GET", "params": [{"default": 0, "description": "Accepted for compatibility; not measured to change the page (max 50)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/music/beats", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-music-chart": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-music-chart", "method": "GET", "params": [{"description": "Region of the chart: AE, BR, CA, DE, EG, FR, JP, KR, LB, MX, NG, SA, TR, US or ZA; omit for the default chart (same as US)", "enum": ["AE", "BR", "CA", "DE", "EG", "FR", "JP", "KR", "LB", "MX", "NG", "SA", "TR", "US", "ZA"], "in": "query", "name": "chart_region", "type": "string", "x-example": "US"}, {"default": 0, "description": "Paging token from the previous response", "in": "query", "name": "cursor", "type": "integer"}, {"default": 10, "description": "Rows per page (max 50)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/music/chart", "pathParams": [], "produces": ["application/json"], "queryParams": [{"enum": ["AE", "BR", "CA", "DE", "EG", "FR", "JP", "KR", "LB", "MX", "NG", "SA", "TR", "US", "ZA"], "in": "query", "name": "chart_region", "type": "string"}, {"in": "query", "name": "cursor", "type": "integer"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-music-choices": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-music-choices", "method": "GET", "params": [], "path": "/tiktok/mobile/music/choices", "pathParams": [], "produces": ["application/json"], "queryParams": [], "security": ["ApiKeyAuth"]}, "tiktok-mobile-music-collection-list": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-music-collection-list", "method": "GET", "params": [{"description": "Page size: honoured (1-50 measured, capped by the collection set)", "in": "query", "name": "count", "type": "integer", "x-example": 5}, {"description": "Next-page token from the previous response (0 = first page)", "in": "query", "name": "cursor", "type": "integer", "x-example": 5}], "path": "/tiktok/mobile/music/collection-list", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-music-collections": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-music-collections", "method": "GET", "params": [{"default": 0, "description": "Collections to request (0 = app default, max 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Accepted for shape compatibility only; not a verified page token", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/music/collections", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-music-detail": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-music-detail", "method": "GET", "params": [{"description": "Sound id", "in": "query", "name": "music_id", "required": true, "type": "string", "x-example": "7329791746055080709"}], "path": "/tiktok/mobile/music/detail", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "music_id", "required": true, "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-music-pick": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-music-pick", "method": "GET", "params": [{"default": 20, "description": "Sounds per page (max 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Pagination cursor", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/music/pick", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-music-posts": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-music-posts", "method": "GET", "params": [{"description": "Sound (music) id", "in": "query", "name": "music_id", "required": true, "type": "string", "x-example": "6799585653702019073"}, {"default": "fresh", "description": "Listing: fresh (newest, default) or popular (app canonical music.posts list; requires the device-backed signer)", "enum": ["fresh", "popular"], "in": "query", "name": "sort", "type": "string"}, {"default": 0, "description": "Accepted for compatibility; the server ignores it (max 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Pagination cursor", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/music/posts", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "music_id", "required": true, "type": "string"}, {"enum": ["fresh", "popular"], "in": "query", "name": "sort", "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-music-related": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-music-related", "method": "GET", "params": [{"description": "Video id to find related sounds for", "in": "query", "name": "aweme_id", "required": true, "type": "string", "x-example": "7681236055831416084"}, {"default": 20, "description": "Sounds per page (max 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Pagination cursor", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/music/related", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "aweme_id", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-music-trending": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-music-trending", "method": "GET", "params": [{"default": 20, "description": "Sounds per page (max 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Pagination cursor", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/music/trending", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-mv-videos": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-mv-videos", "method": "GET", "params": [{"description": "MV channel index (small integer; measured 0-10 populated, 100 empty)", "in": "query", "name": "mv_id", "required": true, "type": "string", "x-example": "1"}, {"description": "Next-page token from the previous response (0 = first page)", "in": "query", "name": "cursor", "type": "integer", "x-example": 0}], "path": "/tiktok/mobile/mv/videos", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "mv_id", "required": true, "type": "string"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-place-detail": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-place-detail", "method": "GET", "params": [{"description": "Place id from the place search endpoint", "in": "query", "name": "poi_id", "required": true, "type": "string", "x-example": "42203861086875992"}, {"description": "Review preview size forwarded upstream (default 20, bounded at 50)", "in": "query", "name": "review_count", "type": "integer", "x-example": 20}, {"description": "Review cursor forwarded upstream (default 0)", "in": "query", "name": "review_cursor", "type": "integer", "x-example": 0}, {"description": "Posts page size forwarded upstream (default 9, bounded at 50)", "in": "query", "name": "merged_count", "type": "integer", "x-example": 9}, {"description": "Top-videos cursor forwarded upstream (default 0)", "in": "query", "name": "video_cursor", "type": "integer", "x-example": 0}, {"description": "Forwarded analytics origin (default search_places)", "in": "query", "name": "enter_from", "type": "string", "x-example": "search_places"}], "path": "/tiktok/mobile/place/detail", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "poi_id", "required": true, "type": "string"}, {"in": "query", "name": "review_count", "type": "integer"}, {"in": "query", "name": "review_cursor", "type": "integer"}, {"in": "query", "name": "merged_count", "type": "integer"}, {"in": "query", "name": "video_cursor", "type": "integer"}, {"in": "query", "name": "enter_from", "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-place-info": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-place-info", "method": "GET", "params": [{"description": "Place id from the place search endpoint", "in": "query", "name": "poi_id", "required": true, "type": "string", "x-example": "42203861086875992"}], "path": "/tiktok/mobile/place/info", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "poi_id", "required": true, "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-place-media": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-place-media", "method": "GET", "params": [{"description": "Place id from the place search/detail endpoints", "in": "query", "name": "poi_id", "required": true, "type": "string", "x-example": "42203861086875992"}, {"description": "Opaque next_cursor token from the previous response", "in": "query", "name": "cursor", "type": "string"}, {"default": 6, "description": "Rows per page hint (max 30)", "in": "query", "name": "size", "type": "integer"}, {"description": "Paging data version carried with cursor (defaults to v1)", "in": "query", "name": "data_version", "type": "string", "x-example": "v1"}], "path": "/tiktok/mobile/place/media", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "poi_id", "required": true, "type": "string"}, {"in": "query", "name": "cursor", "type": "string"}, {"in": "query", "name": "size", "type": "integer"}, {"in": "query", "name": "data_version", "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-place-photos": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-place-photos", "method": "GET", "params": [{"description": "Place id from the place search endpoint", "in": "query", "name": "poi_id", "required": true, "type": "string", "x-example": "42203861086875992"}], "path": "/tiktok/mobile/place/photos", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "poi_id", "required": true, "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-place-reviews": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-place-reviews", "method": "GET", "params": [{"description": "Place id from the place search/detail endpoints", "in": "query", "name": "poi_id", "required": true, "type": "string", "x-example": "42203861086875992"}, {"default": 0, "description": "Paging token from the previous response", "in": "query", "name": "cursor", "type": "integer"}, {"default": 20, "description": "Reviews per page (max 30)", "in": "query", "name": "count", "type": "integer"}, {"default": 1, "description": "Sort order: 1 = default, 2 = recent", "enum": [1, 2], "in": "query", "name": "sort_type", "type": "integer"}], "path": "/tiktok/mobile/place/reviews", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "poi_id", "required": true, "type": "string"}, {"in": "query", "name": "cursor", "type": "integer"}, {"in": "query", "name": "count", "type": "integer"}, {"enum": ["1", "2"], "in": "query", "name": "sort_type", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-place-videos": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-place-videos", "method": "GET", "params": [{"description": "Place id from the place search endpoint", "in": "query", "name": "poi_id", "required": true, "type": "string", "x-example": "42203861086875992"}, {"description": "Page-size hint: 1-50 measured, server caps larger pages (default is the app default)", "in": "query", "name": "count", "type": "integer", "x-example": 10}, {"description": "Next-page token from the previous response (0 = first page)", "in": "query", "name": "cursor", "type": "integer", "x-example": 0}], "path": "/tiktok/mobile/place/videos", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "poi_id", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-playlist-detail": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-playlist-detail", "method": "GET", "params": [{"description": "Playlist id (web playlist id namespace)", "in": "query", "name": "mix_id", "required": true, "type": "string", "x-example": "7681171537575824159"}, {"description": "Playlist owner's numeric user id (one of user_id / sec_user_id is required)", "in": "query", "name": "user_id", "type": "string", "x-example": "107955"}, {"description": "Playlist owner's sec_user_id (one of user_id / sec_user_id is required)", "in": "query", "name": "sec_user_id", "type": "string"}], "path": "/tiktok/mobile/playlist/detail", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "mix_id", "required": true, "type": "string"}, {"in": "query", "name": "user_id", "type": "string"}, {"in": "query", "name": "sec_user_id", "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-playlist-list": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-playlist-list", "method": "GET", "params": [{"description": "Creator's numeric user id (one of user_id / sec_user_id is required)", "in": "query", "name": "user_id", "type": "string", "x-example": "107955"}, {"description": "Creator's sec_user_id (one of user_id / sec_user_id is required)", "in": "query", "name": "sec_user_id", "type": "string"}, {"default": 0, "description": "Paging token from the previous response", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/playlist/list", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "user_id", "type": "string"}, {"in": "query", "name": "sec_user_id", "type": "string"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-playlist-videos": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-playlist-videos", "method": "GET", "params": [{"description": "Playlist id (web playlist id namespace)", "in": "query", "name": "mix_id", "required": true, "type": "string", "x-example": "7681171537575824159"}, {"default": 0, "description": "Paging token from the previous response", "in": "query", "name": "cursor", "type": "integer"}, {"default": 10, "description": "Videos per page (max 30)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/playlist/videos", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "mix_id", "required": true, "type": "string"}, {"in": "query", "name": "cursor", "type": "integer"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-search": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-search", "method": "GET", "params": [{"description": "Search keyword", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "lofi"}, {"default": 5, "description": "Results per page (max 20)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Result offset; pass search_id with a non-zero offset", "in": "query", "name": "offset", "type": "integer"}, {"description": "search_id returned by a previous page of the same search", "in": "query", "name": "search_id", "type": "string"}], "path": "/tiktok/mobile/search", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "offset", "type": "integer"}, {"in": "query", "name": "search_id", "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-search-effects": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-search-effects", "method": "GET", "params": [{"description": "Effect name or text, e.g. green screen", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "green screen"}, {"description": "Accepted for shape parity; the server was measured to ignore it", "in": "query", "name": "count", "type": "integer", "x-example": 20}, {"description": "Next-page token from the previous response (0 = first page)", "in": "query", "name": "cursor", "type": "integer", "x-example": 100}, {"description": "Search-session token from the previous response; required together with cursor", "in": "query", "name": "search_id", "type": "string", "x-example": "20261021173221C8185FF1D4A1062AD97C"}], "path": "/tiktok/mobile/search/effects", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}, {"in": "query", "name": "search_id", "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-search-hashtags": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-search-hashtags", "method": "GET", "params": [{"description": "Text typed so far; the server also accepts prefixes (e.g. lofi -> lofihiphop)", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "lofi"}, {"description": "Accepted for shape parity; the server was measured to ignore it (always up to 10 rows)", "in": "query", "name": "count", "type": "integer", "x-example": 10}], "path": "/tiktok/mobile/search/hashtags", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-search-music": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-search-music", "method": "GET", "params": [{"description": "Search keyword", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "lofi"}, {"default": 20, "description": "Sounds per page (max 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Result offset", "in": "query", "name": "offset", "type": "integer"}], "path": "/tiktok/mobile/search/music", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "offset", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-search-photos": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-search-photos", "method": "GET", "params": [{"description": "Search keyword", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "lofi"}, {"description": "Rows per page, bounded at 50 (default 10)", "in": "query", "name": "count", "type": "integer", "x-example": 10}, {"description": "Row offset forwarded to the app route (first page only in guest mode)", "in": "query", "name": "offset", "type": "integer", "x-example": 0}, {"description": "Sort: 0 relevance, 1 like count, 3 date posted", "in": "query", "name": "sort_type", "type": "integer", "x-example": 0}, {"description": "Recency window in days: 0 all, 1, 7, 30, 90, 180", "in": "query", "name": "publish_time", "type": "integer", "x-example": 0}, {"description": "Forwarded search source (default tab_search)", "in": "query", "name": "search_source", "type": "string", "x-example": "tab_search"}], "path": "/tiktok/mobile/search/photos", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "offset", "type": "integer"}, {"in": "query", "name": "sort_type", "type": "integer"}, {"in": "query", "name": "publish_time", "type": "integer"}, {"in": "query", "name": "search_source", "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-search-place": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-search-place", "method": "GET", "params": [{"description": "Place name or category to search for", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "pizza"}, {"default": 10, "description": "Rows to return, 1-30 measured (a larger count yields an empty upstream page and is rejected)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/search/place", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-search-pre-info": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-search-pre-info", "method": "GET", "params": [{"description": "Seed keyword (optional; the app also requests this with an empty keyword)", "in": "query", "name": "keyword", "type": "string", "x-example": "lofi"}, {"default": 20, "description": "Suggestion count (max 50)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/search/pre-info", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "type": "string"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-search-stream": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-search-stream", "method": "GET", "params": [{"description": "Search keyword", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "lofi"}, {"default": 10, "description": "Requested page size (sanity bound: 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Row offset", "in": "query", "name": "offset", "type": "integer"}, {"description": "Search source hint forwarded to upstream (measured: video)", "in": "query", "name": "search_source", "type": "string", "x-example": "video"}, {"default": 0, "description": "Sort order: 0 relevance, 1 like count (stable ordering measured), 3 date posted", "enum": [0, 1, 3], "in": "query", "name": "sort_type", "type": "integer"}, {"default": 0, "description": "Recency window in days (0 = unlimited; 7 measured)", "in": "query", "name": "publish_time", "type": "integer"}, {"description": "Raw app filter blob as JSON; takes precedence over sort_type/publish_time", "in": "query", "name": "filter_selected", "type": "string", "x-example": "{\"sort_type\":\"1\",\"publish_time\":\"7\"}"}], "path": "/tiktok/mobile/search/stream", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "offset", "type": "integer"}, {"in": "query", "name": "search_source", "type": "string"}, {"enum": ["0", "1", "3"], "in": "query", "name": "sort_type", "type": "integer"}, {"in": "query", "name": "publish_time", "type": "integer"}, {"in": "query", "name": "filter_selected", "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-search-sug": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-search-sug", "method": "GET", "params": [{"description": "Seed keyword to expand", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "lofi"}, {"default": 10, "description": "Suggestions to return (max 50)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/search/sug", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-search-tabs": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-search-tabs", "method": "GET", "params": [{"description": "Search keyword", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "lofi"}], "path": "/tiktok/mobile/search/tabs", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-search-user": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-search-user", "method": "GET", "params": [{"description": "Search keyword (a username or display name)", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "mrbeast"}, {"default": 20, "description": "Users per page (max 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Pagination cursor", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/search/user", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-search-videos": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-search-videos", "method": "GET", "params": [{"description": "Search keyword", "in": "query", "name": "keyword", "required": true, "type": "string", "x-example": "lofi"}, {"default": 10, "description": "Rows per page (sanity bound: 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Row offset; pass search_id with a non-zero offset", "in": "query", "name": "offset", "type": "integer"}, {"default": 0, "description": "Sort order: 0 relevance, 1 like count, 3 date posted", "enum": [0, 1, 3], "in": "query", "name": "sort_type", "type": "integer"}, {"default": 0, "description": "Date-posted window in days (0 to 365; the app uses 0, 1, 7, 30, 90, 180)", "in": "query", "name": "publish_time", "type": "integer"}, {"default": "tab_search", "description": "Search source forwarded as the app sends it", "in": "query", "name": "search_source", "type": "string"}, {"description": "search_id returned by a previous page of the same search", "in": "query", "name": "search_id", "type": "string"}], "path": "/tiktok/mobile/search/videos", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "keyword", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "offset", "type": "integer"}, {"enum": ["0", "1", "3"], "in": "query", "name": "sort_type", "type": "integer"}, {"in": "query", "name": "publish_time", "type": "integer"}, {"in": "query", "name": "search_source", "type": "string"}, {"in": "query", "name": "search_id", "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-translation-description": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-translation-description", "method": "GET", "params": [{"description": "Video id to translate", "in": "query", "name": "item_id", "required": true, "type": "string", "x-example": "7650052642089225493"}, {"description": "Target language code (default en)", "in": "query", "name": "target_lang", "type": "string", "x-example": "en"}], "path": "/tiktok/mobile/translation/description", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "item_id", "required": true, "type": "string"}, {"in": "query", "name": "target_lang", "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-trending-categories": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-trending-categories", "method": "GET", "params": [{"default": 20, "description": "Categories per page (max 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Pagination cursor", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/trending/categories", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-trending-effects": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-trending-effects", "method": "GET", "params": [{"default": 20, "description": "Videos per page (max 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Pagination cursor", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/trending/effects", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-user-followers": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-user-followers", "method": "GET", "params": [{"description": "Creator user id", "in": "query", "name": "user_id", "required": true, "type": "string", "x-example": "7141011492116005914"}, {"description": "Followers per page (sanity bound: 50; omitted = app default, not measured)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Page token: the max_time returned by the previous page (omitted at 0 = first page)", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/user/followers", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "user_id", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-user-following": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-user-following", "method": "GET", "params": [{"description": "Creator's numeric user id", "in": "query", "name": "user_id", "required": true, "type": "string", "x-example": "7343380121988285482"}, {"default": 20, "description": "Rows per page (sanity bound: 50)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Page token: max_time returned by the previous page (omitted at 0)", "in": "query", "name": "cursor", "type": "integer"}], "path": "/tiktok/mobile/user/following", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "user_id", "required": true, "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-user-original-effects": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-user-original-effects", "method": "GET", "params": [{"description": "Creator's numeric uid", "in": "query", "name": "user_id", "required": true, "type": "string", "x-example": "107955"}, {"default": 0, "description": "Paging token from the previous response", "in": "query", "name": "cursor", "type": "integer"}, {"default": 10, "description": "Effects per page (max 50)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/user/original-effects", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "user_id", "required": true, "type": "string"}, {"in": "query", "name": "cursor", "type": "integer"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-user-original-music": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-user-original-music", "method": "GET", "params": [{"description": "Creator's numeric uid", "in": "query", "name": "user_id", "required": true, "type": "string", "x-example": "107955"}, {"default": 0, "description": "Paging token from the previous response", "in": "query", "name": "cursor", "type": "integer"}, {"default": 10, "description": "Groups per page (max 50)", "in": "query", "name": "count", "type": "integer"}], "path": "/tiktok/mobile/user/original-music", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "user_id", "required": true, "type": "string"}, {"in": "query", "name": "cursor", "type": "integer"}, {"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-user-posts": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-user-posts", "method": "GET", "params": [{"description": "Creator user id; one of user_id or sec_user_id is required (user_id wins if both are sent)", "in": "query", "name": "user_id", "type": "string", "x-example": "7141011492116005914"}, {"description": "Creator sec_user_id; one of user_id or sec_user_id is required", "in": "query", "name": "sec_user_id", "type": "string"}, {"default": 9, "description": "Videos per page (sanity bound: 50; omitted = app default, measured 9)", "in": "query", "name": "count", "type": "integer"}, {"default": 0, "description": "Page token: the max_cursor returned by the previous page (omitted at 0 = first page)", "in": "query", "name": "cursor", "type": "integer"}, {"default": 0, "description": "Sort type (0 = the app default; always sent, non-zero values are forwarded as given)", "in": "query", "name": "sort_type", "type": "integer"}], "path": "/tiktok/mobile/user/posts", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "user_id", "type": "string"}, {"in": "query", "name": "sec_user_id", "type": "string"}, {"in": "query", "name": "count", "type": "integer"}, {"in": "query", "name": "cursor", "type": "integer"}, {"in": "query", "name": "sort_type", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-user-profile": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-user-profile", "method": "GET", "params": [{"description": "Creator user id", "in": "query", "name": "user_id", "type": "string", "x-example": "7141011492116005914"}, {"description": "Creator sec_user_id (alternate identifier; use instead of user_id)", "in": "query", "name": "sec_user_id", "type": "string", "x-example": "MS4wLjABAAAAexample"}], "path": "/tiktok/mobile/user/profile", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "user_id", "type": "string"}, {"in": "query", "name": "sec_user_id", "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-user-recommendations": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-user-recommendations", "method": "GET", "params": [{"description": "Requested page size; forwarded when set, but the gateway was measured to ignore it", "in": "query", "name": "count", "type": "integer", "x-example": 10}], "path": "/tiktok/mobile/user/recommendations", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "count", "type": "integer"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-video-detail": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-video-detail", "method": "GET", "params": [{"description": "Video id", "in": "query", "name": "aweme_id", "required": true, "type": "string", "x-example": "7329791719535283462"}], "path": "/tiktok/mobile/video/detail", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "aweme_id", "required": true, "type": "string"}], "security": ["ApiKeyAuth"]}, "tiktok-mobile-video-subtitles": {"bodyParam": null, "bodyRequired": false, "id": "tiktok-mobile-video-subtitles", "method": "GET", "params": [{"description": "Numeric id of the video (aweme id)", "in": "query", "name": "video_id", "required": true, "type": "string", "x-example": "7696141388324293918"}, {"description": "Target language code: ar, de, en, es, fr, hi, id, it, ja, ko, pt, ru, th, tr, vi, zh", "in": "query", "name": "language", "required": true, "type": "string", "x-example": "en"}], "path": "/tiktok/mobile/video/subtitles", "pathParams": [], "produces": ["application/json"], "queryParams": [{"in": "query", "name": "video_id", "required": true, "type": "string"}, {"in": "query", "name": "language", "required": true, "type": "string"}], "security": ["ApiKeyAuth"]}}
23
+ JSON
24
+ OPERATION_IDS = JSON.parse(<<~'JSON').freeze
25
+ ["tiktok-mobile-anchor-list", "tiktok-mobile-collection-detail", "tiktok-mobile-collection-list", "tiktok-mobile-collection-videos", "tiktok-mobile-comment-replies", "tiktok-mobile-comments", "tiktok-mobile-feed", "tiktok-mobile-feed-offline", "tiktok-mobile-feed-post", "tiktok-mobile-hashtag", "tiktok-mobile-hashtag-posts", "tiktok-mobile-hashtags", "tiktok-mobile-live-effects", "tiktok-mobile-live-gifts", "tiktok-mobile-live-hashtags", "tiktok-mobile-live-room", "tiktok-mobile-live-room-by-user", "tiktok-mobile-live-search", "tiktok-mobile-live-tabs", "tiktok-mobile-music-beats", "tiktok-mobile-music-chart", "tiktok-mobile-music-choices", "tiktok-mobile-music-collection-list", "tiktok-mobile-music-collections", "tiktok-mobile-music-detail", "tiktok-mobile-music-pick", "tiktok-mobile-music-posts", "tiktok-mobile-music-related", "tiktok-mobile-music-trending", "tiktok-mobile-mv-videos", "tiktok-mobile-place-detail", "tiktok-mobile-place-info", "tiktok-mobile-place-media", "tiktok-mobile-place-photos", "tiktok-mobile-place-reviews", "tiktok-mobile-place-videos", "tiktok-mobile-playlist-detail", "tiktok-mobile-playlist-list", "tiktok-mobile-playlist-videos", "tiktok-mobile-search", "tiktok-mobile-search-effects", "tiktok-mobile-search-hashtags", "tiktok-mobile-search-music", "tiktok-mobile-search-photos", "tiktok-mobile-search-place", "tiktok-mobile-search-pre-info", "tiktok-mobile-search-stream", "tiktok-mobile-search-sug", "tiktok-mobile-search-tabs", "tiktok-mobile-search-user", "tiktok-mobile-search-videos", "tiktok-mobile-translation-description", "tiktok-mobile-trending-categories", "tiktok-mobile-trending-effects", "tiktok-mobile-user-followers", "tiktok-mobile-user-following", "tiktok-mobile-user-original-effects", "tiktok-mobile-user-original-music", "tiktok-mobile-user-posts", "tiktok-mobile-user-profile", "tiktok-mobile-user-recommendations", "tiktok-mobile-video-detail", "tiktok-mobile-video-subtitles"]
26
+ JSON
27
+ OPERATION_COUNT = OPERATION_IDS.length
28
+
29
+ class Client
30
+ attr_reader :base_url
31
+
32
+ def initialize(api_key: ENV["CRAWLORA_API_KEY"], base_url: "https://api.crawlora.net/api/v1", timeout: 30, user_agent: "crawlora-tiktok-mobile-ruby/0.1.0", transport: nil)
33
+ @api_key = api_key
34
+ @base_url = base_url.to_s.sub(%r{/+$}, "")
35
+ @timeout = Float(timeout)
36
+ @user_agent = user_agent
37
+ @transport = transport
38
+ @closed = false
39
+ end
40
+
41
+ def request(operation_id, params = {}, response_type: :auto)
42
+ raise Errors::ClientError, "client is closed" if @closed
43
+ operation_id = operation_id.to_s
44
+ operation = OPERATIONS[operation_id]
45
+ raise Errors::ClientError.new("unknown operation: #{operation_id}", operation_id: operation_id) unless operation
46
+ raise Errors::ClientError.new("Crawlora API key is required", operation_id: operation_id) if @api_key.nil? || @api_key.to_s.empty?
47
+ normalized = params.each_with_object({}) { |(key, value), out| out[key.to_s] = value }
48
+ payload = request_body(operation, normalized)
49
+ url = build_url(operation, normalized)
50
+ uri = URI.parse(url)
51
+ request = operation["method"] == "POST" ? Net::HTTP::Post.new(uri) : Net::HTTP::Get.new(uri)
52
+ unless payload.nil?
53
+ request.body = payload
54
+ request["Content-Type"] = "application/json"
55
+ end
56
+ request["x-api-key"] = @api_key
57
+ request["User-Agent"] = @user_agent
58
+ request["Accept"] = operation["produces"].include?("text/plain") ? "application/json, text/plain" : "application/json"
59
+ begin
60
+ if @transport
61
+ response = if operation["method"] == "POST"
62
+ @transport.call(url, request.to_hash, @timeout, "POST", payload)
63
+ else
64
+ @transport.call(url, request.to_hash, @timeout)
65
+ end
66
+ status = Integer(response.fetch(:status) { response.fetch("status") })
67
+ body = response.fetch(:body) { response.fetch("body", "") }
68
+ headers = response.fetch(:headers) { response.fetch("headers", {}) }
69
+ content_type = headers["content-type"] || headers["Content-Type"]
70
+ else
71
+ http = Net::HTTP.new(uri.host, uri.port)
72
+ http.use_ssl = uri.scheme == "https"
73
+ http.open_timeout = @timeout
74
+ http.read_timeout = @timeout
75
+ response = http.start { |connection| connection.request(request) }
76
+ status = response.code.to_i
77
+ body = response.body
78
+ content_type = response["content-type"]
79
+ end
80
+ rescue Timeout::Error, SocketError, SystemCallError, IOError, EOFError, Net::HTTPBadResponse, Net::ProtocolError, OpenSSL::SSL::SSLError => error
81
+ raise Errors::NetworkError.new("Crawlora request failed: #{error.message}", operation_id: operation_id)
82
+ end
83
+ unless status >= 200 && status < 300
84
+ klass = status >= 500 ? Errors::ServerError : Errors::ClientError
85
+ raise klass.new("Crawlora returned HTTP #{status}", status: status, operation_id: operation_id, body: body)
86
+ end
87
+ parse_response(body, content_type, operation, normalized, response_type)
88
+ end
89
+
90
+ def close
91
+ @closed = true
92
+ end
93
+
94
+ def closed?
95
+ @closed
96
+ end
97
+
98
+ def with
99
+ return self unless block_given?
100
+ yield self
101
+ ensure
102
+ close if block_given?
103
+ end
104
+
105
+ def self.operation_count
106
+ OPERATION_COUNT
107
+ end
108
+
109
+ def self.operation_ids
110
+ OPERATION_IDS
111
+ end
112
+
113
+ def self.operations
114
+ OPERATIONS
115
+ end
116
+
117
+ define_method('anchor_list') do |**params|
118
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
119
+ request('tiktok-mobile-anchor-list', params, response_type: response_type)
120
+ end
121
+ define_method('collection_detail') do |**params|
122
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
123
+ request('tiktok-mobile-collection-detail', params, response_type: response_type)
124
+ end
125
+ define_method('collection_list') do |**params|
126
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
127
+ request('tiktok-mobile-collection-list', params, response_type: response_type)
128
+ end
129
+ define_method('collection_videos') do |**params|
130
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
131
+ request('tiktok-mobile-collection-videos', params, response_type: response_type)
132
+ end
133
+ define_method('comment_replies') do |**params|
134
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
135
+ request('tiktok-mobile-comment-replies', params, response_type: response_type)
136
+ end
137
+ define_method('comments') do |**params|
138
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
139
+ request('tiktok-mobile-comments', params, response_type: response_type)
140
+ end
141
+ define_method('feed') do |**params|
142
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
143
+ request('tiktok-mobile-feed', params, response_type: response_type)
144
+ end
145
+ define_method('feed_offline') do |**params|
146
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
147
+ request('tiktok-mobile-feed-offline', params, response_type: response_type)
148
+ end
149
+ define_method('feed_post') do |**params|
150
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
151
+ request('tiktok-mobile-feed-post', params, response_type: response_type)
152
+ end
153
+ define_method('hashtag') do |**params|
154
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
155
+ request('tiktok-mobile-hashtag', params, response_type: response_type)
156
+ end
157
+ define_method('hashtag_posts') do |**params|
158
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
159
+ request('tiktok-mobile-hashtag-posts', params, response_type: response_type)
160
+ end
161
+ define_method('hashtags') do |**params|
162
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
163
+ request('tiktok-mobile-hashtags', params, response_type: response_type)
164
+ end
165
+ define_method('live_effects') do |**params|
166
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
167
+ request('tiktok-mobile-live-effects', params, response_type: response_type)
168
+ end
169
+ define_method('live_gifts') do |**params|
170
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
171
+ request('tiktok-mobile-live-gifts', params, response_type: response_type)
172
+ end
173
+ define_method('live_hashtags') do |**params|
174
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
175
+ request('tiktok-mobile-live-hashtags', params, response_type: response_type)
176
+ end
177
+ define_method('live_room') do |**params|
178
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
179
+ request('tiktok-mobile-live-room', params, response_type: response_type)
180
+ end
181
+ define_method('live_room_by_user') do |**params|
182
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
183
+ request('tiktok-mobile-live-room-by-user', params, response_type: response_type)
184
+ end
185
+ define_method('live_search') do |**params|
186
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
187
+ request('tiktok-mobile-live-search', params, response_type: response_type)
188
+ end
189
+ define_method('live_tabs') do |**params|
190
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
191
+ request('tiktok-mobile-live-tabs', params, response_type: response_type)
192
+ end
193
+ define_method('music_beats') do |**params|
194
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
195
+ request('tiktok-mobile-music-beats', params, response_type: response_type)
196
+ end
197
+ define_method('music_chart') do |**params|
198
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
199
+ request('tiktok-mobile-music-chart', params, response_type: response_type)
200
+ end
201
+ define_method('music_choices') do |**params|
202
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
203
+ request('tiktok-mobile-music-choices', params, response_type: response_type)
204
+ end
205
+ define_method('music_collection_list') do |**params|
206
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
207
+ request('tiktok-mobile-music-collection-list', params, response_type: response_type)
208
+ end
209
+ define_method('music_collections') do |**params|
210
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
211
+ request('tiktok-mobile-music-collections', params, response_type: response_type)
212
+ end
213
+ define_method('music_detail') do |**params|
214
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
215
+ request('tiktok-mobile-music-detail', params, response_type: response_type)
216
+ end
217
+ define_method('music_pick') do |**params|
218
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
219
+ request('tiktok-mobile-music-pick', params, response_type: response_type)
220
+ end
221
+ define_method('music_posts') do |**params|
222
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
223
+ request('tiktok-mobile-music-posts', params, response_type: response_type)
224
+ end
225
+ define_method('music_related') do |**params|
226
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
227
+ request('tiktok-mobile-music-related', params, response_type: response_type)
228
+ end
229
+ define_method('music_trending') do |**params|
230
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
231
+ request('tiktok-mobile-music-trending', params, response_type: response_type)
232
+ end
233
+ define_method('mv_videos') do |**params|
234
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
235
+ request('tiktok-mobile-mv-videos', params, response_type: response_type)
236
+ end
237
+ define_method('place_detail') do |**params|
238
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
239
+ request('tiktok-mobile-place-detail', params, response_type: response_type)
240
+ end
241
+ define_method('place_info') do |**params|
242
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
243
+ request('tiktok-mobile-place-info', params, response_type: response_type)
244
+ end
245
+ define_method('place_media') do |**params|
246
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
247
+ request('tiktok-mobile-place-media', params, response_type: response_type)
248
+ end
249
+ define_method('place_photos') do |**params|
250
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
251
+ request('tiktok-mobile-place-photos', params, response_type: response_type)
252
+ end
253
+ define_method('place_reviews') do |**params|
254
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
255
+ request('tiktok-mobile-place-reviews', params, response_type: response_type)
256
+ end
257
+ define_method('place_videos') do |**params|
258
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
259
+ request('tiktok-mobile-place-videos', params, response_type: response_type)
260
+ end
261
+ define_method('playlist_detail') do |**params|
262
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
263
+ request('tiktok-mobile-playlist-detail', params, response_type: response_type)
264
+ end
265
+ define_method('playlist_list') do |**params|
266
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
267
+ request('tiktok-mobile-playlist-list', params, response_type: response_type)
268
+ end
269
+ define_method('playlist_videos') do |**params|
270
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
271
+ request('tiktok-mobile-playlist-videos', params, response_type: response_type)
272
+ end
273
+ define_method('search') do |**params|
274
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
275
+ request('tiktok-mobile-search', params, response_type: response_type)
276
+ end
277
+ define_method('search_effects') do |**params|
278
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
279
+ request('tiktok-mobile-search-effects', params, response_type: response_type)
280
+ end
281
+ define_method('search_hashtags') do |**params|
282
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
283
+ request('tiktok-mobile-search-hashtags', params, response_type: response_type)
284
+ end
285
+ define_method('search_music') do |**params|
286
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
287
+ request('tiktok-mobile-search-music', params, response_type: response_type)
288
+ end
289
+ define_method('search_photos') do |**params|
290
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
291
+ request('tiktok-mobile-search-photos', params, response_type: response_type)
292
+ end
293
+ define_method('search_place') do |**params|
294
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
295
+ request('tiktok-mobile-search-place', params, response_type: response_type)
296
+ end
297
+ define_method('search_pre_info') do |**params|
298
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
299
+ request('tiktok-mobile-search-pre-info', params, response_type: response_type)
300
+ end
301
+ define_method('search_stream') do |**params|
302
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
303
+ request('tiktok-mobile-search-stream', params, response_type: response_type)
304
+ end
305
+ define_method('search_sug') do |**params|
306
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
307
+ request('tiktok-mobile-search-sug', params, response_type: response_type)
308
+ end
309
+ define_method('search_tabs') do |**params|
310
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
311
+ request('tiktok-mobile-search-tabs', params, response_type: response_type)
312
+ end
313
+ define_method('search_user') do |**params|
314
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
315
+ request('tiktok-mobile-search-user', params, response_type: response_type)
316
+ end
317
+ define_method('search_videos') do |**params|
318
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
319
+ request('tiktok-mobile-search-videos', params, response_type: response_type)
320
+ end
321
+ define_method('translation_description') do |**params|
322
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
323
+ request('tiktok-mobile-translation-description', params, response_type: response_type)
324
+ end
325
+ define_method('trending_categories') do |**params|
326
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
327
+ request('tiktok-mobile-trending-categories', params, response_type: response_type)
328
+ end
329
+ define_method('trending_effects') do |**params|
330
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
331
+ request('tiktok-mobile-trending-effects', params, response_type: response_type)
332
+ end
333
+ define_method('user_followers') do |**params|
334
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
335
+ request('tiktok-mobile-user-followers', params, response_type: response_type)
336
+ end
337
+ define_method('user_following') do |**params|
338
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
339
+ request('tiktok-mobile-user-following', params, response_type: response_type)
340
+ end
341
+ define_method('user_original_effects') do |**params|
342
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
343
+ request('tiktok-mobile-user-original-effects', params, response_type: response_type)
344
+ end
345
+ define_method('user_original_music') do |**params|
346
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
347
+ request('tiktok-mobile-user-original-music', params, response_type: response_type)
348
+ end
349
+ define_method('user_posts') do |**params|
350
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
351
+ request('tiktok-mobile-user-posts', params, response_type: response_type)
352
+ end
353
+ define_method('user_profile') do |**params|
354
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
355
+ request('tiktok-mobile-user-profile', params, response_type: response_type)
356
+ end
357
+ define_method('user_recommendations') do |**params|
358
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
359
+ request('tiktok-mobile-user-recommendations', params, response_type: response_type)
360
+ end
361
+ define_method('video_detail') do |**params|
362
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
363
+ request('tiktok-mobile-video-detail', params, response_type: response_type)
364
+ end
365
+ define_method('video_subtitles') do |**params|
366
+ response_type = params.delete(:response_type) || params.delete(:_response_type) || :auto
367
+ request('tiktok-mobile-video-subtitles', params, response_type: response_type)
368
+ end
369
+
370
+ private
371
+
372
+ # POST operations take their JSON body under the body parameter name or "body".
373
+ def request_body(operation, params)
374
+ name = operation["bodyParam"]
375
+ return nil if name.nil?
376
+ value = params.key?(name) ? params[name] : params["body"]
377
+ if value.nil?
378
+ raise Errors::ClientError.new("missing request body: #{name}", operation_id: operation["id"]) if operation["bodyRequired"]
379
+ return nil
380
+ end
381
+ value.is_a?(String) ? value : JSON.generate(value)
382
+ end
383
+
384
+ def build_url(operation, params)
385
+ known = operation["params"].map { |param| param["name"] }
386
+ known << "body" if operation["bodyParam"]
387
+ unknown = params.keys - known
388
+ raise Errors::ClientError.new("unknown parameters: #{unknown.join(', ')}", operation_id: operation["id"]) unless unknown.empty?
389
+ path = operation["path"].dup
390
+ operation["params"].select { |param| param["in"] == "path" }.each do |param|
391
+ value = params[param["name"]]
392
+ raise Errors::ClientError.new("missing path parameter: #{param['name']}", operation_id: operation["id"]) if value.nil?
393
+ path.sub!("{" + param["name"] + "}", percent_encode(value.to_s))
394
+ end
395
+ pairs = []
396
+ operation["queryParams"].each do |param|
397
+ name = param["name"]
398
+ value = params.key?(name) ? params[name] : param["default"]
399
+ if value.nil?
400
+ raise Errors::ClientError.new("missing query parameter: #{name}", operation_id: operation["id"]) if param["required"]
401
+ next
402
+ end
403
+ enum_values = param["enum"] || (param["items"] && param["items"]["enum"])
404
+ if enum_values && !(value.is_a?(Array) ? value : [value]).all? { |item| enum_values.map(&:to_s).include?(item.to_s) }
405
+ raise Errors::ClientError.new("invalid value for #{name}", operation_id: operation["id"])
406
+ end
407
+ if value.is_a?(Array)
408
+ format = param["collectionFormat"] || "csv"
409
+ if format == "multi"
410
+ value.each { |item| pairs << [name, scalar(item)] }
411
+ else
412
+ separator = {"csv" => ",", "ssv" => " ", "tsv" => "\t", "pipes" => "|"}[format] || ","
413
+ pairs << [name, value.map { |item| scalar(item) }.join(separator)]
414
+ end
415
+ else
416
+ pairs << [name, scalar(value)]
417
+ end
418
+ end
419
+ query = pairs.map { |name, value| "#{percent_encode(name)}=#{percent_encode(value)}" }.join("&")
420
+ @base_url + path + (query.empty? ? "" : "?" + query)
421
+ end
422
+
423
+ def scalar(value)
424
+ value == true ? "true" : (value == false ? "false" : value.to_s)
425
+ end
426
+
427
+ def percent_encode(value)
428
+ URI::DEFAULT_PARSER.escape(value.to_s, /[^A-Za-z0-9\-._~]/)
429
+ end
430
+
431
+ def parse_response(body, content_type, operation, params, response_type)
432
+ type = response_type.to_s
433
+ raise Errors::ClientError.new("response_type must be auto, json, or text", operation_id: operation["id"]) unless %w[auto json text].include?(type)
434
+ format = operation["params"].find { |param| param["name"] == "format" }
435
+ text_formats = format && format["enum"] ? format["enum"].reject { |value| %w[json application/json].include?(value.to_s.downcase) } : []
436
+ raw_format = params["format"] && text_formats.include?(params["format"].to_s)
437
+ json_format = format && format["enum"] && format["enum"].any? { |value| %w[json application/json].include?(value.to_s.downcase) } && %w[json application/json].include?(params["format"].to_s.downcase)
438
+ is_json = json_format || content_type.to_s.downcase.include?("json") || operation["produces"] == ["application/json"]
439
+ return body if type == "text" || raw_format || (type == "auto" && !is_json)
440
+ JSON.parse(body)
441
+ rescue JSON::ParserError => error
442
+ raise Errors::Error.new("invalid JSON response from Crawlora: #{error.message}", operation_id: operation["id"], body: body)
443
+ end
444
+
445
+ public
446
+ end
447
+ end
448
+ end
@@ -0,0 +1,5 @@
1
+ module Crawlora
2
+ module TiktokMobile
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ require_relative "tiktok-mobile/version"
2
+ require_relative "tiktok-mobile/client"
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crawlora-tiktok-mobile
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Crawlora
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-10-29 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Ruby client for Crawlora's hosted TikTok Mobile API. Covers mobile feed,
14
+ search, video, creator, music, LIVE, and place endpoints across 63 endpoints.
15
+ email:
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - CHANGELOG.md
21
+ - LICENSE
22
+ - README.md
23
+ - lib/crawlora/tiktok-mobile.rb
24
+ - lib/crawlora/tiktok-mobile/client.rb
25
+ - lib/crawlora/tiktok-mobile/version.rb
26
+ homepage: https://crawlora.net/?utm_source=rubygems&utm_medium=referral&utm_campaign=platform-clients&utm_content=tiktok-mobile-ruby-homepage
27
+ licenses:
28
+ - MIT
29
+ metadata:
30
+ source_code_uri: https://github.com/Crawlora-org/crawlora-tiktok-mobile
31
+ documentation_uri: https://github.com/Crawlora-org/crawlora-tiktok-mobile/blob/main/ruby/README.md
32
+ rubygems_mfa_required: 'true'
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '2.6'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubygems_version: 3.4.19
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: TikTok Mobile API client for Crawlora (63 endpoints)
52
+ test_files: []