公开 API
公开访问的 API 路由,无需认证。返回公开配置数据。
基础路径
/public认证方式
无需认证。所有端点均公开访问,使用 publicCors 中间件支持跨域请求。
端点列表
GET /public/client-config
获取移动端远程配置。接口无需认证,后端只读取 site_settings 中的客户端白名单分组:
前端接入方法、模型清单下载规则和 Tab 功能开关约定见 移动端远程配置接入。
endpoints.apiBaseUrl/oauthBaseUrl/modelGatewayBaseUrl的 origin 部分按发起请求的域名(Host/X-Forwarded-Host)动态推导,不是site_settings里存的固定值;modelManifestUrl/modelBaseUrl/modelDownloadBaseUrl强制https://。详见 移动端远程配置接入。
client.endpointsclient.modelsclient.thresholdsclient.flagsclient.transcribeclient.dyj1client.firmwareclient.vibeBoard
system、secrets、notification.novu_admin 等后端内部配置不会通过该接口返回。
请求参数
| 参数 | 位置 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
groups | Query | string | 否 | 全部分组 | 逗号分隔分组,如 endpoints,models;未知分组会被忽略 |
platform | Query | string | 否 | 无 | 预留给 App 端平台灰度,当前不改变返回内容 |
appVersion | Query | string | 否 | 无 | 预留给版本灰度,当前不改变返回内容 |
缓存
- 响应包含
ETag,客户端可在后续请求携带If-None-Match。 - 配置未变化时返回
304 Not Modified。
响应格式
{
"version": 1782349200000,
"updatedAt": "2026-06-25T01:00:00.000Z",
"groups": {
"endpoints": {
"apiBaseUrl": "https://block2-api.wainao.chat",
"modelGatewayBaseUrl": "/api/model-gateway/v1",
"modelManifestUrl": "https://example.com/mobile/models/manifest.json",
"modelBaseUrl": "https://example.com/mobile/models"
},
"flags": {
"enableLocalAsr": true,
"enableModelDownload": true,
"enableTabAiFriends": true
}
}
}响应字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
version | number | 当前响应视图中最新配置行的 updated_at 毫秒时间戳;无配置时为 0。请求 appStore 组时,该组贡献的值由服务端持久水位保证单调不减:Catalog 内容(与节点域名无关的摘要)变化而行时间不增(下架、撤销等)时前进到 max(上次值 + 1, 当前毫秒),内容不变时保持不变(ETag / 304 照常) |
updatedAt | string | null | 当前响应视图中最新配置行更新时间;无配置时为 null |
groups | object | 命中的客户端配置分组,key 去掉 client. 前缀 |
错误码
| HTTP 状态码 | 说明 |
|---|---|
| 304 | ETag 命中,配置未变化 |
| 500 | 获取客户端远程配置失败 |
GET /public/firmware/:model/:module/:fileName
既有固件对象的长期稳定入口。module 只允许 main|wifi;服务端按白名单参数重建对象路径,每次请求生成短期 HTTPS signed URL 并返回 302。响应带 Cache-Control: no-store。
GET /public/vibe-board/firmware/:channel/:sha256/:fileName
AI-Board-01 content-addressed 固件入口。channel 只允许 stable|beta,sha256 必须为 64 位小写 hex,文件扩展名只允许 .bin。请求方不能指定 bucket 或 storagePath。
GET /public/vibe-board/app/:platform/:sha256/:fileName
Vibe Board 桌面 App content-addressed 稳定入口。平台仅允许 macos-arm64|macos-x86_64|windows-x86_64,扩展名必须与平台匹配,服务端据此重建私有对象路径并核对已登记资产。首次 GET/Range 会把私有 storage 对象流式写入内容寻址的有界本地缓存,完成 size/SHA-256 校验与原子发布后,再由 BunFile 直接交付;响应不返回或暴露 storage signed URL:
- 完整 GET 返回
200、安装包 body、Content-Type、Content-Length、Content-Disposition: attachment与Accept-Ranges: bytes。 - HEAD 返回同一资产的元数据响应头且无 body。
- 单段
Range: bytes=...返回206与正确的Content-Range;非法、多段或不可满足 Range 返回416。 - 完整 GET 与 206 均返回正确
Content-Length;HTTP/1.1 不使用 chunked 代替已知长度。 - 本地缓存按 SHA singleflight,默认总上限 512 MiB;单个客户端断开不会取消仍有其他等待者的共享回源。
- 后端启动会同时校验当前上传上限与数据库既有 App asset 最大尺寸;缓存降容低于任一值会 fail-fast,避免既有下载在启动后永久
502。 - 缓存回源正文使用滚动式 30 秒停滞超时;每收到一个 chunk 会重新计时,超时会中止上游并清理 part、singleflight 与容量预留,返回
504。 - URL 含不可变 SHA-256,成功响应使用
Cache-Control: public, max-age=31536000, immutable。 - 参数非法返回
400,合法形状但未登记或上游对象不存在返回404,storage 暂时不可用返回502/504。
GET /public/client-config?groups=managedResources
返回当前节点已发布的签名 managed resource Envelope。该配置公开匿名,不包含用户信息、凭据或私钥;请求 origin 与节点 API_URL 不一致时省略该 group。
GET /public/client-config?groups=keymapPresets
返回 Driver「键位映射」页「第一次配键位?」推荐区使用的官方键位预设。信封、ETag/304、Cache-Control(public, max-age=60, stale-while-revalidate=300)与其他分组一致,可与其他分组同时请求(如 groups=endpoints,vibeBoard,keymapPresets)。
- 只在显式点名时返回:不带
groups参数(= 返回全部分组)时不包含本组,避免旧客户端默认请求多下载(本组最大约 2MB)。 - 服务端已过滤
enabled=false的预设,并按order升序(同序按id)排好。 - 后台尚未配置时响应中没有
keymapPresets键,客户端按「无预设」处理。 - 后台修改后,受 HTTP 缓存与客户端轮询影响,客户端可能数分钟后才看到。
{
"version": 1792630800000,
"updatedAt": "2026-10-22T01:00:00.000Z",
"groups": {
"keymapPresets": {
"schemaVersion": 1,
"hardwareModelCode": "AI-Board-01",
"presets": [
{
"id": "ime",
"enabled": true,
"order": 10,
"icon": "mic",
"title": { "zh-CN": "输入法为主", "en-US": "Typing first" },
"description": { "zh-CN": "大量口述成文,追求转写又快又准" },
"highlights": { "zh-CN": "语音键→转文本 · YOLO档→翻译" },
"minAppVersion": "1.0.6",
"pack": { "schema_version": 2, "id": "com.reai.preset.ime", "name": "输入法为主", "version": "1.0.0", "bindings": {}, "layers": {}, "disabled": [] }
}
]
}
}
}| 字段 | 说明 |
|---|---|
id | ^[a-z0-9][a-z0-9-]{1,31}$,组内唯一 |
order | 整数,升序展示 |
icon | code pen mic monitor keyboard sparkles globe 之一 |
title / description / highlights | zh-CN 必有;en-US 可选(缺失时客户端回退中文);上限 32 / 120 / 160 字 |
minAppVersion | 可选 SemVer;客户端版本低于它时不显示 |
pack | Driver「导出配置」产出的适配包 v2 原文(schema_version=2,序列化 ≤256KB),服务端不检查其内部字段、原样透传,完整校验由客户端负责 |
GET|HEAD /managed-resources/:resourceId/:version/:target/:channel/:sha256/:fileName
公开、同源、不可变的 .reairuntime 下载入口。支持完整 GET、Range: bytes=N-、准确长度和 SHA-256 ETag,不重定向到对象存储地址。
固件与 App 下载入口均无需登录。既有 firmware 路由继续使用短期 signed URL 302;App 路由不再要求客户端跟随 storage 重定向。
GET /public/devices/bluetooth-filters
获取蓝牙设备名前缀过滤规则。从 hw_product_models 表读取状态为 active 的产品型号的蓝牙前缀配置。
一个型号可携带多个前缀(2026-06-13 一代机归并后),每个前缀产出一条 filter,前端应对同一 productModelId 的多条 filter 作去重处理。
请求参数
无。
响应格式
{
"success": true,
"filters": [
{
"namePrefix": "DYJV1_",
"productModelId": "uuid",
"modelName": "点一机·一代",
"requiresInventoryCheck": true,
"generation": "gen1",
"role": "full"
},
{
"namePrefix": "DYJ-",
"productModelId": "uuid",
"modelName": "点一机·一代",
"requiresInventoryCheck": true,
"generation": "gen1",
"role": "upgrade_only"
}
]
}响应字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
filters[].namePrefix | string | 蓝牙设备名前缀 |
filters[].productModelId | string | 产品型号 ID |
filters[].modelName | string | 产品型号名称 |
filters[].requiresInventoryCheck | boolean | 是否需要库存校验 |
filters[].generation | string | null | 产品代次,gen1 / gen2 / null(第三方型号) |
filters[].role | string | 前缀角色:full(完整功能)/ upgrade_only(仅固件升级识别,不用于常规绑定) |
错误码
| HTTP 状态码 | 说明 |
|---|---|
| 500 | 获取蓝牙过滤规则失败 |
GET /public/payment/prices
获取产品价格列表。
请求参数
| 参数 | 位置 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
currency | Query | string | 否 | CNY | 货币类型 |
响应格式
{
"success": true,
"data": [
{
"id": "uuid",
"productType": "subscription",
"productCode": "pro",
"name": "专业版",
"description": "描述文本",
"amount": 9900,
"currency": "CNY",
"credits": 100000,
"billingPeriod": "monthly",
"isFeatured": true
}
]
}响应字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
data[].id | string | 价格 ID |
data[].productType | string | 产品类型 |
data[].productCode | string | 产品代码 |
data[].name | string | 产品名称 |
data[].description | string | 产品描述 |
data[].amount | number | 价格金额(分) |
data[].currency | string | 货币类型 |
data[].credits | number | 包含积分数 |
data[].billingPeriod | string | 计费周期 |
data[].isFeatured | boolean | 是否推荐 |
错误码
| HTTP 状态码 | 说明 |
|---|---|
| 500 | 获取价格列表失败 |
GET /public/payment/methods
获取可用支付方式。
请求参数
| 参数 | 位置 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
currency | Query | string | 否 | 无 | 按货币类型筛选支付方式 |
响应格式
{
"success": true,
"data": [
{
"id": "wechat_pay",
"name": "微信支付",
"currencies": ["CNY"]
}
]
}错误码
无特殊错误码。
GET /public/wechat/jssdk-signature
微信公众号 JS-SDK 签名(按 OAuth App 隔离,issue #854)。供第三方 landing 页在微信浏览器内调 wx.config()。靠 client_id 识别接入应用,用该应用配置的公众号凭据签名;主安全边界 = 待签名 URL host 必须在该应用绑定的 JS 安全域名内。仓库内功能说明见 docs/features/wechat-jssdk/README.md。
请求参数
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
client_id | Query | string | 是 | 接入应用的 OAuth App client_id |
url | Query | string | 是 | 当前页面 URL(encodeURIComponent;服务端取 # 之前签名) |
apis | Query | string | 否 | 逗号分隔,白名单当前为 openAddress + chooseWXPay(默认 openAddress) |
响应格式
{
"success": true,
"data": {
"appId": "wx公众号AppID",
"timestamp": 1710000000,
"nonceStr": "...",
"signature": "...",
"jsApiList": ["openAddress"]
}
}错误码(信封 { success:false, error:{ code, message } })
| code | HTTP | 场景 |
|---|---|---|
NOT_CONFIGURED | 503 | 未配置 / 配置禁用 / 应用不存在(前端走手填兜底) |
INVALID_REQUEST | 400 | 参数缺失 / 非白名单 api / url 非 https / 域名不在白名单 / Origin 不一致 |
RATE_LIMITED | 429 | 超过 IP 限流 |
INTERNAL_ERROR | 500 | 微信侧异常(不透传原始错误) |
源码
- 路由文件:
apps/backend/src/routes/public.ts - 微信 JS-SDK 签名:
apps/backend/src/routes/public/wechat-jssdk.ts、apps/backend/src/services/wechat/jssdk*.ts
GET /public/features/graphiti
无需认证,返回 Graphiti 功能是否开放及客户端展示所需的公开状态;不暴露模型、Provider 或计费凭据。