Bilibili Creator Research API Workflow | SandBase
Build a Bilibili creator-research workflow: read trends, search a topic, and profile the creator — one SandBase key, no Bilibili login.

Creator research on Bilibili comes down to three moves: see what is trending, search a topic for videos and creators, and profile the creator behind the content. This tutorial wires those three moves into one Bilibili creator research API workflow using the SandBase Bilibili API — no Bilibili login, no scraper. The endpoint API reference is the source of truth for each parameter and for the response envelope; the business-payload field names shown below are an illustrative shape, not a guaranteed schema, so confirm them against a live response for the endpoint you call.
If you want the full endpoint tour first, start with the Bilibili public data API hub. This piece is the applied workflow.
Key takeaway
- Three steps:
hot-search(trends) →search-all(topic) →user-profile(creator).- Every call is
POST /v1/api/bilibili/<path>with oneSANDBASE_API_KEY; responses share the{ id, status, model, outputs }envelope.- Bilibili endpoints wrap their payload in an upstream
{ code, data, message }object, so the useful fields sit underdata.data.- Public, read-only data only, with no posting on your side. No Bilibili login or OAuth is required for this public-data workflow; a SandBase API key is still required.
The workflow at a glance
| Step | Endpoint | Input | You get |
|---|---|---|---|
| 1. Read trends | bilibili/web/hot-search | required limit (integer) | a ranked list of trending keywords |
| 2. Search the topic | bilibili/app/search-all | keyword | matching videos and creators |
| 3. Profile the creator | bilibili/web/user-profile | uid | name, level, signature |
The endpoint API reference is the source of truth for each parameter name and response path.
Step 1 — Read the trending board
Start with a shared helper that branches on status and unwraps the upstream data, then read the hot-search board:
import os
import requests
BASE = "https://api.sandbase.ai/v1/api/bilibili"
HEADERS = {
"Authorization": f"Bearer {os.environ['SANDBASE_API_KEY']}",
"Content-Type": "application/json",
}
def call(path: str, payload: dict) -> dict:
resp = requests.post(f"{BASE}/{path}", headers=HEADERS, json=payload, timeout=90)
resp.raise_for_status()
body = resp.json()
if body.get("status") != "completed":
raise RuntimeError(body.get("error", {}).get("message", "request did not complete"))
# The reference guarantees only the envelope; business fields vary by
# endpoint, so unwrap defensively and confirm against a live response.
data = body["outputs"][0]["data"]
# Bilibili wraps the payload in an upstream { code, data, message } object
return data.get("data", {})
trending = call("web/hot-search", {"limit": 10}).get("trending", {}).get("list", [])
for item in trending[:5]:
print(item.get("keyword"), item.get("heat_score"))
Each item carries a keyword and a heat score. The field names are not a guaranteed schema — confirm them against a live response, since the board changes constantly.
Step 2 — Search a topic
Pick a keyword and pull matching videos and creators. search-all takes a keyword and returns a list under data.item:
results = call("app/search-all", {"keyword": trending[0].get("keyword")})
# the reference guarantees only the envelope; business fields vary by
# endpoint, so use .get() and confirm against a live response
items = results.get("item", [])
# each item carries a goto/type, a uri, and author info;
# read the schema and inspect a real response to map the fields you need
print(len(items), "results")
The search-all response nests its list under data.item, with a pagination block for the next page. Inspect one real response to map where video ids and creator ids sit before you iterate.
search-all returns a list under data.item — read the schema before iterating.
Step 3 — Profile the creator
For a creator id (uid) you surface in step 2, attach account context. user-profile takes a uid:
creator = call("web/user-profile", {"uid": "946974"})
# the reference guarantees only the envelope; business fields vary by
# endpoint, so use .get() and confirm against a live response
print(creator.get("name"), creator.get("level"))
The profile read returns fields like name, level, sign, and sex. The block below is an illustrative response shape — the field names are not a guaranteed schema, so treat them and the values as examples and confirm them against a live response:
{
"id": "cbc4c9c1-84e7-4ea6-9e3f-f5e759c11475",
"status": "completed",
"model": "bilibili/web/user-profile",
"outputs": [
{
"data": {
"code": 0,
"data": { "mid": "946974", "name": "…", "level": 6, "sign": "…" }
}
}
]
}
Note the double nesting: outputs[0].data is the SandBase envelope payload, and the useful profile sits under its inner data. The call helper above already unwraps one level for you.
user-profile returns name, level, and signature under the upstream data object.
Putting it together
A minimal creator-research pass looks like this:
# the reference guarantees only the envelope; business fields vary by
# endpoint, so use .get() and confirm against a live response
trending = call("web/hot-search", {"limit": 10}).get("trending", {}).get("list", [])
report = []
for topic in trending[:10]:
hits = call("app/search-all", {"keyword": topic.get("keyword")})
report.append({
"topic": topic.get("keyword"),
"heat": topic.get("heat_score"),
"result_count": len(hits.get("item", [])),
})
# for creator uids you extract from hits, call user-profile per the schema
Because every call shares the same envelope and the same call helper (including the data.data unwrap), adding retries or rate-limit backoff is a one-place change. When you need more than these reads, check the live Bilibili listing for the endpoint that fits and confirm its parameters before wiring it in.
Handling the rough edges
- Mind the double nesting. Bilibili passes through an upstream
{ code, data, message }object, so the useful payload sits underdata.data. Unwrap it once in your helper. - Inspect
search-allbefore iterating. Its list sits underdata.itemand is structured, not flat — map the fields from a real response and page via itspaginationblock. - Branch on
status. Afailedortimeoutrun carrieserrorand nooutputs. Thecallhelper already enforces this. - Respect rate limits. As a client-side resilience measure, retry with backoff on transient errors such as HTTP 429.
- Public data only. No login, posting, or private/account-only content.
Composing the workflow
The same uniform envelope keeps this composable. Swap the trending keyword for any topic, add a fourth read — a video’s comments, say — and it slots in behind the same call helper with the same status check and unwrap. You can also fan the middle step out: for each of the top N trending keywords, run search-all and collect the counts into one table, so a single pass gives you a ranked snapshot of what is trending and how much content each topic carries. Because the reads share one shape, moving from a quick script to a scheduled job is mostly a matter of adding backoff and a place to store each pass.
Why run this at the API layer
You could open Bilibili in a browser and copy numbers by hand, but that does not scale and it does not give you structured data to trend. Running these three calls on a schedule turns qualitative browsing into a measurable signal: heat scores you can chart over time, topics you can dedupe, and creators you can weight by level and following. Because the calls return named JSON fields behind one consistent unwrap, each pass drops cleanly into a table you can diff against the last one — new trending topics, new creators surfacing on a keyword, and shifts in what the platform is watching.
FAQ
Do I need a Bilibili login or OAuth?
No. You authenticate to SandBase with your SANDBASE_API_KEY, and these three reads need no Bilibili account or OAuth on your side. You still supply a SandBase API key to call them.
Why does hot-search need a limit and why unwrap data.data?
hot-search takes a limit parameter for how many trending items to return, so pass it explicitly. And because Bilibili passes through an upstream { code, data, message } object, the useful payload sits under data.data — unwrap that one level in your call helper. Confirm both against the endpoint reference.
How do I page through search-all results?
Pass the pagination request parameter that search-all accepts on its next call rather than assuming a fixed page size. Check the endpoint reference for the exact parameter name and confirm it against a real response before you loop.
Next steps
You now have a repeatable creator-research workflow built on three public, read-only calls.
- Read the Bilibili public data API hub
- Get a SandBase API key
- API references: hot-search, search-all, and user-profile