两种接入方式 —— REST API 或 MCP server。curl 一行能验证,Claude / Cursor 一分钟接通。样本库(/api/v1/cases/search、/api/v1/search)每条带 source_url 回链中国裁判文书网;走 ES 的 /api/v1/fulltext、/api/v1/judgements,以及本页下面推荐过的免 key 线 /api/cases/search,当前都不返回原文链接,以案号 + 裁判日期溯源(详见下方 FAQ)。
拿到 key 后一条 curl 就能跑通。Beta 阶段返回 sample 字段名与 GA 一致,代码不用改。 但先看下面这一栏再动手写代码:检索类端点单次要几秒到十几秒, 客户端超时设短了你会读到自己的超时、还照样烧配额。
中文参数必须 URL 编码(2026-08-15 实测补齐)
GET 请求里的中文参数请一律写成 -G --data-urlencode 'q=不可抗力',别直接拼进 URL。本站自 2026-08-11 起由 Node standalone 直接收请求,URL 里未编码的中文会被判 400 且响应体为空—— 空体 400 最像「站挂了」,其实只是参数没编码, 而且这一层在鉴权之前(没带 key 也是空体 400,不是 401)。 本页 GET 例子都已按这个写法给出,照抄即可;追加筛选也照样再加一段 --data-urlencode,别在 URL 尾巴上拼 &cause=…。POST 走 JSON body,不受此限(中文照常直接写)。
长的 q 请走 POST(2026-08-27 实测补齐)
GET /api/v1/fulltext 把 q 放进 URL,而整条请求(请求行 + 全部请求头)有一个长度上限,超过就被挡在本服务之前 —— 你拿到的不是本站的 JSON 契约:HTTP/1.1 下先是一个没有响应体的 431、再长一点是 nginx 的 414 HTML 错误页;HTTP/2 下 431 之后直接是连接层报错,连状态码都收不到(SDK 只会报「连不上」)。 这个上限我们不写死成一个数:它随你发多少请求头、key 有多长而浮动 (实测把 key 加长一截,q 的可用长度就正好少掉同样一截);而且中文经 URL 编码后每个字要占好几倍字节,按字数算撞墙比英文早得多 —— 粘一段案情进 q 就可能超。
出路:同一个端点改用 POST /api/v1/fulltext(见下方等价示例),q 走 JSON body,不受这堵墙约束 —— 实测刚过 GET 门槛那一段,同一个 q 走 GET 是 431、走 POST 照常返回结果。 再长下去会碰到上游检索自己的极限(那是另一堵墙), 但那时你拿到的仍是本站的 JSON 契约(带 error 与 code、 说得出是哪一层不行),而不是一个没有响应体的 431。
⚠️ 但别把 POST 读成「这条线从此免疫」(2026-08-28 实测订正):走 body 绕开的只有 414 那一堵(它数的是请求行)。 另一堵 431 数的是请求头, 由本服务之前那一层在解析请求头时发出,在方法与路由都还没分派之前 —— 因此它对每一条线、每一种方法都够得着,而且不需要你把任何东西写长: URL 与 body 都完全正常,只要你自己那侧的请求头(鉴权 token、Cookie、链路追踪头) 加起来够大,拿到的照样是一个没有响应体的 431。实测本站全部对外线一条不落 —— 含只收 POST 的 /api/v1/search 与 /api/v1/cases/search、以及不收 q 的 /api/v1/judgements 与 /api/v1/stats —— 都能被自己的请求头打成空体 431。请把「响应体为空的 431」当成每条线都要处理的一种返回,它没有换方法就躲开的出路(只能把请求头瘦下来);POST 那条路解决的是 414 与「长文本往哪儿放」,不是 431。
那它日常会中吗?——不会,但别把「不会」读成「不必处理」(2026-08-28 实测补齐):给一个带 key 的裸请求加上一整套常见 SDK 默认头(User-Agent、Accept、Accept-Encoding、Content-Type、Authorization、链路追踪头), 这一整套只占掉这份额度的很小一角,绝大部分额度是空着的。要撞上它,得你自己那侧挂着异常大的头(塞满的 Cookie 罐、超长的鉴权 token),而不是日常请求随时会中。⚠️ 但它中的时候没有任何一条线是例外(见上一段), 所以该写的分支还是要写 —— 它是一种低频但全线的返回,不是一种可以赌它不发生的返回。
另外,这份额度不全是你的。请求到本服务之前会先过本站自己的转发层,它会往你的请求里再加几个转发头(你的客户端地址、转发链、原始协议、原始主机), 这几个头吃的是同一份额度;它们有多长还取决于你的地址有多长 (地址长的客户端吃得更多)。所以上面那条「把请求头瘦下来」的出路,有一小截不由你决定,请给自己留出这一截。
最后:请求头这条路上是两堵墙,不是一堵。它们数的东西不一样,给你的东西也不一样 —— 431 数的是全部请求头的合计,由本服务之前那一层发出,没有响应体,连接还留着;而 400(Request Header Or Cookie Too Large)数的是单条请求头行,由转发层发出,给你的是一份 HTML 错误页,而且直接关掉连接。 实测同一个合计长度、只改怎么分装:拆成几条中等长度的头 → 431;原样塞进单独一条 Cookie → 400 —— 也就是说一个 Cookie 罐塞满的浏览器客户端拿到的是400 而不是 431。而且这两堵墙的分工随协议变:同一个超长的单条头,HTTP/1.1 下是 400、HTTP/2 下仍是空体 431。请把 400 与 431 成对当成「请求太大」的两种形态来处理,别只 grep 其中一个。
⚠️ 这一栏不只跟 /api/v1/fulltext 有关(2026-08-28 补齐):431 与 400 都在路由分派之前发出,每条线都够得着。所以每条线的响应里现在都恒发一句提醒,键名统一叫 request_size_wall_note(挂在 _meta 里;/api/v1/health 整个响应没有 _meta 这层,故挂在顶层)。 它是短指针不是全文 —— 全文只在 /api/v1/fulltext 的 _meta.long_q_transport_note 这一处发(单一来源,免得七份各自漂移;也免得给被高频轮询的健康检查响应白白加一大截体积)。
客户端超时口径(2026-08-03 补齐)
接进代码之前必须先设超时:本 API 的检索类端点是秒级到双位数秒,而 fetch / axios / requests 以及多数 LLM 生成的客户端默认只等 10-30 秒 —— 它们会在我们答完之前先 abort,你读到的是自己的超时,不是本 API 的故障。运行时 /api/v1/search 的 _meta.latency_note 对此有近千字的说明,但那段话只有成功拿到响应的人才读得到,而超时的人恰恰拿不到 —— 所以它必须先印在这里。
各端点实测耗时(同一条请求连打 3 次,冷 → 热 → 热)
下面每条 curl 例子都印了对应的 --max-time, 照抄即可;建议值一律显著大于实测最大值,因为秒数极不稳定 —— 同一个 body 冷热能差一倍以上,缓存热身对耗时的影响大于查询形态本身, 把超时卡在「实测值 + 一点点」等于给自己造一条随缓存状态随机翻红的告警线。/api/v1/search 另有服务端实测的 _meta.latency_ms 可用来自校准 (其余端点暂不返回该字段)。
你自己超时 abort 掉的请求,照样按次扣配额 —— 2026-08-03 实测:在固定连接上「读基线 → 3 次 /api/v1/search → 再读余额」共 5 个请求,客户端超时设 10 秒时 2 次 abort、设 5 秒时 3 次全 abort,两次实验的余额都是 59 → 55(第一个读数本身已扣过自己,故这两个数之间隔的是余下 4 个请求 —— 3 次 search 一次不落地各扣了 1,与是否读到 body 无关:请求已经到达并被执行了)。而 abort 时你收不到那次的响应头,故你在客户端侧看不到自己烧掉的量,唯一的观测点是下一次成功响应的 X-RateLimit-Remaining。这条最容易被读成限流问题:超时设太短 → 一条结果都拿不到 → 配额照烧 → 很快撞 429,而 429 的 body 讲的是「打太快、请退避降频」。这时降频是反向药 —— 每次都打冷缓存只会更慢、更容易再 abort。正确的动作是先把超时设足(见各端点建议值),拿到一次成功响应之后再谈频率。
提示:/api/v1/cases/search 的 q 按空白切词后要求逐词命中,词越多结果越少;若全部同时命中为 0 条,会自动降级为「任一关键词命中」并按命中词数排序(响应 _meta.note 会说明)。要更精确请用 1-2 个短词 + province / term_reason 等结构化条件。
similarity 不是语义相似度,别拿它排序或卡阈值
它只是 q 的关键词命中率,三种取值都别误读:
1——不含任何区分度,similarity >= 0.9 之类的过滤等于没过滤。case_title + body_excerpt(摘要,非全文)上判命中。q 为空(纯结构化过滤)时为 null——没有查询词就无从谈相似;此处曾发 0, 会被读成「毫不相关」而误丢掉整页合法结果。每次响应的 _meta.similarity_note 自带该口径。 需要相关度排序请用 /api/v1/search 或 /api/cases/search。
⚠️ 这两条不是同一类线,别按同一套预期接:/api/cases/search 是免 key 的站内线(走 ES,与 /api/v1/fulltext 同侧),不在下方端点表里,而且它的 source_url 系统性为空 —— 2026-07-29 实测 8 种查询形态(带 q 首页 / 带 q 深页 / 不带 q 的大省与小省 / cause 过滤 / 单年份过滤)合计 302/302 = 100% 为 null,与检索条件无关,换词、换页、重试都不会拿到链接(2026-08-17 复测同结论)。要「相关度排序 + 每条带原文链接」两者兼得,本页只有 /api/v1/search 一条;确要用免 key 线,回原文核对请走 case_no + judgement_date。这条线每次响应自带顶层 source_url_missing(本页为空的条数)与 _meta.source_url_note,写代码之前先打一发就能自证,不必先接完再发现。
参数命名口径(对接前必看)
各端点的过滤参数命名并不统一:案由在 /api/v1/fulltext 与 /api/v1/judgements 叫 cause、在 /api/v1/search 叫 reason;年份在 fulltext 是 yearFrom / yearTo、在 search 是 year_from / year_to。
写错名字会返回 400 bad_param,不再静默忽略—— 响应带 unknown_params / allowed_params / docs 与逐个参数的指路。 这条契约是刻意的:静默忽略会让你以为条件生效了,拿到的却是没过滤过的全国结果, 而且零报错线索。下面这四条带 key 的线(/api/v1/fulltext / /api/v1/search / /api/v1/judgements / /api/v1/cases/search)每次响应的 _meta.params_note 还自带该端点的合法参数清单;免 key 的七条 GET 线没有这个字段,它们的参数契约在另一个字段上——见下一段。
免 key 的七条 GET 线恒发 unknown_param_caliber,但挂的层级不统一(2026-08-16 现网逐条实测):这份口径讲清「本端点收哪些键、拼错了会怎样、厂商跟踪 token 怎么处理」,200 也发,不是只在 400 那一支才有——所以你可以在正式对接之前先打一发看契约,不必先踩一次错。
/api/cases/search2 → _meta.unknown_param_caliber/api/cases/aggs → _meta.unknown_param_caliber,并另发 _meta.shared_cache_caliber/api/analytics → _meta.unknown_param_caliber/api/cases/related → _meta.unknown_param_caliber,并另发 _meta.shared_cache_caliber/api/cases/doc → _meta.unknown_param_caliber/api/cases/search → _meta.unknown_param_caliber/api/cases/browse → 顶层 unknown_param_caliber(本端点整份响应没有 _meta)请按端点判位置,别写死 resp._meta.unknown_param_caliber:那样在 /api/cases/browse 上读到的是 undefined——不报错、不抛异常,于是被静默读成「这条线没有参数契约」。
另有 2 条线的响应可能不是刚算出来的: /api/cases/aggs、/api/cases/related 带响应头 Cache-Control: public, max-age=0, s-maxage=300(七条里只有这两条带),源站会按 URL 整份缓存最长 300 秒;完整口径恒发在这两条线的 _meta.shared_cache_caliber 里。 要绕开:换任一合法参数的值即换一把缓存键。
年份的「值」同样有契约(2026-07-31 补齐):yearFrom / yearTo(与 search 的 year_from / year_to)只接受 1900-2100 的四位整数, 是按年的双端闭区间——两端所在的整年都算在内,单查一年就把两端设成同一年 (实测 yearFrom=yearTo=2016 的结果覆盖 2016-01-27 至 2016-12-30;yearFrom=2016 单独 595 条 + yearTo=2016 单独 130 条 − 整年 56 条 = 669 条 = 不带年份的基线,逐位相符)。别传日期(2016-12-31)、两位年份(16)或小数 (2016.9):这类值现在一律返回 400 bad_param。此前 fulltext 会把它们静默丢给上游, 请求照样 200,但拿到的是没按年份筛过的结果——实测 yearFrom=2016&yearTo=abc 返回 669 条,与完全不带年份的结果逐位相同(连写对的那个 yearFrom=2016 也被一并作废), 而 yearTo=16 则静默返回 0 条、读起来像「库里没有」。 另注意:判决日期缺失的文书会被任何年份条件静默排除,故逐年求和通常略小于不带年份的总数 (实测 6 个切片缺口 0-0.2%)。这条口径不适用于劳动争议库:/api/v1/cases/search 的 years_min / years_max 是工龄(双端闭),而 /api/v1/stats 的 years_bucket 是左闭右开, 三者不可互推。
怎么判这一发成没成(对接前必看)
ok 位:2xx 即成功;失败时看 code(bad_param / missing_api_key / invalid_api_key / rate_limit_exceeded / upstream_error …)。if (!body.ok) —— 这条线的成功响应同样没有这一位,undefined 取反是 true,于是每一次成功都会被判成失败;它在 400 上看起来是对的,纯属误打误撞。(2026-08-18 现网逐条实测:6 条带 key 线的 200 与 400、以及 401,响应体里都没有 ok。)ok: true/false(2026-08-18 逐条实测:search / aggs / related / analytics / browse 的 200 为 ok:true,search2 当刻 ok:false,doc 的 400 为 ok:false)。别把在那边试出来的判据照搬到带 key 线上。ok 的带 key 端点是 /api/v1/health,而它的 ok 答的是上游健不健康、不是「本次请求成不成功」:ok:true 可以配 status:"degraded"(某条线挂着),ok:false 配 HTTP 503。params_relevant —— /api/v1/fulltext / /api/v1/search / /api/v1/judgements / /api/v1/cases/search / /api/v1/stats / /api/v1/cases/{doc_id} 的未知键 400 都发 true。curl --max-time 30 https://tob.wenshucha.com/api/v1/healthcurl --max-time 30 'https://tob.wenshucha.com/api/v1/health?shallow=1'接监控前必看这两句:ok 与 HTTP 200/503 是历史契约,只代表检索线;多线汇总在 status(ok / degraded / down),挂掉的线点名在 lines_down 里 —— 因此 ok:true 配 status:"degraded" 是真实会出现的组合 (2026-07-28 全量库线整条挂掉、/api/v1/judgements 全部 502 时, 本端点的 ok 与状态码全程是绿的)。而 ?shallow=1 的 ok 是写死的 true、status 恒为 unknown,它只答「路由在不在」,不答「取不取得到文书」。 完整接法见下方常见问题「怎么把可用性接进监控?」。
curl --max-time 120 -X POST https://tob.wenshucha.com/api/v1/cases/search \
-H "X-API-Key: wsc_trial_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"q": "经济性裁员",
"province": "北京市",
"term_reason": "layoff"
}'cases/search 的过滤值口径(2026-08-03 补齐):参数名对了、值不合法一律返回 400 bad_param——year_from / year_to 须为 1900-2100 的四位整数、years_min / years_max 须为 0-100 的数字(可带小数,years_max=2.999 这类对齐 years_bucket 的写法继续可用)、q / province / reason / term_reason 须为字符串。 此前这三类都不报错,而且失真方向各不相同:{"years_min":"abc"} 与 {"q":123} 会把整条查询打进降级分支, 返回 HTTP 200 + 3 条演示文书(data_mode:"sample"、median=15725)并把它说成「实时查询失败」;{"year_from":"2016","year_to":"abc"} 与{"year_from":"19"} 因年份走字符串比较而静默退化成全时段基线(n_cases 2000 / median 27,509);{"years_max":0} 里的数字 0 被当成「没传」 (而同义的 "0" 是生效的),现已改为真正生效。 响应新增 filters_effective(本次真正进了查询的条件)与filters_ignored:顶层 filters 只是把你发来的 body 印回去的回执,对账请以前者为准。 另注 province / reason / term_reason 是受控词表的等值匹配, 拼写合法但库里没有(如 "广东" 少一个「省」)时返回 0 条而不报错—— 这时该键会出现在 filters_effective 里,0 条是诚实答案。
cases/search 的三个受控词表参数「可用值」(2026-08-03 补齐):此前只说过 「写错会静默返 0」,从没给过值。三者都是 列 = 值 的逐字等值——不 trim、不认核心名/简称/拼音,差一个字与「火星省」表现完全一样。 实测 14 种客户常写的 province 写法 13 种返 0(核心名「广东」「北京」「西藏」「新疆」 「内蒙古」、带空格的「广东省 」、市级「广东省广州市」、英文「GD」、本库确实没有的台湾省 / 香港特别行政区), 唯一活着的是繁体(「廣東省」会自动归一)。
① province:可用值 = GET /api/v1/stats?dimension=province 返回的 31 个 cells[].province(如"广东省"、"内蒙古自治区"、"北京市"),实测 31/31 全部有数据; 检索响应的 _meta.filter_values.province.values 直接给这份表,不必另打一次 stats。 ⚠️ 这 31 个不是全集:底表还有 8 个非规范值同样点得动、 合计 19 条(最高人民法院 9 条、新疆维吾尔自治区生产建设兵团分院 4 条,另 6 条入库残留), stats 那边按「规范省名」剔除了它们,按省循环取全量请把这 8 个也跑一遍(见 filter_values.province.also_matchable)。
② term_reason:layoff / fired / contract_end / mutual / quit 五个英文小写枚举(封闭全集);中文「经济性裁员」「辞退」、大写LAYOFF、带尾空格 "layoff " 实测全部返 0。
③ reason:没有任何端点能列出全集,响应里给的是实测清单(values_measured,700 条跨年抽样所见 6 个取值并逐个回打验证,exhaustive:false)——别把「不在表里」读成「库里没有」。 最像标准案由全称的「劳动争议纠纷」实测返 0,库里绝大多数写的是「劳动争议」; 要发现更多取值,读结果里每条自带的 reason 原值。
④ 别把隔壁端点的经验搬过来:同一个值「西藏」在/api/v1/fulltext(叠 cause=劳动争议)是 669 条、在本端点是 0 条——ES 侧那两个端点的 province 是前缀/分词匹配、 核心名反而取得更全,本端点是逐字等值、只认全称。同一把 key、同一个参数名,两侧规则相反。
命中 0 条时响应会多发 _meta.filter_value_suspects(机读,逐参数标in_value_list 与可选的 did_you_mean);建议归建议,本次查询用的仍是你传来的原值,我们不替你归一。⚠️ 该诊断以你至少传了这三个参数之一为前提;一个都没传(例如只给q)时它算不出来,改由 0 条恒发的_meta.zero_result(机读)与zero_result_note 讲清其它成因。
⑤ 命中 0 条时 stats 是 null——整个对象不发,不是 n_cases:0 的空统计 (分位数与均值在空集上无定义),所以别直接读 stats.n_cases(JS 会抛 TypeError、Python 抛'NoneType' object is not subscriptable);判有没有命中请读顶层count 与 cases。null 也不代表降级或故障——真降级会走data_mode:"sample" + fallback_reason。 另:0 条时 max_page 恒为 1,page 越过它照常返回 page_exceeded:true(2026-08-03 起;此前 0 条这一支被跳过,page=999 只拿到一个静默空页,照文档写while (!page_exceeded) page++ 的客户端会永不退出)。
curl --max-time 60 -H "X-API-Key: wsc_trial_xxxxxxxxxxxxxxxx" \
-G "https://tob.wenshucha.com/api/v1/fulltext" \
--data-urlencode "q=不可抗力" \
--data-urlencode "province=北京" \
--data-urlencode "pageSize=20"
# 中文参数必须走 --data-urlencode,别直接拼进 URL(见上方红框:未编码的中文会拿到空体 400)curl --max-time 60 -X POST https://tob.wenshucha.com/api/v1/fulltext \
-H "X-API-Key: wsc_trial_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"q": "不可抗力",
"province": "北京",
"pageSize": 20
}'fulltext 翻页口径:pageSize 上限 50(超出按 50 处理并在 _meta.param_note 说明),page × pageSize ≤ 10000(pageSize=20 时最多到第 500 页); 越过窗口返回 window_exceeded: true + max_page,不是该条件下没有文书,请加 province / cause / court / 年份缩小范围后重新翻。 两个参数须为 ≥1 的整数,写错会返回 400 bad_param 而非空结果。 判断是否还有下一页,2026-08-21 起本线也发顶层 has_more(与 /api/v1/judgements 同名同义同源,值 = page < max_page 且本页 raw_count > 0);此前本线整个不发这一位, 照 judgements 写的循环搬过来读到的是 undefined——不报错、在 if 里是 false,于是第 1 页就停。 自己算也可以,但只能用 raw_count(去重前条数),不要用 count:本线做本页去重, 中间整页被去重掉时 count 会是 0 而后面还有页。
fulltext 检索口径(读数前必看):四条口径响应里都有对应说明字段,程序对接请直接读它们,不要按字面假设。
total 恒为 10000 并置 total_is_capped: true(见 _meta.total_note),应读作「10000+」;要精确计数请加 province / cause / court / 年份缩小范围。province=北京市 只捞得到字面以它开头的那批。 2026-08-02 本端点实测(窄词 q=貔貅 避开 total 封顶)12/12 个省级行政区全部是「规范全称 < 核心名」:北京 138→117(少 15.22%)、 广东 158→127、云南 133→88(少 33.83%,最差)、宁夏 34→30(少 11.76%,最好), 合计 1,630→1,314 = 少 19.39%;免 key 的 /api/cases/search2(total 不封顶)上 31/31 个省同向, 合计少 3,168 万篇。少掉的是真文书不是脏数据(逐条验过:案号与法院都属该省,零跨省污染), 它们自己的 province 字段返回时也已被归一成规范全称, 所以肉眼看不出来。故请写 province=北京 / 广东 / 内蒙古; 真送了全称时响应会给出 province_form / province_form_note / province_form_compare(本切片现算差 + 自证算式)。注意封顶陷阱:切片太宽时两种写法的 total 会双双压到 10000, 相减得 0 不代表两种写法一样——这种情况响应改发 province_form_compare_capped: true 而不发差值, 要自证请加 q / cause / 单年缩到不触顶再比。q=祖国宏 会命中「尚祖国、陈宏哲」这类拆字组合, 多数结果并不含该姓名(见 _meta.match_note)。需收窄请加 party=<姓名>——但它只在本页结果内做子串过滤,total 仍是上游未过滤口径,本页余 0 条不代表全库无此人。case_title + body_text,当事人字段(plaintiff / defendant)在本线恒不存在(2026-07-31 实测 400 条样本 0/400)。 故正文尾部的审判长 / 审判员 / 书记员署名同样算命中——400 条样本 × 10 个常见姓氏命中 1041 条,其中 18.4% 该姓氏的每一次出现都落在署名块内(即命中的是法官不是当事人,该比例还只是下界);更直白的一枪:party=审判员 照样返 16 条。 另一头,body_text 有 6000 字上限,实测 9.2% 的文书触顶,名字若在截断点之后则永远匹配不到,这一类翻页也救不了。请把 party 读作「本页哪些文书出现过这个字符串」, 而不是「这个人是这些案子的当事人」;完整口径与本页两侧触顶条数见 _meta.party_match_scope_note(恒发)与 kept_truncated / filtered_truncated。传纯空白 party 会被整条丢弃,此时响应给 party_ignored: true。q 时按相关度打分排序,不带 q 的纯条件过滤则按裁判日期倒序。 本次实际口径见顶层 sort 字段(relevance / judgement_date_desc)与 _meta.sort_note,别写死假设。cases 已按案号 + 标题归一(见 _meta.dedup),故 count(去重后)常小于 raw_count(去重前)。q=利息 一页 37 条里 27 条空正文(73%),而 劳动争议 / 离婚 / 工伤 / 交通事故 各 50 条则一条不缺。别按一个全局比例做预算,请按每次响应实测。每条附 body_text_len(正文去首尾空白后的字符数)与 has_full_text,顶层 full_text_missing 为本页空正文条数(另见 _meta.full_text_note)——需要「必须带全文」的样本请据此过滤。 注意正文短不等于缺失(调解书 / 裁定书本就短),故仅空正文才判 has_full_text=false,长度阈值由你自定。court 匹配的字段里存的全是中文法院全称, 传入值中的拉丁字母 / 数字 / 标点 在该字段中不存在,一条也匹配不到。 2026-07-31 实测:court=Shenzhen / Guangdong High Court / 12345 / 。 一律 total=0, 且 HTTP 200、无 error;叠加也归零(q=利息 单独 10000+, 加 court=Shenzhen → 0,而加 court=深圳 → 8553)。混写时非汉字部分等于没写(深 / 深A / A深 / Shenzhen深 / 1234深 五者 total 逐位相同 = 317)。切勿把这个 0 读作「库里没有该法院的文书」——同一套分词下 q=Shenzhen 返 150、q=iPhone 返 10000+, 可见并非分词器只认汉字,而是该字段的取值域里没有非汉字。踩中时响应会给 court_value_termless: true;完整口径随每次响应恒发,见 _meta.court_value_note / 登记条目 court_value_no_cjk_zero。另注意 court 传纯空白(court=%20)会被 trim 成空 → 该过滤条件被整条丢弃、返回未按法院过滤的结果, 此时响应给 court_ignored: true。 还有一条独立的坑:court 是分词 OR 匹配而非精确过滤,写法院全称反而几乎不过滤 (见 _meta.court_query_note),请用不含省名的地名短词。curl --max-time 60 -H "X-API-Key: wsc_trial_xxxxxxxxxxxxxxxx" \
-G "https://tob.wenshucha.com/api/v1/fulltext" \
--data-urlencode "q=祖国宏" \
--data-urlencode "party=祖国宏" \
--data-urlencode "pageSize=50"
# 响应含 party_filtered_out(本页滤掉几条)、kept_truncated / filtered_truncated(两侧正文触顶条数)
# 与 _meta.party_note、_meta.party_match_scope_note(恒发,讲清扫的是标题+正文而非当事人字段)cases/search 翻页口径:同样支持 page + pageSize(上限 50,默认 20),响应回带 count / page / pageSize / max_page;越过 max_page 返回 page_exceeded: true,非法值返回 400 bad_param。注意本端点单次最多取按裁判日期倒序的 最近 2000 条作为统计与翻页样本:命中过多时 stats.n_cases 恒为 2000 并置 n_cases_is_capped: true,该值应读作「2000+」而不是真实命中数, 想要更有代表性的统计请用 province / reason / term_reason / 年份缩小范围。
⚠️ 触顶时 max_page 不是真末页:命中触顶(n_cases_is_capped: true)时,max_page = ceil(2000 / pageSize) 的取样窗口末页,响应同时置 max_page_is_sample_window: true;越过它返回 page_exceeded: true + page_exceeded_reason: "sample_window",含义是窗口翻到底,不是数据翻到底(未触顶时该字段为 "end_of_results",那才是真末页)。别把窗底日期当成语料的时间下界:2026-07-29 实测不带过滤时 pageSize=3 的第 667 页 末条裁判日期为 2022-02-08,而同一底表加 year_to=2021 首页即 2021-12-31 且同样满 2000 条并触顶,year_from=year_to=2015 单年亦然 —— 窗外还有数万条。 取窗外数据的唯一办法是按年份(必要时叠省份/案由)切片,让每格命中 < 2000; 每格未触顶时,该格的 max_page 与 stats 才是真值。口径与复测命令见响应的 _meta.pagination_window_note / measured_claims。
⚠️ 引用金额分位(p25 / 中位 / p75)前必读:时间覆盖度
本试用数据集是 8 万条劳动争议裁判文书样本,逐年实测分布极不均匀: 2013–2017 每年都触到 2000 条取样上限(实际更高)、体量压在这几年;2018 年仅 554 条、2019 年全库只有 1 条(近乎整段缺失); 2020–2021 亦触顶,2022 年 1,976 条、2023 年 290 条,2024 年起为 0(语料截止 2023)。
因此金额分位应读作「以 2010 年代中期为主、截至 2023 年的名义判付金额」, 且金额未做任何价格 / 工资水平调整,不等于当期赔付预期。 按 year_from / year_to 筛 2018–2019 会拿到空或极少的结果, 这是语料缺失而非参数写错——响应的 _meta.param_note 会明确点出; 完整年度分布随 /api/v1/cases/search 与 /api/v1/stats 的 _meta.coverage_note 一起返回。
curl --max-time 60 -H "X-API-Key: wsc_trial_xxxxxxxxxxxxxxxx" \
-G "https://tob.wenshucha.com/api/v1/judgements" \
--data-urlencode "province=广东" \
--data-urlencode "pageSize=20"judgements 口径:不带关键词,按 province / cause 浏览全量库,两者至少传一个 (只传 cause 时上游较慢,可能超时,建议带上 province)。两者不是同一种匹配,请分开看(2026-08-02 实测订正:此前这里写「两者都是分词 OR 匹配」, 对 cause 成立、对 province 不成立)。cause 是分词 / 子串匹配,请送词干(民间借贷 / 劳动争议 / 买卖合同),送规范全称会被通用后缀「纠纷」淹没成近乎不过滤 —— 2026-07-29 实测 cause=离婚纠纷 与 cause=火星纠纷 返回逐条相同的结果(一条离婚案都没有)。province 则是上游对原值做的左前缀匹配,故务必送核心名「广东」,别送规范全称「广东省」 —— 2026-08-02 逐省实测 31/31 个省全称都比核心名少 13.63%-45.81%(广东 8,721,486 → 6,963,106, 云南少 45.81% 最差;合计少 3,168 万篇),原因是库里原值写法不统一(同一页里 「内蒙古自治区」与「内蒙古」并存),前缀匹配下写全称会把另一批整批漏掉。 写市名(如「深圳」)或拼错不会报错,而是静默返回 total 0。详见响应的 _meta.prefix_note 与 _meta.province_form_note(送了全称时还会给出本次现算的差值)。
⚠️ 上面这批逐省数字实测于 2026-07-29 ~ 2026-08-02,其后 2026-08-03 有 3 篇广东省文书依权利人请求下架、已从检索与聚合的全部端点移除,因此今天复测广东这两把键会各比上面少 3 篇(8,721,486 → 8,721,483、6,963,106 → 6,963,103)。这是下架生效的证据,不是数据不稳 —— 两把键的差值 1,758,380 前后不变,上面那些百分比因此照旧成立。下架后的值复测于 2026-08-09 起(08-09 / 08-14 及此后各轮 0d 探针)。
⚠️ 站内全库口径的数字有两个纪元,分界同样是 2026-08-03 那次下架(3 篇文书依权利人请求移除)。最显眼的是全库总数:160,291,681(测于 2026-08-02)→ 160,291,678(测于 2026-08-12);另有 3 把全库口径的键同样各少 1-3 篇(口径清单见 lib/corpus-wide-epoch-drift.ts)。两个纪元各自自洽,站上印的是晚的那个;别拿早晚两个值互相对账,那既不是库在缩水,也不是谁抄错了;复测请以晚的那个为准。
source_url 一并为空 —— 2026-07-29 实测浙江 50 条 + 广东 / 北京 / 西藏各 20 条, 合计 110/110 全为 null(同批 body_text 110/110 非空)。 我们不做拼接伪造,回原文核对请用 case_no + judgement_date;见响应的 _meta.source_url_note 与 empty_fields。其余字段照常:case_no / cause / case_type / procedure / court / judgement_date。total 不再是旧版的「候选池封顶 50」,而是该条件下的真实命中数 (2026-07-29 实测浙江 986.8 万、广东 872.1 万、西藏 10.5 万)。但可翻页范围受上游 ES 的 from+size 窗口限制,max_page = max(1, min(ceil(total/pageSize), floor(10000/pageSize))) —— 切勿按 total 估算可抓取量。 ⚠️ 注意公式里的 max(1, …):total=0 时 max_page 仍返回 1 —— 这是地板值, 不是「有 1 页数据」,且与真有一页数据时逐位相同(2026-08-03 实测 total=21 @pageSize=50 → max_page=1、has_more=false,与 total=0 时完全一样)。 判空请读 total,或读 0 条时才发的机读位 max_page_is_floor 与 pages_with_data。 ⚠️ 别拿本页 count / cases.length 判条件为空:翻过末页时它们同样是 0, 那时条件里有数据、响应改发 page_is_empty_but_data_exists(2026-08-05 实测 total=31 @pageSize=50&page=2 曾被发成 pages_with_data: 0;判据口径见两端恒发的 _meta.max_page_floor_scope_note)。同一口径也适用于 /api/v1/cases/search;而 /api/v1/search 与 /api/v1/fulltext 的 max_page 是固定窗口常量, 0 条时照样返回一个很大的数,与命中数无关。 越过末页返回 window_exceeded: true + 空数组 (上游本身会把末页原样重复,我们不把重复内容当新结果发出去)。要取更深的结果请用 province × cause × 年份把命中数切到 1 万以内分片取(年份切分用 /api/v1/fulltext 的 yearFrom/yearTo),或联系我们取全量导出。province=浙江省&pageSize=50:第 1 页 50 条全为 2025-12-19,第 200 页(即 max_page)全为 2025-11-20 且 has_more: false —— 而同一条件 total 是 751 万。 即翻到底看到的 has_more: false 含义是窗口到底,不是数据到底,「浙江语料始于 2025-11」是彻底的误读: 窗外还有数百万条更早的文书。要真正覆盖时间轴,请按年份切片 (/api/v1/fulltext 的 yearFrom/yearTo), 让每格命中 < 10000。page(≥1)+ pageSize(1-50,默认 20),响应回带 max_page / has_more,越过末页返回 page_exceeded: true(不是静默空数组),非法值返回 400 bad_param。doc_id 形如 ws_new:6f33f010-… / 2025:a8fcfcc3…,喂给 /api/v1/cases/{doc_id} 实测返 200 + 完整详情 (URL 里的冒号请按 URL 编码转义)。多数情况下无需回查:本端点单次已返回全文与全部可用字段。curl --max-time 120 -X POST https://tob.wenshucha.com/api/v1/search \
-H "X-API-Key: wsc_trial_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"reason": "民间借贷纠纷",
"province": "广东省",
"year_from": 2020,
"year_to": 2022,
"pageSize": 20
}'search 口径:beta 试用走全案由样本库(120 万+,覆盖 1200+ 案由, 正式版对齐全量),每条带 source_url 原文链接,但不返回 body_text 裁判文书全文——要全文请用 /api/v1/fulltext 或 /api/v1/judgements。q 只匹配标题(不是全文),可为空、纯用结构化条件过滤; 翻页 page × pageSize ≤ 10000,响应带 max_page / has_more,越窗返回 page_exceeded: true。注意宽泛的 q(如单字词)在冷缓存下可能耗时数十秒,建议配合 reason / province 收窄。
⚠️ 判空别只看 count:本端点没有 total(count 是本页条数,原因见 _meta.count_note)。offset = (page-1) × pageSize,所以 page>1 时 count=0 分不出「条件里真没有」与「翻过了末页」—— 两者的 max_page(窗口常量)与 has_more(false)一模一样。响应 0 条时会按页码发其一:zero_is_condition_empty: true(page=1,可证的真 0 条)或 zero_may_be_past_last_page: true(page>1,不可判定)。后者唯一的判定法是把同一条件原样改成 page:1 再打一次; 在那之前请别照 zero_result_note 的自检法逐个删条件—— 翻过末页时删掉任一收窄条件都会让那页有数据,会把一个正确的参数确诊成元凶 (2026-08-05 实测:西藏自治区 × 劳动争议 page=1 有 3 条,删掉 reason 后 page=2 返回 3 条)。 完整口径见恒发的 _meta.window_line_zero_note。 ⚠️ 这条 page=1 规则只对 /api/v1/search 成立,别带去 /api/v1/fulltext 与 /api/v1/judgements:那两条线 0 条时恒发 zero_result_kind:"indistinguishable",即它们明说 page=1 的 0 也定不了性,本端点这个可证的 zero_is_condition_empty 在那两条线上没有对应物。
改用别的端点前先看这条代价(2026-07-29 实测):上面为「要全文 / 要 total / 要相关度序 / 要快」 指向 /api/v1/fulltext 与 /api/v1/judgements 的几处建议都成立,但它们有同一个代价——原文链接会全部丢失:本端点 source_url 逐条都发(按本条 doc_id 拼成、格式正确;「一定打得开」未经验证—— 落点是 JS 单页应用,2026-08-22 实测真 docId 与空 docId 返回逐字节相同的 200,HTTP 验不出来),而 fulltext 57/57 全为 null、judgements 70/70 全为 null(2026-07-23 切 ES 后的变化,补回需入库侧改动)。且换过去补不回来:两侧 doc_id 不同源 (本端点是 32 位原始 docId,ES 侧是 <分片名>:<内部 id>), 拿标题回查本端点同样落空(实测 judgements 三条标题回查 3/3 返 0 条)。 故需要回链就留在本端点,或取数时当场把 source_url 存下来; 已经在用那两个端点的,回裁判文书网核对请以 case_no + judgement_date 定位。响应里对应的口径是 _meta.cross_endpoint_note 与 _meta.source_url_note,机读判空看顶层 source_url_missing(与 fulltext 同名同义; ⚠️ 但在 /api/v1/search 那一侧它恒为 0—— 链接是拼出来的、空 doc_id 也拼得出非空 URL,那一位证不出链接健康度, 要看真读数请读那一侧顶层的 source_url_doc_id_forms)。 ⚠️ 「机读判空看顶层 source_url_missing」这条只对 /api/v1/fulltext 成立,别带去 /api/v1/judgements(2026-08-15 现网实测):/api/v1/judgements 顶层没有 source_url_missing 这个字段,读它拿到的是 undefined;而同一份响应里有个长得很像的 source_url_fallback_missing,它数的是既没有链接、又缺 case_no 或 judgement_date 的条数,不是缺链条数 —— judgements 那种「链接全空、锚点齐全」的页面它恒为 0,而同一页的 source_url 其实条条为 null。把这两个名字当成一回事,等于拿一个 0 给「本页没有缺链」盖章。/api/v1/judgements 请逐条判 source_url 本身,整页是否全空看 _meta.empty_fields 里有没有 source_url(该端点逐页现算)。 ⚠️ 「要全文」这一条指过去了,判全文的那套字段却不跟着过去(2026-08-15 现网实测):本段把「要全文 / 要 total / 要相关度序 / 要快」的客户同时指向 /api/v1/fulltext 与 /api/v1/judgements,而本页教的判全文读法 —— 逐条 body_text_len 与 has_full_text、顶层 full_text_missing 为本页空正文条数 —— 这三个名字 judgements 一个都没有;它逐条只有 body_text 本身,外加 body_text_truncated 与 body_text_is_withheld_reason 两个标位。照搬过去不会报错,只会往两个相反的方向静默出错:if (!r.has_full_text) 把整页当成「都没有全文」丢光,r.body_text_len < 200 恒为 false、一条也筛不掉。/api/v1/judgements 上请直接判 body_text 本身是否非空,整页是否全空看 _meta.empty_fields 里有没有 body_text(该端点逐页现算)。
换过去的第二项代价:判空口径也换了。 /api/v1/fulltext 与 /api/v1/judgements 在 0 条这一支恒发 zero_result:true + zero_result_kind:"indistinguishable"(2026-08-15 带 key 现网实测:judgements?province=火星省 与 fulltext?q=zzzqqxyz9999loop507 各四位齐发)—— 两条线明说自己分不出「条件真为空」与「这一次没取到」,page=1 也不例外,而这两条线上 total 与 count 在两种情形下逐位相同。所以别把 total:0 / count:0 直接写成「库里没有」入库或上报;拿到 zero_result_kind:"indistinguishable" 时,自查三步照响应里的 _meta.zero_result_note 打(两条线的步骤不同,故不在本页复述)。⚠️ 判空解析必须一并接住这一格例外:/api/v1/fulltext 在 yearFrom 大于 yearTo(区间写反)那一支,顶层 zero_result / zero_result_kind / zero_result_selfcheck / zero_result_filter 与 _meta.zero_result_note 一位都不发,改发顶层 year_range_empty + year_range_empty_note:区间写反是能确定归因的 0,不归「分不出来」那一族管,两族互斥、永不同现。2026-08-29 第 710 发带 key 现网实测:q=民间借贷&yearFrom=2025&yearTo=2010 返 total:0 而零族一位不在,同参正序 yearFrom=2010&yearTo=2025 四位与 _meta.zero_result_note 齐发。⇒ 只认 zero_result 的判空代码在写反那一支读到的是 undefined,在 if 里是 false,请把 year_range_empty 一并认上。/api/v1/judgements 没有年份参数(送 yearFrom 返 400 bad_param),这一格与它无关。
curl --max-time 30 -H "X-API-Key: wsc_trial_xxxxxxxxxxxxxxxx" \
"https://tob.wenshucha.com/api/v1/stats?dimension=term_reason"stats 与其余端点不是同一口径,别把两边数字放一起比: 它读的是仓库内预计算快照(data_mode: "snapshot", 8 万条劳动争议裁判文书样本),不随全量库更新而变;其余端点是实时查全量库 / 样本库。 最容易误读的是 n_cases——它是抽样条数而非该省真实案件量, 每省封顶 4500,实测 9 个省并列触顶(响应里 n_cases_capped: true), 故不可用于跨省比体量:同为 province 维度,本端点山东省是触顶的 4,500 条、与另外 8 个省并列(排序因此是任意的), 而全量预聚合 /api/analytics?dim=province 的山东省是 1,130 万条、稳居第 1。p25 / median / p75 金额分位是真实统计(不像 n_cases 那样被封顶压平), 但不是可直接横向比的同期口径:触顶省份那 4,500 条如何抽出上游未公布,且各省年份构成不同 (实测广东省、江苏省 2018 与 2019 均为 0 条,安徽省 2018 年有 32 条);更要紧的是这三个分位量的是什么——它们与 /api/v1/cases/search 的 compensation_judged 同源同式(2026-07-31 逐位验死:青海省 106 条把原始金额全部拉回自行重算,得 6562 / 21651 / 40488,与本快照该格一字不差; 宁夏 490、黑龙江 603 两省五个字段亦逐位相同),因而口径是「经济补偿金/赔偿金」一项的判付额、不是本案判决总额(不含同案的加班费 / 二倍工资 / 未休年休假 / 社保工伤;实测同案非费用类合计的中位是它的 2.06 倍), 报价与尽调里请写成「经济补偿金一项的判付分位」,详见响应的 _meta.compensation_scope_note;win_rate 当前恒为 null(样本仅含判付案,均衡样本后再开放)。 参数名是 dimension(不是 dim), 取值 province / term_reason / years_bucket,传其它值回 400。years_bucket 的五格是「左闭右开」,与中文标签的字面直觉相反:<1=[0,1)、1-3=[1,3)、3-5=[3,5)、5-10=[5,10)、>10=[10,+∞) 含恰满 10 年 —— 工龄恰为 3.0 年的 6,328 件(全库 7.9%)在 3-5 而不在1-3。要把某一格拉成明细,上界必须减一档——/api/v1/cases/search 的 years_min/years_max 是双端闭区间(work_years >= / <=),右端与桶相反:1-3 应写 years_min=1&years_max=2.999(实测 24,568 条,与本端点该格 24,582 对得上), 照字面写 years_max=3 会返 30,896 条(+25.7%), 且分位被一起抬高(同为 years_min=1,江苏省中位判付 12,000 → 13,400);<1 同理(years_max=1 返 23,611 vs 本格 17,573)。下界不用动(>10 只给 years_min=10 即可)。完整口径与复测命令随两个端点的响应发:_meta.years_range_note / 登记条目 labor_years_range_inclusive。本端点是整体快照、不支持任何过滤参数:province / cause / court / year / q / page 以及写错的 dim 一律返回 400 bad_param(附该去哪查的指路),不再静默忽略——旧行为会让 ?province=广东省 悄悄返回全国 31 省, 客户把全国数字当成广东数字写进材料。要按条件筛真实文书请用 /api/v1/search 或 /api/v1/judgements。 响应的 _meta.snapshot_note / sampling_note / excluded_note 也写了同样的口径。
MCP server 走 stdio,把检索能力变成 AI 助手的工具。安装一次,模型自动看到 7 个 tool(每个 tool 的口径注意事项都写在它自己的 description 里,模型调用前就能读到):
search_cases — 劳动争议类案检索 + 金额分位(p25/中位/p75),带原文链接与摘要,不含裁判文书全文search_judgments — 全案由通用检索(1200+ 案由),带原文链接,不含裁判文书全文search_judgments_full — 全量库关键词检索,有全文,但 source_url 当前恒为 nullbrowse_full_corpus — 全量库按省/案由浏览,有全文;但 source_url 自 2026-07-23 切 ES 后已恒为 null(2026-07-30 实测浙江省 50/50 全空)。要可回链核验的样本请走 search_judgments(旧版说明称本 tool「全文 + 链接双全」,该说法已作废)case_analytics — 全库实时聚合(案由/地域/年份/法院),唯一能给真实全库计数的 toolcase_stats — 8 万条劳动争议预计算快照切片;n_cases 是抽样条数(每省封顶 4500)不可用于跨省比体量,比体量请用 case_analyticsget_case — 按 doc_id 取单条详情;四个检索 tool 返回的 id 现已全部可回查。browse_full_corpus 的 doc_id 不再 404(旧版说明称必 404,已作废):切 ES 后它形如 2025:58de9ca0…(分片名:内部 id, 非旧版裸 hex),2026-07-30 实测回查返 200 + 完整全文。 多数情况下无需回查——该 tool 单次已返全文⚠️ 选 tool 时就得定:全文与原文链接目前拿不到同一份结果里
2026-07-30 同日实测三个检索 tool 的 source_url: search_judgments(/api/v1/search)20/20 非空, 但 不返回 body_text 全文;而 browse_full_corpus 50/50 全为 null、search_judgments_full 15/15 全为 null——这两个 ES 侧 tool 有全文、没链接。
且这不是「先取数、回头再补链」能救的:两侧 doc_id 不同源, 拿案号 / 标题 / 当事人姓名回查 search_judgments 都搭不了桥 (年份不重叠、案号不进标题、两侧匿名化程度不同),用案由这类通用片段去回查更危险—— 它不报错,而是返回「看着很像但根本不是同一份」的文书, 照此补链会把错误的原文链接挂到案子上,比留空严重得多。
所以:需要逐条可点链接 → 一开始就用 search_judgments,并在取数时当场把 source_url 存下来;需要全文 / 真实 total → 用 ES 侧两个 tool,回裁判文书网核对以 case_no + judgement_date 定位,切勿把 null 读作「该文书不可溯源」。完整口径见响应的 _meta.cross_endpoint_note。
git clone https://github.com/jack0752168/wenshucha-mcp ~/wenshucha-mcp
cd ~/wenshucha-mcp && npm install{
"mcpServers": {
"wenshucha": {
"command": "node",
"args": ["/Users/<you>/wenshucha-mcp/bin/wenshucha-mcp.mjs"],
"env": { "WENSHUCHA_API_KEY": "wsc_trial_xxxxxxxxxxxxxxxx" }
}
}
}{
"mcpServers": {
"wenshucha": {
"command": "node",
"args": ["/Users/<you>/wenshucha-mcp/bin/wenshucha-mcp.mjs"],
"env": { "WENSHUCHA_API_KEY": "wsc_trial_xxxxxxxxxxxxxxxx" }
}
}
}claude mcp add wenshucha \
--env WENSHUCHA_API_KEY=wsc_trial_xxxxxxxxxxxxxxxx \
-- node /Users/<you>/wenshucha-mcp/bin/wenshucha-mcp.mjsREST,JSON in / out。所有写操作均无,只读。
| Method | Path | Auth | 说明 |
|---|---|---|---|
| GET | /api/v1/health | (无) | 服务健康检查,也是本 API 唯一免 X-API-Key、不计配额、不限流的端点(可放心高频打;实测一条 keep-alive 连接连打 100 次 ?shallow=1 全部 200、响应上零 X-RateLimit-* 头)。两种模式别混用:①?shallow=1 是 liveness —— 零上游调用、恒快(实测 ~1 秒),只证明 API 路由本身可达,它的 ok 是写死的 true、与后端死活无关,故 status 恒为 unknown,别拿这一模式当可用性依据;②不带参数(默认 deep)是 readiness —— 真去并行打两条上游,各自最多等 20000 / 10000 毫秒,故把它接进监控时客户端超时请设 ≥25 秒,否则你读到的是自己的超时而不是我们返回的诊断。监控要读的字段是 status,不是 ok、更不是 HTTP 状态码:ok 与 200/503 是刻意保留的历史契约,只由 dependencies[0](检索线)决定;status 才是已探线的三态汇总(ok / degraded / down),挂掉的线列在 lines_down 里。所以 ok:true + status:"degraded" 是真实会出现的组合 —— 2026-07-28 全量库线整条挂掉、/api/v1/judgements、/api/cases/browse 全部 502 时,本端点的 ok 与 HTTP 状态码全程是绿的,只挂 curl -f 或只读 ok 的监控当时不会报警。deep 模式也只探到两条线:覆盖 /api/v1/fulltext(检索线)与 /api/v1/judgements、/api/cases/browse(全量库线);未探 /api/v1/cases/{id}、/api/v1/search、/api/v1/stats(各走独立后端,其中 /api/v1/stats 读预计算快照、根本没有上游)。完整接法、两条「假红带」、以及「429 是限流不是故障」见响应里的 ok_semantics_note / coverage_note / rate_limit_note,以及下方常见问题「怎么把可用性接进监控?」。 |
| POST | /api/v1/cases/search | X-API-Key | 混合检索:案情文本 + 结构化字段 → Top 20 类案 + 金额分位(p25/中位/p75)+ 通用举证要点清单。只接受 q / province / reason / term_reason / years_min / years_max / year_from / year_to / page / pageSize,传其它参数名返回 400 bad_param(2026-08-03 起;此前是静默忽略,且未知键还会原样回显进响应的 filters 字段,读起来像一张已生效的过滤回执,实际返回的是全国全时段基线——实测 provinces=西藏自治区 与不加过滤逐位相同,而写对 province 只有 19 条)。注意本端点案由叫 reason(不是 cause)、年份叫 year_from/year_to(不是 yearFrom/yearTo)、不支持 court。参数名对了、值不合法同样返回 400(2026-08-03 补齐):year_from/year_to 须为 1900-2100 的四位整数、years_min/years_max 须为 0-100 的数字(可带小数)、q/province/reason/term_reason 须为字符串。并请读响应里的 filters_effective(本次真正生效的条件)与 filters_ignored——顶层 filters 只是把你发来的 body 印回去的回执。另注:试用样本仅含判付案,stats.win_rate 恒为 null,不提供胜诉率。命中 0 条时 stats 整个对象是 null(不是 n_cases:0 的空统计,也不是故障;判有没有命中读顶层 count / cases),此时响应恒发 _meta.zero_result(机读)+ zero_result_note 讲清可能成因,且 page 越过 max_page(0 条时恒为 1)照常返回 page_exceeded。⚠️ 同名不同形,按一条线写的判空解析放到另一条上会静默失灵(2026-08-15 带 key 现网四条线逐条实测):zero_result 在 /api/v1/fulltext 与 /api/v1/judgements 上是顶层布尔,在 /api/v1/cases/search 上是 _meta.zero_result 对象(顶层没有这个字段),而 /api/v1/search 压根没有这个字段 —— 它按页码发 zero_is_condition_empty 或 zero_may_be_past_last_page。取错位置拿到的是 undefined,不报错、不抛异常,在 if 里就是 false,于是会被读成「本次不是 0 条」。四条线 0 条时都发 _meta.zero_result_note,但有两格例外。例外一,/api/v1/search:page 越过 max_page 那一支在打 SQL 之前就短路,顶层那两位与 _meta.zero_result_note、_meta.window_line_zero_note 一位都不发(2026-08-29 第 708 发现网实测 page=999:只发 page_exceeded:true 与 has_more_is_placeholder_constant:true)—— 即这条线一位恒发的判空机读位都没有,照 _meta.zero_result_note 判空的代码在那一支读到的正是本段要防的那个 undefined,请改认 page_exceeded。例外二,/api/v1/fulltext:yearFrom 大于 yearTo(区间写反)那一支,顶层那四位与 _meta.zero_result_note 一位都不发,改发 year_range_empty + year_range_empty_note(2026-08-29 第 710 发带 key 现网实测 q=民间借贷&yearFrom=2025&yearTo=2010:total:0 而零族一位不在;同参正序四位与 note 齐发)—— 那是能确定归因的 0,与「分不出来」那一族互斥,请把 year_range_empty 一并认上。各线 note 里的自查步骤按端点分流,别把一条线的步骤搬到另一条上。⚠️ 顶层机读位同样按线分叉,四条付费检索线没有两条是同一套(2026-08-15 带 key 现网四条线逐条实测,2026-08-21 复测并按实况订正线名):total(该条件下的真实命中数)只有 /api/v1/fulltext 与 /api/v1/judgements 发,/api/v1/cases/search 与 /api/v1/search 根本没有这个字段 —— /api/v1/cases/search 与 /api/v1/search 的 count 是本页条数、不是命中数;has_more(还有没有下一页)只有 /api/v1/fulltext、/api/v1/judgements 与 /api/v1/search 发,/api/v1/cases/search 根本没有这个字段,故 /api/v1/cases/search 判还有没有下一页只能比 page 与 max_page(⚠️ 该线命中触顶时 max_page 是 ceil(2000/pageSize) 的取样窗口末页、不是真末页,响应同时置 n_cases_is_capped 与 max_page_is_sample_window,此时「翻到 max_page」只等于翻完了那 2000 条样本);source_url_missing(本页 source_url 为空的条数)只有 /api/v1/fulltext 与 /api/v1/search 发,/api/v1/judgements 与 /api/v1/cases/search 根本没有这个字段;本页三个「为空」计数 court_empty、procedure_empty 与 case_type_empty(本页该字段为空的条数)只有 /api/v1/fulltext 发,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(发它的那条线与免费的 /api/cases/search 同名同义),其中 case_type_empty 还只数真空值与裸数字代码、数不到「其他」,要含「其他」的那个「未标注」桶请逐条数 case_type_axis === "" —— 但这个机读出口同样只有一条线逐条发,还是 /api/v1/fulltext(与免费的 /api/cases/search 同名同义):2026-08-15 实测同一分钟四条线,fulltext 一页 40 条 40/40 都带这一位、其中 case_type_axis === "" 的是 19/40,而 /api/v1/judgements 20 条、/api/v1/cases/search 20 条、/api/v1/search 10 条 一条都不带,拿 doc_id 回查 /api/v1/cases/{doc_id} 也没有这个字段。别把这个写法带去那三条线:undefined === "" 恒为 false,于是「未标注」那个桶被静默数成 0 条(同一刻 fulltext 侧实测是 19/40)。那三条线里也只有 /api/v1/judgements 逐条有 case_type 可判(实测 20/20),/api/v1/cases/search 与 /api/v1/search 连这个字段都没有(实测 20/20、10/10 一条都不带;它们逐条的 reason 是案由、不是案件类型)。缺的那一位读到的是 undefined,不报错、不抛异常:在 if 里是 false、参与算术是 NaN,于是「本页没有缺链」「已经翻到最后一页」「命中 0 条」这类结论会被静默地做出来。另有一族讲的不是空不空而是来路:province_backfilled_count、province_backfill_withheld_count 与 province_empty_count(本页 province 的来路计数:几条是本站按案号代字补出来的、几条是闸判分不清而刻意留空的、几条空着没补上)只有 /api/v1/fulltext 发,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(同样与免费的 /api/cases/search 同名同义,口径与线名的单一来源是 lib/province-backfill-marker)。这一族缺席不等于那几条线的 province 更干净:它们整个不跑本站的回填层,逐条的 province 一律是上游原值 —— 为空就是空、不会被补,也没有分辨来路的那三位;而在发它的那条线上,2026-08-23 带 key 现网实测同一分钟、同一 pageSize,逐页比率从一页 0/50 摆到一页 19/37(≈51.4%),拿任何一页外推全库都是错的,请逐页读这三个数。跨线对账时也别把「同一份文书两条线 province 不同」读成数据不一致。还有一位讲的既不是空不空、也不是来路,而是值本身根本不可信:judgement_date_implausible(本页 judgement_date 非空但越界的条数(年份 > 当年 / 早于 1949 / 格式与月日越界;判据是 lib/judgement-date 的 plausibleDate,与 /cases 页排序置末、年份分布、报告 TXT 同一份))只有 /api/v1/fulltext 与 /api/v1/judgements 发,/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(2026-08-29 带 key 现网四条线亲打);它与同一响应里的 judgement_date_empty 严格互斥:那一位只数字段为空,本位只数非空但越界,所以 judgement_date_empty 为 0 不等于本页日期没问题 —— 实测首行写着 2091-09-02 的那一页,judgement_date_empty 照报 0。缺这一位的那两条线请逐条自己判(形如 YYYY-MM-DD 且 1949 ≤ 年 ≤ 当年、月 1-12、日 1-31),但别把发它的那两条线上的数字搬过去:那两条线读的是另一套语料,逐条 doc_id 不带 ws / ws_new / 2025 分片前缀(2026-08-29 实测 300/300、150/150 无前缀),而越界条目在发它的那两条线上实测集中在 ws_new 老分片。⚠️ 同日在缺这一位的两条线上实测 0/300 与 0/150 越界,那不是合格证:同一批脏日期在 /api/v1/fulltext 上带 q 走相关度是 0、不带 q 走日期倒序就顶到第一屏,页面上的 0 可以只是顺序的产物,请按你实际用的顺序与写法各量一次。还有三位讲的是这条记录还能不能回溯到原文:case_no_empty、judgement_date_empty 与 source_url_fallback_missing(本页几条 case_no 为空、几条 judgement_date 为空、几条两条退路同时断掉(既没有链接、又缺这两个锚点之一))只有 /api/v1/fulltext 与 /api/v1/judgements 发,/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(2026-08-29 带 key 现网四条线亲打;三个数恒发、逐页现算,本页为 0 时也发,故「没有这个字段」永远不是「本页没事」的意思)。第三位的判据里那个且是真的:它不是缺链条数、也不是把两个空值率相加,而是「没有 source_url 并且 case_no 与 judgement_date 至少缺一个」的条数 —— 所以「链接全空但锚点齐全」的页面它照返 0,拿它给「本页没有缺链」盖章是反的(缺链条数请读 source_url_missing,而那一位又是另一个切分,见本段前面那句讲它的话)。⚠️ 这三位缺席最要命的一点,是它在缺席的那两条线上根本不会露馅:那两条线逐条 case_no / judgement_date / source_url 三键俱全且非空(2026-08-29 实测 20/20、10/10),自己算出来本来就是 0;于是读 undefined 的解析与算得一手好 0 的解析,在那两条线上输出逐位相同,怎么测都对。搬到发这三位的线上才开始静默说谎:同日 /api/v1/fulltext q=离婚 一页 40 条实测 source_url 40/40 全空、case_no_empty 7、judgement_date_empty 21、source_url_fallback_missing 21 —— 过半的条目当场既回不到原网、也报不出案号或日期。⚠️ 逐条字段同样按线分叉,且分叉法与顶层那三位不是同一套(2026-08-15 带 key 现网四条线各取一页实测,同页各条字段集逐条相同;2026-08-22 复打一遍,逐格相同):案由这一格,/api/v1/fulltext 与 /api/v1/judgements 逐条叫 cause,/api/v1/cases/search 与 /api/v1/search 逐条叫 reason;裁判文书正文这一格,/api/v1/fulltext 与 /api/v1/judgements 逐条叫 body_text,/api/v1/cases/search 逐条叫 body_excerpt(只有约 150 字的摘录),而 /api/v1/search 逐条一个正文字段都没有(⚠️ 回查 /api/v1/cases/{doc_id} 同样拿不到正文 —— 该 doc_id 落通用样本库分支,逐条就是本线这 8 个字段、一个正文字段都没有,响应 _meta.full_text 恒为 false;2026-08-22 实测本线 50 条 doc_id 逐条回查:44 条返 200 但无正文、6 条(sh_ 形态)连回查都不成立(原样 404 / 编码后 400 bad_doc_id),0 条拿到正文。读到的 undefined 与「这条文书没有全文」长得一模一样 —— 要全文只能改用 /api/v1/fulltext 与 /api/v1/judgements(/api/v1/cases/search 逐条只有约 150 字的 body_excerpt,不是全文));「本条正文是不是空的」那一位 has_full_text 只有一条线逐条发,就是 /api/v1/fulltext,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 逐条根本没有这个字段 —— 那三条线里 /api/v1/judgements 逐条只有 body_text 本身(外加 body_text_truncated / body_text_is_withheld_reason 两个标位),顶层也没有 full_text_missing;量长度那一格也另起一套 —— 本条正文有多长这一格,/api/v1/fulltext 逐条叫 body_text_len,/api/v1/cases/search 逐条叫 body_excerpt_len,而 /api/v1/judgements 与 /api/v1/search 逐条一个都没有(/api/v1/cases/search 量的是摘录、还配一个 body_excerpt_truncated,别把它当成正文长度)。缺的那个名字读到的是 undefined,不报错、不抛异常,而且会往两个相反的方向静默出错:if (!r.has_full_text) 这种写法把整页判成「都没有全文」全部丢掉,r.body_text_len < 200 这种写法则恒为 false、一条也筛不掉。案件类型这一族分叉得更狠:本条的案件类型原值这一格,/api/v1/fulltext 与 /api/v1/judgements 逐条叫 case_type,而 /api/v1/cases/search 与 /api/v1/search 逐条连原值都没有(它们逐条的 reason 是案由、不是案件类型);而三个派生位 case_type_axis、case_type_normalized 与 procedure_normalized 只有一条线逐条发,就是 /api/v1/fulltext,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 逐条根本没有这个字段(2026-08-15 同一分钟四条线实测:fulltext 40/40 带,judgements 20 条、cases/search 20 条、search 10 条 一条都不带)。所以本页教的「要含「其他」的那个「未标注」桶请逐条数 case_type_axis === ""」只在一条线上成立 —— /api/v1/fulltext:另外三条线读到的是 undefined,而 undefined === "" 恒为 false,那个桶会被静默数成 0 条 —— 同一刻 fulltext 侧实测它是 19/40。 |
| GET / POST | /api/v1/fulltext | X-API-Key | 全库裁判文书全文检索(近 1.6 亿裁判文书):关键词全文检索,命中裁判文书正文并高亮,可叠加 province / cause / court / yearFrom / yearTo,返回含 body_text 裁判文书全文。GET 走 query string,POST 走同名字段的 JSON body。判空别只读 total:本端点 0 条时恒发顶层 zero_result:true + zero_result_kind:"indistinguishable" + zero_result_selfcheck + zero_result_filter,以及 _meta.zero_result_note —— 即本端点明说自己分不出「这个条件真的一件都没有」与「这一次没取到」(含参数写法不对,以及取到的是一条被记住的空结果),page=1 也不例外;total / count / max_page / has_more / cases 在两种情形下逐位相同。该结论怎么自查(按端点分流的三步)写在响应的 zero_result_note 里,请照那一份打。⚠️ 判空解析必须一并接住这一格例外:/api/v1/fulltext 在 yearFrom 大于 yearTo(区间写反)那一支,顶层 zero_result / zero_result_kind / zero_result_selfcheck / zero_result_filter 与 _meta.zero_result_note 一位都不发,改发顶层 year_range_empty + year_range_empty_note:区间写反是能确定归因的 0,不归「分不出来」那一族管,两族互斥、永不同现。2026-08-29 第 710 发带 key 现网实测:q=民间借贷&yearFrom=2025&yearTo=2010 返 total:0 而零族一位不在,同参正序 yearFrom=2010&yearTo=2025 四位与 _meta.zero_result_note 齐发。⇒ 只认 zero_result 的判空代码在写反那一支读到的是 undefined,在 if 里是 false,请把 year_range_empty 一并认上。/api/v1/judgements 没有年份参数(送 yearFrom 返 400 bad_param),这一格与它无关。⚠️ 同名不同形,按一条线写的判空解析放到另一条上会静默失灵(2026-08-15 带 key 现网四条线逐条实测):zero_result 在 /api/v1/fulltext 与 /api/v1/judgements 上是顶层布尔,在 /api/v1/cases/search 上是 _meta.zero_result 对象(顶层没有这个字段),而 /api/v1/search 压根没有这个字段 —— 它按页码发 zero_is_condition_empty 或 zero_may_be_past_last_page。取错位置拿到的是 undefined,不报错、不抛异常,在 if 里就是 false,于是会被读成「本次不是 0 条」。四条线 0 条时都发 _meta.zero_result_note,但有两格例外。例外一,/api/v1/search:page 越过 max_page 那一支在打 SQL 之前就短路,顶层那两位与 _meta.zero_result_note、_meta.window_line_zero_note 一位都不发(2026-08-29 第 708 发现网实测 page=999:只发 page_exceeded:true 与 has_more_is_placeholder_constant:true)—— 即这条线一位恒发的判空机读位都没有,照 _meta.zero_result_note 判空的代码在那一支读到的正是本段要防的那个 undefined,请改认 page_exceeded。例外二,/api/v1/fulltext:yearFrom 大于 yearTo(区间写反)那一支,顶层那四位与 _meta.zero_result_note 一位都不发,改发 year_range_empty + year_range_empty_note(2026-08-29 第 710 发带 key 现网实测 q=民间借贷&yearFrom=2025&yearTo=2010:total:0 而零族一位不在;同参正序四位与 note 齐发)—— 那是能确定归因的 0,与「分不出来」那一族互斥,请把 year_range_empty 一并认上。各线 note 里的自查步骤按端点分流,别把一条线的步骤搬到另一条上。⚠️ 顶层机读位同样按线分叉,四条付费检索线没有两条是同一套(2026-08-15 带 key 现网四条线逐条实测,2026-08-21 复测并按实况订正线名):total(该条件下的真实命中数)只有 /api/v1/fulltext 与 /api/v1/judgements 发,/api/v1/cases/search 与 /api/v1/search 根本没有这个字段 —— /api/v1/cases/search 与 /api/v1/search 的 count 是本页条数、不是命中数;has_more(还有没有下一页)只有 /api/v1/fulltext、/api/v1/judgements 与 /api/v1/search 发,/api/v1/cases/search 根本没有这个字段,故 /api/v1/cases/search 判还有没有下一页只能比 page 与 max_page(⚠️ 该线命中触顶时 max_page 是 ceil(2000/pageSize) 的取样窗口末页、不是真末页,响应同时置 n_cases_is_capped 与 max_page_is_sample_window,此时「翻到 max_page」只等于翻完了那 2000 条样本);source_url_missing(本页 source_url 为空的条数)只有 /api/v1/fulltext 与 /api/v1/search 发,/api/v1/judgements 与 /api/v1/cases/search 根本没有这个字段;本页三个「为空」计数 court_empty、procedure_empty 与 case_type_empty(本页该字段为空的条数)只有 /api/v1/fulltext 发,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(发它的那条线与免费的 /api/cases/search 同名同义),其中 case_type_empty 还只数真空值与裸数字代码、数不到「其他」,要含「其他」的那个「未标注」桶请逐条数 case_type_axis === "" —— 但这个机读出口同样只有一条线逐条发,还是 /api/v1/fulltext(与免费的 /api/cases/search 同名同义):2026-08-15 实测同一分钟四条线,fulltext 一页 40 条 40/40 都带这一位、其中 case_type_axis === "" 的是 19/40,而 /api/v1/judgements 20 条、/api/v1/cases/search 20 条、/api/v1/search 10 条 一条都不带,拿 doc_id 回查 /api/v1/cases/{doc_id} 也没有这个字段。别把这个写法带去那三条线:undefined === "" 恒为 false,于是「未标注」那个桶被静默数成 0 条(同一刻 fulltext 侧实测是 19/40)。那三条线里也只有 /api/v1/judgements 逐条有 case_type 可判(实测 20/20),/api/v1/cases/search 与 /api/v1/search 连这个字段都没有(实测 20/20、10/10 一条都不带;它们逐条的 reason 是案由、不是案件类型)。缺的那一位读到的是 undefined,不报错、不抛异常:在 if 里是 false、参与算术是 NaN,于是「本页没有缺链」「已经翻到最后一页」「命中 0 条」这类结论会被静默地做出来。另有一族讲的不是空不空而是来路:province_backfilled_count、province_backfill_withheld_count 与 province_empty_count(本页 province 的来路计数:几条是本站按案号代字补出来的、几条是闸判分不清而刻意留空的、几条空着没补上)只有 /api/v1/fulltext 发,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(同样与免费的 /api/cases/search 同名同义,口径与线名的单一来源是 lib/province-backfill-marker)。这一族缺席不等于那几条线的 province 更干净:它们整个不跑本站的回填层,逐条的 province 一律是上游原值 —— 为空就是空、不会被补,也没有分辨来路的那三位;而在发它的那条线上,2026-08-23 带 key 现网实测同一分钟、同一 pageSize,逐页比率从一页 0/50 摆到一页 19/37(≈51.4%),拿任何一页外推全库都是错的,请逐页读这三个数。跨线对账时也别把「同一份文书两条线 province 不同」读成数据不一致。还有一位讲的既不是空不空、也不是来路,而是值本身根本不可信:judgement_date_implausible(本页 judgement_date 非空但越界的条数(年份 > 当年 / 早于 1949 / 格式与月日越界;判据是 lib/judgement-date 的 plausibleDate,与 /cases 页排序置末、年份分布、报告 TXT 同一份))只有 /api/v1/fulltext 与 /api/v1/judgements 发,/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(2026-08-29 带 key 现网四条线亲打);它与同一响应里的 judgement_date_empty 严格互斥:那一位只数字段为空,本位只数非空但越界,所以 judgement_date_empty 为 0 不等于本页日期没问题 —— 实测首行写着 2091-09-02 的那一页,judgement_date_empty 照报 0。缺这一位的那两条线请逐条自己判(形如 YYYY-MM-DD 且 1949 ≤ 年 ≤ 当年、月 1-12、日 1-31),但别把发它的那两条线上的数字搬过去:那两条线读的是另一套语料,逐条 doc_id 不带 ws / ws_new / 2025 分片前缀(2026-08-29 实测 300/300、150/150 无前缀),而越界条目在发它的那两条线上实测集中在 ws_new 老分片。⚠️ 同日在缺这一位的两条线上实测 0/300 与 0/150 越界,那不是合格证:同一批脏日期在 /api/v1/fulltext 上带 q 走相关度是 0、不带 q 走日期倒序就顶到第一屏,页面上的 0 可以只是顺序的产物,请按你实际用的顺序与写法各量一次。还有三位讲的是这条记录还能不能回溯到原文:case_no_empty、judgement_date_empty 与 source_url_fallback_missing(本页几条 case_no 为空、几条 judgement_date 为空、几条两条退路同时断掉(既没有链接、又缺这两个锚点之一))只有 /api/v1/fulltext 与 /api/v1/judgements 发,/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(2026-08-29 带 key 现网四条线亲打;三个数恒发、逐页现算,本页为 0 时也发,故「没有这个字段」永远不是「本页没事」的意思)。第三位的判据里那个且是真的:它不是缺链条数、也不是把两个空值率相加,而是「没有 source_url 并且 case_no 与 judgement_date 至少缺一个」的条数 —— 所以「链接全空但锚点齐全」的页面它照返 0,拿它给「本页没有缺链」盖章是反的(缺链条数请读 source_url_missing,而那一位又是另一个切分,见本段前面那句讲它的话)。⚠️ 这三位缺席最要命的一点,是它在缺席的那两条线上根本不会露馅:那两条线逐条 case_no / judgement_date / source_url 三键俱全且非空(2026-08-29 实测 20/20、10/10),自己算出来本来就是 0;于是读 undefined 的解析与算得一手好 0 的解析,在那两条线上输出逐位相同,怎么测都对。搬到发这三位的线上才开始静默说谎:同日 /api/v1/fulltext q=离婚 一页 40 条实测 source_url 40/40 全空、case_no_empty 7、judgement_date_empty 21、source_url_fallback_missing 21 —— 过半的条目当场既回不到原网、也报不出案号或日期。⚠️ 逐条字段同样按线分叉,且分叉法与顶层那三位不是同一套(2026-08-15 带 key 现网四条线各取一页实测,同页各条字段集逐条相同;2026-08-22 复打一遍,逐格相同):案由这一格,/api/v1/fulltext 与 /api/v1/judgements 逐条叫 cause,/api/v1/cases/search 与 /api/v1/search 逐条叫 reason;裁判文书正文这一格,/api/v1/fulltext 与 /api/v1/judgements 逐条叫 body_text,/api/v1/cases/search 逐条叫 body_excerpt(只有约 150 字的摘录),而 /api/v1/search 逐条一个正文字段都没有(⚠️ 回查 /api/v1/cases/{doc_id} 同样拿不到正文 —— 该 doc_id 落通用样本库分支,逐条就是本线这 8 个字段、一个正文字段都没有,响应 _meta.full_text 恒为 false;2026-08-22 实测本线 50 条 doc_id 逐条回查:44 条返 200 但无正文、6 条(sh_ 形态)连回查都不成立(原样 404 / 编码后 400 bad_doc_id),0 条拿到正文。读到的 undefined 与「这条文书没有全文」长得一模一样 —— 要全文只能改用 /api/v1/fulltext 与 /api/v1/judgements(/api/v1/cases/search 逐条只有约 150 字的 body_excerpt,不是全文));「本条正文是不是空的」那一位 has_full_text 只有一条线逐条发,就是 /api/v1/fulltext,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 逐条根本没有这个字段 —— 那三条线里 /api/v1/judgements 逐条只有 body_text 本身(外加 body_text_truncated / body_text_is_withheld_reason 两个标位),顶层也没有 full_text_missing;量长度那一格也另起一套 —— 本条正文有多长这一格,/api/v1/fulltext 逐条叫 body_text_len,/api/v1/cases/search 逐条叫 body_excerpt_len,而 /api/v1/judgements 与 /api/v1/search 逐条一个都没有(/api/v1/cases/search 量的是摘录、还配一个 body_excerpt_truncated,别把它当成正文长度)。缺的那个名字读到的是 undefined,不报错、不抛异常,而且会往两个相反的方向静默出错:if (!r.has_full_text) 这种写法把整页判成「都没有全文」全部丢掉,r.body_text_len < 200 这种写法则恒为 false、一条也筛不掉。案件类型这一族分叉得更狠:本条的案件类型原值这一格,/api/v1/fulltext 与 /api/v1/judgements 逐条叫 case_type,而 /api/v1/cases/search 与 /api/v1/search 逐条连原值都没有(它们逐条的 reason 是案由、不是案件类型);而三个派生位 case_type_axis、case_type_normalized 与 procedure_normalized 只有一条线逐条发,就是 /api/v1/fulltext,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 逐条根本没有这个字段(2026-08-15 同一分钟四条线实测:fulltext 40/40 带,judgements 20 条、cases/search 20 条、search 10 条 一条都不带)。所以本页教的「要含「其他」的那个「未标注」桶请逐条数 case_type_axis === ""」只在一条线上成立 —— /api/v1/fulltext:另外三条线读到的是 undefined,而 undefined === "" 恒为 false,那个桶会被静默数成 0 条 —— 同一刻 fulltext 侧实测它是 19/40。 |
| GET | /api/v1/cases/{doc_id} | X-API-Key | 依 doc_id 取单条判决详情。两种 doc_id 都认:cases/search 与 search 返回的样本库 id(32 位十六进制),以及 fulltext 返回的全量库 id(形如 2025:13fdca…,带分片前缀)。两种 id 返回的字段集不同,请读响应里的 _meta.full_text 布尔位判断本次有没有全文(2026-08-03 起该位逐条实算:此前全量库分支写死 true,实测 8/8 空正文条目照发 true,现已按本次正文实判);全量库 id → 带 body_text 裁判文书全文字段(是「带这个字段」不是「逐条保证非空」:上游个别文书未入正文,且正文有 6000 字入库上限、触顶条目 full_text 仍为 true,故另逐条给出 body_text_len / has_full_text / body_text_truncated,与 fulltext 同名同义;source_url 当前为 null,用 case_no + judgement_date 回裁判文书网自行核对 —— 这一侧是系统性为空、不是「部分文书暂缺」(2026-08-03 实测 39 条互异 doc_id 横跨 6 个分片 39/39 全 null),换文书、换分片、重试都不会拿到链接,故别把它写成重试逻辑;2026-08-03 起该分支恒发机读位 _meta.source_url_absent(逐条实算)与完整口径 _meta.source_url_note;court 少量条目被数据源写成了「不上网事由」,核对时以案号为准,详见 fulltext 响应的 _meta.court_note);样本库 id → 不含 body_text 全文(劳动争议分片只有约 150 字的 body_excerpt 摘录,通用分片连摘录也没有),但 source_url 有值(由 doc_id 按固定前缀拼接得到,未逐条验证可达)。另注意案由字段名分叉:样本库分支叫 reason、全量库分支叫 cause,按单边字段名硬解另一边会拿到 undefined。⚠️ 拿检索结果的 doc_id 回查本端点,逐条字段集与那条检索线并不相等(2026-08-15 带 key 现网实测,三条分支各取 3 个互异 doc_id、每条分支 3/3 逐条相同;2026-08-22 复打一遍,逐格相同):全量库分支(<dataset>:<id> 形态,doc_id 来自 /api/v1/fulltext)逐条 17 个字段,= 那条检索线的 20 个、减去 case_type_axis、case_type_normalized、court_is_court_name 与 procedure_normalized 这 4 个、另多 struct 这 1 个;劳动争议样本库分支(doc_id 来自 /api/v1/cases/search)逐条 17 个字段,= 那条检索线的 17 个、减去 similarity 这 1 个、另多 monthly_salary 这 1 个;通用样本库分支(doc_id 来自 /api/v1/search)逐条 8 个字段,与那条检索线完全相同。掉的这几个里,court_is_court_name 是本页明文教客户用来过滤 court 脏值的那一位——而脏值本身照样过得来:同一条文书 ws_new:ba057575-31dc-437b-aae4-a91200a1b7ae 在 /api/v1/fulltext 侧被标 court_is_court_name:false、court 为「人民法院认为不宜在互联网公布的其他情形」,回查本端点 court 一字不差,而那个布尔位不在响应里、_meta 里也没有 court_note。读它拿到的是 undefined,不报错、不抛异常,还会往两个相反的方向静默出错:if (!c.court_is_court_name) 连干净的法院名一起丢光,c.court_is_court_name === false 则一条脏值也标不出来。本端点要判 court 干不干净,请在检索线那一侧先过滤好再回查,或自行按「以法院/法庭结尾」判、并以案号回原网核对。掉的这几个里还有第二位是本页明文教客户用的:case_type_axis —— 本页写着「要含「其他」的那个「未标注」桶请逐条数 case_type_axis === ""」,而本端点逐条没有这个字段。同一条文书 ws_new:edfe2619-8c78-4a55-b3e1-eb17cd6c38a6 在 /api/v1/fulltext 侧是 case_type:"其他" + case_type_axis:""(正是那个桶里的条目),回查本端点 case_type 一字不差照样返回,而 case_type_axis 不在响应里、_meta 里也没有 case_type_note。undefined === "" 恒为 false,于是那个桶在本端点侧被静默数成 0 条(同一刻同一页 fulltext 侧实测是 19/40)。本端点要判这条属不属于「未标注」桶,请在 /api/v1/fulltext 那一侧读好 case_type_axis 再回查。⚠️ 「两条样本库检索线的 doc_id 都收得下」只有一条成立(2026-08-22 带 key 现网普查,/api/v1/search 10 组条件 × 各 20 条、/api/v1/cases/search 3 组条件 × 各 20 条):/api/v1/cases/search 60/60 逐条收得下,而 /api/v1/search 20/200(10.0%)是sh_ 前缀形态,本端点一律 400 bad_doc_id,且其中 10 条串内含 /、原样拼进 URL 会打到站点 404 页(text/html)。本 API 没有第二个按 id 回查的端点 ⇒ 这部分文书取不到详情,请按响应里的 unsupported_known_form 分流,别把检索结果里的 doc_id 无条件排队回查。 |
| GET | /api/v1/judgements | X-API-Key | 全量库按省份 / 案由浏览(不带关键词)。每条带 body_text 裁判文书全文 + 结构化的 case_no / cause / procedure / 法院 / 日期;source_url 当前为 null(2026-07-23 切 ES 后的变化,回原文核对用 case_no + judgement_date)。适合按地域或案由批量取样。至少传 province 或 cause 之一,两者均为分词 OR 匹配(非左前缀,案由请送词干);total 为真实命中数,但可翻页范围为排序后的前 10000 条,需更深请用 fulltext 或 search 分片取。判空别只读 total:本端点 0 条时恒发顶层 zero_result:true + zero_result_kind:"indistinguishable" + zero_result_selfcheck + zero_result_filter,以及 _meta.zero_result_note —— 即本端点明说自己分不出「这个条件真的一件都没有」与「这一次没取到」(含参数写法不对,以及取到的是一条被记住的空结果),page=1 也不例外;total / count / max_page / has_more / cases 在两种情形下逐位相同。该结论怎么自查(按端点分流的三步)写在响应的 zero_result_note 里,请照那一份打。⚠️ 同名不同形,按一条线写的判空解析放到另一条上会静默失灵(2026-08-15 带 key 现网四条线逐条实测):zero_result 在 /api/v1/fulltext 与 /api/v1/judgements 上是顶层布尔,在 /api/v1/cases/search 上是 _meta.zero_result 对象(顶层没有这个字段),而 /api/v1/search 压根没有这个字段 —— 它按页码发 zero_is_condition_empty 或 zero_may_be_past_last_page。取错位置拿到的是 undefined,不报错、不抛异常,在 if 里就是 false,于是会被读成「本次不是 0 条」。四条线 0 条时都发 _meta.zero_result_note,但有两格例外。例外一,/api/v1/search:page 越过 max_page 那一支在打 SQL 之前就短路,顶层那两位与 _meta.zero_result_note、_meta.window_line_zero_note 一位都不发(2026-08-29 第 708 发现网实测 page=999:只发 page_exceeded:true 与 has_more_is_placeholder_constant:true)—— 即这条线一位恒发的判空机读位都没有,照 _meta.zero_result_note 判空的代码在那一支读到的正是本段要防的那个 undefined,请改认 page_exceeded。例外二,/api/v1/fulltext:yearFrom 大于 yearTo(区间写反)那一支,顶层那四位与 _meta.zero_result_note 一位都不发,改发 year_range_empty + year_range_empty_note(2026-08-29 第 710 发带 key 现网实测 q=民间借贷&yearFrom=2025&yearTo=2010:total:0 而零族一位不在;同参正序四位与 note 齐发)—— 那是能确定归因的 0,与「分不出来」那一族互斥,请把 year_range_empty 一并认上。各线 note 里的自查步骤按端点分流,别把一条线的步骤搬到另一条上。⚠️ 顶层机读位同样按线分叉,四条付费检索线没有两条是同一套(2026-08-15 带 key 现网四条线逐条实测,2026-08-21 复测并按实况订正线名):total(该条件下的真实命中数)只有 /api/v1/fulltext 与 /api/v1/judgements 发,/api/v1/cases/search 与 /api/v1/search 根本没有这个字段 —— /api/v1/cases/search 与 /api/v1/search 的 count 是本页条数、不是命中数;has_more(还有没有下一页)只有 /api/v1/fulltext、/api/v1/judgements 与 /api/v1/search 发,/api/v1/cases/search 根本没有这个字段,故 /api/v1/cases/search 判还有没有下一页只能比 page 与 max_page(⚠️ 该线命中触顶时 max_page 是 ceil(2000/pageSize) 的取样窗口末页、不是真末页,响应同时置 n_cases_is_capped 与 max_page_is_sample_window,此时「翻到 max_page」只等于翻完了那 2000 条样本);source_url_missing(本页 source_url 为空的条数)只有 /api/v1/fulltext 与 /api/v1/search 发,/api/v1/judgements 与 /api/v1/cases/search 根本没有这个字段;本页三个「为空」计数 court_empty、procedure_empty 与 case_type_empty(本页该字段为空的条数)只有 /api/v1/fulltext 发,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(发它的那条线与免费的 /api/cases/search 同名同义),其中 case_type_empty 还只数真空值与裸数字代码、数不到「其他」,要含「其他」的那个「未标注」桶请逐条数 case_type_axis === "" —— 但这个机读出口同样只有一条线逐条发,还是 /api/v1/fulltext(与免费的 /api/cases/search 同名同义):2026-08-15 实测同一分钟四条线,fulltext 一页 40 条 40/40 都带这一位、其中 case_type_axis === "" 的是 19/40,而 /api/v1/judgements 20 条、/api/v1/cases/search 20 条、/api/v1/search 10 条 一条都不带,拿 doc_id 回查 /api/v1/cases/{doc_id} 也没有这个字段。别把这个写法带去那三条线:undefined === "" 恒为 false,于是「未标注」那个桶被静默数成 0 条(同一刻 fulltext 侧实测是 19/40)。那三条线里也只有 /api/v1/judgements 逐条有 case_type 可判(实测 20/20),/api/v1/cases/search 与 /api/v1/search 连这个字段都没有(实测 20/20、10/10 一条都不带;它们逐条的 reason 是案由、不是案件类型)。缺的那一位读到的是 undefined,不报错、不抛异常:在 if 里是 false、参与算术是 NaN,于是「本页没有缺链」「已经翻到最后一页」「命中 0 条」这类结论会被静默地做出来。另有一族讲的不是空不空而是来路:province_backfilled_count、province_backfill_withheld_count 与 province_empty_count(本页 province 的来路计数:几条是本站按案号代字补出来的、几条是闸判分不清而刻意留空的、几条空着没补上)只有 /api/v1/fulltext 发,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(同样与免费的 /api/cases/search 同名同义,口径与线名的单一来源是 lib/province-backfill-marker)。这一族缺席不等于那几条线的 province 更干净:它们整个不跑本站的回填层,逐条的 province 一律是上游原值 —— 为空就是空、不会被补,也没有分辨来路的那三位;而在发它的那条线上,2026-08-23 带 key 现网实测同一分钟、同一 pageSize,逐页比率从一页 0/50 摆到一页 19/37(≈51.4%),拿任何一页外推全库都是错的,请逐页读这三个数。跨线对账时也别把「同一份文书两条线 province 不同」读成数据不一致。还有一位讲的既不是空不空、也不是来路,而是值本身根本不可信:judgement_date_implausible(本页 judgement_date 非空但越界的条数(年份 > 当年 / 早于 1949 / 格式与月日越界;判据是 lib/judgement-date 的 plausibleDate,与 /cases 页排序置末、年份分布、报告 TXT 同一份))只有 /api/v1/fulltext 与 /api/v1/judgements 发,/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(2026-08-29 带 key 现网四条线亲打);它与同一响应里的 judgement_date_empty 严格互斥:那一位只数字段为空,本位只数非空但越界,所以 judgement_date_empty 为 0 不等于本页日期没问题 —— 实测首行写着 2091-09-02 的那一页,judgement_date_empty 照报 0。缺这一位的那两条线请逐条自己判(形如 YYYY-MM-DD 且 1949 ≤ 年 ≤ 当年、月 1-12、日 1-31),但别把发它的那两条线上的数字搬过去:那两条线读的是另一套语料,逐条 doc_id 不带 ws / ws_new / 2025 分片前缀(2026-08-29 实测 300/300、150/150 无前缀),而越界条目在发它的那两条线上实测集中在 ws_new 老分片。⚠️ 同日在缺这一位的两条线上实测 0/300 与 0/150 越界,那不是合格证:同一批脏日期在 /api/v1/fulltext 上带 q 走相关度是 0、不带 q 走日期倒序就顶到第一屏,页面上的 0 可以只是顺序的产物,请按你实际用的顺序与写法各量一次。还有三位讲的是这条记录还能不能回溯到原文:case_no_empty、judgement_date_empty 与 source_url_fallback_missing(本页几条 case_no 为空、几条 judgement_date 为空、几条两条退路同时断掉(既没有链接、又缺这两个锚点之一))只有 /api/v1/fulltext 与 /api/v1/judgements 发,/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(2026-08-29 带 key 现网四条线亲打;三个数恒发、逐页现算,本页为 0 时也发,故「没有这个字段」永远不是「本页没事」的意思)。第三位的判据里那个且是真的:它不是缺链条数、也不是把两个空值率相加,而是「没有 source_url 并且 case_no 与 judgement_date 至少缺一个」的条数 —— 所以「链接全空但锚点齐全」的页面它照返 0,拿它给「本页没有缺链」盖章是反的(缺链条数请读 source_url_missing,而那一位又是另一个切分,见本段前面那句讲它的话)。⚠️ 这三位缺席最要命的一点,是它在缺席的那两条线上根本不会露馅:那两条线逐条 case_no / judgement_date / source_url 三键俱全且非空(2026-08-29 实测 20/20、10/10),自己算出来本来就是 0;于是读 undefined 的解析与算得一手好 0 的解析,在那两条线上输出逐位相同,怎么测都对。搬到发这三位的线上才开始静默说谎:同日 /api/v1/fulltext q=离婚 一页 40 条实测 source_url 40/40 全空、case_no_empty 7、judgement_date_empty 21、source_url_fallback_missing 21 —— 过半的条目当场既回不到原网、也报不出案号或日期。⚠️ 逐条字段同样按线分叉,且分叉法与顶层那三位不是同一套(2026-08-15 带 key 现网四条线各取一页实测,同页各条字段集逐条相同;2026-08-22 复打一遍,逐格相同):案由这一格,/api/v1/fulltext 与 /api/v1/judgements 逐条叫 cause,/api/v1/cases/search 与 /api/v1/search 逐条叫 reason;裁判文书正文这一格,/api/v1/fulltext 与 /api/v1/judgements 逐条叫 body_text,/api/v1/cases/search 逐条叫 body_excerpt(只有约 150 字的摘录),而 /api/v1/search 逐条一个正文字段都没有(⚠️ 回查 /api/v1/cases/{doc_id} 同样拿不到正文 —— 该 doc_id 落通用样本库分支,逐条就是本线这 8 个字段、一个正文字段都没有,响应 _meta.full_text 恒为 false;2026-08-22 实测本线 50 条 doc_id 逐条回查:44 条返 200 但无正文、6 条(sh_ 形态)连回查都不成立(原样 404 / 编码后 400 bad_doc_id),0 条拿到正文。读到的 undefined 与「这条文书没有全文」长得一模一样 —— 要全文只能改用 /api/v1/fulltext 与 /api/v1/judgements(/api/v1/cases/search 逐条只有约 150 字的 body_excerpt,不是全文));「本条正文是不是空的」那一位 has_full_text 只有一条线逐条发,就是 /api/v1/fulltext,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 逐条根本没有这个字段 —— 那三条线里 /api/v1/judgements 逐条只有 body_text 本身(外加 body_text_truncated / body_text_is_withheld_reason 两个标位),顶层也没有 full_text_missing;量长度那一格也另起一套 —— 本条正文有多长这一格,/api/v1/fulltext 逐条叫 body_text_len,/api/v1/cases/search 逐条叫 body_excerpt_len,而 /api/v1/judgements 与 /api/v1/search 逐条一个都没有(/api/v1/cases/search 量的是摘录、还配一个 body_excerpt_truncated,别把它当成正文长度)。缺的那个名字读到的是 undefined,不报错、不抛异常,而且会往两个相反的方向静默出错:if (!r.has_full_text) 这种写法把整页判成「都没有全文」全部丢掉,r.body_text_len < 200 这种写法则恒为 false、一条也筛不掉。案件类型这一族分叉得更狠:本条的案件类型原值这一格,/api/v1/fulltext 与 /api/v1/judgements 逐条叫 case_type,而 /api/v1/cases/search 与 /api/v1/search 逐条连原值都没有(它们逐条的 reason 是案由、不是案件类型);而三个派生位 case_type_axis、case_type_normalized 与 procedure_normalized 只有一条线逐条发,就是 /api/v1/fulltext,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 逐条根本没有这个字段(2026-08-15 同一分钟四条线实测:fulltext 40/40 带,judgements 20 条、cases/search 20 条、search 10 条 一条都不带)。所以本页教的「要含「其他」的那个「未标注」桶请逐条数 case_type_axis === ""」只在一条线上成立 —— /api/v1/fulltext:另外三条线读到的是 undefined,而 undefined === "" 恒为 false,那个桶会被静默数成 0 条 —— 同一刻 fulltext 侧实测它是 19/40。 |
| POST | /api/v1/search | X-API-Key | 全案由通用检索(beta 样本库 120 万+,覆盖 1200+ 案由):按 reason / province / court / year_from / year_to 结构化过滤,q 为标题关键词(可空)。每条带 source_url 原文链接,可深翻(page × pageSize ≤ 10000)。不返回裁判文书全文,要全文用 fulltext。⚠️ 同名不同形,按一条线写的判空解析放到另一条上会静默失灵(2026-08-15 带 key 现网四条线逐条实测):zero_result 在 /api/v1/fulltext 与 /api/v1/judgements 上是顶层布尔,在 /api/v1/cases/search 上是 _meta.zero_result 对象(顶层没有这个字段),而 /api/v1/search 压根没有这个字段 —— 它按页码发 zero_is_condition_empty 或 zero_may_be_past_last_page。取错位置拿到的是 undefined,不报错、不抛异常,在 if 里就是 false,于是会被读成「本次不是 0 条」。四条线 0 条时都发 _meta.zero_result_note,但有两格例外。例外一,/api/v1/search:page 越过 max_page 那一支在打 SQL 之前就短路,顶层那两位与 _meta.zero_result_note、_meta.window_line_zero_note 一位都不发(2026-08-29 第 708 发现网实测 page=999:只发 page_exceeded:true 与 has_more_is_placeholder_constant:true)—— 即这条线一位恒发的判空机读位都没有,照 _meta.zero_result_note 判空的代码在那一支读到的正是本段要防的那个 undefined,请改认 page_exceeded。例外二,/api/v1/fulltext:yearFrom 大于 yearTo(区间写反)那一支,顶层那四位与 _meta.zero_result_note 一位都不发,改发 year_range_empty + year_range_empty_note(2026-08-29 第 710 发带 key 现网实测 q=民间借贷&yearFrom=2025&yearTo=2010:total:0 而零族一位不在;同参正序四位与 note 齐发)—— 那是能确定归因的 0,与「分不出来」那一族互斥,请把 year_range_empty 一并认上。各线 note 里的自查步骤按端点分流,别把一条线的步骤搬到另一条上。⚠️ 顶层机读位同样按线分叉,四条付费检索线没有两条是同一套(2026-08-15 带 key 现网四条线逐条实测,2026-08-21 复测并按实况订正线名):total(该条件下的真实命中数)只有 /api/v1/fulltext 与 /api/v1/judgements 发,/api/v1/cases/search 与 /api/v1/search 根本没有这个字段 —— /api/v1/cases/search 与 /api/v1/search 的 count 是本页条数、不是命中数;has_more(还有没有下一页)只有 /api/v1/fulltext、/api/v1/judgements 与 /api/v1/search 发,/api/v1/cases/search 根本没有这个字段,故 /api/v1/cases/search 判还有没有下一页只能比 page 与 max_page(⚠️ 该线命中触顶时 max_page 是 ceil(2000/pageSize) 的取样窗口末页、不是真末页,响应同时置 n_cases_is_capped 与 max_page_is_sample_window,此时「翻到 max_page」只等于翻完了那 2000 条样本);source_url_missing(本页 source_url 为空的条数)只有 /api/v1/fulltext 与 /api/v1/search 发,/api/v1/judgements 与 /api/v1/cases/search 根本没有这个字段;本页三个「为空」计数 court_empty、procedure_empty 与 case_type_empty(本页该字段为空的条数)只有 /api/v1/fulltext 发,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(发它的那条线与免费的 /api/cases/search 同名同义),其中 case_type_empty 还只数真空值与裸数字代码、数不到「其他」,要含「其他」的那个「未标注」桶请逐条数 case_type_axis === "" —— 但这个机读出口同样只有一条线逐条发,还是 /api/v1/fulltext(与免费的 /api/cases/search 同名同义):2026-08-15 实测同一分钟四条线,fulltext 一页 40 条 40/40 都带这一位、其中 case_type_axis === "" 的是 19/40,而 /api/v1/judgements 20 条、/api/v1/cases/search 20 条、/api/v1/search 10 条 一条都不带,拿 doc_id 回查 /api/v1/cases/{doc_id} 也没有这个字段。别把这个写法带去那三条线:undefined === "" 恒为 false,于是「未标注」那个桶被静默数成 0 条(同一刻 fulltext 侧实测是 19/40)。那三条线里也只有 /api/v1/judgements 逐条有 case_type 可判(实测 20/20),/api/v1/cases/search 与 /api/v1/search 连这个字段都没有(实测 20/20、10/10 一条都不带;它们逐条的 reason 是案由、不是案件类型)。缺的那一位读到的是 undefined,不报错、不抛异常:在 if 里是 false、参与算术是 NaN,于是「本页没有缺链」「已经翻到最后一页」「命中 0 条」这类结论会被静默地做出来。另有一族讲的不是空不空而是来路:province_backfilled_count、province_backfill_withheld_count 与 province_empty_count(本页 province 的来路计数:几条是本站按案号代字补出来的、几条是闸判分不清而刻意留空的、几条空着没补上)只有 /api/v1/fulltext 发,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(同样与免费的 /api/cases/search 同名同义,口径与线名的单一来源是 lib/province-backfill-marker)。这一族缺席不等于那几条线的 province 更干净:它们整个不跑本站的回填层,逐条的 province 一律是上游原值 —— 为空就是空、不会被补,也没有分辨来路的那三位;而在发它的那条线上,2026-08-23 带 key 现网实测同一分钟、同一 pageSize,逐页比率从一页 0/50 摆到一页 19/37(≈51.4%),拿任何一页外推全库都是错的,请逐页读这三个数。跨线对账时也别把「同一份文书两条线 province 不同」读成数据不一致。还有一位讲的既不是空不空、也不是来路,而是值本身根本不可信:judgement_date_implausible(本页 judgement_date 非空但越界的条数(年份 > 当年 / 早于 1949 / 格式与月日越界;判据是 lib/judgement-date 的 plausibleDate,与 /cases 页排序置末、年份分布、报告 TXT 同一份))只有 /api/v1/fulltext 与 /api/v1/judgements 发,/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(2026-08-29 带 key 现网四条线亲打);它与同一响应里的 judgement_date_empty 严格互斥:那一位只数字段为空,本位只数非空但越界,所以 judgement_date_empty 为 0 不等于本页日期没问题 —— 实测首行写着 2091-09-02 的那一页,judgement_date_empty 照报 0。缺这一位的那两条线请逐条自己判(形如 YYYY-MM-DD 且 1949 ≤ 年 ≤ 当年、月 1-12、日 1-31),但别把发它的那两条线上的数字搬过去:那两条线读的是另一套语料,逐条 doc_id 不带 ws / ws_new / 2025 分片前缀(2026-08-29 实测 300/300、150/150 无前缀),而越界条目在发它的那两条线上实测集中在 ws_new 老分片。⚠️ 同日在缺这一位的两条线上实测 0/300 与 0/150 越界,那不是合格证:同一批脏日期在 /api/v1/fulltext 上带 q 走相关度是 0、不带 q 走日期倒序就顶到第一屏,页面上的 0 可以只是顺序的产物,请按你实际用的顺序与写法各量一次。还有三位讲的是这条记录还能不能回溯到原文:case_no_empty、judgement_date_empty 与 source_url_fallback_missing(本页几条 case_no 为空、几条 judgement_date 为空、几条两条退路同时断掉(既没有链接、又缺这两个锚点之一))只有 /api/v1/fulltext 与 /api/v1/judgements 发,/api/v1/cases/search 与 /api/v1/search 根本没有这个字段(2026-08-29 带 key 现网四条线亲打;三个数恒发、逐页现算,本页为 0 时也发,故「没有这个字段」永远不是「本页没事」的意思)。第三位的判据里那个且是真的:它不是缺链条数、也不是把两个空值率相加,而是「没有 source_url 并且 case_no 与 judgement_date 至少缺一个」的条数 —— 所以「链接全空但锚点齐全」的页面它照返 0,拿它给「本页没有缺链」盖章是反的(缺链条数请读 source_url_missing,而那一位又是另一个切分,见本段前面那句讲它的话)。⚠️ 这三位缺席最要命的一点,是它在缺席的那两条线上根本不会露馅:那两条线逐条 case_no / judgement_date / source_url 三键俱全且非空(2026-08-29 实测 20/20、10/10),自己算出来本来就是 0;于是读 undefined 的解析与算得一手好 0 的解析,在那两条线上输出逐位相同,怎么测都对。搬到发这三位的线上才开始静默说谎:同日 /api/v1/fulltext q=离婚 一页 40 条实测 source_url 40/40 全空、case_no_empty 7、judgement_date_empty 21、source_url_fallback_missing 21 —— 过半的条目当场既回不到原网、也报不出案号或日期。⚠️ 逐条字段同样按线分叉,且分叉法与顶层那三位不是同一套(2026-08-15 带 key 现网四条线各取一页实测,同页各条字段集逐条相同;2026-08-22 复打一遍,逐格相同):案由这一格,/api/v1/fulltext 与 /api/v1/judgements 逐条叫 cause,/api/v1/cases/search 与 /api/v1/search 逐条叫 reason;裁判文书正文这一格,/api/v1/fulltext 与 /api/v1/judgements 逐条叫 body_text,/api/v1/cases/search 逐条叫 body_excerpt(只有约 150 字的摘录),而 /api/v1/search 逐条一个正文字段都没有(⚠️ 回查 /api/v1/cases/{doc_id} 同样拿不到正文 —— 该 doc_id 落通用样本库分支,逐条就是本线这 8 个字段、一个正文字段都没有,响应 _meta.full_text 恒为 false;2026-08-22 实测本线 50 条 doc_id 逐条回查:44 条返 200 但无正文、6 条(sh_ 形态)连回查都不成立(原样 404 / 编码后 400 bad_doc_id),0 条拿到正文。读到的 undefined 与「这条文书没有全文」长得一模一样 —— 要全文只能改用 /api/v1/fulltext 与 /api/v1/judgements(/api/v1/cases/search 逐条只有约 150 字的 body_excerpt,不是全文));「本条正文是不是空的」那一位 has_full_text 只有一条线逐条发,就是 /api/v1/fulltext,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 逐条根本没有这个字段 —— 那三条线里 /api/v1/judgements 逐条只有 body_text 本身(外加 body_text_truncated / body_text_is_withheld_reason 两个标位),顶层也没有 full_text_missing;量长度那一格也另起一套 —— 本条正文有多长这一格,/api/v1/fulltext 逐条叫 body_text_len,/api/v1/cases/search 逐条叫 body_excerpt_len,而 /api/v1/judgements 与 /api/v1/search 逐条一个都没有(/api/v1/cases/search 量的是摘录、还配一个 body_excerpt_truncated,别把它当成正文长度)。缺的那个名字读到的是 undefined,不报错、不抛异常,而且会往两个相反的方向静默出错:if (!r.has_full_text) 这种写法把整页判成「都没有全文」全部丢掉,r.body_text_len < 200 这种写法则恒为 false、一条也筛不掉。案件类型这一族分叉得更狠:本条的案件类型原值这一格,/api/v1/fulltext 与 /api/v1/judgements 逐条叫 case_type,而 /api/v1/cases/search 与 /api/v1/search 逐条连原值都没有(它们逐条的 reason 是案由、不是案件类型);而三个派生位 case_type_axis、case_type_normalized 与 procedure_normalized 只有一条线逐条发,就是 /api/v1/fulltext,/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 逐条根本没有这个字段(2026-08-15 同一分钟四条线实测:fulltext 40/40 带,judgements 20 条、cases/search 20 条、search 10 条 一条都不带)。所以本页教的「要含「其他」的那个「未标注」桶请逐条数 case_type_axis === ""」只在一条线上成立 —— /api/v1/fulltext:另外三条线读到的是 undefined,而 undefined === "" 恒为 false,那个桶会被静默数成 0 条 —— 同一刻 fulltext 侧实测它是 19/40。 |
| GET | /api/v1/stats?dimension=province | X-API-Key | 按维度切片聚合(省份 / 解雇原因 / 工龄分桶)。注意本端点读的是预计算快照——8 万条劳动争议样本,非实时查询,与其余端点走的全量库不是同一口径:n_cases 是每省封顶 4500 的抽样条数(实测 9 省并列 4500),不可用于跨省比体量;要真实案件量请用 /api/analytics。p25 / 中位 / p75 金额分位为真实统计,但口径是「经济补偿金/赔偿金」一项的判付额、不是本案判决总额(与 /api/v1/cases/search 的 compensation_judged 同源同式,2026-07-31 用青海 106 / 宁夏 490 / 黑龙江 603 三省逐位验死,详见响应 _meta.compensation_scope_note);cells 另返 avg_salary=劳动者月工资均值(元/月,是经济补偿金基数、非判付金额,同为未做价格调整的名义值,响应 _meta.avg_salary_note 有详解);win_rate 当前恒为 null。参数名是 dimension(非 dim),取值 province / term_reason / years_bucket。years_bucket 五格左闭右开(1-3=[1,3),恰满 3.0 年的 6,328 件在 3-5),而 /api/v1/cases/search 的 years_min/years_max 是双端闭——要按同一口径拉明细,上界须减一档(1-3 写 years_min=1&years_max=2.999 得 24,568,写 years_max=3 得 30,896,+25.7%),详见响应 _meta.years_range_note。cells 的排序也在响应里说清了(_meta.cells_order / cells_order_note):years_bucket 按工龄从小到大(2026-08-03 修正,此前跟另两维一样按 n_cases 倒序、把工龄轴打乱在第二格),province / term_reason 按 n_cases 倒序——province 那个顺序不是各省案件量排行榜,榜首 9 个省是被 4500 削平后并列的,先后任意,要真实排序请用 /api/analytics?dim=province。 |
每个数字都来自真实判决统计,不是模型推测。原文链接分端点看,而且要认全路径:样本库端点 /api/v1/cases/search、/api/v1/search 每条挂中国裁判文书网原文链接;走 ES 的 /api/v1/fulltext、/api/v1/judgements,以及免 key 的 /api/cases/search,当前都不返回直链(source_url 系统性为 null),以案号 + 裁判日期溯源 —— 详见下方各端点口径。
工龄、月薪、解雇原因、经济补偿金判付额 —— 规则引擎抽取,直接可查询,不用自己再清洗。注:金额字段 compensation_judged 是「经济补偿金/赔偿金」单项的判付额,不是本案判决总额(不含同案的加班费 / 二倍工资 / 未休年休假 / 社保工伤等判项;实测同案非费用类合计的中位是它的 2.06 倍)。每次响应的 _meta.compensation_scope_note 附完整口径与复测命令。
<dataset>:<id> 走的就是这一侧,source_url 同样为 null,而传 search / cases/search 发的 32 位十六进制 id 走样本库侧,source_url 有值但是按 doc_id 拼接出来的(未逐条验证可达),两侧可信度口径不同,响应 _meta 里各自写明。⚠️ 至于「这两侧什么时候会有链接」,本页不给排期、也不写成进行时:此处原先写着「补齐 ES 侧原文链接是在做的事项」,该句已删除并在此点名作废 —— 它没有日期、没有进度,站内也没有任何字段能让你核验它,却足以让你按「等等就有」去做接入设计。换成三条你查得到的:①成因不是「还没来得及补」,而是 ES 索引里的 doc_id 是内部 id(形如 <分片名>:<内部 id>)、不是裁判文书网的原始 docId,无从派生真实链接;这要上游在入库侧重新带上原始 docId 才会有,不是本层补个字段的事,故我们既不拼假链接、也不给你一个我们兑现不了的日期。②现状:自 2026-07-23 后端切 ES 起,/api/v1/fulltext 与 /api/v1/judgements 两侧的 source_url 口径未变(登记实测 2026-07-29,四省合计 110/110 全为 null)。③别信这一段,自己打一发就知道当下是什么状态:看 _meta.source_url_note 还在不在、_meta.empty_fields 是否仍含 source_url —— 真补上了,这两处会先变。请按「现在没有原文直链」做接入设计,把回链当可选增强,不要当既定路线图。③court 字段有个已知脏值:数据源把裁判文书网的「不上网事由」(如「以调解方式结案的」)写进了少量文书的 court,实测 fulltext 侧约 6%、另约 5% 为空。我们原样返回不改写,但每条附 court_is_court_name 布尔位、每页附 court_nonstandard 计数与 _meta.court_note——按法院统计请先按该布尔位过滤,核对文书请以案号为准。⚠️ 「每条附 court_is_court_name 布尔位、每页附 court_nonstandard 计数与 _meta.court_note——按法院统计请先按该布尔位过滤」这一句,只在 /api/v1/fulltext 这 1 条线上成立(2026-08-15 带 key 现网逐条实测,2026-08-22 复打一遍,逐格相同):/api/v1/judgements、/api/v1/cases/search 与 /api/v1/search 逐条都没有 court_is_court_name,顶层也没有 court_nonstandard、_meta 里没有 court_note;按 doc_id 回查的 /api/v1/cases/{doc_id} 3 条分支同样一个都没有。而 court 的脏值在这几条线上照样出现 —— 同一条文书在 /api/v1/fulltext 侧被标 court_is_court_name:false,回查详情端点 court 一字不差、布尔位却不在。缺的那一位读到 undefined,不报错、不抛异常:if (!c.court_is_court_name) 把整页当成脏值全丢,c.court_is_court_name === false 则一条也标不出来。这几条线上要判 court 干不干净,只能自行按「以法院/法庭结尾」判,核对文书仍以案号为准。⚠️ 「每条附 court_is_court_name 布尔位、每页附 court_nonstandard 计数」这两样虽并列着写,判据却不同、两个数对不上,不能互校(2026-08-15 带 key 现网 /api/v1/fulltext 逐页实测 6 个关键词):顶层的 court_nonstandard 只数 court 非空、且不以「法院/法庭」结尾的条目,court 是空串的一条都不计;而逐条的 court_is_court_name 把空串也判 false,与「写着不上网事由」混进同一格。两者因此恒差一个「本页 court 空串数」——实测 6/6 页逐页满足「court_nonstandard + 本页 court 空串数 = court_is_court_name:false 的条数」。差得最开的是 q=利息 那页:顶层报 3,逐条 court_is_court_name:false 的实有 15 条(相差的 12 条 court 是空串)。最容易读岔的反而是两个数相等的那种页:q=离婚 那页 court 一条空串都没有,两个数字恰好都是 7 —— 只抽这一个词核对,会得出「两者一致」的结论并把它写进代码,换个词就对不上了。而能把这两个数对上的第三个数,court_empty(本页 court 为空串的条数),自 2026-08-15 起已在本端点顶层发出,与同一份响应里 case_no_empty / cause_empty / judgement_date_empty 那三个同族计数并列(在此之前它只写在 _meta.court_note 的中文散文里,对账得去解析那段话)。但这一位只有一条线发:/api/v1/fulltext 之外的三条付费检索线顶层没有它,读到的是 undefined(不报错、不抛异常,在 if 里是 false、参与算术是 NaN)。所以:要统计「court 脏不脏」请以逐条的 court_is_court_name 为准、空串数直接读 court_empty(等价于 court_is_court_name:false 的条数减去 court_nonstandard);court_nonstandard 回答的是另一个问题——本页有多少条 court 被写成了非法院名的内容(不上网事由那一类)。④上面①②只覆盖带 key 的端点,而本页另一处推荐过的免 key 线 /api/cases/search 两边都不在:它走 ES 侧,source_url 与 fulltext / judgements 一样系统性为空(2026-07-29 实测 8 种查询形态合计 302/302 = 100% 为 null,与检索条件无关),核对同样走 case_no + judgement_date。别因为它的简称里也有「cases/search」就按①那条样本库口径预期它带链接 —— 这两个名字指的不是同一条线,判断请一律认全路径。/api/v1/fulltext 与 /api/v1/judgements:0 条恒发 zero_result_kind 为 indistinguishable,即这两条线明说自己分不出「这个条件真的一件都没有」与「这一次没取到」,page=1 也不例外,total / count / max_page / cases 在两种情形下逐位相同 —— 所以这两条线的 0 条不能直接入库或上报成「库里没有」。②/api/v1/cases/search:0 条恒发 _meta.zero_result(对象)与 _meta.zero_result_note;传了 province / reason / term_reason 之一时另发 _meta.filter_value_suspects,逐参数点名是哪个值不在词表里。③/api/v1/search:只有 page=1 的 0 条是可证的(zero_is_condition_empty),page>1 的 0 条不可判定(zero_may_be_past_last_page),判定法是把同一条件原样改成 page=1 再打一次。⚠️ 同名不同形,按一条线写的判空解析放到另一条上会静默失灵(2026-08-15 带 key 现网四条线逐条实测):zero_result 在 /api/v1/fulltext 与 /api/v1/judgements 上是顶层布尔,在 /api/v1/cases/search 上是 _meta.zero_result 对象(顶层没有这个字段),而 /api/v1/search 压根没有这个字段 —— 它按页码发 zero_is_condition_empty 或 zero_may_be_past_last_page。取错位置拿到的是 undefined,不报错、不抛异常,在 if 里就是 false,于是会被读成「本次不是 0 条」。四条线 0 条时都发 _meta.zero_result_note,但有两格例外。例外一,/api/v1/search:page 越过 max_page 那一支在打 SQL 之前就短路,顶层那两位与 _meta.zero_result_note、_meta.window_line_zero_note 一位都不发(2026-08-29 第 708 发现网实测 page=999:只发 page_exceeded:true 与 has_more_is_placeholder_constant:true)—— 即这条线一位恒发的判空机读位都没有,照 _meta.zero_result_note 判空的代码在那一支读到的正是本段要防的那个 undefined,请改认 page_exceeded。例外二,/api/v1/fulltext:yearFrom 大于 yearTo(区间写反)那一支,顶层那四位与 _meta.zero_result_note 一位都不发,改发 year_range_empty + year_range_empty_note(2026-08-29 第 710 发带 key 现网实测 q=民间借贷&yearFrom=2025&yearTo=2010:total:0 而零族一位不在;同参正序四位与 note 齐发)—— 那是能确定归因的 0,与「分不出来」那一族互斥,请把 year_range_empty 一并认上。各线 note 里的自查步骤按端点分流,别把一条线的步骤搬到另一条上。怎么自查:步骤按端点分流(去掉一个过滤键 / 换参数写法 / 换页码或 pageSize / 换另一个服务交叉复核),(那一步换的是服务不是语料:两条线在双方都认识的条件上返回的是同样的行,它能证的是「这次取到没取到」,不是「数据对不对得上」)单一来源是本次响应里的 _meta.zero_result_note,请照那一份打,别照另一条线的步骤打。zero_result_selfcheck 里的 conclusive_only_if_all_zero 说的就是几步全为 0 才算数:任何一步取到了数据,这个 0 就是「没取到」而不是「没有」。X-RateLimit-Remaining: 59 出现了 3 次(一把 key 一个计数器的话,这 12 个值必然互不相同)⇒ 当时至少 3 个独立计数器;②串行打满一个实例,第 61 次准时 429 —— 即 60 这个数字对单个实例是准的;③紧接着用同一把 key 在同一秒发 10 个并发,收到 3 个 429(Remaining 0、Retry-After 6)与 7 个 200(Remaining 59)。④这条对你选客户端最有用:同一把 key、同一分钟、同样的请求数,走一条 keep-alive 连接打 70 次 = 精确 60 个 200 + 10 个 429(全落同一实例);而每次新开连接串行打 66 次 = 一次 429 都没有(被摊到多个实例)。即 429 出不出现取决于你的 HTTP 客户端复不复用连接,与这把 key 用了多少次无关 —— 用连接池的客户端会更早撞 429,短连接脚本反而打不满。这意味着两件对你有影响的事:(a) 你一分钟内实际能打的次数是 60 × 当时活着的实例数,会随流量伸缩、你在客户端观察不到,照 60 自限流会系统性少用;(b) 你最后读到的 Remaining 即使是 59,下一个请求照样可能 429,而且 Remaining 会往上走(实测 0 → 一秒后 59)。所以请不要拿 X-RateLimit-Remaining 做自限流,它是「当前这个实例还剩多少」的实时读数,不是你的预算;改用你自己侧的固定发包速率,遇 429 就退避重试(换连接后往往立刻成功,不必等满 Retry-After)。每个响应都带机读位 X-RateLimit-Scope: instance 与 X-RateLimit-Policy: 60;w=60;scope=instance;shared=false,程序里按它判断即可。余额头在每一个经过鉴权的响应上都有(X-RateLimit-Limit / Remaining / Reset / Scope / Policy / X-Trial-Key),包括 400、404、500、502——因为它们同样按次计入(2026-08-03 第 224 发起);只有三种响应不带,因为它们根本没扣:401(key 缺失 / 无效)、503(试用通道未开)、以及 429 本身(它带 Retry-After + Remaining: 0)。正式合作会换成跨实例共享的计数器,届时 scope 才会变成 key;试用阶段的限流上限本身是宽松方向,不影响你试用。把 API 接进监控时还有一件事(2026-08-03 第 229 发补):健康探针 /api/v1/health 免 key、不计配额、也不限流(实测一条 keep-alive 连接连打 100 次 ?shallow=1 全部 200、零 X-RateLimit-* 头),可以放心高频打;但你对取数端点打的每一次探针都按次计入配额,而监控 agent 通常复用连接 = 全部落在同一个实例上,是最容易撞 429 的那类客户端(实测一条连接打 70 次 /api/v1/stats = 60 个 200 + 10 个 429,首个落在第 61 次)。请把 429 单独一档、与 5xx / 超时分开计:它是「打太快」不是「服务挂了」,而且 429 的 body 里没有 cases / cells,照「HTTP 200 且响应内有数据」判活会把它误报成故障。还有一幕别看糊涂:限流按 key + 实例数计、健康端点既不带 key 也不计数,所以完全可能同一秒里 health 绿、你的探针全红(实测 08:11:36 同一条连接上 stats 第 61 次 429、紧接着 health 返 200 ok:true)——那是限流,不是 health 在撒谎。机读位见 /api/v1/health 响应的 rate_limit(含 http_429_means: rate_limited_not_down)。还有一种情况这里此前一个字都没说,而它最容易被误诊成限流问题(2026-08-03 第 235 发补):你自己超时 abort 掉的请求,照样按次扣配额 —— 2026-08-03 实测:在固定连接上「读基线 → 3 次 /api/v1/search → 再读余额」共 5 个请求,客户端超时设 10 秒时 2 次 abort、设 5 秒时 3 次全 abort,两次实验的余额都是 59 → 55(第一个读数本身已扣过自己,故这两个数之间隔的是余下 4 个请求 —— 3 次 search 一次不落地各扣了 1,与是否读到 body 无关:请求已经到达并被执行了)。而 abort 时你收不到那次的响应头,故你在客户端侧看不到自己烧掉的量,唯一的观测点是下一次成功响应的 X-RateLimit-Remaining。这条最容易被读成限流问题:超时设太短 → 一条结果都拿不到 → 配额照烧 → 很快撞 429,而 429 的 body 讲的是「打太快、请退避降频」。这时降频是反向药 —— 每次都打冷缓存只会更慢、更容易再 abort。正确的动作是先把超时设足(见各端点建议值),拿到一次成功响应之后再谈频率。/api/v1/health 的 HTTP 状态码或 ok 上。 这两个信号是本端点上线起就有的历史契约,只代表 dependencies[0](检索线),不是多线汇总 —— 我们没有把 ok 改成「所有线相与」,是因为全量库线的修复在机房侧、可能长期挂着,一旦相与本端点就会长期 503,而检索 / search / stats 三条线此时实测照常可用,那等于拿一条线的故障谎报「整套 API 全挂」。要读的是 status(ok = 已探的线全好、degraded = 部分挂、down = 已探的线全挂),挂掉的线点名在 lines_down 里;ok:true + status:"degraded" 是真实会出现的组合,2026-07-28 全量库线整条挂掉、/api/v1/judgements、/api/cases/browse 全部 502 时,本端点的 ok 与 HTTP 状态码全程是绿的。接法(三层,按你要的粒度取):①liveness 用 GET /api/v1/health?shallow=1 —— 零上游调用、恒快(实测 ~1 秒),免 key、不计配额、不限流,可以按秒打;但它的 ok 是写死的 true、status 恒为 unknown,它只回答「路由还在不在」,不回答「取不取得到文书」。②readiness 用 GET /api/v1/health(deep) —— 并行实探两条上游,读 status 与 lines_down;客户端超时请设 ≥25 秒(探针本身最多等上游 20000 / 10000 毫秒),否则上游一慢你量到的全是自己的超时。③它照不到的端点得自己探:deep 覆盖的是 /api/v1/fulltext 与 /api/v1/judgements、/api/cases/browse,未探 /api/v1/cases/{id}、/api/v1/search、/api/v1/stats —— 这三个走各自独立的后端,故本端点为绿不代表它们可用、为红同样不代表它们挂了。各探一条的写法(注意方法不同,动词用错拿到的是 405 而不是故障):GET /api/v1/judgements?province=广东&pageSize=1(2026-08-15 实测补注:中文参数请用 curl 的 -G --data-urlencode 送,别像这里一样裸拼进 URL —— 本站自 2026-08-11 起未编码的中文回 HTTP 400 且响应体为空,且这一层在鉴权之前,别把空体 400 读成「站挂了」)、POST /api/v1/search(只收 POST + JSON body,如 {"q":"利息","pageSize":1};这条线常态就慢,实测 35-50 秒仍属正常返回,给它的探针超时请单独设 ≥90 秒,别套用上面给 health 的那个数)、GET /api/v1/cases/{id} 配一个 fulltext 返回的 <dataset>:<id> 形态 id(别拿 judgements 的裸 id,那必然 404)、GET /api/v1/stats?dimension=province。判活看 HTTP 200 且响应内确实有数据(cases / cells 非空),别只看状态码 —— 只看外壳 200 正是「页面还在、数据没了」这类故障漏报的老路。两个必须知道的读数陷阱:(a)假红带 —— 两条探针给上游的预算都比真实端点紧(检索线探针 20000ms vs 端点 50000ms;全量库线 10000ms vs 50000ms),上游只是变慢时对应 dependency 会 ok:false 而真去调它代言的端点仍可能正常出货,这种失败带 reason:"probe_timeout",请读成「劣化」而非「宕机」,升级告警前先用一次真实取数调用复核;硬故障带 reason:"upstream_error",后端地址没配到位带 reason:"not_configured"(重试无用)。(b)缓存造成的非对称 —— 两条探针都走不带缓存的直连,而 /api/v1/judgements、/api/cases/browse 实际经一层 6 小时缓存,故 full_corpus 探针报红时命中缓存的调用仍可能返回文书;请读成「这条线的实时取数不可用」。最后:429 是限流不是故障,请单独一档、与 5xx / 超时分开计。 上面那些取数探针都带 key、都按次计入配额,而监控 agent 通常复用连接 = 全落同一个实例,是最容易撞 429 的那类客户端;429 的 body 里没有 cases / cells,照上面那条判活规则会被误报成故障。健康端点既不带 key 也不计数,所以完全可能同一秒里 health 绿、你的探针全红 —— 那是限流,不是 health 在撒谎,详见上一条「限流?」与 health 响应里的 rate_limit(机读)。<分片名>:<内部 id> 查的 /api/v1/cases/{id})标题多已由上游匿名成「某」(judgements 实测 20/20、fulltext 11/20),但正文没有 —— body_text 尾部的审判长 / 审判员 / 书记员署名一律保留(实测 14/20),另约 5/20 条含住所地。我们不改写正文,因为改写判决书原文比不脱敏更糟。⚠️ /api/v1/cases/{id} 横跨上面两条线,落在哪条由 doc_id 形态逐请求决定(32 位 hex → ①,<分片名>:<内部 id> → ②),是本 API 上唯一一个线会变的端点,也是单次调用个人信息密度最高的一个(全量库分支返回 body_text 全文)。2026-08-03 经该端点逐条实测各 20 条:样本库分支 20/20 标题为真实姓名、15/20 的 body_excerpt 把姓名与判付金额写进同一句;全量库分支 20/20 含全文、标题 11/20 含「某」、14/20 正文可匹配审判人员署名、6/20 含住所地。请勿按端点配置去标识策略,逐响应读 _meta.party_name_redaction.line。合规责任在使用方:「裁判文书网已公开」不等于「可以随意再发布」,转载 / 展示 / 再加工仍受个人信息保护法与《人民法院在互联网公布裁判文书的规定》约束,把姓名与金额 / 工龄一并呈现给第三方时请自行评估。每个检索响应的 _meta.party_name_redaction(机读)与 _meta.privacy_note(人读)是同一口径,可按响应自查。