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 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,commit5b3f778),逐条 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(自托管首启创建的超级管理员,本文演示用)
二、前置条件
- 一套已部署并完成初始化的 Ever Gauzy(部署步骤见 《Ever Gauzy 111.48.2 Docker Compose 部署指南》),并能访问 API 端口。
- 确认 API 可达(版本端点无需认证):
curl -s http://localhost:8144/api/version
实测输出:
{"name":"api","version":"v111.48.2","commit":"5b3f778e1d12f21992b39b42218fd2fd354c21d6"}
- 准备好一个具备超级管理员角色的账号(或任意普通账号,权限差异见 §5.5 错误码)。
三、API 总览
Ever Gauzy 对外暴露两类 HTTP 端点:
| 端点 | 协议 | 认证 | 适用 |
|---|---|---|---|
/api/* | REST(NestJS,JSON) | Bearer JWT + Tenant-Id 头 | 主要集成面:任务、费用、发票、员工、组织等全部业务域 |
/graphql | GraphQL(Apollo Server 4) | 同上 | 辅助面:schema 暴露但本版本业务 resolver 覆盖有限(见 §5.6 实测) |
约定要点:
- 认证方式:
POST /api/auth/login换取 JWT,后续请求带Authorization: Bearer <token>。 - 租户头:多租户隔离依赖请求头
Tenant-Id(值为登录响应中用户的tenantId)。⚠️ 本版本服务端按原始头大小写精确匹配tenant-id或Tenant-Id两种写法,Tenant-ID(大写 ID)不匹配会被判 403——HTTP 头本应大小写不敏感,这是该版本实现的特殊行为,跨语言客户端(如 Go/Java 的 http2 大写规范化)要特别注意。 - 限流:全局限流默认开启(
THROTTLE_TTL=60000毫秒窗口、THROTTLE_LIMIT=60000次,配置来源见配置篇),超限返回 429。 - 媒体类型:请求与响应均为
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 错误码与失败响应实测
| 场景 | 状态码 | 实测响应体 |
|---|---|---|
| 不带 Token | 401 | {"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 适合内省受限场景下的探索与官方后续补齐。
六、分页/过滤/排序通用约定
- 分页信封:列表端点统一返回
{"items":[...],"total":n}(实测/api/expense、/api/invoices)。 - 过滤参数两种形态:
- 独立查询参数(实测可用):
?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>"},...}。
- 独立查询参数(实测可用):
- 任务号:
POST /api/tasks返回的number为组织内自增编号,适合做外部系统映射键。 - 软删除:
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 语义处理即可。