Ever Gauzy 111.48.2 API 使用详解

Ever Gauzy v111.48.2 API 编程对接指南:REST /api 与 GraphQL /graphql 双端点形态、Bearer Token + Tenant-Id 认证约定(含头大小写敏感实测陷阱)、登录/用户/任务 CRUD/费用/发票各域逐条 curl 实测响应、错误码表与分页过滤约定,全部请求响应实测于自托管 v111.48.2 环境。

Ever GauzyEver GauzyREST API自托管2026/10/23

Ever Gauzy 111.48.2 API 使用详解

目标:面向需要以编程方式对接 Ever Gauzy v111.48.2 的开发者,讲清平台 HTTP API 的双端点形态(REST /api 与 GraphQL /graphql)、Bearer Token + 租户头的认证约定、各业务域端点的请求/响应实测、通用错误码与分页过滤约定。全部请求与响应均在 Ubuntu 22.04 自托管环境实测(API 端口 8144,版本 v111.48.2,commit 5b3f778),逐条 curl 可复现。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • Ever Gauzy:v111.48.2(GET /api/version 实测返回,见 §5.1)
  • API 端点:http://localhost:8144/api(镜像部署宿主映射端口;容器内 3000)
  • GraphQL 端点:http://localhost:8144/graphql
  • 认证:JWT Bearer Token + Tenant-Id 请求头(多租户架构)
  • 客户端工具:curl 7.81.0(系统自带)
  • 种子账号:admin@ever.co / q1w2e3r4(自托管首启创建的超级管理员,本文演示用)

二、前置条件

  1. 一套已部署并完成初始化的 Ever Gauzy(部署步骤见 《Ever Gauzy 111.48.2 Docker Compose 部署指南》),并能访问 API 端口。
  2. 确认 API 可达(版本端点无需认证):
curl -s http://localhost:8144/api/version

实测输出:

{"name":"api","version":"v111.48.2","commit":"5b3f778e1d12f21992b39b42218fd2fd354c21d6"}
  1. 准备好一个具备超级管理员角色的账号(或任意普通账号,权限差异见 §5.5 错误码)。

三、API 总览

Ever Gauzy 对外暴露两类 HTTP 端点:

端点协议认证适用
/api/*REST(NestJS,JSON)Bearer JWT + Tenant-Id 头主要集成面:任务、费用、发票、员工、组织等全部业务域
/graphqlGraphQL(Apollo Server 4)同上辅助面:schema 暴露但本版本业务 resolver 覆盖有限(见 §5.6 实测)

约定要点:

  1. 认证方式:POST /api/auth/login 换取 JWT,后续请求带 Authorization: Bearer <token>。
  2. 租户头:多租户隔离依赖请求头 Tenant-Id(值为登录响应中用户的 tenantId)。⚠️ 本版本服务端按原始头大小写精确匹配 tenant-id 或 Tenant-Id 两种写法,Tenant-ID(大写 ID)不匹配会被判 403——HTTP 头本应大小写不敏感,这是该版本实现的特殊行为,跨语言客户端(如 Go/Java 的 http2 大写规范化)要特别注意。
  3. 限流:全局限流默认开启(THROTTLE_TTL=60000 毫秒窗口、THROTTLE_LIMIT=60000 次,配置来源见配置篇),超限返回 429。
  4. 媒体类型:请求与响应均为 application/json;列表类响应统一 { "items": [...], "total": n } 分页信封。

💡 说明:API 头部的规范集合可从源码 packages/core/src/lib/graphql/graphql-helper.ts 的 CORS 配置确认:Authorization、Language、Tenant-Id、Organization-Id、X-Requested-With 等——Tenant-Id 的大小写写法以此为准。

四、认证与首次调用

4.1 登录取 Token

curl -s -X POST http://localhost:8144/api/auth/login -H 'Content-Type: application/json' \
  -d '{"email":"admin@ever.co","password":"q1w2e3r4"}'

实测输出(节选,全文含完整 JWT):

{"user":{"deletedAt":null,"createdAt":"2026-10-23T01:18:09.420Z","updatedAt":"2026-10-23T01:21:54.389Z",...,"id":"6295738d-c86e-4b33-a47c-7f33098277c1","isActive":true,...,"tenantId":"5fc5f20d-9363-4860-b516-883d1c1eb408",...,"firstName":"Super","lastName":"Admin","email":"admin@ever.c...

响应为 {"user": {...}, "token": "<JWT>"} 两个顶层字段。实测 JWT 长度 6671 字符——载荷里带全量权限声明,解析后关键声明如下(解码 Payload 节选):

{
 "id": "6295738d-c86e-4b33-a47c-7f33098277c1",
 "tenantId": "5fc5f20d-9363-4860-b516-883d1c1eb408",
 "organizationId": "8426a8c4-92f6-4fed-9da4-769b33288367",
 "employeeId": "f483832d-6a8e-4eca-9e8b-917c2be3e943",
 "role": "SUPER_ADMIN",
 "permissions": ["ADMIN_DASHBOARD_VIEW","TEAM_DASHBOARD","ORG_INCOMES_VIEW","ORG_EXPENSES_VIEW",...
}

把 tenantId、organizationId 记下来,后续所有请求都要用。

4.2 首个认证请求

用 Token 调当前用户信息(此端点实测不强制租户头):

TOKEN="<4.1 的 token>"
curl -s http://localhost:8144/api/user/me -H "Authorization: Bearer $TOKEN"

实测返回 200,响应体(键序列节选):

{"deletedAt":null,"createdAt":"2026-10-23T01:18:09.420Z","updatedAt":"...","createdByUserId":null,"updatedByUserId":null,"id":"6295738d-c86e-4b33-a47c-7f33098277c1","isActive":true,"isArchived":false,...

五、各 API 域详解

5.1 平台元信息域

版本号(免认证,升级验证必用):

curl -s http://localhost:8144/api/version

实测输出:

{"name":"api","version":"v111.48.2","commit":"5b3f778e1d12f21992b39b42218fd2fd354c21d6"}

健康检查:

curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8144/api/health

实测输出:

200

详细健康项(数据库/存储/缓存)在 API 日志中输出:{"status":"ok","info":{"database":{"status":"up"},"storage":{"status":"up"},"cache":{"status":"up","cacheType":"memory"}},...}。

5.2 任务域(Task)

任务域是最典型的业务域,覆盖计数、创建、单条查询、删除的完整闭环。

计数(organizationId 作为独立查询参数传入):

curl -s "http://localhost:8144/api/tasks/count?organizationId=8426a8c4-92f6-4fed-9da4-769b33288367" \
  -H "Authorization: Bearer $TOKEN" -H "Tenant-Id: 5fc5f20d-9363-4860-b516-883d1c1eb408"

实测输出:

1

创建(返回 202 Accepted,正文为已受理的实体):

curl -s -X POST http://localhost:8144/api/tasks \
  -H "Authorization: Bearer $TOKEN" -H "Tenant-Id: 5fc5f20d-9363-4860-b516-883d1c1eb408" \
  -H 'Content-Type: application/json' \
  -d '{"title":"API 使用详解示例任务","description":"D5 文档实测","organizationId":"8426a8c4-92f6-4fed-9da4-769b33288367","tenantId":"5fc5f20d-9363-4860-b516-883d1c1eb408","status":"Open","priority":"Low"}'

实测输出(状态码 202,响应节选):

{"tenantId":"5fc5f20d-9363-4860-b516-883d1c1eb408","organizationId":"8426a8c4-92f6-4fed-9da4-769b33288367","title":"API 使用详解示例任务","number":2,"prefix":null,"description":"D5 文档实测","status":"Open","priority":"Low","tenant":{"id":"5fc5f20d-9363-4860-b516-883d1c1eb408"},"members":[],"createdByUserId":"6295738d-c86e-4b33-a47c-7f33098277c1",...

number 字段是组织内自增任务编号(实测第二次创建得到 2),可直接作为对外展示的任务号。

单条查询:

curl -s "http://localhost:8144/api/tasks/08b03f85-16b7-4e8d-9558-ee3bf3559a56" \
  -H "Authorization: Bearer $TOKEN" -H "Tenant-Id: 5fc5f20d-9363-4860-b516-883d1c1eb408"

实测输出(节选):

{"deletedAt":null,"createdAt":"2026-10-23T02:35:45.495Z","updatedAt":"...","createdByUserId":"6295738d-c86e-4b33-a47c-7f33098277c1",...,"id":"08b03f85-16b7-4e8d-9558-ee3bf3559a56","isActi...

删除(软删除,返回 200):

curl -s -o /dev/null -w '%{http_code}\n' -X DELETE \
  "http://localhost:8144/api/tasks/08b03f85-16b7-4e8d-9558-ee3bf3559a56" \
  -H "Authorization: Bearer $TOKEN" -H "Tenant-Id: 5fc5f20d-9363-4860-b516-883d1c1eb408"

实测输出:

200

5.3 费用域(Expense)

列表端点,organizationId 独立查询参数(实测空库返回空分页信封):

curl -s "http://localhost:8144/api/expense?organizationId=8426a8c4-92f6-4fed-9da4-769b33288367" \
  -H "Authorization: Bearer $TOKEN" -H "Tenant-Id: 5fc5f20d-9363-4860-b516-883d1c1eb408"

实测输出:

{"items":[],"total":0}

5.4 发票域(Invoice)

与费用域同形态:

curl -s "http://localhost:8144/api/invoices?organizationId=8426a8c4-92f6-4fed-9da4-769b33288367" \
  -H "Authorization: Bearer $TOKEN" -H "Tenant-Id: 5fc5f20d-9363-4860-b516-883d1c1eb408"

实测输出:

{"items":[],"total":0}

⚠️ 注意:资源路径是复数 /api/invoices;单数 /api/invoice 实测 404({"message":"Cannot GET /api/invoice?...","error":"Not Found","statusCode":404})。各域路径单复数以实测为准,对接前先探测。

5.5 错误码与失败响应实测

场景状态码实测响应体
不带 Token401{"message":"Unauthorized","statusCode":401}
带租户头但大小写不匹配(Tenant-ID)403{"message":"Forbidden resource","error":"Forbidden","statusCode":403}
路径不存在404{"message":"Cannot GET /api/me","error":"Not Found","statusCode":404}
参数校验失败400{"message":["organizationId must be a UUID","organizationId should not be empty"],"error":"Bad Request","statusCode":400}
限流超限429默认 60000 次/分钟阈值内未触发(配置见限流参数)

排查 403 的固定套路:先核对头名是否为 Tenant-Id(精确大小写),再核对值是否等于 JWT 里 tenantId 声明。

5.6 GraphQL 端点实测

GraphQL 挂载在 /graphql(Apollo Server 4,生产关闭 playground 与内省)。最基本的请求可用:

curl -s -X POST http://localhost:8144/graphql -H 'Content-Type: application/json' \
  -d '{"query":"{ __typename }"}'

实测输出:

{"data":{"__typename":"Query"}}

内省被禁用(生产安全默认,官方错误信息原文):

curl -s -X POST http://localhost:8144/graphql -H 'Content-Type: application/json' \
  -d '{"query":"{ __schema { queryType { fields { name } } } }"}'

实测输出:

{"errors":[{"message":"GraphQL introspection is not allowed by Apollo Server, but the query contained __schema or __type. To enable introspection, pass introspection: true to ApolloServer in productio...

带认证调 schema 中定义的查询(schema 见源码 packages/core/src/lib/role/schema/role.api.gql):

curl -s -X POST http://localhost:8144/graphql -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $TOKEN" -H "Tenant-Id: 5fc5f20d-9363-4860-b516-883d1c1eb408" \
  -d '{"query":"{ role(id: \"f02e47a5-5729-412b-8459-c1c795215f13\") { id name } }"}'

实测输出:

{"data":{"role":null}}

而根字段 roles 实测返回 resolver 级错误:{"errors":[{"message":"Cannot return null for non-nullable field Query.roles.",...,"extensions":{"code":"INTERNAL_SERVER_ERROR"}}],"data":null}。

实测结论:v111.48.2 的 GraphQL 面向 schema 定义完整、端点可达、认证链路工作,但部分业务 resolver 返回空值或内部错误,生产集成应以 REST /api 为主,GraphQL 适合内省受限场景下的探索与官方后续补齐。

六、分页/过滤/排序通用约定

  1. 分页信封:列表端点统一返回 {"items":[...],"total":n}(实测 /api/expense、/api/invoices)。
  2. 过滤参数两种形态:
    • 独立查询参数(实测可用):?organizationId=<uuid>,适用于 /api/tasks/count、/api/expense、/api/invoices 等。
    • ?data=<URL 编码 JSON> 结构化形态:源码 BaseQueryDTO 定义了 where(必填,嵌套 tenantId/organizationId)、relations、order、take(0–100)、skip 字段。⚠️ 实测该版本对 where 缺失校验严格——GET /api/tasks、/api/tasks/pagination 等基础列表端点缺 data.where 时返回 400 {"message":["where should not be empty"],...},对接时优先使用「独立参数 + /count」形态,或按源码 DTO 补全 data={"where":{"organizationId":"<uuid>","tenantId":"<uuid>"},...}。
  3. 任务号:POST /api/tasks 返回的 number 为组织内自增编号,适合做外部系统映射键。
  4. 软删除:DELETE 为软删除(实体保留 deletedAt 标记),配合 withDeleted=true 查询参数可取回含已删除记录的列表(DTO 定义支持,见源码 FindOptionsQueryDTO)。

七、常见问题

Q1:登录成功但业务接口全部 403? 九成是租户头问题:确认头名为 Tenant-Id(tenant-id 亦可),值为 JWT 载荷里的 tenantId。Tenant-ID 这种大写写法在本版本会被判 403(见 §5.5 实测)。

Q2:Token 有时效吗? 默认 24 小时(JWT_TOKEN_EXPIRATION_TIME=86400,配置见配置篇)。过期返回 401,重新登录换取即可;刷新令牌端点由登录响应中的 refresh token 配套使用。

Q3:GraphQL 里为什么查不到业务数据? 生产模式禁用内省且部分 resolver 未完整实现(实测 role 返回 null、roles 报 INTERNAL_SERVER_ERROR,见 §5.6)。先用 REST /api 完成集成,GraphQL 作为补充观察面。

Q4:列表接口一直 400 "where should not be empty"? 该端点要求结构化查询参数 ?data={"where":{...}}(where 为必填字段,需含 organizationId/tenantId)。也可换用接受独立 organizationId 参数的端点(如 /api/tasks/count、/api/expense)。

Q5:如何确认请求被限流了? 连续高频请求会收到 429;阈值由环境变量 THROTTLE_TTL/THROTTLE_LIMIT 控制(本环境 60000ms/60000 次),可在服务端配置中调整。

Q6:POST 为什么返回 202 而不是 201? Gauzy 的创建走命令总线异步受理模式,实测 POST /api/tasks 返回 202 Accepted 且响应体即实体全文(含 id/number),按 202 语义处理即可。

八、参考链接