Blog/Developer Tools/

Short-Video Trends API: Douyin + Kuaishou

Aggregate Douyin and Kuaishou hot lists into one normalized trend feed with a single SandBase API key — read both boards, map to one schema, no login, no SDK.

Dark cinematic render of Douyin and Kuaishou hot-list boards merging into one normalized short-video trend feed feeding an agent core

If you track short-video trends in China, you watch more than one platform. Douyin and Kuaishou each surface a hot list, but their payloads look nothing alike — Douyin wraps its ranked videos in a code/data.objs envelope, while Kuaishou returns its board as a plain list. Reconcile those by hand and you spend your time on parsing quirks instead of on the trend signal itself.

This tutorial builds a small aggregator: one function reads both hot lists through the SandBase public-data API, normalizes them into a single schema, and hands your agent one ranked feed. One SandBase API key, no platform login, no SDK. Each endpoint’s API reference is the source of truth for its parameters and for the response envelope (id/status/model/outputs[0].data); the business-payload field names shown below are an illustrative shape, not a guaranteed schema, so confirm them against a live response.

For the full endpoint surface behind each platform, see the Douyin public data API and Kuaishou public data API hubs. Ready to build? Get a SandBase API key.

Key takeaway

  • Two hot-list endpoints — douyin/billboard/hot-total-high-like-list and kuaishou/web/hot-list-v1 — read through one API key.
  • Both use the same { id, status, model, outputs } envelope, so one status check and one HTTP helper cover both.
  • The payloads differ: Douyin nests ranked videos under data.data.objs; Kuaishou returns a list directly under data. Normalize both into one row shape.
  • Public, read-only data only. No posting, no login on your side; authenticate with a SandBase API key.

The plan

Three steps, each a plain REST call plus a small mapper:

  1. Read the Douyin board. Call douyin/billboard/hot-total-high-like-list and pull ranked videos from data.data.objs.
  2. Read the Kuaishou board. Call kuaishou/web/hot-list-v1 and read the list directly under data.
  3. Normalize and merge. Map each platform’s fields into one row shape, tag the source, and return a single feed.

SandBase Douyin API page showing the billboard endpoints and their paths The Douyin API page on SandBase — the billboard cluster includes the hot-total-high-like-list endpoint.

Step 1 — read the Douyin hot list

Every SandBase Model API call is a POST to /v1/api/<vendor>/<path> with your key in the Authorization header. Branch on status before reading outputs, and — because Douyin wraps its result — check the upstream code before reading data.objs.

import os
import requests

SANDBASE = "https://api.sandbase.ai/v1/api"
HEADERS = {
    "Authorization": f"Bearer {os.environ['SANDBASE_API_KEY']}",
    "Content-Type": "application/json",
}


def call(path: str, body: dict) -> dict:
    resp = requests.post(f"{SANDBASE}/{path}", headers=HEADERS, json=body, timeout=90)
    resp.raise_for_status()
    out = resp.json()
    if out.get("status") != "completed":
        raise RuntimeError(out.get("error", {}).get("message", f"{path} did not complete"))
    return out["outputs"][0]["data"]


def douyin_hot() -> list[dict]:
    data = call("douyin/billboard/hot-total-high-like-list", {})
    if data.get("code") != 0:
        raise RuntimeError(data.get("message", "douyin upstream error"))
    # The reference only guarantees the envelope; business fields vary by endpoint — confirm against a live response.
    objs = data.get("data", {}).get("objs", [])
    return [
        {
            "source": "douyin",
            "rank": i + 1,
            "title": o.get("item_title"),
            "author": o.get("nick_name"),
            "metric": o.get("like_cnt"),
            "url": o.get("item_url"),
        }
        for i, o in enumerate(objs)
    ]

The block below is an illustrative response shape — the field names are not a guaranteed schema, so confirm them against a live response, since payloads vary and change over time:

{
  "id": "5494b9ed-3c71-4b9b-a1ee-b248ffaa6fae",
  "status": "completed",
  "model": "douyin/billboard/hot-total-high-like-list",
  "outputs": [
    {
      "data": {
        "code": 0,
        "message": "…",
        "data": {
          "objs": [
            { "item_id": "…", "item_title": "…", "nick_name": "…", "like_cnt": 0, "item_url": "…" }
          ]
        }
      }
    }
  ]
}

SandBase API reference for the Douyin billboard endpoint, showing the vendor-qualified URL and response schema The endpoint API reference is the source of truth for each parameter name and response path.

Step 2 — read the Kuaishou hot list

Kuaishou returns its board as a list directly under data — no inner code wrapper — so the mapper is a touch simpler. Reuse the same call helper:

def kuaishou_hot() -> list[dict]:
    items = call("kuaishou/web/hot-list-v1", {})
    # The reference only guarantees the envelope; business fields vary by endpoint — confirm against a live response.
    return [
        {
            "source": "kuaishou",
            "rank": it.get("rank"),
            "title": it.get("name"),
            "author": None,
            "metric": it.get("viewCount"),
            "url": None,
        }
        for it in items
    ]

Here the payload under data is the list of ranked topics itself — each item carries a name and a rank. Because call already branched on status, the mapper only shapes fields.

SandBase API reference for the Kuaishou hot-list-v1 endpoint, showing the response schema Kuaishou’s hot-list-v1 returns its board as a list directly under data.

Step 3 — normalize and merge

Both mappers already emit the same row shape: source, rank, title, author, metric, url. Merging is now trivial — concatenate, and optionally interleave by rank so the top of each board rises to the top of the combined feed:

def combined_trends() -> list[dict]:
    rows = douyin_hot() + kuaishou_hot()
    # keep each platform's own ranking, group by source
    rows.sort(key=lambda r: (r["source"], r["rank"] if isinstance(r["rank"], int) else 999))
    return rows


for row in combined_trends()[:10]:
    print(f"[{row['source']}] #{row['rank']} {row['title']}")

The payoff is that the rest of your pipeline — dedup, keyword tagging, storage, alerting — sees one uniform row, not two vendor-specific payloads. Add a third platform later and it slots in behind the same call helper and the same row shape.

Why the uniform envelope matters here

The two boards look different on the wire, but they arrive in the same { id, status, model, outputs } envelope. That means the fragile part — auth, transport, status handling, retries — is written once in call and reused for both. The only per-platform code is the tiny mapper that knows where each board keeps its rows: data.data.objs for Douyin, a list under data for Kuaishou. When a payload shifts, you fix one mapper, not a whole scraper.

That separation is what makes the aggregator easy to grow. Swap in another hot-list endpoint, write a five-line mapper to the same row shape, and every downstream consumer keeps working unchanged. Your attention stays on what the trends mean, not on reconciling envelopes. When you need more than the hot list — a video’s detail, a creator’s profile — check each platform’s hub for the endpoint that fits and confirm its parameters before wiring it in.

Limitations and boundaries

  • Public, read-only data only. No posting, following, or private/account-only data.
  • Payload shapes differ and change. Douyin nests under data.data.objs with an upstream code; Kuaishou returns a list under data. Inspect a real response and read each schema first.
  • Rate and volume. Treat responses as best-effort reads; retry with backoff on transient errors such as HTTP 429.
  • Verify endpoints against the live reference. Availability and fields can change; confirm before building on a specific endpoint.
  • Not an official partnership. SandBase provides uniform access to public data; respect each platform’s terms and applicable rules.

FAQ

Do I need a Douyin or Kuaishou login? No. You authenticate to SandBase with your SANDBASE_API_KEY. These read endpoints need no platform account or OAuth on your side.

Why does the Douyin mapper check code but the Kuaishou one doesn’t? Douyin wraps its result in an upstream code/data object, so you check code == 0 before reading data.objs. Kuaishou returns the list directly under data. Always inspect one real response per endpoint.

Can I add a third platform? Yes. Reuse the call helper, write a mapper to the same row shape, and concatenate. That is the whole point of normalizing early.

Build it

Create a SandBase API key, run the two hot-list calls, and normalize into one feed. When you are ready: