100 free credits. No credit card required.Start building
Logo
Back to blog

TikTok video search API: region on 30/30, 1 credit

··15 min read

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.

TikTok video search API: region on 30/30, 1 credit

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.

A tall rounded video tile with a clay-amber map pin on the corner, showing country labeled on each TikTok video search row.

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:

bash
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):

json
{
  "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.regionVideos
US24
CA2
BE1
DK1
IT1
GB1
Filled30/30
ES0

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:

python
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])
A fanned stack of video tiles with one clay-amber map pin on the front tile, showing how to filter a TikTok video search by country.

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.

bash
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&region=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:

json
{
  "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&region=ES&limit=30 page:

post.ext.regionVideos
ES25
CO2
PL1
DE1
US1
Filled30/30
ES share25/30 (83.3%)
Unique usernames (page)27
Unique ES creators22

Five rows prove region= did not filter:

usernamepost.ext.regionvideo id
rubi.chefCO7617623218760862996
isabellarodriguezuCO7632395980767366418
officialepiceatsPL7672057190374460705
tagesrezeptDE7643897506845248800
nurysuleUS7584637852428897550

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:

bash
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&region=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.

CallProduct claimObserved credits_used
No limit, query=cooking1-credit page of ~301
No limit, query=receta&region=ES1-credit page of ~301
limit=30ceil(30/4)+2 = 1010
limit=60formula → 17not run
limit=90formula → 25not run
limit=120formula → 32not 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=3010 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:

json
{
  "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:

Leafcooking (1 cr)receta ES limit=30 (10 cr)receta ES no limit (1 cr)
post.author.username30/3030/3030/30
post.ext.author_id30/3030/3030/30
post.ext.author_followers30/3030/3030/30
post.ext.author_following0/30 (key absent)30/300/30 (key absent)
post.ext.download_count30/3030/3030/30
post.ext.region30/3030/3030/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.

  1. Video registered regionpost.ext.region on GET /v1/tiktok/search (this post). You filter.
  2. Request routed through a country (proxy)region= on video search. Sets the proxy only. Harvest: 5 non-ES on the ES limit=30 page; 4 non-ES on the no-limit ES page.
  3. Creator’s public account regionauthor.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.
  4. Audience geographyGET /v1/tiktok/user/audience on a known handle. Not search.
omit limit (this harvest)limit=30 (this harvest)GET /v1/tiktok/search/users?country= (sibling / docs)
Credits1 per page105 per delivered creator
Rows30, paginates with cursor30, no cursor8 default / 20 max, no cursor
Country leafpost.ext.region on each video (30/30)sameauthor.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/EScountry= 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?

  1. Get a SocialCrawl key. The free plan includes 100 credits, no card. Auth is x-api-keyone API key. No TikTok developer app, no Research API application.
  2. Run the cooking curl above (query=cooking, no limit). Expect about 30 rows, post.ext.region on each, 1 credit, a cursor. Counts will not match this snapshot. The shape and the credit charges should match.
  3. To keep one country: add region= only as a proxy hint, then keep rows where post.ext.region equals "ES" (or KR, GB, …). Do not expect the API to drop the rest.
  4. 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-key surface 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).

Call GET /v1/tiktok/search?query=…, read post.ext.region on each row, keep one ISO-2. This harvest: query=receta&region=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.

Topics
#tiktok-video-search-api#tiktok-video-search#tiktok-keyword-search#tiktok-keyword-search-api#filter-tiktok-videos-by-country#tiktok-search-by-country#tiktok-videos-by-country#tiktok-search-region

Related posts

🤖 AI agent or LLM? Read this page as markdown