Blog/Developer Tools/

Douyin Billboard Monitoring API Tutorial

Monitor Douyin's high-like billboard and resolve each video to a stable aweme_id with one SandBase API key — poll the board, dedup by ID, no login, no SDK.

Dark cinematic render of a Douyin billboard resolving into stable video IDs feeding an agent core

Douyin’s billboards surface what’s spiking right now — but a monitoring job needs more than a snapshot. You want to poll the board on a schedule, recognize which videos you’ve already seen, and key everything to a stable identifier so your dedup and storage stay clean across runs. This tutorial wires exactly that: poll the high-like billboard, resolve each entry to a canonical aweme_id, and hand your agent a deduplicated feed.

One SandBase API key, no Douyin 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 Douyin endpoint surface, see the Douyin public data API hub. Ready to build? Get a SandBase API key.

Key takeaway

  • Two endpoints — douyin/billboard/hot-total-high-like-list and douyin/web/aweme-id — read through one API key.
  • The billboard nests its ranked videos under data.data.objs with an upstream code; each item carries item_id, item_title, nick_name, and like_cnt.
  • Resolve a video URL to a canonical aweme_id so dedup and storage key off one stable identifier.
  • Public, read-only data only. No posting, no login on your side; authenticate with a SandBase API key.

The plan

  1. Poll the billboard. Call douyin/billboard/hot-total-high-like-list, check the upstream code, and read ranked videos from data.data.objs.
  2. Resolve stable IDs. For each video, call douyin/web/aweme-id to get a canonical aweme_id.
  3. Dedup and diff. Keep a set of seen IDs; emit only the entries that are new since the last run.

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

Step 1 — poll the billboard

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 the billboard 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 billboard() -> 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.
    return data.get("data", {}).get("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": "d4727a3b-18ea-403d-b90b-db093210ecac",
  "status": "completed",
  "model": "douyin/billboard/hot-total-high-like-list",
  "outputs": [
    {
      "data": {
        "code": 0,
        "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 — resolve a stable aweme_id

Board entries carry an item_id, but for monitoring you want the canonical aweme_id that a share URL resolves to — it’s the identifier you’ll dedup and store against. Feed the video URL to douyin/web/aweme-id, which returns the ID as a plain string:

def resolve_aweme_id(video_url: str) -> str:
    return call("douyin/web/aweme-id", {"url": video_url})

Because call already branched on status, this endpoint returns the ID string directly under data. Pass a Douyin video or share URL and you get back a canonical ID you can trust across runs.

SandBase API reference for the Douyin aweme-id endpoint, showing the URL-to-ID resolution The aweme-id endpoint resolves a video URL to a canonical aweme_id string.

Step 3 — dedup and diff across runs

Now the monitoring loop. Keep a set of seen IDs between runs (in memory here; back it with a store in production). Each poll, resolve IDs, drop anything you’ve seen, and emit only the new entries:

SEEN: set[str] = set()


def poll_new() -> list[dict]:
    fresh = []
    # The reference only guarantees the envelope; business fields vary by endpoint — confirm against a live response.
    for i, obj in enumerate(billboard()):
        item_url = obj.get("item_url")
        if not item_url:
            continue
        aweme_id = resolve_aweme_id(item_url)
        if aweme_id in SEEN:
            continue
        SEEN.add(aweme_id)
        fresh.append(
            {
                "aweme_id": aweme_id,
                "rank": i + 1,
                "title": obj.get("item_title"),
                "author": obj.get("nick_name"),
                "likes": obj.get("like_cnt"),
            }
        )
    return fresh


for entry in poll_new():
    print(f"NEW #{entry['rank']} {entry['title']} — {entry['author']} ({entry['likes']} likes)")

Run it on a schedule and each execution prints only what changed. Because everything keys off the canonical aweme_id, a video that stays on the board across polls is recognized as the same item — no duplicate alerts, no double storage.

Why the uniform envelope matters here

Both endpoints arrive in the same { id, status, model, outputs } envelope, so the fragile part — auth, transport, status handling, retries — lives once in call and is reused for both. The billboard nests its rows under data.data.objs behind an upstream code; the aweme-id endpoint returns a bare string under data. Those are the only two per-endpoint facts your code needs to know, and both are visible in one real response.

That separation keeps the monitor easy to extend. Want to enrich each new entry with more detail? Add one more call behind the same helper, key it by the aweme_id you already resolved, and the dedup logic doesn’t change. Your attention stays on the monitoring signal — what’s newly trending — instead of on parsing quirks. When you need more than the board and the ID, check the Douyin 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 shape and upstream code. The billboard nests under data.data.objs behind code; aweme-id returns a string under data. Inspect a real response and read the schema first.
  • Rate and volume. Treat responses as best-effort reads; retry with backoff on transient errors such as HTTP 429, and pace your polling.
  • 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 Douyin’s terms and applicable rules.

FAQ

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

Why resolve an aweme_id when the board already has item_id? The canonical aweme_id is the identifier a share URL resolves to — using it for dedup and storage keeps your keys consistent regardless of which URL form you started from.

How often should I poll? Pace it to your needs and handle transient errors with backoff. Treat the board as a best-effort read, not a real-time stream.

Build it

Create a SandBase API key, poll the billboard, resolve IDs, and diff across runs. When you are ready: