Orisumi

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
titleHTTP 状态的简短名称
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
删除仍有业务版本使用的 EntitySchemaHISTORY-9

认证与授权

认证与授权尚未决定。目前 API 只通过绑定本机的入口提供,见 ADR · 定位与边界。

契约

content/docs/api/openapi.json 是 HTTP 契约的权威来源,后端、客户端、测试和本节文档都按它同步,见 ADR · 文档的权威与传播。文档站按契约为每个接口生成一页,地址是 /api/<operationId>。

贡献者

页面历史