Skip to content

Data API ​

Data API 是一个 Firebase 风格的文档数据库服务,允许第三方应用通过 HTTP 请求读写结构化 JSON 数据。每个应用可以创建多个数据集合(Collection),每个集合包含若干文档(Document)。

概览 ​

项目说明
基础路径/data/api/
认证方式Basic(应用模式)或 Bearer(用户模式)
数据格式JSON
文档大小限制512 KB
分页上限每页最多 100 条

认证 ​

Data API 支持两种认证模式,通过 Authorization 请求头区分:

应用模式(Basic) ​

使用应用的 client_id 和 client_secret 进行认证。应用模式拥有集合内所有文档的完整读写权限,不受权限配置限制。

Authorization: Basic base64(client_id:client_secret)

⚠️ 安全提示:client_secret 拥有应用的完整管理权限。绝对不要将其写入分发给用户的客户端程序(游戏包体、桌面应用等),否则密钥可被逆向提取。应用模式仅应在受信任的服务器环境或内部开发工具中使用。

**推荐架构:**面向用户的客户端应使用 Bearer 模式(用户 OIDC 登录),配合云函数和权限组实现业务逻辑。

用户模式(Bearer) ​

使用用户的 OAuth access_token 进行认证。用户模式的访问范围取决于集合的权限配置。

Authorization: Bearer <access_token>

**🔒 访问说明:**数据集合默认为私有,用户无法通过 API 直接访问。如需客户端直接调用,请在开发者面板中将集合的 visibility 设置为公开。推荐统一使用云函数处理数据操作。

重放防护 ​

所有 API 请求必须携带以下请求头以防止重放攻击:

请求头说明
X-Timestamp当前 Unix 时间戳(秒),与服务器时差不超过 ±5 分钟
X-Nonce随机唯一字符串,16~64 个字母数字字符,每次请求不同

可见性模型 ​

每个集合通过 visibility(可见性位掩码)和 publicEditOp(公开编辑操作)控制用户模式的访问权限。

集合默认为私有——只有应用模式(Basic)和云函数可以访问。

visibility 位掩码 ​

值含义
0私有(默认)——用户模式不可访问
1公开只读——用户模式可读取文档,不可写入
3公开读写——用户模式可读取,且可按 publicEditOp 规则写入

publicEditOp(编辑操作) ​

仅当 visibility = 3 时生效,控制用户模式下允许的写入类型:

值含义
add仅允许新增/修改文档(PUT / PATCH)
remove仅允许删除文档(DELETE)
all允许新增、修改和删除(默认)

应用模式(Basic 认证)不受可见性限制,始终可以读写所有文档。

集合管理 ​

集合通过开发者管理面板创建和管理,不在 Data API 中提供公开接口。

每个合集可选填写 0~512 字符的合集描述,供开发者标记和注释用途。描述仅在管理接口、管理面板和开发工具中可见,不会通过公开 Data API 返回。

合集名称规则:

  • 以字母或下划线开头
  • 只允许字母、数字、下划线和连字符
  • 长度 1~128 字符
  • 正则:/^[a-zA-Z_][a-zA-Z0-9_-]{0,127}$/

有序索引集合 ​

普通集合保持旧行为:文档完整 JSON 存在 data_document.data,列表接口可按单个 JSON 字段排序。需要排行榜、竞速榜、价格榜等高频排序场景时,可以把集合的数据能力设为 indexed,并在 settings.indexes 中声明一个或多个 ordered 索引。

有序索引不会把整个榜单写进一个文档。仍然是“一个参赛实体 = 一个文档”,服务端会把 开发者指定的排序字段投影到内部 data_document_index 表中。这样单文档仍受 512 KB 限制, 但集合内文档数量可以持续增长,排行查询走索引投影。

示例:通用分区最佳分数榜,一个合集内可按 groupId + variant 拆分多个独立排行:

json
{
  "indexes": [
    {
      "name": "best_score",
      "kind": "ordered",
      "partition": [
        { "path": "groupId", "type": "string" },
        { "path": "variant", "type": "string" }
      ],
      "fields": [
        { "path": "score", "type": "int", "order": "desc" },
        { "path": "completedAt", "type": "int", "order": "asc" }
      ]
    }
  ],
  "writePolicy": {
    "mode": "keepBest",
    "index": "best_score"
  }
}

字段说明:

字段说明
indexes[].name索引名,API 查询时使用
indexes[].kind当前支持 ordered
indexes[].partition可选,分区字段。查询传入相同 partition 时,每个分区就是独立排行榜
fields[].path排序字段路径。默认指向 document.data,也支持 data.xxx、documentId、ownerUid、createdAt、updatedAt
fields[].typeint、number、string 或 boolean
fields[].orderasc 或 desc
fields[].decimalsnumber 的小数缩放精度,0~6
fields[].required默认 true。设为 false 时缺失值按 missing 放到开头或末尾
writePolicy.modeoverwrite 或 keepBest
writePolicy.indexkeepBest 使用哪个 ordered index 判断新文档是否更靠前

keepBest 会在 PUT / PATCH / $data.set / $data.update / 管理面板运行脚本写入时生效: 如果新数据在指定索引下不比旧数据靠前,服务端保留旧文档并返回 kept: true。开发者管理 面板的单文档编辑用于维护数据,会直接写入并重建索引。

建议把参与排序的时间、分数、价格等关键值按业务需要标准化为整数,避免浮点排序误差。

管理面板运行指令 ​

开发者管理面板提供“运行指令”工具,用于对应用数据库执行 SQL-like 命令或内嵌 JavaScript 脚本。该功能走管理端接口,需要登录用户拥有应用的 data 编辑权限;它不是 公开 Data API,不能用 Basic 应用密钥或用户 Bearer token 直接调用。

每次操作都可以选择:

模式行为
preview只计算影响范围,返回 before / after 差异,不写入数据库
execute实际写入数据库,返回影响条数和本次变更差异

响应中的关键字段:

字段说明
affected本次命中或修改的文档数量
result命令或脚本的返回值
changes写操作的差异列表,包含 operation、collection、documentId、before、after
logs脚本中 console.log/info/warn/error 捕获的日志

maxRows 用于限制单次扫描/操作的文档数量,默认 100,最大 5000。

命令模式 ​

命令模式不是完整 SQL,也不会执行原生 SQL;它只支持面向文档集合的安全子集:

sql
SELECT * FROM players WHERE level >= 10 LIMIT 20;
UPDATE players SET reviewed = true, season = 'S1' WHERE score > 1000 LIMIT 100;
DELETE FROM players WHERE banned = true LIMIT 50;

支持的条件操作符:

写法含义
=等于
!= / <>不等于
> / >=大于 / 大于等于
< / <=小于 / 小于等于

字段名默认指向文档 data 中的顶层字段;也可以使用 documentId、ownerUid,或 data.profile.level 这样的嵌套路径。UPDATE SET 只能修改文档 data 字段,不能修改 documentId。

JS 脚本模式 ​

脚本会在受控 VM 中执行,可使用内置 db 对象和 console。脚本必须通过 return 返回结果:

javascript
const docs = await db.select('players', {
  where: { level: { $gte: 10 } },
  limit: 100
})

for (const doc of docs) {
  await db.update('players', {
    where: { documentId: doc.documentId },
    patch: { reviewed: true }
  })
}

console.log('reviewed', docs.length)
return { reviewed: docs.length }

可用方法:

方法说明
db.select(collection, { where, limit, offset, sortBy, sortOrder })查询文档,返回包含 documentId、ownerUid、data、时间戳的数组
db.update(collection, { where, patch, limit })浅合并更新;patch 也支持 $set、$unset、$inc、$push、$pull
db.delete(collection, { where, limit })删除匹配文档
db.set(collection, documentId, data, { ownerUid })创建或覆盖单个文档
db.indexTop(collection, indexName, { limit, offset, ownerUid, ownerUids, partition })按有序索引读取前排或筛选后的排行
db.indexAround(collection, indexName, documentId, { before, after, partition })读取指定文档所在的排名窗口

脚本 where 对象支持 { field: value } 和 { field: { $gt: 1 } } 形式,操作符包括 $eq、$ne、$gt、$gte、$lt、$lte、$in。字段路径规则与命令模式相同。

API 接口 ​

列出文档 ​

http
GET /data/api/{collectionName}

查询参数:

参数类型说明
ownerUidstring可选,按所有者筛选
limitnumber可选,每页条数,默认 50,最大 100
offsetnumber可选,偏移量,默认 0
sortBystring可选,按 data 中的字段排序
sortOrderstring可选,排序方向:asc 或 desc
wherestring可选,JSON 过滤条件字符串(如 {"level":{"$gt":5}})

响应:

json
{
  "documents": [
    {
      "documentId": "player_001",
      "ownerUid": "R10001",
      "createdAt": "1713168000",
      "updatedAt": "1713168000"
    }
  ],
  "total": 42,
  "limit": 50,
  "offset": 0
}

注意: list 不会返回 data 字段以节省带宽。data 字段包含文档的全部 JSON 内容,可能很大。使用 GET /data/api/{collectionName}/{documentId} 获取完整文档。

按有序索引读取前 N 名 ​

http
GET /data/api/{collectionName}/indexes/{indexName}/top

查询参数:

参数类型说明
limitnumber可选,默认 50,最大 100
offsetnumber可选,用于分页
ownerUidstring可选,只看单个 owner
ownerUidsstring可选,逗号分隔的 ownerUid 列表,适合好友榜
partitionJSON string可选,分区查询,例如 {"groupId":"item_001","variant":"hard"}

响应会返回完整 data 和 rank:

json
{
  "indexName": "best_score",
  "documents": [
    {
      "rank": 1,
      "documentId": "uid_001",
      "ownerUid": "uid_001",
      "data": {
        "score": 1000000,
        "label": "Entry 001",
        "completedAt": 1713168000
      },
      "createdAt": "1713168000",
      "updatedAt": "1713168000"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}

获取自己或指定文档所在排行 ​

http
GET /data/api/{collectionName}/indexes/{indexName}/me?before=5&after=5&partition={"groupId":"item_001","variant":"hard"}
GET /data/api/{collectionName}/indexes/{indexName}/around/{documentId}?before=5&after=5&partition={"groupId":"item_001","variant":"hard"}

me 需要 Bearer 用户认证,并按当前用户 uid 查找该索引中最靠前的一条文档。 around/{documentId} 可用于应用模式或已知文档 ID 的场景。响应包含目标 rank、目标 document 以及前后窗口 documents。

获取文档 ​

http
GET /data/api/{collectionName}/{documentId}

响应:

json
{
  "document": {
    "documentId": "player_001",
    "ownerUid": "R10001",
    "data": { "score": 9800, "level": 15 },
    "createdAt": "2026-03-10 12:00:00",
    "updatedAt": "2026-03-15 08:30:00"
  }
}

document.data 才是你的业务 JSON 内容;外层 document 用于承载文档 ID、所有者和时间戳等元数据。

创建或覆盖文档 ​

使用 PUT 语义——如果文档已存在则完全覆盖,不存在则创建。

http
PUT /data/api/{collectionName}/{documentId}
Content-Type: application/json

请求体:

json
{
  "data": { "score": 9800, "level": 15 },
  "ownerUid": "R10001"
}
字段类型说明
dataobject必填,文档内容(任意 JSON 对象)
ownerUidstring可选,仅应用模式下可指定文档所有者。用户模式下自动设置为当前用户

响应:

json
{
  "status": "ok",
  "documentId": "player_001",
  "created": true
}

文档 ID 规则:

  • 只允许字母、数字、下划线、连字符、冒号和点
  • 长度 1~128 字符
  • 正则:/^[a-zA-Z0-9_\-:.]{1,128}$/

**注意:**PUT 是整文档写入。文档已存在时,data 会被替换为请求体中的新对象; 没有出现在新 data 中的旧字段会被删除。PUT 不会因为缺少某个业务字段而报错。

部分更新文档 ​

使用 PATCH 语义——对文档数据进行浅合并(Shallow Merge)。

http
PATCH /data/api/{collectionName}/{documentId}
Content-Type: application/json

请求体:

json
{
  "data": { "level": 16 }
}

只更新指定的字段,其他字段保持不变。例如原文档的 score 不会被影响。

如果 PATCH 的 data 中包含文档原本没有的顶层字段,该字段会被自动新增;如果文档本身 不存在,则返回 404 not_found。PATCH 的浅合并只作用于顶层字段,不会自动把 "profile.name" 解析成嵌套路径。

响应:

json
{
  "status": "ok",
  "documentId": "player_001"
}

删除文档 ​

http
DELETE /data/api/{collectionName}/{documentId}

响应:

json
{
  "status": "ok"
}

错误响应 ​

所有错误以 JSON 格式返回,包含 error 和 error_description 字段:

json
{
  "error": "not_found",
  "error_description": "Document not found"
}

常见错误码:

HTTP 状态码error说明
400invalid_document_id文档 ID 格式不合法
400payload_too_large文档大小超过 512 KB 上限
401unauthorized缺少 Authorization 头
401invalid_clientBasic 认证失败(无效的 client_id 或 secret)
401invalid_tokenBearer 令牌无效或已过期
403forbidden权限不足(例如用户尝试访问私有集合,或对只读集合执行写操作)
403service_not_enabled应用未开通数据库服务
404not_found集合或文档不存在

示例 ​

cURL — 应用模式写入 ​

bash
# 写入文档
curl -X PUT https://api.starecho.net/data/api/player_data/player_001 \
  -H "Authorization: Basic $(echo -n 'your_client_id:your_client_secret' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"data": {"score": 9800, "level": 15}, "ownerUid": "R10001"}'

# 读取文档
curl https://api.starecho.net/data/api/player_data/player_001 \
  -H "Authorization: Basic $(echo -n 'your_client_id:your_client_secret' | base64)"

cURL — 用户模式读写 ​

bash
# 用户写入自己的文档
curl -X PUT https://api.starecho.net/data/api/player_data/my_settings \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"data": {"theme": "dark", "language": "zh-CN"}}'

# 用户读取自己的文档
curl https://api.starecho.net/data/api/player_data/my_settings \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

JavaScript(Fetch API) ​

js
const CLIENT_ID = 'your_client_id'
const CLIENT_SECRET = 'your_client_secret'
const BASE_URL = 'https://api.starecho.net/data/api'

// 写入文档
await fetch(`${BASE_URL}/player_data/player_001`, {
  method: 'PUT',
  headers: {
    'Authorization': `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    data: { score: 9800, level: 15 },
    ownerUid: 'R10001',
  }),
})

// 列出文档
const res = await fetch(`${BASE_URL}/player_data?limit=20&offset=0`, {
  headers: {
    'Authorization': `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
  },
})
const { documents, total } = await res.json()

© 2026 StarEcho