TikTok video search API: region on 30/30, 1 credit
TikTok video search API: 30/30 cooking rows labeled at 1 credit. region=ES still returned 5 non-ES videos — filter country client-side on post.ext.region.
A TikTok video search API returns the country on every video (post.ext.region, ISO 3166-1 alpha-2). Production GET /v1/tiktok/search?query=cooking at https://www.socialcrawl.dev on 2026-09-18 returned 30 videos with post.ext.region filled 30/30 — US 24, CA 2, BE / DK / IT / GB 1 each. 1 credit, a cursor, no redirect. Request req-bwS0R7rf6fSolozv.
TikTok Search does not expose ISO-2 on each video as an API field. The Research API Query Videos spec can return region_code only after an approved research app, a required ≤30-day date window, and a POST boolean query — not a commercial GET ?query=. That official dead-end is covered in TikTok data without the official API.
Stack: curl · GET https://www.socialcrawl.dev/v1/tiktok/search · header x-api-key. No TikTok developer app. Shipped 18/09/2026 (changelog). The 1-credit ~30-row page is unchanged in price — this ship labels those rows (unfiltered ~30-row page at 1 credit). Three captured 200s sit below. After those comes the region= mix that still is not a filter.
How does TikTok video search work as an API?
One GET with query= returns about 30 videos, each already carrying post.ext.region (ISO 3166-1 alpha-2), at 1 credit, and still paginates with cursor. A TikTok keyword search API is this call: keyword in query=, country already on the row. No region= was sent.
The 2026-09-18 production harvest hit https://www.socialcrawl.dev with Cache-Control: no-cache and --max-redirs 0. A 3xx would have failed. Live call:
curl -s --max-redirs 0 \
-H "x-api-key: $SOCIALCRAWL_API_KEY" \
-H "Cache-Control: no-cache" -H "Pragma: no-cache" \
"https://www.socialcrawl.dev/v1/tiktok/search?query=cooking"HTTP 200 at 2026-09-18T22:07:19Z, 30 videos, region 30/30, credits_used: 1, cached: false, dropped: 0, pagination.has_more: true, next_cursor present (1,711 characters). Request req-bwS0R7rf6fSolozv. Unique video ids 30/30 — no in-page repeats. Trimmed envelope from that response (cursor string cut; author_following was absent on this path, not null):
{
"success": true,
"platform": "tiktok",
"endpoint": "/v1/tiktok/search",
"credits_used": 1,
"cached": false,
"request_id": "req-bwS0R7rf6fSolozv",
"pagination": {
"has_more": true,
"page_size": 30
},
"data": {
"dropped": 0,
"items": [
{
"post": {
"id": "7678333675544513825",
"url": "https://www.tiktok.com/@alya.cookss/video/7678333675544513825",
"author": {
"username": "alya.cookss",
"display_name": "Alya.Cooks"
},
"ext": {
"region": "BE",
"author_id": "7455670780618359830",
"author_followers": 94159,
"download_count": 1194
}
}
}
]
}
}Country mix on that unlabeled query=cooking page:
post.ext.region | Videos |
|---|---|
| US | 24 |
| CA | 2 |
| BE | 1 |
| DK | 1 |
| IT | 1 |
| GB | 1 |
| Filled | 30/30 |
| ES | 0 |
Read the nested path: data.items[i].post.ext.region. That leaf is the unified schema — a canonical post, not a one-off geo blob. The first row was alya.cookss / BE, not a US video. Unlabeled keyword search is global; the labels still fill. This page had 24 unique usernames across 30 videos. Creator leaves on this 1-credit page: username / author_id / author_followers / download_count 30/30; author_following 0/30 (key absent). Do not code against author_following on the cheap page; it was missing today. The United Kingdom row is GB, not UK.
Docs also list optional date_posted (yesterday | this-week | this-month | last-3-months | last-6-months | all-time) and sort_by (relevance | most-liked | date-posted). This harvest did not send them, so treat those as documented params, not measured here.
TikTok's public search page has no ISO-2 API field. That is why TikTok video search SERPs as the app, and this section is the API.
This is not GET /v1/tiktok/search/users?country=, not trending?region=&feed=local, and not hashtags/popular?countryCode=. Live param table: GET /v1/tiktok/search.
Optional Python twin of the same call:
import os
import requests
r = requests.get(
"https://www.socialcrawl.dev/v1/tiktok/search",
params={"query": "cooking"},
headers={"x-api-key": os.environ["SOCIALCRAWL_API_KEY"]},
timeout=60,
)
payload = r.json()
items = payload["data"]["items"]
print(r.status_code, payload["credits_used"], len(items))
print([row["post"]["ext"]["region"] for row in items])
How do I filter TikTok videos by country?
The SocialCrawl API does not drop other countries for you. You filter post.ext.region client-side. TikTok videos by country, or a TikTok search by country in this product, means ISO-2 on each video — you keep the code you want. region= only sets the proxy — it does not filter. On this harvest, region=ES still returned non-ES rows.
curl -s --max-redirs 0 \
-H "x-api-key: $SOCIALCRAWL_API_KEY" \
-H "Cache-Control: no-cache" -H "Pragma: no-cache" \
"https://www.socialcrawl.dev/v1/tiktok/search?query=receta®ion=ES&limit=30"HTTP 200 at 2026-09-18T22:07:42Z, 30 videos, region 30/30, credits_used: 10 (matches ceil(30/4)+2), no next_cursor, has_more: false, dropped: 0, cached: false. Request req-ucdIYc9pt7XQPkIf. This is the path where author_following is present. Trimmed ES row:
{
"success": true,
"platform": "tiktok",
"endpoint": "/v1/tiktok/search",
"credits_used": 10,
"cached": false,
"request_id": "req-ucdIYc9pt7XQPkIf",
"pagination": {
"next_cursor": null,
"has_more": false,
"page_size": 30
},
"data": {
"dropped": 0,
"total": 30,
"items": [
{
"post": {
"id": "7684609864760364320",
"url": "https://www.tiktok.com/@sofiatecocina/video/7684609864760364320",
"author": { "username": "sofiatecocina" },
"ext": {
"region": "ES",
"author_id": "6862032819960693766",
"author_followers": 417506,
"author_following": 91,
"download_count": 1
}
}
}
]
}
}Mix on that query=receta®ion=ES&limit=30 page:
post.ext.region | Videos |
|---|---|
| ES | 25 |
| CO | 2 |
| PL | 1 |
| DE | 1 |
| US | 1 |
| Filled | 30/30 |
| ES share | 25/30 (83.3%) |
| Unique usernames (page) | 27 |
| Unique ES creators | 22 |
Five rows prove region= did not filter:
| username | post.ext.region | video id |
|---|---|---|
| rubi.chef | CO | 7617623218760862996 |
| isabellarodriguezu | CO | 7632395980767366418 |
| officialepiceats | PL | 7672057190374460705 |
| tagesrezept | DE | 7643897506845248800 |
| nurysule | US | 7584637852428897550 |
Keep rows where post.ext.region equals "ES": 25 videos → 22 unique ES usernames. That filtered page is already a country creator list. Filtering is free. limit=30 billed 10 for the labeled page; you drop the five non-ES rows yourself. Unique video ids were 30/30. For current exact follower count, bio and links, pass the username to GET /v1/tiktok/profile (1 credit) or POST /v1/prism/profiles (1 credit per resolved profile, up to 50). Deduplicate usernames across pages; this bulk call has no cursor to walk.
receta plus region=ES is a Spanish-language cooking query with a Spain proxy. A reasonable seed, not a census. A Korea beauty query, or the same receta at a different hour, will mix differently. 25/30 ES on this page is a snapshot.
Research API Query Videos uses region_code as a server-side WHERE clause on an academic POST with a required 30-day window. We label every row; you filter.
post.ext.region is the country TikTok registered the video to (typically the creator’s account country at post time) — not viewer country, not audience geo, and not user-search country=.
How many credits does a labeled TikTok video search cost?
Omitting limit is still 1 credit, still labeled, still paginates. limit= is the opt-in bulk labeled page: a single shot, no cursor. The 1-credit price is the 2026-09-04 density ship (unfiltered ~30-row page at 1 credit); this 18/09 ship adds the label.
Third captured call, same headers, no limit:
curl -s --max-redirs 0 \
-H "x-api-key: $SOCIALCRAWL_API_KEY" \
-H "Cache-Control: no-cache" -H "Pragma: no-cache" \
"https://www.socialcrawl.dev/v1/tiktok/search?query=receta®ion=ES"HTTP 200 at 2026-09-18T22:08:02Z, 30 videos, region 30/30, credits_used: 1, cursor present, has_more: true, dropped: 0. Request req-BDe1R2pa5B09A6xA. Mix: ES 26, MX 2, US 1, CO 1 → 4 non-ES, 24 unique usernames, 20 unique ES creators. region= still does not filter when you omit limit.
Omit limit when you want to walk pages at 1 credit each and stop when you have enough matching rows. Pass limit=30 when you want one 30-row labeled shot and will filter after the call. This harvest did not re-run four 1-credit pages on production — do not assume a 4-page unique count.
| Call | Product claim | Observed credits_used |
|---|---|---|
No limit, query=cooking | 1-credit page of ~30 | 1 |
No limit, query=receta®ion=ES | 1-credit page of ~30 | 1 |
limit=30 | ceil(30/4)+2 = 10 | 10 |
limit=60 | formula → 17 | not run |
limit=90 | formula → 25 | not run |
limit=120 | formula → 32 | not observed (504, refunded) |
limit is documented as 1–120 and rounds up to 30 / 60 / 90 / 120. Credits follow ceil(n/4)+2 → 10 / 17 / 25 / 32. Only limit=30 → 10 was observed this run. 17 / 25 / 32 are the documented formula, not this harvest.
Two production limit=120 attempts (req-UCkQA3eOkYqcSEJH, retry req-7WXb1xv9LVXb8ljT) returned HTTP 504 UPSTREAM_ERROR, credits_used: 0. Trimmed retry body:
{
"success": false,
"error": {
"type": "UPSTREAM_ERROR",
"message": "The upstream data provider timed out. Your credits have been refunded.",
"status": 504,
"doc_url": "https://www.socialcrawl.dev/docs/errors#upstream-error"
},
"credits_used": 0
}Docs also say repeats are removed after billing (a 120 page can land a little under 120 unique) and unfilled slots are refunded. Neither unique-count nor unfilled-slot refund was demonstrated this run, because the call never returned 200. The 504s were refunded. Prefer limit=30 (observed 10 credits, 30 videos, no cursor) or omit limit (1 credit + cursor).
Creator leaves on each path — this is what you get:
| Leaf | cooking (1 cr) | receta ES limit=30 (10 cr) | receta ES no limit (1 cr) |
|---|---|---|---|
post.author.username | 30/30 | 30/30 | 30/30 |
post.ext.author_id | 30/30 | 30/30 | 30/30 |
post.ext.author_followers | 30/30 | 30/30 | 30/30 |
post.ext.author_following | 0/30 (key absent) | 30/30 | 0/30 (key absent) |
post.ext.download_count | 30/30 | 30/30 | 30/30 |
post.ext.region | 30/30 | 30/30 | 30/30 |
Cache miss bills; cache hit 0; empty/hard fail refunded; 400 never charged. All three 200s this run were misses. Harvest spend on those 200s was 12 credits (1 + 10 + 1). Both 504s billed 0.
Is video-search region= the same as user-search country=?
No. These “country” ideas get mixed constantly. This endpoint is ISO-2 on each video row, and you filter.
- Video registered region —
post.ext.regiononGET /v1/tiktok/search(this post). You filter. - Request routed through a country (proxy) —
region=on video search. Sets the proxy only. Harvest: 5 non-ES on the ESlimit=30page; 4 non-ES on the no-limit ES page. - Creator’s public account region —
author.location.GET /v1/tiktok/search/users?country=keeps only matches, 5 credits per matching creator, default 8 max 20, no cursor. That product is TikTok user search by country: 3 KR hits, 15 credits. This harvest did not call it. - Audience geography —
GET /v1/tiktok/user/audienceon a known handle. Not search.
omit limit (this harvest) | limit=30 (this harvest) | GET /v1/tiktok/search/users?country= (sibling / docs) | |
|---|---|---|---|
| Credits | 1 per page | 10 | 5 per delivered creator |
| Rows | 30, paginates with cursor | 30, no cursor | 8 default / 20 max, no cursor |
| Country leaf | post.ext.region on each video (30/30) | same | author.location on each creator; server keeps only matches |
region= / country= | region= sets proxy only (4 non-ES on receta/ES) | 5 non-ES on receta/ES | country= filters |
If you want creators whose account is in KR, use the sibling. If you want cooking videos labeled ES, use this endpoint and filter post.ext.region.
Related, and not this call: include=profile hydrates a global user-search page; GET /v1/tiktok/profile looks up a known handle; hashtags/popular?countryCode= is a TikTok Creative Center board; trending?region=&feed=local is a For-You feed. None of them are keyword video search.
How do you start using this?
- Get a SocialCrawl key. The free plan includes 100 credits, no card. Auth is
x-api-key— one API key. No TikTok developer app, no Research API application. - Run the cooking curl above (
query=cooking, nolimit). Expect about 30 rows,post.ext.regionon each, 1 credit, a cursor. Counts will not match this snapshot. The shape and the credit charges should match. - To keep one country: add
region=only as a proxy hint, then keep rows wherepost.ext.regionequals"ES"(or KR, GB, …). Do not expect the API to drop the rest. - After that: the explorer to see the payload before writing a single line; the TikTok video search endpoint for the live param table; the TikTok docs and the TikTok platform catalogue for the rest of the reads. If you want creators whose account region matches, that is 5 credits per matching creator. The scrape-vs-official-API landscape lives in How to scrape TikTok in 2026 — not this endpoint. MCP and Agent Skills wrap the same
x-api-keysurface if an agent should fire the GET.
Replay the cooking curl first.
Frequently asked questions
Can I run a TikTok search by country on videos?
Yes — as a label plus client-side filter, not as a server-side drop. GET /v1/tiktok/search returns post.ext.region (ISO 3166-1 alpha-2) on every video. Keep the rows you want. This is not GET /v1/tiktok/search/users?country= (that keeps matching creators at 5 credits each).
How do I get TikTok videos by country from keyword search?
Call GET /v1/tiktok/search?query=…, read post.ext.region on each row, keep one ISO-2. This harvest: query=receta®ion=ES&limit=30 returned 25 ES of 30 (22 unique ES usernames) at 10 credits. region= is optional proxy, not the filter.
Does region= filter TikTok search results?
No. region= only sets the proxy. On 2026-09-18, region=ES&limit=30 still returned 5 non-ES rows (CO 2, PL, DE, US). The no-limit ES page still returned 4 non-ES (MX 2, US, CO). Filter on post.ext.region.
Does the 1-credit TikTok video search page include country labels?
Yes. Omitting limit is still ~30 rows, still labeled (post.ext.region 30/30 on both 1-credit pages this run), still 1 credit, still paginates with cursor. The 1-credit price is the 2026-09-04 density ship — this 18/09 ship adds the label. See the unfiltered ~30-row page at 1 credit.
What happens if I pass limit=120?
Documented: rounds to a 120-row single shot, no cursor, billed ceil(120/4)+2 = 32, repeats stripped after billing, unfilled slots refunded. This run: two production attempts returned HTTP 504, credits_used: 0. Do not quote unique / ES / creator counts for a 120 page. Prefer limit=30 (observed 10 credits, 30 videos, no cursor) or omit limit (1 credit + cursor).
How is this different from finding TikTok creators by country?
Video search labels videos with post.ext.region at 1 credit (or 10 at limit=30); you filter. User search GET /v1/tiktok/search/users?country= keeps matching creators at 5 credits per delivered row, max 20, no cursor. See TikTok user search by country: 3 KR hits, 15 credits. This harvest did not call that endpoint.
Related posts
YouTube Shorts vs Long Form: 1.91× More Long-Form Views
YouTube Shorts vs long form: median long-form views are 1.91× Shorts (1,261 videos, 17 channels). Shorts win views-per-second (9.95×). Full dated method.
TikTok user search by country: 3 KR hits, 15 credits
3 KR cooking accounts at 15 credits, all location KR. Omit country= and TikTok user search is 1 credit with location null. country=ZZ returns a free 400.
Instagram Reels Stats API: 12 Reels, 2 Credits
Instagram Reels Stats API: 12 public reels with views, likes, shares, and saves for 2 credits. Graph Insights still needs a Page you own. Live 2026-09-15.
