TikTok Shop seller products: export a store's catalog
Get every product a US TikTok Shop seller offers, with price, discount, rating and sold count, in one list. Save it each week to see what a competitor launched, discounted or started selling more of. One request covers 50 products.
Setup
You need Python 3, requests (pip install requests) and a key from the RapidAPI listing; the free plan's 100 requests are enough to try it. Put the key in an environment variable called RAPIDAPI_KEY.
import json
import os
import time
from datetime import date
from pathlib import Path
import requests
BASE = "https://tiktok-shop-api5.p.rapidapi.com"
HEADERS = {
"X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"],
"X-RapidAPI-Host": "tiktok-shop-api5.p.rapidapi.com",
}
def get(path, **params):
"""GET a path and return the JSON. Retries rate limits and upstream errors."""
for attempt in range(4):
response = requests.get(BASE + path, headers=HEADERS, params=params, timeout=30)
if response.status_code in (429, 502, 503) and attempt < 3:
time.sleep(2 ** attempt)
continue
response.raise_for_status()
return response.json()
Find the seller ID
Every product card from product search has seller.id. If you start from a product link instead, product detail returns seller_id:
detail = get("/v1/products/1731016350288417305")
seller_id = detail["seller_id"] # "7495957834594814489"
That product is an office chair sold by Sweetcrispy Shop. The rest of this guide uses its seller ID.
Read the catalog
GET /v1/shops/{id}/products returns the seller's products with the same card fields as search.
curl --request GET \
--url 'https://tiktok-shop-api5.p.rapidapi.com/v1/shops/7495957834594814489/products?count=20' \
--header "X-RapidAPI-Key: $RAPIDAPI_KEY" \
--header 'X-RapidAPI-Host: tiktok-shop-api5.p.rapidapi.com'
Real response, shortened to one product card, image links shortened
{
"products": [
{
"id": "1732242819673461273",
"title": "Sweetcrispy 3-Wheel Foldable Compact All-Seasons Pet Stroller Carrier for Small-Med Dogs - Durable Frame, Smooth Ride, Stability, Breathable Mesh Window, Large Storage Basket (Room for Groceries), Cup Holder, Push Handle, Rear Brakes (Blue) for Outings",
"url": "https://shop.tiktok.com/us/pdp/sweetcrispy-3-wheel-pet-stroller-with-mesh-window-foldable/1732242819673461273",
"image": "https://p16-oec-general-useast5.ttcdn-us.com/…",
"price": {
"currency": "USD",
"symbol": "$",
"value": "70.17",
"formatted": "70.17",
"original": "142.97",
"discount": "51%"
},
"rating": { "score": 4.6, "count": "633" },
"sold": { "count": "4855" },
"seller": {
"id": "7495957834594814489",
"name": "Sweetcrispy Shop",
"logo": "https://p16-oec-general-useast5.ttcdn-us.com/…"
}
}
],
"has_more": true,
"next_offset": 20,
"next_cursor": "40_WzE4MTEsODk5OTAwMDAsMTczNTgxMTQxNTg1OCwiMTczMDQwNTU5MjM1OTM0MjYxNyJd"
}
Get every product
Omit cursor on the first page, then pass each response's next_cursor while has_more is true. Use the cursor from your own response; the one above belongs to the example page.
def catalog(seller_id, max_pages=20):
products, cursor = {}, None
for _ in range(max_pages):
params = {"count": 50}
if cursor:
params["cursor"] = cursor
data = get(f"/v1/shops/{seller_id}/products", **params)
for card in data.get("products") or []:
products[card["id"]] = card
cursor = data.get("next_cursor")
if not data.get("has_more") or not cursor:
break
return products
products = catalog(seller_id)
print(len(products), "products")
Track changes week over week
Save each run as a dated snapshot and compare it with the previous one. This prints new and removed products, price changes and the change in sold count. The to_number helper turns TikTok's "1.2K" style counts into numbers.
def to_number(text):
"""'4855' -> 4855, '1.2K' -> 1200, None -> 0"""
if not text:
return 0
text = text.strip().upper().replace(",", "")
multiplier = {"K": 1_000, "M": 1_000_000}.get(text[-1], 1)
if multiplier > 1:
text = text[:-1]
try:
return int(float(text) * multiplier)
except ValueError:
return 0
def summary(card):
return {
"title": card["title"][:80],
"price": (card.get("price") or {}).get("value"),
"sold": to_number((card.get("sold") or {}).get("count")),
}
folder = Path("snapshots") / seller_id
folder.mkdir(parents=True, exist_ok=True)
current = {pid: summary(card) for pid, card in products.items()}
previous_files = sorted(folder.glob("*.json"))
previous = json.loads(previous_files[-1].read_text()) if previous_files else None
(folder / f"{date.today()}.json").write_text(json.dumps(current))
if previous is not None:
for pid in current.keys() - previous.keys():
print("NEW ", current[pid]["price"], current[pid]["title"])
for pid in previous.keys() - current.keys():
print("REMOVED ", previous[pid]["title"])
for pid in current.keys() & previous.keys():
old, new = previous[pid], current[pid]
if old["price"] != new["price"]:
print("PRICE ", old["price"], "->", new["price"], new["title"])
if new["sold"] > old["sold"]:
print("SOLD +", new["sold"] - old["sold"], new["title"])
Run it once a week (a cron job is enough). For the pet stroller above, a PRICE line would show 70.17 and the new price.
Things to know
- Responses are cached for up to 7 days, so compare snapshots at least a week apart. Daily runs will mostly see the same data.
- Sold count is TikTok's displayed total, rounded for larger numbers (
"1.2K"). The difference between two snapshots is an estimate of units sold in between, not an exact count. - US storefront only. Prices are in USD.
- Errors:
400for a bad parameter,429past your plan's rate limit,502when TikTok fails after our retries. - This API is unofficial. It reads public storefront data and isn't affiliated with TikTok.
Next steps
- Export the reviews of a seller's best sellers.
- Search products by keyword to find the sellers in a niche.
- Full API reference.
Plans
A free plan covers testing. Plans, monthly quotas and rate limits are listed on the RapidAPI listing. For more volume or a custom plan, use Contact provider on the listing.