Explore our Marketplace to buy and sell APIs, services, and social media solutions. Connect with a vibrant community of developers, businesses, and creators!
| 参数 | 说明 |
|---|---|
keyword | 搜索词;空=只筛选不搜索 |
search_type | 1=按笔记关键词找博主(默认)0=按昵称搜 |
column | 排序字段,共 34 个:comprehensiverank=智能排序(默认) fansCount=粉丝数 price=全部报价 clickNum=阅读中位数-日常 mEngagementNum=互动中位数-日常 accumCommonImpMedinNum30d=曝光中位数-日常 accumPicCommonImpMedinNum30d=图文曝光中位数-日常 accumVideoCommonImpMedinNum30d=视频曝光中位数-日常 pictureClickMidNum=图文阅读中位数-日常 videoClickMidNum=视频阅读中位数-日常 pictureInterMidNum=图文互动中位数-日常 videoInterMidNum=视频互动中位数-日常 thousandLikePercent30=千赞笔记比例 pictureThousandLikePercent30=图文千赞比例 videoThousandLikePercent30=视频千赞比例 hundredLikePercent30=百赞笔记比例 pictureHundredLikePercent30=图文百赞比例 videoHundredLikePercent30=视频百赞比例 videoFinishRate=视频完播率 accumCoopImpMedinNum30d=曝光中位数-合作 readMidCoop30=阅读中位数-合作 interMidCoop30=互动中位数-合作 mCpuvNum30d=外溢进店中位数-合作 picturePrice=图文报价 videoPrice=视频报价 pictureReadCost=图文预估阅读单价 videoReadCost=视频预估阅读单价 inviteReply48hNumRatio=邀约48h回复率 fans30GrowthRate=粉丝量变化幅度 fansActiveIn28dLv=活跃粉丝占比 fansEngageNum30dLv=互动粉丝占比 kliveCnt30d=近30天直播场次 avgLiveViewerNum=场均观播人数 avgAgmv90d=场均销售额 |
sort | desc 降序(默认)/ asc 升序 |
brand_user_id | 品牌方/广告主用户ID,可不填。带上后按该品牌做个性化推荐与已合作标记 |
similar_user_id | 找相似博主:填一个博主的 userId,返回与他相似的博主,并且可以叠加下面所有筛选 |
similar_word | 配合 similar_user_id 的博主昵称,仅用于回显、不影响结果 |
similar_user_id与/get_blogger_similar是两条不同链路:前者走本接口、能叠加筛选;
后者只按相似度返回、不支持筛选、每页最多 4 条。想「找相似 + 再按粉丝量/报价筛」用前者。
筛选按维度分成 7 组,每组是一个对象:
| 分组 | 覆盖的筛选 |
|---|---|
goal | 营销目标(曝光 / 种草 / 转化 × 指标 × 成本或规模) |
blogger | 博主类目、人设、擅长内容、内容题材、二十大人群、行业特色画像、预估消费行为、笔记类目、性别、地域、签约情况 |
fans | 粉丝量、粉丝年龄、粉丝性别、粉丝地域、婚恋状态、消费水平、母婴阶段、手机价格、手机品牌 |
note | 日常笔记的曝光/阅读/互动中位数、千赞笔记比例、笔记类型 |
coop | 合作报价、合作订单数、合作信用度、近期合作行业与品牌、传播规模、预估 CPM / 阅读单价 / 互动单价、外溢进店单价 |
live | 近 30 天直播场次、场均观播人数、场均销售额 |
flags | 明星、优质博主、新锐博主、笔记+直播均可合作、意向行业匹配、行业推荐博主、热门活动、剔除低活/掉粉/已合作/已邀约 |
数值区间统一写成 {"min": 10000, "max": 50000}。两端都可省略:省略 min 视为 0,
省略 max 视为不限;但两端全空会 422。
筛选值分三类:照表填的固定枚举、要先查字典的、直接填名称的。
填错不会报错,只会安静返回 0 条 —— 所以拿不到结果时先回来核对这一节。
左列是请求 JSON 里的路径,例如 fans.age 就是 {"fans": {"age": 3}}。
| 参数路径 | 对应筛选 | 全部取值 |
|---|---|---|
search_type | 搜笔记 / 搜昵称 | 1=搜笔记(默认)0=搜昵称 |
sort | 排序方向 | desc=降序(默认)asc=升序 |
blogger.gender | 性别 | 男 女 |
blogger.signed | 签约情况 | -1=不限 0=个人博主 1=机构博主 |
fans.age | 粉丝年龄 | 0=不限 1=<18 2=183=254=35~44 5=>44 占比高 |
fans.gender | 粉丝性别 | 0=不限 1=男性占比高 2=女性占比高 |
fans.marital_status | 婚恋状态 | -1=不限 0=未婚 1=已婚 |
fans.consumption_level | 消费水平 | -1=不限 0=低消费 1=中消费 2=高消费 |
fans.child_age(数组) | 母婴阶段 | 0=备孕 2=0-6月 3=7-12月 4=1-3岁 5=4-6岁 6=7-12岁 7=孕早期 8=孕晚期 |
fans.device_price(数组) | 手机价格 | 0-999 1000-1999 2000-2999 3000-3999 4000-4999 5000-5999 6000-6999 7000-7999 8000+ |
fans.device_brand(数组) | 手机品牌 | 苹果 华为 OPPO VIVO 荣耀 小米 一加 魅族 中兴 联想 |
note.note_type | 笔记类型 | 0=不限 1=图文笔记为主 2=视频笔记为主 |
coop.industry | 近期合作行业 | 不限 美妆个护 食品饮料 母婴 3c及电器 日用百货 服装配饰 互联网 生活服务 家居建材 汽车 |
column | 排序字段 | 34 个,见上面第 1 节的排序字段表 |
goal.market_target | 营销目标 | 见该字段在 Swagger 中的说明(曝光/种草/转化 × 指标 × 成本或规模) |
下列筛选的可选项由平台维护、会随运营调整。先调 /get_blogger_filter_options
拿字典,再按对应层级填。字典基本不变,建议缓存后复用,不必每次筛选前都调。
这些参数的写法不统一,下表是唯一可靠的对照。 大多数是「裸名、任意层级、可混填」,
但有三个例外(audience20 要复合串、note_scene / consumer_behavior 要全路径、
industry_crowd 要数字 ID),照着别的参数的写法填会失败。
| 参数路径 | 对应筛选 | 字典键 | 填什么 | 能填哪一层 |
|---|---|---|---|---|
blogger.content_tag | 博主类目 | contentTags | 裸名 | 一级 / 二级,可混填 |
blogger.personal_tags | 博主人设 | personalTagsV2 | 裸 tagName | 一级 / 二级 / 叶子,可混填 |
blogger.feature_tags | 擅长内容 | featureTags | 裸名 | 一级 / 二级,可混填 |
blogger.location | 地域(博主所在地) | areas | 裸地名 | 省 / 市 / 区县,可混填 |
fans.location | 粉丝地域 | fansAreas | 裸地名 | 省 / 市(没有区县),可混填 |
blogger.content_theme | 内容题材 | contentTheme | 裸 label | 只有一级,子项无效 |
blogger.audience20 | 二十大人群 | audience20 | 一级裸名,或 "一级 一级-二级" | 二级必须用复合串 |
blogger.note_scene | 笔记类目 | contentScenes | 含根节点的空格全路径 | 任意深度 |
blogger.consumer_behavior | 预估消费行为 | consumerBehavior | 含根节点的空格全路径 | 任意深度 |
blogger.industry_crowd | 行业特色画像 | automotiveIndustryTag | 最深层叶子的 value(数字 ID) | 只有叶子 |
flags.industry_first / industry_second | 行业(不是博主类目) | industryTags | 裸名,单值不是数组 | 一级 / 二级各一个 |
flags.activity_codes | 热门活动 | activities | 每项的 code | — |
数组型参数一律是「或」,多个值取并集去重;上限 30 项、单项 64 字符。
| 失败形态 | 表现 | 哪些参数 |
|---|---|---|
| 过度过滤 | total=0,看起来像「真的没有博主」 | 除 consumer_behavior 外的全部 |
| 静默不过滤 | 返回未经筛选的结果,你以为筛了其实没筛 | consumer_behavior |
实测(粉丝 100 万~120 万,无其它筛选时 total=455):
content_tag=["护肤品"](多一个字)→ total=0fans.location=["浙江省"](多一个「省」)→ total=0audience20=["挑战极限者"](照字典 label 裸填)→ total=0note_scene=["选车攻略"](只填末段,没填全路径)→ total=0industry_crowd=["身心疗愈兴趣人群"](填了 label 不是数字 ID)→ total=0consumer_behavior=["保时捷911"](裸叶子名)→ total=455,等于没筛["zzz不存在zzz"] 同样 455。只有全路径写法才真的生效["内容行为预估 汽车"] → 54)。看到 total=0 时先怀疑取值写法,再怀疑「真的没有符合条件的博主」;
看到结果多得可疑时,检查是不是 consumer_behavior 被静默忽略了。
所有取值都应从 /get_blogger_filter_options 里原样复制,不要手写。
blogger.content_tag)怎么多选网页上勾了好几个类目、有的勾「全部」有的只勾几个子项时,就按下面这样填。
contentTags 是两层树:taxonomy1Tag 是一级(美妆、护肤、个人护理…共 28 个),
taxonomy2Tags[] 是它的子项。一级名和二级名混在同一个扁平数组里就行,
不用分开传、也没有嵌套结构。二级名全局唯一、也不与一级名重名,所以不会有歧义。
举个网页上的实际场景:美妆勾了「全部」,护肤只勾了 2 个子项,个人护理也勾了 2 个:
{"blogger": {"content_tag": ["美妆", "面部保养", "面部清洁", "头发产品", "身体护理"]}}
其中 美妆 是一级名(= 该类目全部),后面四个是二级名
(面部保养/面部清洁 属于护肤,头发产品/身体护理 属于个人护理)。
实测规则(固定同一组粉丝量条件、只改类目,对比返回的 total):
| 传什么 | total | 说明 |
|---|---|---|
["面部保养"] | 200 | — |
["面部清洁"] | 2 | — |
["面部保养","面部清洁"] | 201 | 多个之间是「或」,并集去重(有 1 个博主两个都占) |
["护肤"] | 379 | 一级名 = 该类目全部 |
| 护肤的全部 4 个二级 | 328 | 比一级名少 51 个 |
["护肤","面部保养"] | 379 | 父子同填不报错,但子项本就被父项包含,等于只填父 |
["护肤品"](错别字) | 0 | 静默返回 0 条、不报错 |
两条要特别注意:
/get_blogger_filter_options 的contentTags 里原样复制,别手写。看到 total=0 时先怀疑名字,再怀疑「真的没有博主」。数组最多 30 项,单个标签名最长 64 字符。
blogger.location / fans.location)怎么多选和类目一样是扁平的:直接填地名,省和市可以混在一起,多个之间是「或」。
网页上浙江勾了「全部」,江苏只勾了 2 个市,广东也勾了 2 个:
{"fans": {"location": ["浙江", "南京", "苏州", "广州", "深圳"]}}
要选整个省就填省名,不用展开成它下面所有的市。博主地域多一层区县,写法一样:
{"blogger": {"location": ["上海", "杭州", "上城区"]}}
上游对这两个字段的要求其实相反(博主地域吃裸地名、粉丝地域吃完整路径
"中国 浙江 杭州"),上游服务会各自转好,你填裸名就行。fans.location 侧
实际会转成:浙江→中国 浙江(不会展开成 11 个市)、杭州→中国 浙江 杭州、
吉林→中国 吉林 和 中国 吉林 吉林(省市同名,两条都发,「或」)。
字典里没有的名字(如 浙江省)原样透传 → total=0。
注意 fans.location 这棵树只到市,没有区县(blogger.location 才有)。
blogger.audience20)怎么多选这个和上面两个不一样,不是扁平裸名。 二级必须写成 "一级 一级-二级" ——
一级名要出现两次,中间一个空格,后半段用连字符。
网页上自在户外勾了「全部」,自由畅行只勾了 2 个子项,运动焕活也勾了 2 个
(实测 total=169):
{"blogger": {"audience20": [
"自在户外",
"自由畅行 自由畅行-都市漫游家", "自由畅行 自由畅行-静奢新贵",
"运动焕活 运动焕活-轻松健体派", "运动焕活 运动焕活-线条雕塑家"
]}}
注意对比:勾「全部」的那个只写一级裸名,勾了具体子项的每一个都要写成复合串。
实测(基线 455):["自在户外"]=135、["自在户外 自在户外-挑战极限者"]=9、
["挑战极限者"]=0(照字典 label 裸填,静默返回 0)、两个复合值=18(「或」)、
["自由畅行","自在户外 自在户外-挑战极限者"]=176(单独 自由畅行=175,可混填)。
personalTagsV2 是三层树,featureTags 是两层,任意一层都能填、可以混填。
想要整组就填那一组的名字,不用把叶子一个个列出来。
{"blogger": {"personal_tags": ["职业身份", "妈妈", "留学背景"],
"feature_tags": ["风格", "vlog"]}}
实测:personal_tags 一级 职业身份=91、二级 互联网=8、叶子 妈妈=31,
["职业身份","妈妈"]=114(并集去重);feature_tags 一级 形式=183、风格=37,
二级 vlog=136、韩系=19,["风格","vlog"]=150。
blogger.content_theme)—— 只能填一级例外:只有一级 label 有效,填子项一律 0 条。实测 ["通用"]=366,
其子项 ["口播"]=0。想按子项筛只能退而求其次用 feature_tags。
这两个不能填裸名,要把从根节点到目标节点每一层用空格连起来,可以停在任意深度:
{"blogger": {
"note_scene": ["汽车 理性决策", "汽车 理性决策 选车攻略 政策"],
"consumer_behavior": ["内容行为预估 汽车"]
}}
实测 note_scene:["汽车"]=75、["汽车 理性决策"]=23、四级=1,
而只填末段 ["选车攻略"]=0。
⚠️ consumer_behavior 填错的后果和别的参数不一样:不是返回 0 条,而是
整个筛选被静默忽略。实测 ["保时捷911"](裸叶子名)和 ["zzz不存在zzz"]
都返回 455 —— 和完全不加这个筛选一模一样;只有全路径写法才生效
(["内容行为预估 汽车"]=54)。用它时务必用加/不加两次请求的 total 对比确认。
另外这个字典按账号灰度,有些账号取到 null,那就用不了。
blogger.industry_crowd)—— 只能填数字 ID四层树,但只有最深层叶子的 value 有效,而且是数字 ID,不是 label。
实测 ["17121263"]=110,填对应 label ["身心疗愈兴趣人群"]=0。
flags.industry_first / industry_second)—— 单值,不能多选这两个是字符串不是数组,各只能填一个,且要配套使用(二级必须属于一级):
{"flags": {"industry_first": "美妆个护", "industry_second": "护肤"}}
注意这是「行业」,和「博主类目」(blogger.content_tag)是两套完全不同的分类。
| 参数路径 | 对应筛选 | 怎么填 |
|---|---|---|
coop.brand_ids | 近期合作品牌 | 品牌方 userId 列表(平台暂无品牌搜索接口,userId 需自行提供) |
想按粉丝人群圈博主,用
blogger.audience20(二十大人群)而不是coop.brand_ids。
{"min": x, "max": y})| 参数路径 | 对应网页筛选 |
|---|---|
fans.count | 粉丝量 |
note.imp_median / note.read_median / note.inter_median | 日常笔记的曝光 / 阅读 / 互动中位数 |
note.thousand_like_percent | 千赞笔记比例(单位 %) |
coop.pic_price / coop.video_price | 合作报价(图文 / 视频一口价,元) |
coop.order_count | 合作订单数 |
coop.reply_48h_ratio | 合作信用度 → 48h 邀约回复率(%) |
coop.imp_median / coop.read_median / coop.inter_median | 传播规模(合作笔记的中位数) |
coop.cpuv / coop.estimate_cpuv | CPUV / 外溢进店单价 |
coop.pic_cpm / coop.video_cpm | 预估 CPM(图文 / 视频) |
coop.pic_read_price / coop.video_read_price | 预估阅读单价(图文 / 视频) |
coop.pic_engage_cost / coop.video_engage_cost | 预估互动单价(图文 / 视频) |
live.live_count / live.avg_viewer / live.avg_gmv | 近 30 天直播场次 / 场均观播 / 场均销售额 |
true)| 参数路径 | 对应网页筛选 |
|---|---|
flags.is_star / flags.high_quality / flags.new_high_quality | 明星 / 优质博主 / 新锐博主 |
flags.note_and_live | 笔记 + 直播均可合作 |
flags.filter_intention | 意向行业匹配 |
flags.exclude_low_active / flags.exclude_fans_down | 剔除低活 / 掉粉博主 |
flags.exclude_cooperated / flags.exclude_invited | 剔除已合作 / 已邀约博主 |
coop.exclude_brands | 配合 coop.brand_ids:true = 排除与这些品牌合作过的博主 |
此外 flags.industry_first / flags.industry_second 是行业推荐博主的一级/二级行业名,
flags.activity_codes 是热门活动 code(取自 /get_blogger_filter_options 的 activities)。
枚举型字段(性别、签约情况、粉丝年龄等)的完整取值见各字段在 Swagger 中的说明。
本页最下方的 [示例/Example] 区里有 15 条可直接照抄的请求体,覆盖关键词搜索、画像选号、预算/性价比、直播带货、找相似博主、地域投放等场景,外加一节常见错误写法(那几种不会报错、只会安静返回 0 条)。
| 参数 | 取值 | 说明 |
|---|---|---|
page_num | 1-250 | 上游只放出前 5000 条 = 250 页,第 251 页起返回空数组 |
page_size | 1-20 | 蒲公英网页固定 20。建议保持 20:上游会对当页结果做二次过滤(下架、不可合作等),取太小可能整页被过滤成 0 条 |
翻页只改 page_num,其余筛选必须原样保持不变,否则两页口径不一致会重复或遗漏。
total 不是命中总数,是可翻上限,恒为 5000。想缩小结果集请加筛选,而不是指望翻到底。
kols[]: 命中的博主,每条约 140 个字段
userId / name / redId / headPhoto / location / gendercontentTags / featureTags / personalTags / tradeTypefansNum / clickMidNum 阅读中位数 / interMidNum 互动中位数 / fans30GrowthRate 粉丝增速 / kliveCnt30d 直播场次picturePrice / videoPrice / lowerPrice / estimatePictureCpm / cooperateStatetotal: 可翻上限(恒 5000)trackId / highlightWords/get_blogger_filter_options 实时拉取,不要写死resp["data"] 是蒲公英原样信封(含 code/msg/success),业务数据还在它里面一层data = resp["data"]["data"];下面「返回」列的字段都在这一层下data 为 null,如 ID 不存在或筛选无命中)——| Param | Description |
|---|---|
keyword | Search term; empty = filter only |
search_type | 1=find bloggers by note keyword (default), 0=search by nickname |
column | Sort field, 34 options — see the Chinese table above for the full list with labels |
similar_user_id | Find similar bloggers: pass a blogger's userId; results can be combined with every filter below |
similar_word | Nickname echo for similar_user_id; does not affect results |
sort | desc (default) / asc |
brand_user_id | Brand/advertiser user ID, optional; enables brand-specific recommendation and "already cooperated" marks |
| Group | Covers |
|---|---|
goal | Marketing goal (exposure / seeding / conversion × metric × cost or scale) |
blogger | Category, persona, expertise, content theme, top-20 crowds, industry-specific portrait, estimated consumption, note category, gender, location, agency status |
fans | Fans count, age, gender, location, marital status, consumption level, parenting stage, phone price, phone brand |
note | Organic note impression / read / engagement medians, 1000-like ratio, note type |
coop | Quotes, order count, credit score, recent industries & brands, reach scale, estimated CPM / read cost / engagement cost, store-visit cost |
live | Live sessions in last 30d, average viewers, average GMV |
flags | Celebrity, high-quality, rising bloggers, note+live capable, intention match, industry recommendation, campaigns, exclude inactive / fans-declining / already-cooperated / already-invited |
Numeric ranges are written as {"min": 10000, "max": 50000}. Either bound may be omitted
(omitting min means 0, omitting max means unlimited), but omitting both returns 422.
Filter values come in three kinds: fixed enums (copy from the table),
dictionary-backed (fetch first), and plain names.
A wrong value does not raise — it just silently returns 0 results, so check
this section first when a query comes back empty.
| Path | Web filter | Allowed values |
|---|---|---|
search_type | Search notes / nickname | 1=notes (default) 0=nickname |
sort | Sort direction | desc (default) / asc |
blogger.gender | Gender | 男 女 |
blogger.signed | Agency status | -1=any 0=individual 1=agency |
fans.age | Fans age | 0=any 1=<18 2=183=254=35~44 5=>44 |
fans.gender | Fans gender | 0=any 1=male-skewed 2=female-skewed |
fans.marital_status | Marital status | -1=any 0=single 1=married |
fans.consumption_level | Consumption level | -1=any 0=low 1=mid 2=high |
fans.child_age (array) | Parenting stage | 0 2 3 4 5 6 7 8 |
fans.device_price (array) | Phone price | 0-999 … 8000+ |
fans.device_brand (array) | Phone brand | 苹果 华为 OPPO VIVO 荣耀 小米 一加 魅族 中兴 联想 |
note.note_type | Note type | 0=any 1=image-text 2=video |
coop.industry | Recent industry | 不限 美妆个护 食品饮料 母婴 3c及电器 日用百货 服装配饰 互联网 生活服务 家居建材 汽车 |
column | Sort field | 34 options — see the sort table in section 1 |
Call /get_blogger_filter_options first, then fill values from the matching
level. The dictionary rarely changes — cache it rather than calling it before
every search.
These parameters do NOT share one convention — this table is the only reliable
reference. Most take plain names at any level and can be mixed, but three are
exceptions (audience20 needs a compound string, note_scene /
consumer_behavior need a full path, industry_crowd needs a numeric ID).
Copying the convention from another parameter will fail.
| Path | Web filter | Dict key | What to pass | Which level |
|---|---|---|---|---|
blogger.content_tag | Blogger category | contentTags | plain name | level 1 / 2, mixable |
blogger.personal_tags | Persona | personalTagsV2 | plain tagName | level 1 / 2 / leaf, mixable |
blogger.feature_tags | Expertise | featureTags | plain name | level 1 / 2, mixable |
blogger.location | Blogger location | areas | plain place name | province / city / district, mixable |
fans.location | Fans location | fansAreas | plain place name | province / city (no district), mixable |
blogger.content_theme | Content theme | contentTheme | plain label | level 1 only, children invalid |
blogger.audience20 | Top-20 crowds | audience20 | level-1 name, or "L1 L1-L2" | level 2 needs the compound form |
blogger.note_scene | Note category | contentScenes | space-joined full path incl. root | any depth |
blogger.consumer_behavior | Estimated consumption | consumerBehavior | space-joined full path incl. root | any depth |
blogger.industry_crowd | Industry portrait | automotiveIndustryTag | deepest leaf value (numeric ID) | leaves only |
flags.industry_first / industry_second | Industry (not blogger category) | industryTags | plain name, single value, not an array | one each |
flags.activity_codes | Campaigns | activities | each item's code | — |
Array parameters are always OR-ed (union, de-duplicated); max 30 items,
64 characters per item.
| Failure | What you see | Which parameters |
|---|---|---|
| Over-filtering | total=0, looks like "no such bloggers" | everything except consumer_behavior |
| Silently not filtering | unfiltered results — you think it applied, it didn't | consumer_behavior |
Measured (fans 1.0M–1.2M, total=455 with no other filter):
content_tag=["护肤品"] (one extra character) → total=0fans.location=["浙江省"] (extra 省) → total=0audience20=["挑战极限者"] (bare level-2 label) → total=0note_scene=["选车攻略"] (last segment only, not the full path) → total=0industry_crowd=["身心疗愈兴趣人群"] (label instead of numeric ID) → total=0consumer_behavior=["保时捷911"] (bare leaf) → total=455 — no filtering at["zzz不存在zzz"] also returns 455. Only the full-path form works["内容行为预估 汽车"] → 54).When you get total=0, suspect the value format before concluding "no such
bloggers". When you get suspiciously many results, check whether
consumer_behavior was silently ignored. Always copy values verbatim from
/get_blogger_filter_options.
How to fill location (the most common mistake): just pass the plain place
name —["上海"],["杭州"],["上城区"]all work, OR-ed together.
A full path ("中国 浙江 杭州") also works; upstream normalizes it.
The only difference:fans.locationhas no district level — city is the finest.
blogger.content_tag)contentTags is a two-level tree: taxonomy1Tag is level 1 (28 of them — 美妆,
护肤, 个人护理 …) and taxonomy2Tags[] are its children. Mix level-1 and level-2
names in the same flat array — there is no nesting and nothing to send separately.
Level-2 names are globally unique and never collide with level-1 names, so a flat
array is unambiguous.
A real case from the web UI: 美妆 = "all", plus 2 children of 护肤 and 2 of 个人护理:
{"blogger": {"content_tag": ["美妆", "面部保养", "面部清洁", "头发产品", "身体护理"]}}
美妆 is a level-1 name (= the whole category); the other four are level-2 names.
Measured behaviour (same fan-count filter throughout, only the category changed,
comparing the returned total):
| Value | total | Note |
|---|---|---|
["面部保养"] | 200 | — |
["面部清洁"] | 2 | — |
["面部保养","面部清洁"] | 201 | Multiple tags are OR-ed, de-duplicated (1 blogger has both) |
["护肤"] | 379 | A level-1 name means the whole category |
| all 4 children of 护肤 | 328 | 51 fewer than the level-1 name |
["护肤","面部保养"] | 379 | Parent + child is accepted but pointless — the child is already covered |
["护肤品"] (typo) | 0 | Silently returns 0 — no error |
Two things to watch out for:
contentTags in /get_blogger_filter_options. When you seetotal=0, suspect the spelling before concluding "no such bloggers".Max 30 items per array; each tag name at most 64 characters.
blogger.location / fans.location)Flat, like categories: pass plain place names; provinces and cities can be
mixed; multiple values are OR-ed. The web-UI case — 浙江 set to "all", 2 cities
picked under 江苏, 2 more under 广东:
{"fans": {"location": ["浙江", "南京", "苏州", "广州", "深圳"]}}
For a whole province just pass the province name — do not expand it into all its
cities. Blogger location has one extra level (district), same syntax:
{"blogger": {"location": ["上海", "杭州", "上城区"]}}
Upstream actually wants opposite formats for these two fields (blogger location
takes bare names, fans location takes full paths like "中国 浙江 杭州"); the
upstream service normalizes each, so plain names are all you need. On the
fans.location side: 浙江→中国 浙江 (not expanded into its 11 cities),
杭州→中国 浙江 杭州, 吉林→ both 中国 吉林 and 中国 吉林 吉林 (a province
and a city share that name; both are sent, OR-ed). A name absent from the
dictionary (e.g. 浙江省) is passed through as-is → total=0.
Note fans.location only goes down to city — no districts (blogger.location
has them).
blogger.audience20)This one is NOT a flat plain name. Level 2 must be written as
"L1 L1-L2" — the level-1 name appears twice, separated by a space, joined by a
hyphen in the second half.
The web-UI case — 自在户外 set to "all", 2 children under 自由畅行, 2 under
运动焕活 (measured total=169):
{"blogger": {"audience20": [
"自在户外",
"自由畅行 自由畅行-都市漫游家", "自由畅行 自由畅行-静奢新贵",
"运动焕活 运动焕活-轻松健体派", "运动焕活 运动焕活-线条雕塑家"
]}}
Note the contrast: the "all" one is a bare level-1 name; every individually
picked child must use the compound form.
Measured (baseline 455): ["自在户外"]=135, ["自在户外 自在户外-挑战极限者"]=9,
["挑战极限者"]=0 (bare level-2 label — silently returns 0), two compound
values=18 (OR), ["自由畅行","自在户外 自在户外-挑战极限者"]=176 (vs 175 for
自由畅行 alone — mixing works).
personalTagsV2 is a three-level tree, featureTags two. Any level works and
they can be mixed. For a whole group just pass that group's name — no need to
enumerate its leaves.
{"blogger": {"personal_tags": ["职业身份", "妈妈", "留学背景"],
"feature_tags": ["风格", "vlog"]}}
Measured: personal_tags level-1 职业身份=91, level-2 互联网=8, leaf 妈妈=31,
["职业身份","妈妈"]=114 (union). feature_tags level-1 形式=183, 风格=37;
level-2 vlog=136, 韩系=19; ["风格","vlog"]=150.
blogger.content_theme) — level 1 onlyAn exception: only the level-1 label works; children always return 0.
Measured ["通用"]=366 but its child ["口播"]=0. To filter by a child, use
feature_tags instead.
These two do not take plain names. Join every level from the root with spaces;
you may stop at any depth:
{"blogger": {
"note_scene": ["汽车 理性决策", "汽车 理性决策 选车攻略 政策"],
"consumer_behavior": ["内容行为预估 汽车"]
}}
Measured note_scene: ["汽车"]=75, ["汽车 理性决策"]=23, 4-level=1, while the
last segment alone ["选车攻略"]=0.
⚠️ consumer_behavior fails differently from every other parameter: instead of
returning 0, the whole filter is silently ignored. Measured ["保时捷911"]
(bare leaf) and ["zzz不存在zzz"] both return 455 — identical to not passing the
filter at all. Only the full-path form works (["内容行为预估 汽车"]=54). When
using it, compare total with and without the filter to confirm it applied.
This dictionary is also rolled out per account — some accounts get null, in
which case the filter is unavailable.
blogger.industry_crowd) — numeric IDs onlyA four-level tree, but only the deepest leaf's value works, and it is a numeric
ID, not a label. Measured ["17121263"]=110; the matching label
["身心疗愈兴趣人群"]=0.
flags.industry_first / industry_second) — single valueThese are strings, not arrays — one value each, used together (the level-2 value
must belong to the level-1 one):
{"flags": {"industry_first": "美妆个护", "industry_second": "护肤"}}
This is industry, a completely different taxonomy from blogger category
(blogger.content_tag).
| Path | Web filter | How to fill |
|---|---|---|
coop.brand_ids | Recent brands | Brand userId list (no brand search endpoint — supply userIds yourself) |
{"min": x, "max": y})| Path | Web filter |
|---|---|
fans.count | Fans count |
note.imp_median / note.read_median / note.inter_median | Organic note impression / read / engagement median |
note.thousand_like_percent | 1000-like note ratio (%) |
coop.pic_price / coop.video_price | Sponsored quote (image-text / video, CNY) |
coop.order_count | Sponsored order count |
coop.reply_48h_ratio | 48h invitation reply rate (%) |
coop.imp_median / coop.read_median / coop.inter_median | Sponsored note medians |
coop.cpuv / coop.estimate_cpuv | CPUV / store-visit cost |
coop.pic_cpm / coop.video_cpm | Estimated CPM (image-text / video) |
coop.pic_read_price / coop.video_read_price | Estimated cost per read |
coop.pic_engage_cost / coop.video_engage_cost | Estimated cost per engagement |
live.live_count / live.avg_viewer / live.avg_gmv | Live sessions / avg viewers / avg GMV |
true)| Path | Web filter |
|---|---|
flags.is_star / flags.high_quality / flags.new_high_quality | Celebrity / high-quality / rising blogger |
flags.note_and_live | Accepts both note and live cooperation |
flags.filter_intention | Industry intention match |
flags.exclude_low_active / flags.exclude_fans_down | Exclude inactive / fans-declining bloggers |
flags.exclude_cooperated / flags.exclude_invited | Exclude already-cooperated / already-invited |
coop.exclude_brands | With coop.brand_ids: true = exclude bloggers who worked with those brands |
flags.industry_first / flags.industry_second are industry names for industry-recommended
bloggers; flags.activity_codes are campaign codes from /get_blogger_filter_options.
The [示例/Example] block at the bottom of this page has 15 ready-to-copy request bodies covering keyword search, audience targeting, budget/ROI, live commerce, similar-blogger expansion and geo targeting — plus a common mistakes section (those silently return 0 results instead of erroring).
| Param | Range | Note |
|---|---|---|
page_num | 1-250 | Upstream exposes only the first 5000 items = 250 pages; page 251+ returns an empty array |
page_size | 1-20 | Fixed at 20 on the web page. Keep it at 20: upstream post-filters each page, so a small size may leave 0 rows |
Change page_num only and keep every other filter identical, otherwise pages overlap or skip.
total is not the match count, it is the paging cap and is always 5000.
kols[]: matched bloggers (~140 fields each)
userId / name / redId / headPhoto / location / gendercontentTags / featureTags / personalTags / tradeTypefansNum / clickMidNum (read median) / interMidNum (engagement median) /fans30GrowthRate (fan growth) / kliveCnt30d (live sessions)picturePrice / videoPrice / lowerPrice / estimatePictureCpm /cooperateStatetotal: paging cap (always 5000 — not the real match count, see Paging above)trackId / highlightWords/get_blogger_filter_options instead of hardcoding themresp["data"] is PGY's raw envelope (with code/msg/success); the payload is one level deeperdata = resp["data"]["data"] — every field listed under "Return" lives heredata is null, e.g. ID not found or filters matched// ① 关键词找博主:搜「咖啡」相关笔记的博主,按粉丝数降序;带品牌方 ID 做个性化推荐并剔除已合作过的
{"page_num": 1, "page_size": 20, "keyword": "咖啡", "search_type": 1,
"column": "fansCount", "sort": "desc",
"brand_user_id": "5dcfa5370000000001006030",
"flags": {"exclude_cooperated": true}}
// ② 按昵称找号:search_type=0 时 keyword 匹配的是博主昵称而不是笔记内容
{"page_num": 1, "page_size": 20, "keyword": "小鹿", "search_type": 0}
// ③ 最基础的类目 + 粉丝量筛选:粉丝 1 万-5 万的美妆博主,按互动中位数降序
{"page_num": 1, "page_size": 20, "column": "mEngagementNum",
"fans": {"count": {"min": 10000, "max": 50000}},
"blogger": {"content_tag": ["美妆"]}}
// ④ 按画像选号:1 万-5 万粉、粉丝以 25-34 岁女性为主的美妆「妈妈」博主
{"page_num": 1, "page_size": 20,
"blogger": {"content_tag": ["美妆"], "personal_tags": ["妈妈"]},
"fans": {"count": {"min": 10000, "max": 50000}, "gender": 2, "age": 3}}
// ⑤ 按预算与响应速度选号:图文报价 1 万-5 万、48h 邀约回复率 ≥80%
{"page_num": 1, "page_size": 20,
"coop": {"pic_price": {"min": 10000, "max": 50000},
"reply_48h_ratio": {"min": 80}}}
// ⑥ 按性价比选号:图文报价 ≤5000、阅读中位数 ≥3000 的视频博主,只看优质号并剔除低活
{"page_num": 1, "page_size": 20,
"note": {"read_median": {"min": 3000}, "note_type": 2},
"coop": {"pic_price": {"max": 5000}},
"flags": {"high_quality": true, "exclude_low_active": true}}
// ⑦ 找会直播带货的博主:近 30 天开播 1-5 场、场均销售额 ≥1 万,且笔记+直播均可合作
{"page_num": 1, "page_size": 20,
"live": {"live_count": {"min": 1, "max": 5}, "avg_gmv": {"min": 10000}},
"flags": {"note_and_live": true}}
// ⑧ 多维组合:7 个筛选组同时生效(字段名写错会直接 422,不会被静默忽略)
{"page_num": 1, "page_size": 20, "column": "comprehensiverank", "sort": "desc",
"goal": {"market_target": "mAccumImpNum"},
"blogger": {"content_tag": ["美妆"], "gender": "女", "signed": 0},
"fans": {"count": {"min": 10000}, "age": 3, "device_brand": ["苹果"]},
"note": {"inter_median": {"min": 500}, "thousand_like_percent": {"min": 20}},
"coop": {"industry": "美妆个护", "video_price": {"max": 8000}},
"live": {"avg_viewer": {"min": 5000}},
"flags": {"exclude_cooperated": true}}
// ⑨ 找相似博主:给一个满意的标杆博主,扩一批相似的,**并叠加自己的筛选**
// (/get_blogger_similar 只按相似度返回、不支持筛选,需要叠加筛选就用这里)
{"page_num": 1, "page_size": 20,
"similar_user_id": "5a8cf39111be10466d285d6b",
"fans": {"count": {"min": 50000}},
"coop": {"pic_price": {"max": 20000}}}
// ⑩ 预算优先:按「图文预估阅读单价」从低到高排,先看最便宜的
{"page_num": 1, "page_size": 20,
"column": "pictureReadCost", "sort": "asc",
"coop": {"pic_price": {"min": 1000, "max": 10000}}}
// ⑪ 涨粉快的新秀:按近 30 天粉丝增长率降序,并剔除掉粉的
{"page_num": 1, "page_size": 20,
"column": "fans30GrowthRate", "sort": "desc",
"fans": {"count": {"min": 5000, "max": 100000}},
"flags": {"exclude_fans_down": true}}
// ⑫ 爆款率优先:按百赞笔记占比降序(看重内容稳定性而非粉丝量时用)
{"page_num": 1, "page_size": 20,
"column": "hundredLikePercent30", "sort": "desc",
"note": {"thousand_like_percent": {"min": 10}}}
// ⑬ 地域投放:找上海本地的探店博主
// blogger.location 直接填地名即可,不要填「上海市-上海市-黄浦区」这种全路径
{"page_num": 1, "page_size": 20,
"blogger": {"location": ["上海"], "content_tag": ["美食"]}}
// ⑭ 按粉丝所在地投放(和 ⑬ 是两个不同维度:⑬ 是博主在哪,⑭ 是粉丝在哪)
{"page_num": 1, "page_size": 20,
"fans": {"location": ["广东"], "count": {"min": 20000}}}
// ⑮ 直播带货 + 90 天口径:场均观播 ≥5000、场均销售额 ≥1 万
// 注意 avg_viewer / avg_gmv 是近 90 天,而 live_count 是近 30 天
{"page_num": 1, "page_size": 20,
"live": {"live_count": {"min": 1}, "avg_viewer": {"min": 5000}, "avg_gmv": {"min": 10000}}}
分两类。第二类才是真正的坑 —— 它们不会报错,只会安静返回 0 条。
// ✗ 区间写成了数字 —— 区间型字段必须是对象
{"fans": {"count": 10000}}
// ✓
{"fans": {"count": {"min": 10000}}}
// ✗ 字段名拼错(驼峰)—— 本接口 extra="forbid",写错直接 422,不会被静默忽略
{"blogger": {"contentTag": ["美妆"]}}
// ✓
{"blogger": {"content_tag": ["美妆"]}}
// ✗ 排序列填了未放行的值
{"column": "forecastReadUv"}
// ✓ 见第 1 节的 34 个排序字段
{"column": "avgAgmv90d"}
// ✗ page_size 超上限(1-20)
{"page_size": 100}
// ✓
{"page_size": 20}
// ✗ 百分比量纲错了 —— 千赞比例填 200 或 9500(按万分比/原始条数填)
{"note": {"thousand_like_percent": {"min": 200}}}
// ✓ 想表达「20%」就填 20
{"note": {"thousand_like_percent": {"min": 20}}}
下面这些全都能通过校验,返回 200,结果却是 0 条 —— 因为值本身合法、只是没有匹配项。
// ✗ 地域填了带分隔符的全路径
{"blogger": {"location": ["上海市-上海市-黄浦区"]}}
// ✓ 直接填地名
{"blogger": {"location": ["上海"]}}
// ✗ 百分比填了小数(0.2 是合法的 0.2%,不是 20%)
{"note": {"thousand_like_percent": {"min": 0.2}}}
// ✓ 想表达「20%」
{"note": {"thousand_like_percent": {"min": 20}}}
// ✗ 类目值是自己编的
{"blogger": {"content_tag": ["美妆护肤"]}}
// ✓ 用 /get_blogger_filter_options 的 contentTags 里真实存在的值
{"blogger": {"content_tag": ["美妆"]}}
// ✗ 开关型填 false —— 等于没筛,不是「反向筛」
{"flags": {"exclude_cooperated": false}}
// ✓ 想生效就填 true
{"flags": {"exclude_cooperated": true}}
// ✗ 把「行业」当成「博主类目」用(两个是不同的筛选)
{"flags": {"industry_first": "美妆"}}
// ✓ 博主类目用 blogger.content_tag
{"blogger": {"content_tag": ["美妆"]}}
搜不到结果时的排查顺序:先把筛选一个个去掉,定位是哪个条件卡住的;
需要查字典的筛选(类目 / 场景 / 消费行为 / 地域)优先怀疑值写错了。
curl --location 'https://api.tikhub.io/api/v1/xiaohongshu/pgy/get_blogger_list' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{
"page_num": 1,
"page_size": 20,
"keyword": "",
"search_type": 1,
"column": "comprehensiverank",
"sort": "desc",
"brand_user_id": "string",
"similar_user_id": "string",
"similar_word": "string",
"goal": {
"market_target": "estimateAllCpm"
},
"blogger": {
"content_tag": [
"string"
],
"personal_tags": [
"string"
],
"feature_tags": [
"string"
],
"content_theme": [
"string"
],
"audience20": [
"string"
],
"industry_crowd": [
"string"
],
"consumer_behavior": [
"string"
],
"note_scene": [
"string"
],
"gender": "男",
"location": [
"string"
],
"signed": -1
},
"fans": {
"count": {
"min": 0,
"max": 0
},
"age": 0,
"gender": 0,
"marital_status": -1,
"consumption_level": -1,
"child_age": [
0
],
"device_price": [
"0-999"
],
"device_brand": [
"苹果"
],
"location": [
"string"
]
},
"note": {
"imp_median": {
"min": 0,
"max": 0
},
"read_median": {
"min": 0,
"max": 0
},
"inter_median": {
"min": 0,
"max": 0
},
"thousand_like_percent": {
"min": 0,
"max": 0
},
"note_type": 0
},
"coop": {
"pic_price": {
"min": 0,
"max": 0
},
"video_price": {
"min": 0,
"max": 0
},
"order_count": {
"min": 0,
"max": 0
},
"reply_48h_ratio": {
"min": 0,
"max": 0
},
"industry": "不限",
"brand_ids": [
"string"
],
"exclude_brands": true,
"imp_median": {
"min": 0,
"max": 0
},
"read_median": {
"min": 0,
"max": 0
},
"inter_median": {
"min": 0,
"max": 0
},
"cpuv": {
"min": 0,
"max": 0
},
"pic_cpm": {
"min": 0,
"max": 0
},
"video_cpm": {
"min": 0,
"max": 0
},
"pic_read_price": {
"min": 0,
"max": 0
},
"video_read_price": {
"min": 0,
"max": 0
},
"pic_engage_cost": {
"min": 0,
"max": 0
},
"video_engage_cost": {
"min": 0,
"max": 0
},
"estimate_cpuv": {
"min": 0,
"max": 0
}
},
"live": {
"live_count": {
"min": 0,
"max": 0
},
"avg_viewer": {
"min": 0,
"max": 0
},
"avg_gmv": {
"min": 0,
"max": 0
}
},
"flags": {
"is_star": true,
"new_high_quality": true,
"high_quality": true,
"note_and_live": true,
"filter_intention": true,
"activity_codes": [
"string"
],
"exclude_low_active": true,
"exclude_fans_down": true,
"exclude_cooperated": true,
"exclude_invited": true,
"industry_first": "string",
"industry_second": "string"
}
}'{
"code": 200,
"request_id": "string",
"message": "Request successful. This request will incur a charge.",
"message_zh": "请求成功,本次请求将被计费。",
"support": "Discord: https://discord.gg/aMEAS8Xsvz",
"time": "string",
"time_stamp": 0,
"time_zone": "America/Los_Angeles",
"docs": "string",
"cache_message": "string",
"cache_message_zh": "string",
"cache_url": "string",
"router": "",
"params": {},
"data": null
}