{
  "openapi": "3.0.1",
  "info": {
    "title": "Bilibili Scraper — Videos, Comments, Danmaku & Creator Uploads",
    "description": "Bilibili (哔哩哔哩, B站) scraper API for China social media video: search, video details, comment threads, Chinese influencer uploads and creator profiles (followers, likes), the popular/trending board — plus danmaku (弹幕), the bullet comments scrolling over each video. No cookie needed.",
    "version": "1.3",
    "x-build-id": "aGmShZbIEc9B3bBSD"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/zhorex~bilibili-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-zhorex-bilibili-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/zhorex~bilibili-scraper/runs": {
      "post": {
        "operationId": "runs-sync-zhorex-bilibili-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/zhorex~bilibili-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-zhorex-bilibili-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "required": [
          "mode"
        ],
        "properties": {
          "mode": {
            "title": "Mode",
            "enum": [
              "search",
              "video_detail",
              "video_comments",
              "user_videos",
              "user_profile",
              "popular"
            ],
            "type": "string",
            "description": "Pick ONE per run; no login or cookie in any mode. search: videos matching searchQuery (known Latin brand names also search their Chinese name). Comments for a keyword in ONE run: search + includeComments:true (each comment is its own row; keep maxResults small, e.g. 5-10 videos). video_detail / video_comments: need videoUrls (URL or BV id). user_videos / user_profile: need userIds (numeric mid or space.bilibili.com link). popular: trending videos, optional category.",
            "default": "search"
          },
          "searchQuery": {
            "title": "Search query",
            "type": "string",
            "description": "Search query — works in Chinese (人工智能) and English (AI tutorial). Used in 'search' mode."
          },
          "autoLocalize": {
            "title": "Auto-localize brand names (search mode)",
            "type": "boolean",
            "description": "When on, a Latin brand name (e.g. 'Nike') also searches its Chinese name ('耐克') and results are merged — so you get full native recall even when searching in English. Capped at maxResults (no extra cost). Add your own variants via Search aliases below.",
            "default": true
          },
          "searchAliases": {
            "title": "Search aliases (search mode)",
            "type": "array",
            "description": "Extra term variants to also search and merge with your query — e.g. add '耐克' for Nike, or alternate spellings. Useful for brands not in the built-in dictionary.",
            "items": {
              "type": "string"
            }
          },
          "sortOrder": {
            "title": "Sort order (search mode)",
            "enum": [
              "totalrank",
              "click",
              "pubdate",
              "dm",
              "stow",
              "scores"
            ],
            "type": "string",
            "description": "How to sort search results. Used in 'search' mode.",
            "default": "totalrank"
          },
          "durationFilter": {
            "title": "Duration filter (search mode)",
            "enum": [
              "any",
              "short",
              "medium",
              "long",
              "verylong"
            ],
            "type": "string",
            "description": "Filter search results by video duration. Used in 'search' mode.",
            "default": "any"
          },
          "pubtimeBegin": {
            "title": "Published after (search mode)",
            "type": "string",
            "description": "Only videos published on or after this date (or moment). Leave empty for no filter. Used in 'search' mode. Accepted formats: 2026-04-15 (read as 00:00 UTC that day), 2026/04/15, 20260415, 2026-04-15 10:30 or 2026-04-15 10:30:00, and ISO 8601 date-times with or without a zone, e.g. 2026-04-15T10:30:00Z or 2026-04-15T18:30:00+08:00. A time without a zone is read as UTC — add +08:00 for Beijing time. A value the Actor cannot read (e.g. 15-04-2026 or -7d) is NOT applied: the run searches without it and says so in the run status."
          },
          "pubtimeEnd": {
            "title": "Published before (search mode)",
            "type": "string",
            "description": "Only videos published on or before this date (or moment). Leave empty for no filter. Used in 'search' mode. Same formats as 'Published after': 2026-04-15, 2026/04/15, 20260415, 2026-04-15 10:30 or 2026-04-15 10:30:00, and ISO 8601 date-times with or without a zone, e.g. 2026-04-15T10:30:00Z or 2026-04-15T18:30:00+08:00; a time without a zone is read as UTC. A bare date means 00:00 UTC at the START of that day, so 2026-04-15 leaves out videos published later on the 15th — use 2026-04-15T23:59:59Z to include the whole day. A value the Actor cannot read (e.g. 15-04-2026 or -7d) is NOT applied: the run searches without it and says so in the run status."
          },
          "deltaMode": {
            "title": "Delta mode — only NEW videos since last run (search + popular)",
            "type": "boolean",
            "description": "Turn a one-off pull into a recurring monitor: when on, each run returns only videos not seen in previous runs (deduped by video ID across runs, per Delta state key). Pair it with an Apify Schedule for a hands-off daily/weekly 'what's new' feed — you pay only for genuinely new videos. Works in 'search' mode (new videos for your keyword) and 'popular' mode (new videos trending now that weren't before). Tip: in search mode set Sort order to 'Newest first' for monitoring. <b>Know the trade before you switch it on:</b> a full run returns the whole current result set and bills for it; a delta run returns only what has appeared since the last run. On fast-moving boards (trending, hot search, popular) that is still a healthy batch every run. On a narrow keyword or a single creator it can be a handful of rows, or none on a quiet day — which is the feature working, not a broken run. Pick delta when you want to be told what is NEW; leave it off when you want the data itself.",
            "default": false
          },
          "deltaStateKey": {
            "title": "Delta state key (search + popular mode)",
            "type": "string",
            "description": "Names the saved 'already-seen' state so independent monitors don't collide. Use a distinct key per query/category you track (e.g. 'nike-weekly', 'game-trending'). Only used when Delta mode is on.",
            "default": "default"
          },
          "videoUrls": {
            "title": "Video URLs or BVIDs",
            "type": "array",
            "description": "Bilibili video URLs or BV IDs. Used in 'video_detail' and 'video_comments' modes. Example: https://www.bilibili.com/video/BV1xx411c7mD or BV1xx411c7mD",
            "items": {
              "type": "string"
            }
          },
          "userIds": {
            "title": "User IDs (mid)",
            "type": "array",
            "description": "Bilibili user numeric IDs (mid). Used in 'user_profile' and 'user_videos' modes. Find a user's mid in their profile URL: space.bilibili.com/{mid} — pasting that link works too; a channel name does not. <b>In 'user_profile' mode</b> each creator returns ONE row — followers, following, video count, total likes, level, verification and bio — from one request per creator, no search involved; maxResults caps how many profiles a run returns, and an id Bilibili has no profile for is named in the run status and not charged. Schedule it daily or weekly to track follower growth. <b>How 'user_videos' mode works, so the row count is not a surprise:</b> Bilibili's direct space listing is behind risk control that rejects plain HTTP requests, so the Actor searches the creator's channel name and keeps the videos whose mid matches. It reads that search by date first, where the matches are the creator's newest uploads. When other people's videos crowd it (a famous name is on many fans' clips), the rest come from the relevance-ranked search, which favours the creator's best-known videos and can skip recent uploads — the run status says when that happened. Delivered newest first; for a well-known creator expect about 20 videos, not a complete upload list. For a wider sweep use 'search' mode with the creator's name.",
            "items": {
              "type": "string"
            }
          },
          "category": {
            "title": "Category for popular/trending",
            "enum": [
              "all",
              "animation",
              "music",
              "dance",
              "game",
              "knowledge",
              "tech",
              "sports",
              "car",
              "life",
              "food",
              "animal",
              "fashion",
              "entertainment"
            ],
            "type": "string",
            "description": "Filter popular videos by category. Used in 'popular' mode.",
            "default": "all"
          },
          "maxResults": {
            "title": "Max results (raise for bulk monitoring)",
            "minimum": 1,
            "maximum": 5000,
            "type": "integer",
            "description": "Maximum number of results to scrape. Typical patterns: 20-50 for a quick lookup, 100-300 for daily creator / category monitoring, 500 for a full keyword sweep or content-analytics pull. Higher values automatically walk more result pages. See the Pricing tab for per-result cost.",
            "default": 100
          },
          "includeComments": {
            "title": "Also scrape comments for each video",
            "type": "boolean",
            "description": "In <b>search</b>, <b>user videos</b> and <b>popular</b> modes, also pull the comment thread of every video found and return each comment as its own row (<code>type: comment</code>, with <code>likeCount</code>, <code>replyCount</code> and author). Comments are where the audience opinion is — the video is the prompt, the replies are the sentiment. No login or cookie needed. Cap it with <b>Max comments per video</b>, and note that <b>this multiplies the rows a run returns, and therefore what it costs</b>: 100 videos × 20 comments is ~2,100 rows instead of 100. Off by default.",
            "default": false
          },
          "includeReplies": {
            "title": "Expand comment threads (reply chains)",
            "type": "boolean",
            "description": "Each top-level comment can carry a thread of replies. Turn this on and every reply becomes its own row, tagged with <code>rootRpid</code> and <code>isThreadReply</code> so you can rebuild the conversation. The flag reads the replies Bilibili inlines in the same response AND pages any further ones the root declares in <code>replyCount</code>, one request per thread, stopping as soon as your <code>maxComments</code> is met. <b>How much it adds depends on the video:</b> a popular root measured on 2026-09-16 declared 30 replies and returned 10 on its first page, while quiet roots declare none — and then no extra request is made and no rows are added. <b>Caveats:</b> replies bill as ordinary comment rows, and videos with comments closed return none at all. Off by default.",
            "default": false
          },
          "followUpComments": {
            "title": "Follow-up comments on earlier videos (search + Delta mode + comments, charged per row)",
            "type": "boolean",
            "description": "Off by default. Only for <b>search</b> mode with <b>Delta mode</b> and <b>Also scrape comments</b> on. A delta search reads each video's comments once, when it first finds the video — usually hours after upload, while the thread is still growing — and never again. With this on, the videos each delta run delivers are remembered (in a record of their own beside the Delta state key's list of seen videos, which is left as it was, with the time of the newest comment delivered for each), and a later delta run re-reads each one's comment thread <b>once</b>: on the first run at least 24 hours after the video was published and 12 hours after it was delivered, before it is 7 days old, newest videos first. Only comments posted after the newest comment already delivered for that video come back (all of them if none was), as ordinary comment rows (<code>type: comment</code>, linked by <code>videoBvid</code>) marked <code>isFollowUp: true</code>, up to <b>Max comments per video</b> per video, from at most 5 pages of each thread (100 top-level comments), ordered like your other comment rows. <b>Cost:</b> each comment row is one result, charged like a video ($0.02); a thread with no new comments adds nothing. At most 200 threads, 300 comment requests and about 4 minutes of re-reading per run, one thread at a time, and never more rows than your run's charge limit can pay for; videos not reached keep their place for the next run. If Bilibili's risk control refuses a request, the re-reads stop for that run without retrying; if a run stops part-way through its re-reads, the videos it was re-reading are not re-read again, so nothing is charged twice. A run takes up to ~4 minutes longer: keep the schedule interval longer than a run lasts. It starts with the videos delivered from the first run with this on.",
            "default": false
          },
          "includeDanmaku": {
            "title": "Add a danmaku profile per video (no cookie needed)",
            "type": "boolean",
            "description": "Danmaku (弹幕) is Bilibili's on-video scrolling commentary — comment text bound to a specific <b>second</b> of the video. With this on, each video also returns one <code>type: danmaku</code> row with <code>danmakuFetched</code>, <code>danmakuUniqueSenders</code>, <code>danmakuPerMinute</code>, the <b>peak minute</b> and its count, first/last timestamps, and an evenly-spread text sample across the whole video. <b>What it is for:</b> second-by-second audience reaction on an ad or a launch, and creator vetting — danmaku-per-minute beats view count as an engagement signal because a danmaku costs the viewer effort. <b>What is counted:</b> Bilibili serves the danmaku it keeps in the video's pool, and that pool is capped, so on busy videos <code>danmakuFetched</code> is lower than the video's own total. The row says so: <code>danmakuTotal</code> is the video's own danmaku count (single-part videos; null on multi-part uploads) and <code>danmakuPoolCapped</code> is true when fewer were fetched than that total. <b>Cost is predictable:</b> this adds ONE row per video, not one per danmaku. Off by default. <b>Billing:</b> each danmaku row is its own <code>danmaku-profile</code> event at <b>$0.05</b> — 2.5x a normal $0.02 row — because it fetches and reduces the danmaku Bilibili keeps in the video's pool (capped; 3,600 of 4,542 on one measured video). Profiling 200 videos costs $10. Off by default.",
            "default": false
          },
          "danmakuSampleSize": {
            "title": "Danmaku text samples per video",
            "minimum": 0,
            "maximum": 500,
            "type": "integer",
            "description": "How many danmaku texts to include in the sample, spread evenly across the video timeline (not the first N, which would all come from the opening seconds). The sample rides inside the single danmaku row, so raising it does not add billable rows. Set 0 for metrics only (no sample texts).",
            "default": 25
          },
          "maxComments": {
            "title": "Max comments per video",
            "minimum": 1,
            "maximum": 1000,
            "type": "integer",
            "description": "Comments per video: used in video_comments mode AND for every video when includeComments is on. Each comment (replies included) is its own $0.02 row, so comments add at most videos x maxComments rows on top of the video rows. Not counted in that: includeDanmaku adds up to one $0.05 row per video, user_videos up to one creator-profile row per creator, and followUpComments (delta search) up to maxComments rows for each earlier video it re-reads.",
            "default": 20
          },
          "sortComments": {
            "title": "Sort comments by",
            "enum": [
              "hot",
              "time",
              "likes"
            ],
            "type": "string",
            "description": "How to sort comments. Used in 'video_comments' mode.",
            "default": "hot"
          },
          "sentimentAnalysis": {
            "title": "Sentiment analysis",
            "type": "boolean",
            "description": "Tag each item with lexicon-based Chinese sentiment — polarity + a -1.0…+1.0 score (comments + video titles/descriptions). Optional add-on — for Chinese text it loads a model, so enable with run memory set to 512 MB or more.",
            "default": false
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}