接口详解
基础地址 $BASE = https://caselaw.sunlawai.com/v1。除 /health 外都需要 X-API-Key(见鉴权)。下面的示例响应取自真实服务返回并做了裁剪(长文本以 … 截断,不含任何 key)。
示例中的 Python 使用 httpx,JavaScript 使用内置 fetch(Node 18+)。公共辅助:
import os, httpx
BASE = os.environ["LAMBERT_BASE"]
H = {"X-API-Key": os.environ["LAMBERT_API_KEY"]}
const BASE = process.env.LAMBERT_BASE;
const H = { "X-API-Key": process.env.LAMBERT_API_KEY };
GET /health
服务状态与数据新鲜度,无需鉴权。last_sync 可能为 null(仅在一轮同步完整结束时写入,不代表同步未运行)。
curl "$BASE/health"
{
"status": "ok",
"service": "Lambert Planning Caselaw API",
"version": "1.0.0",
"opinions": 153,
"with_text": 108,
"pending_text": 45,
"last_sync": {
"courtlistener": null,
"govinfo": null
}
}
GET /opinions/recent
按 date_filed 倒序返回本地库中的判例,不耗上游额度。
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
since |
YYYY-MM-DD |
— | 只返回该日及之后提交的判例 |
court |
string | — | 法院 id,如 michctapp、ca5(见 /courts) |
source |
courtlistener | govinfo |
— | 来源过滤 |
limit |
int 1–100 | 20 | 页大小 |
cursor |
string | — | 上一页响应的 next_cursor |
curl -H "X-API-Key: $LAMBERT_API_KEY" "$BASE/opinions/recent?limit=2&since=2026-10-01"
page = httpx.get(f"{BASE}/opinions/recent", params={"limit": 50}, headers=H).json()
while True:
for op in page["items"]:
print(op["id"], op["case_name"])
if not page["next_cursor"]:
break
page = httpx.get(f"{BASE}/opinions/recent", params={"limit": 50, "cursor": page["next_cursor"]}, headers=H).json()
let cursor = null;
do {
const q = new URLSearchParams({ limit: 50, ...(cursor && { cursor }) });
const page = await (await fetch(`${BASE}/opinions/recent?${q}`, { headers: H })).json();
page.items.forEach(o => console.log(o.id, o.case_name));
cursor = page.next_cursor;
} while (cursor);
{
"items": [
{
"id": "cl:11499595",
"source": "courtlistener",
"court_id": "guam",
"court_name": "Supreme Court of Guam",
"case_name": "People of Guam v. D.J. Tosiuo (aka DJ Peter Robert Tosiuo; aka David Peter Robert Tosiuo; aka D.J.R. Tosiuo; aka D.J. BJ Saki Prick from Barrigada)",
"date_filed": "2026-10-12",
"docket_number": "CRA25-012",
"url": "https://www.courtlistener.com/opinion/11031905/people-of-guam-v-dj-tosiuo-aka-dj-peter-robert-tosiuo-aka-david-peter/",
"download_url": "https://guamcourts.gov/sites/default/files/SupremeCourtOpinions/2026%20Guam%2012.pdf",
"text_source": "court_pdf",
"text_status": "ok",
"fetched_at": "2026-10-13T08:44:26Z",
"has_text": true,
"text_length": 17295
}
],
"count": 1,
"next_cursor": "MjAyNi0xMC0xMnwxNg=="
}
字段说明:text_status 为 ok(已有全文)、pending / retry(全文尚未取到,会自动补取);text_source 为 court_pdf、cl_plain_text、cl_html;has_text/text_length 表示是否已有全文及长度(列表接口不返回全文本身)。
GET /opinions/{id}
读取单篇判例。id 形如 cl:<CourtListener opinion id> 或 gi:<GovInfo granule id>。本地没有时会向上游按需拉取(cl: 可能耗额度,见响应 quota_cost)。
| 参数 | 位置 | 说明 |
|---|---|---|
opinion_id |
path | 统一 id |
include_text |
query, bool,默认 false | 为 true 时返回字段 text(全文,可能很大) |
curl -H "X-API-Key: $LAMBERT_API_KEY" "$BASE/opinions/cl:11499595?include_text=true"
op = httpx.get(f"{BASE}/opinions/cl:11499595", params={"include_text": "true"}, headers=H, timeout=30).json()
print(op["case_name"], len(op.get("text", "")))
const op = await (await fetch(`${BASE}/opinions/cl:11499595?include_text=true`, { headers: H })).json();
console.log(op.case_name, (op.text || "").length);
示例响应(不带 include_text):
{
"id": "cl:11499595",
"source": "courtlistener",
"court_id": "guam",
"court_name": "Supreme Court of Guam",
"case_name": "People of Guam v. D.J. Tosiuo (aka DJ Peter Robert Tosiuo; aka David Peter Robert Tosiuo; aka D.J.R. Tosiuo; aka D.J. BJ Saki Prick from Barrigada)",
"date_filed": "2026-10-12",
"docket_number": "CRA25-012",
"url": "https://www.courtlistener.com/opinion/11031905/people-of-guam-v-dj-tosiuo-aka-dj-peter-robert-tosiuo-aka-david-peter/",
"download_url": "https://guamcourts.gov/sites/default/files/SupremeCourtOpinions/2026%20Guam%2012.pdf",
"text_source": "court_pdf",
"text_status": "ok",
"fetched_at": "2026-10-13T08:44:26Z",
"has_text": true,
"text_length": 17295,
"quota_cost": 0
}
未找到返回 404 not_found;上游额度不足返回 429。
GET /search
全文检索。
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
q |
string 1–500 | 必填 | 检索词,多个词为 AND |
court |
string | — | 法院 id |
since / until |
YYYY-MM-DD |
— | 提交日期范围 |
source |
local | courtlistener | govinfo |
local |
local 免费;courtlistener 耗 1 次上游额度(缓存 30 分钟);govinfo 走 GovInfo |
limit |
int 1–100 | 20 | 最大结果数(courtlistener 最多 20) |
curl -H "X-API-Key: $LAMBERT_API_KEY" "$BASE/search?q=negligence&limit=5"
res = httpx.get(f"{BASE}/search", params={"q": "negligence duty of care", "court": "ca5"}, headers=H).json()
for r in res["results"]:
print(r["date_filed"], r["case_name"], r["snippet"])
const q = new URLSearchParams({ q: "negligence duty of care", court: "ca5" });
const res = await (await fetch(`${BASE}/search?${q}`, { headers: H })).json();
res.results.forEach(r => console.log(r.date_filed, r.case_name));
{
"source": "local",
"query": "negligence",
"count": 1,
"quota_cost": 0,
"results": [
{
"id": "cl:11499617",
"source": "courtlistener",
"court_id": "txctapp1",
"court_name": "Texas Court of Appeals, 1st District (Houston)",
"case_name": "Jason Alexander v. the Woodlands Land Development Company L.P., the Howard Hughes Corporation, LJA Engineering, Inc. F/K/A LJA Engineering & Surveying, Inc., an…",
"date_filed": "2026-10-09",
"docket_number": "01-22-00827-CV",
"url": "https://www.courtlistener.com/opinion/11031927/jason-alexander-v-the-woodlands-land-development-company-lp-the-howard/",
"download_url": "https://search.txcourts.gov/SearchMedia.aspx?MediaVersionID=75eeda3f-164f-45c1-b71c-ed41ec74f9d5&coa=coa01&DT=Opinion&MediaID=78643a98-7871-45cc-ba22-f4bdd151a0…",
"text_source": "court_pdf",
"text_status": "ok",
"fetched_at": "2026-10-13T08:44:32Z",
"has_text": true,
"text_length": 89855,
"snippet": " … Appellants brought claims against appellees for <b>negligence</b>, gross <b>negligence</b>,\nand <b>negligent</b> undertaking. As to their <b>negligence</b> a…"
}
]
}
本地检索的 snippet 中命中词以 <b>…</b> 标出。
GET /courts
本地库中出现过的法院及判例数、最近提交日期。
curl -H "X-API-Key: $LAMBERT_API_KEY" "$BASE/courts"
courts = httpx.get(f"{BASE}/courts", headers=H).json()["courts"]
const { courts } = await (await fetch(`${BASE}/courts`, { headers: H })).json();
{
"count": 40,
"courts": [
{
"id": "ala",
"name": "Supreme Court of Alabama",
"short_name": null,
"jurisdiction": null,
"source": "courtlistener",
"opinions": 2,
"latest": "2026-10-09"
},
{
"id": "alacivapp",
"name": "Court of Civil Appeals of Alabama",
"short_name": null,
"jurisdiction": null,
"source": "courtlistener",
"opinions": 1,
"latest": "2026-10-09"
}
]
}
GET /dockets/{cl_docket_id}
CourtListener 案卷(docket)代理,首次耗 1 次上游额度,之后永久缓存。响应为 CourtListener 的 docket 对象并附 quota_cost。
curl -H "X-API-Key: $LAMBERT_API_KEY" "$BASE/dockets/74951954"
d = httpx.get(f"{BASE}/dockets/74951954", headers=H).json()
const d = await (await fetch(`${BASE}/dockets/74951954`, { headers: H })).json();
POST /citations/verify
在文本中识别判例引注(如 576 U.S. 644),并核对是否对应真实案件。每次耗 1 次上游额度(缓存 24 小时)。
请求体 JSON:text(string,1–64000 字符,必填)。结果中每条的 status:200 找到,404 未找到,300 有歧义。
curl -X POST -H "X-API-Key: $LAMBERT_API_KEY" -H "Content-Type: application/json" \
-d '{"text":"See Obergefell v. Hodges, 576 U.S. 644 (2015)."}' "$BASE/citations/verify"
r = httpx.post(f"{BASE}/citations/verify", headers=H,
json={"text": "See Obergefell v. Hodges, 576 U.S. 644 (2015)."}).json()
const r = await (await fetch(`${BASE}/citations/verify`, {
method: "POST", headers: { ...H, "Content-Type": "application/json" },
body: JSON.stringify({ text: "See Obergefell v. Hodges, 576 U.S. 644 (2015)." }),
})).json();
{"quota_cost": 1, "results": [{"citation": "576 U.S. 644", "status": 200, "clusters": [{"case_name": "Obergefell v. Hodges"}]}]}
(该示例为按接口约定构造的结构示意,非实录响应。)
GET /govinfo/uscourts/recent
GovInfo USCOURTS 联邦法院意见。
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
since |
YYYY-MM-DD |
7 天前 | 签发日期下限 |
limit |
int 1–100 | 20 | 最大条数 |
live |
bool | false | 为 true 时先实时向 GovInfo 拉一页再返回(不耗 CourtListener 额度) |
curl -H "X-API-Key: $LAMBERT_API_KEY" "$BASE/govinfo/uscourts/recent?since=2026-10-01&live=true"
r = httpx.get(f"{BASE}/govinfo/uscourts/recent", params={"live": "true"}, headers=H, timeout=60).json()
const r = await (await fetch(`${BASE}/govinfo/uscourts/recent?live=true`, { headers: H })).json();
示例响应(撰写文档时 GovInfo 尚未入库,故为空;有数据时 items 结构与 /opinions/recent 相同,id 以 gi: 开头):
{
"count": 0,
"items": []
}
GET /quota
上游 CourtListener 的 api-usage(不占额度)与本服务各窗口的本地计数、后台预算。
curl -H "X-API-Key: $LAMBERT_API_KEY" "$BASE/quota"
q = httpx.get(f"{BASE}/quota", headers=H).json()
print(q["remaining"])
const q = await (await fetch(`${BASE}/quota`, { headers: H })).json();
{
"courtlistener_local": {
"minute": {
"upstream_limit": 5,
"effective_limit": 4,
"used": 1,
"remaining": 3,
"resets_in_seconds": 12
},
"hour": {
"upstream_limit": 50,
"effective_limit": 45,
"used": 43,
"remaining": 2,
"resets_in_seconds": 7
},
"day": {
"upstream_limit": 125,
"effective_limit": 112,
"used": 61,
"remaining": 51,
"resets_in_seconds": 78630
},
"background_daily_budget": {
"limit": 80,
"used": 11,
"remaining": 69
},
"backoff_seconds_left": 0
},
"courtlistener_upstream": {
"current_usage": [
{
"scope": "user",
"rate": "50/hour",
"used": 43,
"limit": 50,
"remaining": 7,
"window_seconds": 3600,
"reset_at": "2026-10-13T08:49:22.307544+00:00",
"blocked": false
},
{
"scope": "user",
"rate": "125/day",
"used": 61,
"limit": 125,
"remaining": 64,
"window_seconds": 86400,
"reset_at": "2026-10-14T06:39:45.512954+00:00",
"blocked": false
},
{
"scope": "user",
"rate": "5/min",
"used": 1,
"limit": 5,
"remaining": 4,
"window_seconds": 60,
"reset_at": "2026-10-13T08:49:28.578915+00:00",
"blocked": false
}
],
"note": "truncated"
},
"remaining": 2
}
管理接口(仅管理员 key)
| 方法与路径 | 说明 |
|---|---|
POST /admin/sync |
立即触发一轮后台同步,返回 {"status":"started"} 或 {"status":"already_running"} |
GET /admin/keys |
列出 key 元数据(不含密钥) |
POST /admin/keys |
创建 key,请求体 {"name": "...", "admin": false},响应中 api_key 仅显示一次 |
DELETE /admin/keys/{id} |
吊销 key(立即生效) |
POST /admin/keys/{id}/revoke |
同上,供无法发送 DELETE 的客户端使用 |
用法示例见鉴权。