Answers

TikTokApi keeps throwing EmptyResponseException. Is there a more reliable way to get TikTok data?

Short answer

EmptyResponseException means TikTok treated your automated browser session as a bot and sent back nothing. If you only need public data, a hosted API skips the browser, ms_token and proxies: okdata returns a video for $0.002, a profile for $0.002 and a search page of about 30 videos for $0.002, and failed calls cost nothing.

The script worked on your laptop on Friday. On Monday, running on a server, every video.info() and user.info() call ends in EmptyResponseException: TikTok returned an empty response. Nothing is wrong with your code. TikTokApi opens a headless browser through Playwright, and TikTok's bot detection decided that browser was not a person, so it answers with an empty body.

Things to try before switching

The library's own message lists them, and they do help for small jobs:

  1. Pass a fresh ms_token copied from the cookies of a logged-in browser.
  2. Run with headless=False, or try browser="webkit".
  3. Use a residential proxy instead of a data-center IP, and slow down.

The catch is upkeep. Tokens expire, proxies cost money, and each TikTok front-end change can break things again. If the job is reading public numbers, not automating an account, you can drop the browser.

The same calls without a browser

TikTokApi Endpoint Price
api.video(url=...).info() TikTok video detail $0.002
api.user(username).info() TikTok account profile $0.002
Keyword video search, a common feature request TikTok video search $0.002 per page
video.comments() TikTok video comments $0.002 per page

Not everything has a twin. There is no endpoint for a user's full video list, hashtag pages, trending feeds or video downloads.

import os, requests

API = "https://api.okdata.online/v1"
H = {"Authorization": f"Bearer {os.environ['OKDATA_API_KEY']}"}

def call(path, **params):
    body = requests.get(f"{API}/{path}", headers=H, params=params, timeout=60).json()
    if body["code"] != "ok":
        # every error says whether you were charged and whether a retry makes sense
        raise RuntimeError(f"{body['code']} charged={body['charged']} retry={body['retry']}")
    return body["data"]

video = call("tiktok/videos/get", url="https://www.tiktok.com/@scout2015/video/6718335390845095173")["item"]
author = call("tiktok/users/get", username=video["author"]["username"])["item"]
print(video["metrics"]["views"], video["metrics"]["likes"], author["followers"])

What changes in your code

  • Responses are plain JSON in one shape for every platform: data.item for one object, data.items plus page.next_cursor for lists. No async, no browser lifecycle.
  • Errors come back as codes. upstream_busy and upstream_failed are worth retrying; not_found means the video or account is gone. None of them are charged. The full table is on errors.
  • Field names differ from TikTok's raw JSON: playCount becomes metrics.views, diggCount becomes metrics.likes.

When to stay with TikTokApi

Keep it if you need things this catalog does not offer, such as a profile's full video list, or if you run a few hundred requests a month from a home connection where it still works. It is free and open source. For research at scale with academic credentials, TikTok's Research API is the official route.

Updated