TikTok API with Bash and curl: A DevOps Cookbook

Published on May 29, 2026

Why bash for a TikTok API client

Most TikTok scraper integrations get written in Python or Node, but when you live in a terminal, shipping a 12-line script that runs in cron is faster than scaffolding a project. Bash plus curl plus jq is the smallest possible client: zero runtime to install on a fresh server, no virtualenv to forget, no package.json to keep in sync. It pipes naturally into awk, sort, tee, and your existing log pipeline.

This cookbook targets sysadmins and DevOps engineers using the TikLiveAPI REST API. Every recipe authenticates with the X-Api-Key header against https://api.tikliveapi.com, returns JSON, and works inside a GitHub Actions job, a Jenkins step, or a plain crontab -e entry.

Prerequisites

  • curl 7.50 or newer (for --fail-with-body if you upgrade past 7.76; --fail works on older versions).
  • jq 1.6+ for JSON parsing. On Debian: apt install jq. On macOS: brew install jq.
  • Bash 4 minimum, Bash 5 recommended for EPOCHSECONDS.
  • A TikLiveAPI key from your profile page. Top up credits on the pricing page; 1 request equals 1 credit and credits never expire.

Storing the API key

Never hardcode the key. Two patterns work well:

# Option 1: environment variable in ~/.profile or systemd unit
export TIKLIVE_KEY="sk_live_xxxxxxxxxxxxxxxx"

# Option 2: ~/.netrc style file with 600 perms
install -m 600 /dev/null ~/.tiklive
echo "TIKLIVE_KEY=sk_live_xxxxxxxxxxxxxxxx" > ~/.tiklive
# then in scripts:
source ~/.tiklive

For CI, drop the key into the runner's secret store and expose it as TIKLIVE_KEY.

The one-liners cookbook

1. Fetch a user profile

The /userinfo-by-username/ endpoint returns a nested object with user{} and stats{} blocks. Counters live under stats in camelCase: followerCount, videoCount, heartCount.

curl -s "https://api.tikliveapi.com/userinfo-by-username/?username=tiktok" \
  -H "X-Api-Key: $TIKLIVE_KEY" \
  | jq '{name: .user.nickname, followers: .stats.followerCount, hearts: .stats.heartCount}'

2. Resolve a username to a numeric userid

Most paginated endpoints want the numeric userid, not the @handle. /userid/ returns a flat { "id": "..." } object.

resolve_uid() {
  curl -s "https://api.tikliveapi.com/userid/?username=$1" \
    -H "X-Api-Key: $TIKLIVE_KEY" | jq -r .id
}
UID=$(resolve_uid mrbeast)
echo "Numeric UID: $UID"

3. Get the post count of the last page

/user-posts/ returns videos[], a string cursor, and a camelCase hasMore boolean. To count items in one page:

curl -s "https://api.tikliveapi.com/user-posts/?userid=$UID&count=30" \
  -H "X-Api-Key: $TIKLIVE_KEY" \
  | jq '.videos | length'

4. Cursor pagination in pure bash

Walk every page until hasMore is false. Note the cursor is a millisecond timestamp string for posts; followers and following use a different field name covered below.

cursor="0"
while :; do
  resp=$(curl -s "https://api.tikliveapi.com/user-posts/?userid=$UID&count=30&cursor=$cursor" \
    -H "X-Api-Key: $TIKLIVE_KEY")
  echo "$resp" | jq -c '.videos[] | {id: .aweme_id, plays: .play_count}'
  has_more=$(echo "$resp" | jq -r '.hasMore')
  [ "$has_more" != "true" ] && break
  cursor=$(echo "$resp" | jq -r '.cursor')
  sleep 0.4
done

Followers and following use a time cursor (unix seconds), not cursor. The following list also returns the plural key followings, not following:

# /user-followers/ pagination
time_cur="0"
curl -s "https://api.tikliveapi.com/user-followers/?userid=$UID&count=50&time=$time_cur" \
  -H "X-Api-Key: $TIKLIVE_KEY" | jq '.followers[] | .unique_id'

# /user-following/ - note the response key is 'followings'
curl -s "https://api.tikliveapi.com/user-following/?userid=$UID&count=50&time=0" \
  -H "X-Api-Key: $TIKLIVE_KEY" | jq '.followings[].unique_id'

5. Filter videos above a play_count threshold

Every video item under /user-posts/ is flat snake_case. Filter viral ones inline:

curl -s "https://api.tikliveapi.com/user-posts/?userid=$UID&count=30" \
  -H "X-Api-Key: $TIKLIVE_KEY" \
  | jq -r '.videos[] | select(.play_count > 1000000) | "\(.play_count)\t\(.title)"'

6. Bulk fetch from usernames.txt with xargs

Parallelism via xargs -P is the closest thing bash has to a thread pool. Respect the 200 requests per minute default cap by capping concurrency.

cat usernames.txt | xargs -n1 -P4 -I{} bash -c '
  u=$(curl -s "https://api.tikliveapi.com/userinfo-by-username/?username={}" \
    -H "X-Api-Key: $TIKLIVE_KEY")
  echo "{}|$(echo $u | jq -r .stats.followerCount)"
' > followers.csv

7. Download a watermark-free video

/post-detail/ returns a flat snake_case object (no wrapping data{}) with three download fields: play (no watermark), wmplay (with watermark), and hdplay (HD no watermark).

POST_URL="https://www.tiktok.com/@tiktok/video/7300000000000000000"
detail=$(curl -s "https://api.tikliveapi.com/post-detail/?url=$POST_URL" \
  -H "X-Api-Key: $TIKLIVE_KEY")
hd=$(echo "$detail" | jq -r .hdplay)
id=$(echo "$detail" | jq -r .aweme_id)
curl -L -o "${id}.mp4" "$hd"

8. Daily snapshot to CSV

Pipe through awk to build a tracking row per run. Append-only files survive log rotation and graph well in Grafana via the CSV datasource.

#!/usr/bin/env bash
set -euo pipefail
: "${TIKLIVE_KEY:?missing}"
USER="$1"
out=$(curl -s "https://api.tikliveapi.com/userinfo-by-username/?username=$USER" \
  -H "X-Api-Key: $TIKLIVE_KEY")
echo "$out" | jq -r --arg d "$(date -u +%F)" --arg u "$USER" \
  '[$d, $u, .stats.followerCount, .stats.videoCount, .stats.heartCount] | @csv' \
  >> snapshots.csv

9. Cron schedule

Run the snapshot every day at 03:15 UTC for a list of creators:

15 3 * * * source /etc/tiklive.env && \
  while read u; do /opt/tiklive/snapshot.sh "$u"; done < /etc/tiklive/users.txt

10. Slack alert when followers drop

Compare today's count against yesterday's last CSV row.

today=$(curl -s "https://api.tikliveapi.com/userinfo-by-username/?username=$USER" \
  -H "X-Api-Key: $TIKLIVE_KEY" | jq -r .stats.followerCount)
yesterday=$(tail -n2 snapshots.csv | head -n1 | awk -F, '{print $3}' | tr -d '"')
if [ "$today" -lt "$yesterday" ]; then
  delta=$((yesterday - today))
  curl -s -X POST -H 'Content-type: application/json' \
    --data "{\"text\":\"$USER lost $delta followers ($yesterday -> $today)\"}" \
    "$SLACK_WEBHOOK_URL"
fi

Robust error handling

The default curl behavior on 5xx is to print the error body and exit 0, which silently corrupts your CSV. The fix is a three-line preamble plus curl --fail:

set -euo pipefail
shopt -s inherit_errexit

api() {
  local path="$1"; shift
  curl --fail --silent --show-error --retry 3 --retry-delay 2 \
    --max-time 30 \
    -H "X-Api-Key: $TIKLIVE_KEY" \
    "https://api.tikliveapi.com${path}" "$@"
}

# usage
api "/userinfo-by-username/?username=tiktok" | jq .stats.followerCount

For 429 rate-limit responses, sleep and retry. The standard cap is 200 requests per minute:

fetch_with_backoff() {
  local url="$1" tries=0
  until body=$(curl -sS -w '\n%{http_code}' -H "X-Api-Key: $TIKLIVE_KEY" "$url"); do
    code=$(echo "$body" | tail -n1)
    [ "$code" = "429" ] || return 1
    tries=$((tries+1))
    [ "$tries" -gt 5 ] && return 1
    sleep $((2 ** tries))
  done
  echo "$body" | sed '$d'
}

Comments and replies

/post-comments/ returns comments[] where each item carries an id field (not cid), plus text, digg_count, reply_total, and a nested snake_case user{}. Top-level keys are comments, total, cursor, and the camelCase hasMore.

curl -s "https://api.tikliveapi.com/post-comments/?url=$POST_URL&count=50" \
  -H "X-Api-Key: $TIKLIVE_KEY" \
  | jq -r '.comments[] | "\(.id)\t\(.digg_count)\t\(.text)"'

# follow up: pull replies to a specific comment
curl -s "https://api.tikliveapi.com/post-comment-replies/?video_id=$VID&comment_id=$CID&count=20" \
  -H "X-Api-Key: $TIKLIVE_KEY" | jq '.'

GitHub Actions example

Drop this into .github/workflows/snapshot.yml to commit a daily snapshot back to the repo:

name: tiktok-snapshot
on:
  schedule: [{ cron: '15 3 * * *' }]
  workflow_dispatch:
jobs:
  snap:
    runs-on: ubuntu-latest
    env:
      TIKLIVE_KEY: ${{ secrets.TIKLIVE_KEY }}
    steps:
      - uses: actions/checkout@v4
      - run: sudo apt-get install -y jq
      - run: ./snapshot.sh tiktok >> data/snapshots.csv
      - run: |
          git config user.name bot
          git config user.email bot@example.com
          git add data/snapshots.csv
          git commit -m "daily snapshot $(date -u +%F)" || exit 0
          git push

When bash is the right tool, and when it is not

Bash is correct when:

  • The script is under roughly 100 lines and lives in cron, systemd timers, or a CI step.
  • You shuffle JSON straight into a CSV, a webhook, or another shell tool.
  • You need zero deploy ceremony - copying one .sh file to a server is the spec.

Pivot to Python (or Go) when:

  • You need real concurrency with backpressure beyond xargs -P.
  • State machines get complicated: resumable cursors, dedup across runs, multi-account orchestration.
  • You parse comments for sentiment, or fan out into a database with transactions.
  • You hit 100+ lines of bash with nested functions. That is the threshold; past it, bash maintenance cost eats the install-simplicity savings.

FAQ

Do I need to URL-encode usernames or video URLs?

For ASCII handles, no. For unicode or URLs containing & and ?, yes. Use curl --data-urlencode with -G to be safe: curl -G --data-urlencode "url=$POST_URL" ....

How do I avoid leaking my key in process listings?

Pass headers via -H @file or environment variables, never on the command line. ps auxe exposes args but not headers built from env vars inside the child process.

What happens when I run out of credits?

The API returns an HTTP error and your curl --fail script exits nonzero. Top up at the pricing page; refunds are available on unused credit packages.

Can I test endpoints before scripting them?

Yes. The playground sends real requests through a server-side proxy that injects your key, so you can iterate on parameters before wiring them into a script.

Where do I report a broken cookbook recipe?

Reach the team via the contact page or read related guides on the blog. Field shapes can drift; the documentation is the canonical reference.

Build with the TikTok API

Ready to put what you read into code? Try our endpoints live or grab the full reference.

Open Playground Read Documentation