完整文档

这里是唯一权威口径:认证、三档一级 API、二级单元、授权 scope、计费、快照、限流、错误码。 所有示例都是真实响应(长数组已截断)——照着抄就能跑。

对外地址 https://search.ozforce.cn · 机器可读目录 /v1/catalog · 商品与价格 /products · 五步接入 /quickstart

1. 认证

每个需要计费的端点都要求请求头 Authorization: Bearer <你的 key>。 key 形如 sk_live_ + 32 位;明文只在签发或领取时出现一次。

公开端点(不要 key):GET /v1/units、GET /v1/catalog、 GET /s/<token>、GET /healthz、GET /products、 GET /quickstart、GET /docs。

申请流程:GET /apply 填表 → 管理员审批 → GET /claim/<领取码> 领明文。 领取码默认 72 小时有效,过期让管理员重新批准(换新码,不会重复签 key)。

2. 一级 API · 结果档(l1)

只给结果,不给答案

POST /v1/search · 1 credits(≈ ¥0.05 / 次)

返回来源列表(标题 + 出处 + 说明 + 分数)+ 一条永久快照链接。answer 恒为 null。

请求参数(JSON body)

字段类型必填说明
q string 必填 查询词,1~500 字符,必填
count integer 可选 返回条数 1~20,默认 10
freshness string 可选 时间窗:day / week / month / year / any,默认 any
provider string 可选 选源偏好:auto(默认)/ deepseek / bocha;偏好没命中时响应 degraded=true 并写明原因
snapshot boolean 可选 是否生成永久快照,默认 true
site string[] 可选 只在这些域名内检索(可传数组)
lang string 可选 语言偏好,形如 zh / en
need_answer boolean 可选 已弃用(B33):仍可用但按答案档计价,请改调 /v1/answer
depth string 可选 已弃用(B33):只接受 "deep",请改调 /v1/answer + depth
format string 可选 传 "model" 让每条 source 多一句 brief(写作引擎);不传则响应形状不变

响应字段

字段类型说明
request_id string 本次请求 id(排查时把它给我们)
provider / provider_requested / provider_preference_honored string / string / boolean 实际用的源、请求偏好的源、偏好是否被满足
sources[] array title / url / snippet / publishedAt / domain / score / scoreRaw / scoreScale
snapshot_url / h5_url string|null 永久快照链接(snapshot:false 时为 null)
latency_ms number 本次检索耗时
usage object count / credits_charged / credits_base / tier_discount / floor_applied / balance_after / quota_source;另含 revenue_cny / gross_margin_cny / cost_cny
quality object 质量闸门:usable / dropped / ratio / gate_passed
degraded / degraded_reason boolean / string|null 降级必须可见(fail-loud)

真实响应(200,已截断)

POST /v1/search → 200
{
  "request_id": "r_m3x0kq9v2d4tq7a1",
  "provider": "deepseek",
  "provider_requested": "auto",
  "provider_preference_honored": true,
  "answer": null,
  "sources": [
    {
      "title": "杭州三日游攻略:路线、交通与住宿",
      "url": "https://example.com/hangzhou-3d2n",
      "snippet": "这份杭州3天2晚攻略包含景点路线、交通、住宿、美食和游玩tips……",
      "publishedAt": "2026-10-26T16:00:00.000Z",
      "domain": "example.com",
      "score": 0.5471,
      "scoreRaw": 1.6412975788116455,
      "scoreScale": "0-3"
    }
  ],
  "snapshot_url": "https://search.ozforce.cn/s/0m0sfptw98xs",
  "h5_url": "https://search.ozforce.cn/s/0m0sfptw98xs",
  "latency_ms": 469,
  "usage": {
    "count": 3,
    "credits_charged": 1,
    "credits_base": 1,
    "tier_discount": 1,
    "floor_applied": false,
    "balance_after": 499,
    "quota_source": "balance"
  },
  "quality": { "usable": 3, "gate_passed": true },
  "degraded": false,
  "degraded_reason": null
}
curl(macOS / Linux / Git Bash)
# 第一个请求:结果档 l1 —— 拿回来源列表 + 一条永久快照链接
curl -sS -X POST "https://search.ozforce.cn/v1/search" \
  -H "Authorization: Bearer ${WS_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"q":"杭州 三日游 攻略","count":5,"freshness":"month","snapshot":true}'
curl(Windows 命令行 cmd.exe)
rem Windows 命令行(cmd.exe):在同一窗口先 set WS_API_KEY=你的key,再粘贴下一行
curl -sS -X POST "https://search.ozforce.cn/v1/search" -H "Authorization: Bearer %WS_API_KEY%" -H "Content-Type: application/json" -d "{\"q\":\"杭州 三日游 攻略\",\"count\":5,\"freshness\":\"month\",\"snapshot\":true}"
Node
// 存成 first.mjs,然后:node first.mjs(Node 18+ 自带 fetch,不用装任何包)
const BASE = 'https://search.ozforce.cn'
const KEY = process.env.WS_API_KEY
if (!KEY) {
  console.error('先设置环境变量 WS_API_KEY(值是你的 API key)')
  process.exit(1)
}

const res = await fetch(BASE + '/v1/search', {
  method: 'POST',
  headers: { Authorization: 'Bearer ' + KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({ q: '杭州 三日游 攻略', count: 5, freshness: 'month', snapshot: true })
})

const data = await res.json()
if (!res.ok) {
  console.error('失败', res.status, data.error)
  process.exit(1)
}

console.log('扣费', data.usage.credits_charged, 'credits,余额', data.usage.balance_after)
console.log('永久链接', data.snapshot_url)
for (const s of data.sources) console.log('-', s.title, s.url)
Python
# Python 3.8+,只用标准库
import json, os, urllib.request

BASE = 'https://search.ozforce.cn'
KEY = os.environ.get('WS_API_KEY')
if not KEY:
    raise SystemExit('先设置环境变量 WS_API_KEY(值是你的 API key)')

body = json.dumps({'q': '杭州 三日游 攻略', 'count': 5, 'freshness': 'month', 'snapshot': True}).encode('utf-8')
req = urllib.request.Request(
    BASE + '/v1/search', data=body, method='POST',
    headers={'Authorization': 'Bearer ' + KEY, 'Content-Type': 'application/json'},
)
with urllib.request.urlopen(req) as res:
    data = json.loads(res.read().decode('utf-8'))

print('扣费', data['usage']['credits_charged'], 'credits,余额', data['usage']['balance_after'])
print('永久链接', data['snapshot_url'])
for s in data['sources']:
    print('-', s['title'], s['url'])

3. 一级 API · 快答案档(l1_answer)

结果 + 一段带引用的答案(不抓原文)

POST /v1/answer · 2 credits(≈ ¥0.1 / 次)

读搜索结果里的片段与摘要写答案,返回 answer + citations[] + sources[]。 答案一定带 [1][2][3] 引用;引用对不上时如实标 cited:false,不编造。

请求参数

字段类型必填说明
q string 必填 问题或查询词,1~500 字符
depth string 可选 不传 = fast(快答案);传 "deep" 升到深答案档(另一个商品、另一个 scope)
count integer 可选 检索条数 1~20,默认 10
freshness string 可选 day / week / month / year / any
provider string 可选 auto / deepseek / bocha
snapshot boolean 可选 默认 true
need_answer — 可选 不要传:这个端点天生就是答案档,传了会 400(同样不能传 needAnswer)

响应字段

字段类型说明
answer string|null 答案正文;为空时 answer_reason / answer_message 写明原因(绝不静默)
citations[] / cited number[] / boolean 引用编号(对应 sources 下标)与"是否真带引用"
answer_source string provider / summarizer / deep / none —— 这段答案是谁给的
answer_model / summary string / object 模型名与完整口径(tokens / cost_cny / latency_ms / cached)
depth_degraded / depth_degraded_reason boolean / string|null 深答案抓不到原文时退回快答案,会标 true 并给原因
deliverable boolean 这一档此刻是否真的可交付(与 /healthz 的 sellable 同源)
charged / credits_charged / refunded number / number / boolean 这次扣了多少;没交付时按结果档结算并 refunded=true
latency_ms object search / answer / total(深答案档为 search / fetch / write / total)

真实响应(200,已截断)

POST /v1/answer → 200
{
  "request_id": "r_qms0v6hx8m4nah3j",
  "product": "l1_answer",
  "requested_product": "l1_answer",
  "depth": "fast",
  "charged": 2,
  "refunded": false,
  "deliverable": true,
  "provider": "deepseek",
  "answer": "天空的蓝色是阳光与大气分子、冰晶、水滴等共同作用的结果……蓝色光更容易被散射并进入人眼,因此天空呈蓝色 [1][2][3]。",
  "answer_source": "summarizer",
  "citations": [1, 2, 3],
  "cited": true,
  "depth_degraded": false,
  "answer_model": "deepseek",
  "summary": {
    "model": "deepseek-flash",
    "tokens_in": 473,
    "tokens_out": 91,
    "cost_cny": 0.001783,
    "latency_ms": 1179,
    "cached": false
  },
  "sources": [{ "title": "天空为什么是蓝色的", "url": "https://example.com/sky-blue", "domain": "example.com", "score": 0.6648 }],
  "snapshot_url": "https://search.ozforce.cn/s/peh58hj5m8wt",
  "latency_ms": { "search": 523, "answer": 1183, "total": 1706 },
  "usage": {
    "count": 3,
    "credits_charged": 2,
    "search_cny": 0.005,
    "fetch_cny": 0,
    "writer_cny": 0.001783,
    "balance_after": 497
  },
  "degraded": false
}
curl(快答案)
# 快答案档:读片段写答案(同一个端点,不传 depth 就是 fast)
curl -sS -X POST "https://search.ozforce.cn/v1/answer" \
  -H "Authorization: Bearer ${WS_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"q":"天空为什么是蓝色的","count":5}'
Python(快答案)
# 快答案档(不传 depth)
import json, os, urllib.request

BASE = 'https://search.ozforce.cn'
KEY = os.environ.get('WS_API_KEY')
if not KEY:
    raise SystemExit('先设置环境变量 WS_API_KEY')

body = json.dumps({'q': '天空为什么是蓝色的', 'count': 5}).encode('utf-8')
req = urllib.request.Request(
    BASE + '/v1/answer', data=body, method='POST',
    headers={'Authorization': 'Bearer ' + KEY, 'Content-Type': 'application/json'},
)
with urllib.request.urlopen(req) as res:
    data = json.loads(res.read().decode('utf-8'))

print(data['answer'])
print('引用编号', data['citations'], '永久链接', data['snapshot_url'])

4. 一级 API · 深答案档(l1_deep)

同一入口 + depth:"deep":读原文写答案

POST /v1/answer · 4 credits(≈ ¥0.2 / 次)

从搜索结果里择优 K 篇真去抓原文(Markdown),再写带引用的答案;返回材料清单与成本/时延拆分。 数字、参数、对比类问题靠这一步才答得准。抓取失败不会让整次调用失败:退回快答案并标 depth_degraded:true。

请求参数与第 3 节相同,只是 depth 传 "deep";key 上需要 l1:deep(或 *)。

响应里多出来的字段

字段类型说明
materials[] / material_stats array / object 读了哪几篇(哪几条读了原文、哪几条用摘要兜底、各多少字符、是否被截断)
cost object search / fetch / writer / total(¥)—— 花钱花在哪一目了然
citation_sources[] array 引用编号 → 具体来源的映射证据
deep.fetch / deep.writer object 逐条抓取与写作明细(含失败原因)
curl(深答案)
# 深答案档:同一个端点 + depth:"deep"(会真去抓原文,更准也更慢)
curl -sS -X POST "https://search.ozforce.cn/v1/answer" \
  -H "Authorization: Bearer ${WS_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"q":"天空为什么是蓝色的","depth":"deep","count":8}'
Node(深答案)
// 深答案:答案 + 引用编号 + 材料清单(读了哪几篇原文)
const BASE = 'https://search.ozforce.cn'
const KEY = process.env.WS_API_KEY
if (!KEY) { console.error('先设置环境变量 WS_API_KEY'); process.exit(1) }

const res = await fetch(BASE + '/v1/answer', {
  method: 'POST',
  headers: { Authorization: 'Bearer ' + KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({ q: '天空为什么是蓝色的', depth: 'deep', count: 8 })
})
const data = await res.json()
if (!res.ok) { console.error('失败', res.status, data.error); process.exit(1) }

console.log(data.answer)
console.log('引用编号', data.citations.join(','), '· 是否带引用', data.cited)
console.log('读了', data.materials.length, '篇材料,永久链接', data.snapshot_url)

5. 二级单元(6 条独立二级 API)

跑一个单元

POST /v2/{middleware}/{unit}/run

单元是"已经替你调好一级 API 并按领域整理好"的成品。当前中间件是 m1, 下表每个单元就是一条独立商品(各自 scope、各自定价)。

单元端点mode价格scope
wiki
世界百科
POST /v2/m1/wiki/run live 1 credits l2:wiki
news
即时新闻
POST /v2/m1/news/run scheduled 1 credits l2:news
music
流行音乐时
POST /v2/m1/music/run scheduled 1 credits l2:music
travel
旅行灵感
POST /v2/m1/travel/run both 3 credits l2:travel
why
十万个为什么
POST /v2/m1/why/run live 3 credits l2:why
finance
财经速览
POST /v2/m1/finance/run scheduled 3 credits l2:finance

请求参数

字段类型必填说明
q string 可选 主题词(不传就用单元声明的 defaultTopic)
templates string[] 可选 整体覆盖检索模板(用 {topic} 占位),条数上限见单元声明
count integer 可选 本次要几条;最终取 min(请求, 单元 maxCount, 全局 20)
freshness string 可选 day / week / month / year / any(覆盖单元默认时间窗)
provider string 可选 auto / deepseek / bocha(偏好,不是硬绑定)
snapshot boolean 可选 默认 true
summarize boolean 可选 true = 额外给一段带出处的总结(默认 false)
writer object 可选 {"format":true} = 给每条结果补一句 brief(默认不补)
live boolean 可选 true = 跳过当期归档,走一次实时检索(会产生正常的检索成本)

响应字段

字段类型说明
items[] 或 tracks[] array 按单元的 resultSchema 决定(music.v1 用 tracks);字段见下方各单元
snapshot_url / live_url / h5_url / primary_url string|null 快照链接、实时入口、人读入口、主推链接(按 snapshotPolicy 选)
served_from / cost_source string digest / on_demand / previous / live —— 这份是归档还是现搜、成本怎么记
usage object credits_charged / balance_after 等
answer / why string|null / object 单元自带答案时出现(why.v1 的 why = question/answer/citations/sources)

真实响应(200,已截断)

POST /v2/m1/wiki/run → 200
{
  "request_id": "r_8kq2s9v1n0xb7t3c",
  "middleware": "m1",
  "unit": "news",
  "name": "即时新闻",
  "domain": "news",
  "result_schema": "news.v1",
  "snapshot_policy": "freeze",
  "items": [
    {
      "title": "人工智能,加速融入你我生活",
      "summary": "在广东佛山,巡检无人机用上AI算法,为城市治理提供精准依据……",
      "source": "example.com",
      "url": "https://example.com/ai-news",
      "publishedAt": "2026-10-28T16:00:00.000Z",
      "heat": 84
    }
  ],
  "snapshot_url": "https://search.ozforce.cn/s/j5zj1m22mxfr",
  "live_url": "https://search.ozforce.cn/u/m1/news",
  "h5_url": "https://search.ozforce.cn/u/m1/news",
  "primary_url": "https://search.ozforce.cn/s/j5zj1m22mxfr",
  "answer": null,
  "served_from": "live",
  "cost_source": "live_search",
  "usage": { "credits_charged": 1, "balance_after": 496 },
  "degraded": false
}
curl(m1/wiki)
# 二级单元:m1/wiki(结果结构更贴合该领域,价格见页面上的单元卡片)
curl -sS -X POST "https://search.ozforce.cn/v2/m1/wiki/run" \
  -H "Authorization: Bearer ${WS_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"q":"人工智能","count":5,"snapshot":true}'
Node(m1/wiki)
// 二级单元 m1/wiki:返回 items[](结构按单元的 resultSchema)
const BASE = 'https://search.ozforce.cn'
const KEY = process.env.WS_API_KEY
if (!KEY) { console.error('先设置环境变量 WS_API_KEY'); process.exit(1) }

const res = await fetch(BASE + '/v2/m1/wiki/run', {
  method: 'POST',
  headers: { Authorization: 'Bearer ' + KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({ q: '人工智能', count: 5, snapshot: true })
})
const data = await res.json()
if (!res.ok) { console.error('失败', res.status, data.error); process.exit(1) }

console.log('共', data.items.length, '条,扣费', data.usage.credits_charged, 'credits')
console.log('永久链接', data.snapshot_url)
for (const item of data.items) console.log('-', item.title, item.url)
Python(m1/wiki)
# 二级单元 m1/wiki
import json, os, urllib.request

BASE = 'https://search.ozforce.cn'
KEY = os.environ.get('WS_API_KEY')
if not KEY:
    raise SystemExit('先设置环境变量 WS_API_KEY')

body = json.dumps({'q': '人工智能', 'count': 5, 'snapshot': True}).encode('utf-8')
req = urllib.request.Request(
    BASE + '/v2/m1/wiki/run', data=body, method='POST',
    headers={'Authorization': 'Bearer ' + KEY, 'Content-Type': 'application/json'},
)
with urllib.request.urlopen(req) as res:
    data = json.loads(res.read().decode('utf-8'))

print('共', len(data['items']), '条,扣费', data['usage']['credits_charged'], 'credits')
print('永久链接', data['snapshot_url'])

各单元单独看

世界百科live

查一个词条/概念的释义、起源与发展脉络(百科类不追时效);每条结果标出条目类型(百科/词条/教程/文档/问答/other)

端点
POST /v2/m1/wiki/run
价格
1 credits(≈ ¥0.05 / 次)
scope
l2:wiki
默认条数 / 时间窗
10 条 · any
结果结构
wiki.v1

即时新闻scheduled

按主题取当天的新闻速览,按发布时间倒序;已强制过滤时政/军事/外交/社会突发等话题

端点
POST /v2/m1/news/run
价格
1 credits(≈ ¥0.05 / 次)
scope
l2:news
默认条数 / 时间窗
10 条 · day
结果结构
news.v1

流行音乐时scheduled

按主题取近一个月的热歌榜/新歌发行/榜单信息;只出榜单与外链,不托管任何音频

端点
POST /v2/m1/music/run
价格
1 credits(≈ ¥0.05 / 次)
scope
l2:music
默认条数 / 时间窗
10 条 · month
结果结构
music.v1

旅行灵感both

按目的地取攻略/推荐/季节玩法;冻结快照与实时入口都给

端点
POST /v2/m1/travel/run
价格
3 credits(≈ ¥0.15 / 次)
scope
l2:travel
默认条数 / 时间窗
10 条 · month
结果结构
travel.v1

十万个为什么live

有什么疑问直接问:实时调用一级答案 API 取一段带引用的答案(问题不可预知,所以不预生成)

端点
POST /v2/m1/why/run
价格
3 credits(≈ ¥0.15 / 次)
scope
l2:why
默认条数 / 时间窗
6 条 · any
结果结构
why.v1

财经速览scheduled

(演示扩展性单元 · order 90)按主题取当日财经与行业动态;本文件是「加单元只加 JSON」这条硬标准的证据

端点
POST /v2/m1/finance/run
价格
3 credits(≈ ¥0.15 / 次)
scope
l2:finance
默认条数 / 时间窗
8 条 · day
结果结构
list.v1

6. 授权 scope

一条 key 上写着它租了哪些商品;调了没租的入口就是 403 SCOPE_FORBIDDEN (响应里给出你实际能调的商品清单)。

scope token能调什么
*全部(一级三档 + 全部二级单元)
l1结果档 POST /v1/search
l1:answer快答案档 POST /v1/answer(不传 depth)
l1:deep深答案档 POST /v1/answer + depth:"deep"
l2:*全部二级单元
l2:<单元> / l2:<中间件>/<单元>某一个单元(例:l2:wiki)

三档一级 API 互不越权:拿 l1:deep 的 key 开不了 l1 与 l1:answer,反之亦然。 l1:* 这种通配恒为非法(400)—— 三档必须逐个写明。

7. 计费与套餐

计价单位是 credits:1 credit = ¥0.05。 每次调用都在响应里写 usage.credits_charged 与 usage.balance_after。

商品价格端点
l1 · 基础搜索 API1 credits(≈ ¥0.05 / 次)POST /v1/search
l1_answer · 快答案 API(读片段写答案)2 credits(≈ ¥0.1 / 次)POST /v1/answer
l1_deep · 深答案 API(读原文)4 credits(≈ ¥0.2 / 次)POST /v1/search
l2:wiki · 世界百科1 credits(≈ ¥0.05 / 次)POST /v2/m1/wiki/run
l2:news · 即时新闻1 credits(≈ ¥0.05 / 次)POST /v2/m1/news/run
l2:music · 流行音乐时1 credits(≈ ¥0.05 / 次)POST /v2/m1/music/run
l2:travel · 旅行灵感3 credits(≈ ¥0.15 / 次)POST /v2/m1/travel/run
l2:why · 十万个为什么3 credits(≈ ¥0.15 / 次)POST /v2/m1/why/run
l2:finance · 财经速览3 credits(≈ ¥0.15 / 次)POST /v2/m1/finance/run

两本账:预付余额(credits)与包月套餐额度。响应里的 usage.quota_source 告诉这次扣的是哪本账(balance / plan)。包月到期默认回落按量,不会突然断服。

查询:GET /v1/usage(已用/剩余/配额)、GET /v1/balance(余额/档位/已租商品)、 GET /v1/billing?period=YYYY-MM(月度账单,只含本 key)、 GET /v1/billing.csv?period=YYYY-MM(同数据的 CSV,带 BOM,Excel 直接开)。

会员折扣不会跌破成本线:折后低于该商品的最低成本线时按成本线收, 并用 usage.floor_applied / usage.cost_floor_credits 标出来(宪法 8.6)。 客户侧账单不含进价与毛利明细。

8. 快照与永久链接

永久快照页(不需要 key)

GET /s/{token}

每次 snapshot:true 的调用都会冻结一份副本,返回 https://search.ozforce.cn/s/<12 位 token>。 内容从此不再变化:20 天后、明年打开都和当时一字不差(正文逐字节冻结,页脚合规信息实时渲染); 同一个 token 永不覆盖。

双通道:/s/<token> 是人读的 HTML(不依赖前端 JS,离线保存也能读); /s/<token>.json 是同一份内容的原始数据(长缓存、不可变)。

打开快照
# 永久快照:把上面响应里的 snapshot_url 原样打开即可(不需要 key)
#   https://search.ozforce.cn/s/<token>        人读的 HTML 页
#   https://search.ozforce.cn/s/<token>.json   原始数据(长缓存、不可变)
curl -sS "https://search.ozforce.cn/s/<token>.json"

9. 限流与配额

项当前值说明
同 key 限速5 rps(突发 5)令牌桶;超了 429 RATE_LIMITED,响应带 retry_after
日 / 月配额按会员档位(见 /products 的计费说明)用尽 429 QUOTA_EXCEEDED;与余额是两本账
余额credits不足 402 INSUFFICIENT_CREDITS,响应带 balance / required
单次条数上限20count 越界直接 400,不静默夹取
请求体上限1 MB超过 413

上游硬约束(实测):搜索 100 QPS、抓取 25 QPS,账号级且两种接口互不占用; 429 响应没有 Retry-After 头,所以退避必须由调用方自己做。我们已在服务内做了排队与降级, 客户侧看到的是"这次慢一点"或"degraded:true 并写明原因"。

10. 辅助端点

端点要 key用途
GET /v1/units否单元目录(有哪些二级 API、每条多少钱)
GET /v1/catalog否商品目录:一级三档 + 全部二级,含价格与 rent_scope(机器可读真相)
GET /v1/usage是本 key 的日/月已用、剩余额度、余额、tier、scope、plan
GET /v1/balance是余额 + tier + 已租商品 + 套餐到期时间
GET /v1/billing是本 key 某账期账单(?period=YYYY-MM)
GET /v1/billing.csv是同上,CSV(UTF-8 带 BOM)
POST /v1/apply否用程序提交申请表(返回 application_id)
GET /s/{token}.json否快照原始数据(不可变长缓存)
GET /u/{middleware}/{unit}否单元的实时入口:跑一次并 302 到快照页(给人看)
GET /healthz否健康检查(含 sellable 与 blockers:现在哪些档能交付)

curl

列单元
# 单元目录(公开,不要 key)
curl -sS "https://search.ozforce.cn/v1/units"
商品目录
# 商品目录(公开,不要 key):每个商品的价格与 rent_scope
curl -sS "https://search.ozforce.cn/v1/catalog"

11. 错误码全表

所有错误体统一长这样:{ "error": { "code": "...", "message": "...", ...业务字段 }, "request_id": "..." }。 状态码与 code 都取自实现(src/util/errors.mjs)。

HTTPcode含义 / 怎么办
400 INVALID_ARGUMENT 入参非法:q 空/超 500 字、count 越界、freshness 不在枚举内、body 不是 JSON 对象
401 INVALID_API_KEY 没带 Authorization 头 / 不是 Bearer / key 形状不对 / key 不存在
401 KEY_REVOKED key 存在但已被吊销
402 INSUFFICIENT_CREDITS 预付余额不足(响应带 balance / required)
403 SCOPE_FORBIDDEN 这个 key 没租该商品(响应带 required / granted / available)
402 PLAN_EXPIRED 包月套餐已到期且配置为 block(默认是回落按量,不会出现这个码)
429 QUOTA_EXCEEDED 日/月配额用尽(响应带 retry_after)
429 RATE_LIMITED 同一个 key 超速(响应带 retry_after)
503 ANSWER_UNAVAILABLE 答案档当前不可交付(缺写作引擎/抓取):扣费前就拒,charged:0
404 UNIT_NOT_FOUND middleware/unit 对不上 config/units/*.json 里的声明
503 DIGEST_NOT_READY 定时单元的当期归档还没生成(响应带 date / slot / miss_policy)
503 UNIT_CIRCUIT_OPEN 该单元熔断冷却中(其它单元不受影响)
504 UNIT_TIMEOUT 该单元单次执行超时,沙盒被就地终止(其它单元不受影响)
503 UNIT_CRASHED 该单元沙盒崩溃,已隔离重启(其它单元不受影响)
503 UNIT_UNAVAILABLE 该单元已卸载/沙盒已释放(排空期的尾巴)
503 UNIT_SEARCH_FAILED 单元的多路检索全部失败(fail-loud,不会拿空列表冒充"今天没内容")
429 PROVIDER_RATE_LIMITED 上游搜索源限流(429,自动换源仍失败才抛给客户)
504 PROVIDER_TIMEOUT 上游搜索源超时
503 ALL_PROVIDERS_FAILED 降级链全部失败(两家源都不可用)
404 SNAPSHOT_NOT_FOUND 快照 token 不存在
400 INVALID_TOKEN 快照 token 非法(含 .. / \ 或超长)
500 SNAPSHOT_WRITE_FAILED 快照落盘失败:整次调用失败,绝不给"点了会变"的链接
410 CLAIM_EXPIRED 领取码超过 TTL(72 小时),让管理员重新批准
503 APPLY_DISABLED 公开申请入口被显式关闭(APPLY_ENABLED=false)

12. FAQ

我只要链接,不要答案,买哪档?
结果档 l1(POST /v1/search):最便宜,answer 恒为 null。
答案里为什么有 [1][2][3]?
那是引用编号,对应 sources 的下标;citations 给出本次真正用到的编号。 编号对不上时 cited:false,我们不会编造引用。
为什么调用返回 200 但 degraded:true?
降级必须可见:上游换了源、偏好没命中、深答案退回快答案都会标 true,并在 degraded_reason 写明原因。
快照会不会被后来的同名查询覆盖?
不会。同一个 token 永不覆盖,同样的 q 再调一次会得到新的 token。
定时单元(news/music/finance)拿到的是什么时候的内容?
看响应里的 served_from 与 digest_date:digest = 当期归档, previous = 最近一期(会显著标注是哪天的),live = 实时现搜。 想要实时就在 body 里加 "live": true。
能在浏览器里直接调吗?
不建议:key 会暴露在前端。请从服务端调;给人看的页面用 /u/<中间件>/<单元> 或 /s/<token>。