接口详解

基础地址 $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。


全文检索。

参数 类型 默认 说明
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 的客户端使用

用法示例见鉴权。