Overview
HTTP 契约共用的约定:接口、版本的表示、提交、列出与查询、引用方、时间、错误格式与授权
Orisumi API 按 Core 的实体与版本模型保存、读取和删除数据。本页写所有接口共用的约定;每个接口的路径、请求和响应以 openapi.json 为准。接口这样设计的理由见 ADR · 实体 API。
服务地址
OpenAPI 的 servers 声明相对地址 /api,所以契约中的 /example 对外是 /api/example。
本地环境有三个入口:8080 只提供 API;3000 是文档站,它把 API 路径转发给后端,所以接口页的 Playground 可以同源调用 API;8081 是 Web 控制台,/api/ 同样转发给后端。这是主检出目录的端口,工作树的端口由 orisumi env status 给出。转发规则见 tools/Orisumi.AppHost/README.md,入口的分工见 ADR · 后端运行与本地环境。公开站点的接口页只展示契约,Playground 在本地文档站中使用。
字段命名
API 字段使用 camelCase,对应 C# 的 PascalCase 和数据库的 snake_case,见 Storage · 命名。
时间
API 中的时间精确到 1 微秒,UTC 指协调世界时。事实时间的含义见 Observation · 事实时间。
| 参与方 | 职责 |
|---|---|
| Observer | 提交事实时间,字符串以 Z 结尾或带明确的 UTC 偏移 |
| 后端 | 转换为 UTC,向下截断到整微秒后保存;读取时返回以 Z 结尾的字符串;首次保存每个版本时赋值 recordedAt |
| 前端 | 按浏览器或系统时区显示时间 |
例如:
- 提交
2026-10-06T09:00:00+08:00,读回2026-10-06T01:00:00Z。 - 不足一微秒的尾数舍去:提交
1999-12-31T23:59:59.9999999Z,读回1999-12-31T23:59:59.999999Z。截断的位置见 PostgreSQL · 时间。
timeZone 非空时,必须是可解析的 IANA 时区 ID。时间或时区校验失败时返回 400。
保存时间
读取版本时,recordedAt 一定存在。它是后端首次保存这个版本的时刻,以 Z 结尾,精确到微秒。写入方不得提交 recordedAt;重试一个已保存的版本,recordedAt 保持不变。见 VERSION-10 和 VERSION-7。
接口
接口按身份寻址:版本用 VersionId,实体用 EntityId。所有类型的实体共用同一组接口,读取一个版本之前不需要知道它的类型。
| 操作 | 接口 |
|---|---|
| 提交一组版本 | POST /versions |
| 读取一个版本 | GET /versions/{versionId} |
| 读取实体的当前版本 | GET /entities/{entityId} |
| 读取实体的版本链 | GET /entities/{entityId}/versions |
| 列出实体 | GET /entities |
| 按事实时间查询观测 | GET /observations |
| 列出引用实体的版本 | GET /entities/{entityId}/referrers |
| 删除实体 | DELETE /entities/{entityId} |
版本链从首版到当前版排列,一次返回全部版本。删除一次作用于一个实体,成组删除时逐个请求,见 HISTORY-6。删除已删除的实体也返回 204,所以响应丢失后可以直接重试。
版本的表示
读取时,版本的 JSON 成员就是 Version · 成员 中的七个,recordedAt 一定存在。提交时成员相同,只是没有 recordedAt。
category必须写出,后端按它解释content。previousVersionId和schemaVersionId必须出现,没有值时为null。- Core 类型的
content是该类型的成员,见各类型页;业务版本的content就是业务字段组成的对象。
读回的内容与提交的值相同,写法可以不同:对象的键不保留原来的顺序,数字可以换成等值的写法,例如提交 1e2,读回 100。时间的写法见时间。
内容相同
重试一个已保存的 VersionId 时,后端判断提交的内容与已保存的版本是否相同,见 VERSION-7。比较按 JSON 的值进行:
- 对象的键的顺序不影响比较。
- 数字按数学值比较,
1、1.0和1e0相同。 - 字符串逐个字符比较,不做 Unicode 规范化。
- 数组按顺序比较。InputSet 的
members无序,见 INPUTSET-4。 - 省略的字段与值为
null的字段不同,见 SCHEMA-13。 - Observation 的时间先换算为 UTC、截断到整微秒,再比较。所以
2026-10-06T09:00:00+08:00与2026-10-06T01:00:00Z相同。
提交
一次提交的请求体是 { "versions": [ … ] },至少有一个版本,由写入方按依赖顺序排列。后端按这个顺序逐个保存,见 VERSION-13。
全部版本都已保存时,返回 200,响应按提交的顺序列出每个版本的 versionId 和 recordedAt。重试的版本返回首次保存时的 recordedAt。
请求体不符合契约时,例如 JSON 无效、缺少成员或带有 recordedAt,整次提交都不处理,返回 400。
某个版本违反 Core 规则或与已保存的数据冲突时,这个版本被拒绝,错误响应中的 index 是它在 versions 中的位置(从 0 开始),versionId 是它的身份,rule 是拒绝它的规则。位置在它之前的版本都已保存,之后的版本没有处理。写入方确认已保存的版本,处理完被拒绝的原因后,可以原样重发整次提交。
基于旧版本的版本被拒绝时(VERSION-6),写入方用 GET /entities/{entityId} 读取新的当前版本,再重新处理。
一次提交中的版本数不设上限,请求体受 10 MiB 的上限约束,见错误格式。
列出与查询
GET /entities 列出实体的当前版本,按 EntityId 升序排列。两个筛选参数都可以省略:
| 参数 | 只返回 |
|---|---|
category | 这一类内容的实体,例如 entity_schema 列出全部 EntitySchema |
resourceName | 这种业务类型的实体,类型由当前使用这个资源名的 EntitySchema 决定,见 SCHEMA-5 |
GET /observations 按事实时间列出 Observation 的当前版本。from 和 to 必须给出,表示范围 [from, to);resourceName 可以省略,给出时只返回观测内容属于这种业务类型的 Observation。匹配只看当前版本中已知的边界:
| 已知的边界 | 返回的条件 |
|---|---|
| 两个都已知 | 时段 [startAt, endAt) 与范围有重叠;时间点 t 满足 from ≤ t < to |
| 只知道一个 | 把这个边界当作时间点 t,满足 from ≤ t < to |
| 两个都未知 | 不返回,这样的 Observation 按实体或类型找到 |
结果按已知的开始边界升序排列,开始边界未知时用结束边界,相同时按 EntityId 升序。
引用方
GET /entities/{entityId}/referrers 列出引用这个实体的版本,也就是它的引用方。例如找和 VS Code 有关的数据:先列出引用 VS Code 的前台时段,再分别列出引用这些时段的 Observation。接口只返回直接的引用方,更远的关系由读取方逐层查询。
查询 VS Code 的引用方,一页中的一项:
{
"version": {
"entityId": "01a12e31-9050-7000-8000-000000000020",
"versionId": "01a12e31-9050-7000-8000-000000000120",
"previousVersionId": null,
"category": "business",
"schemaVersionId": "019e1bbb-ec00-7000-8000-000000000101",
"recordedAt": "2026-10-06T02:20:01Z",
"content": {
"localApplicationVersionId": "01a12e31-9050-7000-8000-000000000101",
"name": "Visual Studio Code"
}
},
"references": [
{
"path": "/content/localApplicationVersionId",
"targetVersionId": "01a12e31-9050-7000-8000-000000000101"
}
]
}- 目标按实体匹配:引用 VS Code 任何一个版本的都算,见 ENTITY-7。
targetVersionId给出引用的是哪一版。 - 只匹配
content中的引用:Core 类型的引用成员,以及业务字段中的reference,见 ENTITY-5。前一版本通过版本链读取,使用某个 EntitySchema 的数据通过resourceName列出。 - 每一项是引用方的完整版本。
references列出其中引用目标的每个位置,path是从版本根部写起的 JSON Pointer(RFC 6901),例如 InputSet 的第四个成员是/content/members/3。 versions默认为current,只返回当前版本仍引用目标的实体,每个实体一个版本。versions=all返回每一个引用过目标的版本,包括后来改为引用别处的旧版本;一份延长过多次的 Observation,每个版本各占一项。- 结果按引用方的 VersionId 升序排列。
- 目标实体已被删除时,照常列出引用方,读取方据此知道哪些数据的追溯不完整,见 HISTORY-8。EntityId 从未出现过时返回
404。
分页
三个列表接口都分页。limit 是一页最多返回的版本数,默认 100,最大 1000;列出引用方时,一个版本连同它的全部位置算一项。响应中的 next 是下一页的游标,把它作为 after 传入就得到下一页;没有下一页时,next 为 null。游标的内容由后端决定,调用方原样传回。
错误格式
错误响应的 Content-Type 是 application/problem+json,结构遵循 RFC 9457。
| 必填字段 | 内容 |
|---|---|
type | 固定为 about:blank |
title | HTTP 状态的简短名称 |
status | 实际的 HTTP 状态码 |
detail | 这次错误的具体说明 |
| 扩展字段 | 出现的时机 | 内容 |
|---|---|---|
rule | 请求被某条 Core 规则拒绝 | 规则编号,例如 VERSION-6 |
index | 提交中的某个版本被拒绝 | 这个版本在 versions 中的位置,从 0 开始 |
versionId | 提交中的某个版本被拒绝 | 这个版本的 VersionId |
调用方按状态码和 rule 判断原因,detail 供人阅读。
状态码
| 状态码 | 含义 |
|---|---|
400 | 请求本身有错:请求体或参数不符合契约,路径中的 ID 不是 UUID,或者版本违反 Core 规则。违反规则时带 rule |
404 | 路径中的身份从未出现过,或者没有 EntitySchema 使用参数中的 resourceName |
409 | 请求与已保存的数据冲突,带 rule,见下表 |
410 | 读取的实体或版本已被删除,读取方把它列为不可读取,见 HISTORY-8 |
413 | 请求体超过 10 MiB(10,485,760 字节) |
500 | 后端出现意外错误 |
409 的版本本身合法,换一个时机或在实体的另一个状态下可能被接受;400 的版本本身需要修改。
| 冲突 | rule |
|---|---|
| 版本基于旧版本 | VERSION-6 |
| 重试的 VersionId 已保存了不同的内容 | VERSION-7 |
| 资源名已被另一个业务类型使用 | SCHEMA-5 |
| 实体已被删除 | HISTORY-7 |
| VersionId 属于已删除的实体 | VERSION-9 |
| 删除仍有业务版本使用的 EntitySchema | HISTORY-9 |
认证与授权
认证与授权尚未决定。目前 API 只通过绑定本机的入口提供,见 ADR · 定位与边界。
契约
content/docs/api/openapi.json 是 HTTP 契约的权威来源,后端、客户端、测试和本节文档都按它同步,见 ADR · 文档的权威与传播。文档站按契约为每个接口生成一页,地址是 /api/<operationId>。