{ "openapi": "3.1.0", "info": { "title": "RawMark API", "version": "1", "description": "RawMark 的 REST API。自动化调用请在「API 令牌」中创建令牌,以 `Authorization: Bearer ` 访问;只读令牌只能使用 GET。分享与令牌的管理接口只接受登录会话。所有错误响应均为 `{\"error\": \"说明\"}`。" }, "servers": [ { "url": "/api/v1" } ], "security": [ { "bearer": [] }, { "cookie": [] } ], "paths": { "/health": { "get": { "summary": "健康检查", "tags": [ "系统" ], "security": [ {} ], "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ok" } } } } } } }, "/openapi.json": { "get": { "summary": "本文档", "tags": [ "系统" ], "security": [ {} ], "responses": { "200": { "description": "OpenAPI 文档", "content": { "application/json": { "schema": { "type": "object" } } } } } } }, "/auth/state": { "get": { "summary": "是否已初始化、当前是否已登录", "tags": [ "登录" ], "security": [ {} ], "responses": { "200": { "description": "状态", "content": { "application/json": { "schema": { "type": "object", "properties": { "setUp": { "type": "boolean" }, "authenticated": { "type": "boolean" } } } } } } } } }, "/auth/setup": { "post": { "summary": "首次设置登录密码(需服务器数据目录中的启动码)", "tags": [ "登录" ], "security": [ {} ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "code": { "type": "string" }, "password": { "type": "string" } }, "required": [ "code", "password" ] } } } }, "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ok" } } } }, "400": { "description": "启动码错误或密码过短", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "已初始化", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/auth/login": { "post": { "summary": "登录,成功后设置会话 Cookie", "tags": [ "登录" ], "security": [ {} ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "password": { "type": "string" } }, "required": [ "password" ] } } } }, "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ok" } } } }, "401": { "description": "密码错误", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "description": "失败次数过多", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/auth/logout": { "post": { "summary": "退出登录", "tags": [ "登录" ], "security": [ {} ], "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ok" } } } } } } }, "/auth/password": { "post": { "summary": "修改登录密码;当前会话保留,其他设备需重新登录", "tags": [ "登录" ], "security": [ { "cookie": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "current": { "type": "string" }, "next": { "type": "string" } }, "required": [ "current", "next" ] } } } }, "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ok" } } } }, "400": { "description": "新密码过短", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "当前密码错误", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "description": "失败次数过多", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/settings": { "get": { "summary": "应用设置(全部设备共用的一份 JSON 对象)", "tags": [ "设置" ], "responses": { "200": { "description": "设置", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } } } }, "patch": { "summary": "合并修改设置", "tags": [ "设置" ], "security": [ { "cookie": [] } ], "description": "按顶层键合并:请求中的键覆盖原值,值为 null 的键被删除。成功后经 WebSocket 推送 settings 事件。", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } }, "responses": { "200": { "description": "合并后的设置", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } }, "400": { "description": "不是 JSON 对象", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "413": { "description": "超过 1 MB", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/tree": { "get": { "summary": "全部笔记与文件夹(不含 .assets 图片目录与隐藏文件)", "tags": [ "笔记" ], "responses": { "200": { "description": "目录树", "content": { "application/json": { "schema": { "type": "object", "properties": { "entries": { "type": "array", "items": { "$ref": "#/components/schemas/Entry" } } } } } } } } } }, "/files/{path}": { "parameters": [ { "name": "path", "in": "path", "required": true, "description": "仓库内的相对路径,如 `目录/笔记.md`(各段需 URL 编码)", "schema": { "type": "string" } } ], "get": { "summary": "读取笔记", "tags": [ "笔记" ], "responses": { "200": { "description": "Markdown 原文", "headers": { "ETag": { "description": "内容版本(rev),带双引号", "schema": { "type": "string" } } }, "content": { "text/markdown": { "schema": { "type": "string" } } } }, "404": { "description": "不存在", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } }, "put": { "summary": "写入笔记(不存在的上级目录会自动创建)", "tags": [ "笔记" ], "description": "请求体整体作为笔记内容。`If-Match: \"rev\"` 要求当前版本一致,否则返回 412 与最新 rev;`If-None-Match: *` 要求笔记尚不存在;都不带则直接覆盖。", "parameters": [ { "name": "If-Match", "in": "header", "schema": { "type": "string" } }, { "name": "If-None-Match", "in": "header", "schema": { "type": "string", "enum": [ "*" ] } } ], "requestBody": { "required": true, "content": { "text/markdown": { "schema": { "type": "string" } } } }, "responses": { "200": { "description": "已写入", "content": { "application/json": { "schema": { "type": "object", "properties": { "rev": { "type": "string" } } } } }, "headers": { "ETag": { "description": "内容版本(rev),带双引号", "schema": { "type": "string" } } } }, "400": { "description": "路径无效", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "If-None-Match 时已存在", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "412": { "description": "版本冲突", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string" }, "rev": { "type": "string", "description": "服务器上的当前版本;为空表示已不存在" } } } } } }, "413": { "description": "超过 20 MB", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } }, "delete": { "summary": "把笔记或文件夹移入回收站(连同 .assets)", "tags": [ "笔记" ], "responses": { "200": { "description": "回收站条目", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TrashItem" } } } }, "404": { "description": "不存在", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/move": { "post": { "summary": "移动或重命名笔记/文件夹", "tags": [ "笔记" ], "description": "笔记的 .assets 目录随之移动,改名时同步改写正文中指向自身图片目录的链接;分享链接随之更新。不改写其他笔记指向它的链接。", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "from": { "type": "string" }, "to": { "type": "string" } }, "required": [ "from", "to" ] } } } }, "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ok" } } } }, "404": { "description": "源不存在", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "目标已存在", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/folders": { "post": { "summary": "新建文件夹", "tags": [ "笔记" ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "path": { "type": "string" } }, "required": [ "path" ] } } } }, "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ok" } } } }, "409": { "description": "已存在", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/assets": { "post": { "summary": "上传图片到笔记的 .assets 目录", "tags": [ "图片" ], "parameters": [ { "name": "note", "in": "query", "required": true, "description": "所属笔记路径", "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "file": { "type": "string", "format": "binary" } }, "required": [ "file" ] } } } }, "responses": { "200": { "description": "相对笔记所在目录的链接,可直接写入 Markdown", "content": { "application/json": { "schema": { "type": "object", "properties": { "link": { "type": "string" } } } } } }, "413": { "description": "超过 50 MB", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/raw/{path}": { "parameters": [ { "name": "path", "in": "path", "required": true, "description": "仓库内的相对路径,如 `目录/笔记.md`(各段需 URL 编码)", "schema": { "type": "string" } } ], "get": { "summary": "读取仓库中的原始文件(图片等)", "tags": [ "图片" ], "responses": { "200": { "description": "文件内容", "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } } }, "404": { "description": "不存在", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/search": { "get": { "summary": "全文搜索", "tags": [ "笔记" ], "description": "空格分隔多个词,全部命中才返回;最多 50 条。", "parameters": [ { "name": "q", "in": "query", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "结果", "content": { "application/json": { "schema": { "type": "object", "properties": { "results": { "type": "array", "items": { "$ref": "#/components/schemas/SearchResult" } } } } } } } } } }, "/trash": { "get": { "summary": "回收站列表", "tags": [ "回收站" ], "responses": { "200": { "description": "条目", "content": { "application/json": { "schema": { "type": "object", "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/TrashItem" } } } } } } } } } }, "/trash/{id}/restore": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "post": { "summary": "从回收站恢复", "tags": [ "回收站" ], "responses": { "200": { "description": "恢复的条目", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TrashItem" } } } }, "404": { "description": "不存在", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "409": { "description": "原位置已有同名文件", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/trash/{id}": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "delete": { "summary": "彻底删除回收站条目", "tags": [ "回收站" ], "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ok" } } } }, "404": { "description": "不存在", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/events": { "get": { "summary": "WebSocket:仓库变更推送", "tags": [ "笔记" ], "description": "升级为 WebSocket 后,服务器推送 JSON 数组,元素为 Event。客户端不发送消息。", "responses": { "101": { "description": "切换为 WebSocket" } } } }, "/shares": { "get": { "summary": "分享列表", "tags": [ "分享" ], "security": [ { "cookie": [] } ], "parameters": [ { "name": "path", "in": "query", "description": "只列出该笔记的分享", "schema": { "type": "string" } } ], "responses": { "200": { "description": "分享", "content": { "application/json": { "schema": { "type": "object", "properties": { "shares": { "type": "array", "items": { "$ref": "#/components/schemas/Share" } } } } } } } } }, "post": { "summary": "创建只读分享", "tags": [ "分享" ], "security": [ { "cookie": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "path": { "type": "string" }, "password": { "type": "string", "description": "访问密码,留空为不设密码" }, "expiresIn": { "type": "integer", "description": "有效时长(秒),0 为永久" } }, "required": [ "path" ] } } } }, "responses": { "200": { "description": "新分享,访问地址为 /s/{token}", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Share" } } } }, "400": { "description": "不是笔记", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "笔记不存在", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/shares/{token}": { "parameters": [ { "name": "token", "in": "path", "required": true, "schema": { "type": "string" } } ], "delete": { "summary": "撤销分享", "tags": [ "分享" ], "security": [ { "cookie": [] } ], "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ok" } } } }, "404": { "description": "不存在", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/tokens": { "get": { "summary": "API 令牌列表", "tags": [ "API 令牌" ], "security": [ { "cookie": [] } ], "responses": { "200": { "description": "令牌", "content": { "application/json": { "schema": { "type": "object", "properties": { "tokens": { "type": "array", "items": { "$ref": "#/components/schemas/APIToken" } } } } } } } } }, "post": { "summary": "创建 API 令牌", "tags": [ "API 令牌" ], "security": [ { "cookie": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" }, "scope": { "type": "string", "enum": [ "read", "write" ] } }, "required": [ "name", "scope" ] } } } }, "responses": { "200": { "description": "token 明文只返回这一次", "content": { "application/json": { "schema": { "type": "object", "properties": { "token": { "type": "string" }, "info": { "$ref": "#/components/schemas/APIToken" } } } } } }, "400": { "description": "名称或范围无效", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/tokens/{id}": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "delete": { "summary": "吊销 API 令牌", "tags": [ "API 令牌" ], "security": [ { "cookie": [] } ], "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ok" } } } }, "404": { "description": "不存在", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/public/shares/{token}": { "parameters": [ { "name": "token", "in": "path", "required": true, "schema": { "type": "string" } } ], "get": { "summary": "访客读取分享的笔记", "tags": [ "分享(公开)" ], "security": [ {} ], "responses": { "200": { "description": "笔记", "content": { "application/json": { "schema": { "type": "object", "properties": { "title": { "type": "string" }, "content": { "type": "string" }, "updatedAt": { "type": "integer" } } } } } }, "401": { "description": "需要访问密码", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string" }, "passwordRequired": { "type": "boolean" } } } } } }, "404": { "description": "分享不存在、已过期或笔记已删除", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/public/shares/{token}/unlock": { "parameters": [ { "name": "token", "in": "path", "required": true, "schema": { "type": "string" } } ], "post": { "summary": "输入分享密码,成功后设置访客 Cookie", "tags": [ "分享(公开)" ], "security": [ {} ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "password": { "type": "string" } }, "required": [ "password" ] } } } }, "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ok" } } } }, "401": { "description": "密码错误", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "分享不存在或已失效", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "429": { "description": "失败次数过多", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/public/shares/{token}/assets/{path}": { "parameters": [ { "name": "token", "in": "path", "required": true, "schema": { "type": "string" } }, { "name": "path", "in": "path", "required": true, "description": "相对笔记所在目录的图片路径,只能位于该笔记的 .assets 目录", "schema": { "type": "string" } } ], "get": { "summary": "访客读取分享笔记中的图片", "tags": [ "分享(公开)" ], "security": [ {} ], "responses": { "200": { "description": "文件内容", "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } } }, "404": { "description": "不存在或不在允许范围内", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "securitySchemes": { "bearer": { "type": "http", "scheme": "bearer", "description": "API 令牌(rm_ 开头)" }, "cookie": { "type": "apiKey", "in": "cookie", "name": "rawmark_session", "description": "浏览器登录会话" } }, "schemas": { "Error": { "type": "object", "properties": { "error": { "type": "string" } }, "required": [ "error" ] }, "Ok": { "type": "object", "properties": { "ok": { "type": "boolean" } } }, "Entry": { "type": "object", "properties": { "path": { "type": "string" }, "dir": { "type": "boolean" }, "size": { "type": "integer" }, "mtime": { "type": "integer", "description": "修改时间(Unix 毫秒)" } } }, "TrashItem": { "type": "object", "properties": { "id": { "type": "string" }, "path": { "type": "string", "description": "原路径" }, "dir": { "type": "boolean" }, "deletedAt": { "type": "integer", "description": "Unix 毫秒" } } }, "SearchResult": { "type": "object", "properties": { "path": { "type": "string" }, "line": { "type": "integer", "description": "第一处匹配所在行(从 1 开始);只有路径匹配时为 0" }, "snippet": { "type": "string" } } }, "Event": { "type": "object", "description": "tree:目录结构变化;changed:内容变化(rev 为新版本);deleted:删除;moved:移动(from → path);settings:设置变化", "properties": { "type": { "type": "string", "enum": [ "tree", "changed", "deleted", "moved", "settings" ] }, "path": { "type": "string" }, "from": { "type": "string" }, "rev": { "type": "string" } } }, "Share": { "type": "object", "properties": { "token": { "type": "string" }, "path": { "type": "string" }, "createdAt": { "type": "integer" }, "expiresAt": { "type": "integer", "description": "Unix 毫秒,0 为永久" }, "hasPassword": { "type": "boolean" }, "exists": { "type": "boolean", "description": "笔记是否仍存在" } } }, "APIToken": { "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "scope": { "type": "string", "enum": [ "read", "write" ] }, "createdAt": { "type": "integer" }, "lastUsedAt": { "type": "integer", "description": "Unix 毫秒,0 为从未使用" } } } } } }