关于 n8n:public-api
面向 n8n Public API v1 开发者的技能,说明控制器、DTO、授权、分页、OpenAPI 和测试规范,帮助在扩展接口时维护公开契约。
主要功能
- 01控制器与服务边界
- 02严格的公开 DTO 规范
- 03API 密钥与 RBAC 授权
- 04基于游标的列表分页
- 05OpenAPI 生成与接入
- 06集成测试指导
适用场景
- 01新增 Public API 端点
- 02迁移旧版端点
- 03实现游标分页列表
- 04测试授权与输入验证
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ n8n-io/ n8n --skill n8n:public-api安装后在 Claude Code 中输入/n8n:public-api确认已加载。
附带文件reference.md
详细介绍
公共 API v1
公共 API v1 位于 packages/cli/src/public-api/v1/,挂载在 /api/v1,
使用 API 密钥进行身份验证,并通过 PublicApiControllerRegistry
(packages/cli/src/public-api/public-api-controller.registry.ts)统一格式化公共错误。
规则分为两个层级:不可违反的规则(绝不能破坏)和团队默认规范(除非现有公共契约另有要求,否则应遵循)。如果本规范与代码在某个细节上不一致,以代码为准——请打开下面的文件。因此应检查代码,而不是借此忽略团队默认规范。
不可妥协的规则
- 新端点必须是在
v1/controllers/下的@PublicApiController类,每个功能对应一个*.public.controller.ts文件。 - 公共 API 和内部 REST 是彼此独立的 HTTP 接口。公共控制器绝不调用内部控制器或端点;两者复用同一个服务。
- 控制器和处理器必须委托给服务——绝不导入存储库或使用
Container.get(…Repository)(no-repository-in-public-api-handler)。 - 输入和输出必须使用
@n8n/api-types中的 DTO;每个 JSON 路由都必须声明@ApiResponse(Dto)。 - 通过
v1/controllers/index.ts中的副作用导入注册每个控制器(否则public-api-controllers.test.ts会失败)。 - 迁移旧端点时不得更改其公共契约。
这些是 n8n-local-rules ESLint 规则(参见 packages/cli/eslint.config.mjs),不得通过内联方式禁用(no-public-api-guardrail-disable)。其中的 off 允许列表仅涵盖已有的旧文件——只能缩减,不要添加条目。
团队默认规范
- 编写能够自我说明的代码。模式、装饰器和测试本身就应清楚地表达规则,无需注释。
- 列表端点:使用基于游标的分页(内部 API 同时使用基于游标和基于页码的分页——不要照搬内部端点的模型)。
- 分页参数始终为
offset和limit——应用于你新增的服务方法、处理器调用和存储库方法。绝不使用skip/take(TypeORM 的命名)。仅在存储库的 TypeORMfind调用中将其转换为skip/take。公共查询字符串仍使用cursor+limit;offset是传递给服务的已解码游标字段,绝不是面向客户端的参数。 - 更新:使用完整对象
PUT,而非PATCH。成功的GET响应体应可作为同一资源的PUT请求体(往返),服务器管理字段或不可变字段除外。 - 输入 DTO 必须严格;输出 DTO 是公共字段的允许列表。
- 响应或错误详情中绝不返回真实密钥或令牌——使用资源的哨兵值/占位符进行屏蔽(或省略)。在
PUT中回传该哨兵值表示保留;任何其他值都会替换。详情参见: 更新和只写密钥。 - “测试连接/配置”端点必须验证请求体(先测试,再保存)。
架构
公共和内部路由是同一个与 HTTP 无关的服务之上的同级路由;两者互不调用。
GET /rest/tags → TagsController ┐ JWT auth, internal shape
├─→ TagService
GET /api/v1/tags → TagsPublicController ┘ API-key auth, public DTO
复用服务行为。只有在公共契约和内部契约有意保持一致时才复用 DTO;否则应创建专用的公共 DTO,不要依赖面向 UI 的内部结构。
编辑前
请先查看以下内容——它们才是事实来源,而不是本技能:
v1/controllers/——参考tags.public.controller.ts(列表 + 游标)或workflows.public.controller.ts(@Param+@ProjectScope)复制结构,并参考index.ts中的导出汇总。packages/@n8n/decorators/src/controller/中的装饰器:public-api-controller.ts、api-key-scope.ts、api-response.ts、api-error-response.ts、api-summary.ts、api-description.ts、api-tags.ts、route.ts、scoped.ts、args.ts、licensed.ts。- OpenAPI 生成器(读取上述装饰器,无需为控制器路由手写 YAML):
v1/openapi-gen/generate.ts、v1/openapi-gen/decorator-routes.ts。 - 分页辅助工具:
v1/shared/services/pagination.service.ts(decodeCursor、encodeNextCursor)。 - DTO:
packages/@n8n/api-types/src/dto/。 - 门控测试:
v1/__tests__/public-api-controllers.test.ts、v1/__tests__/scope-parity.test.ts、v1/openapi-gen/__tests__/generated-spec-drift.test.ts。 - 此资源的内部控制器及其相邻的功能测试。
声明控制器
控制器是一个标记了 @PublicApiController('/base') 的类,它通过构造函数注入
共享服务,并将请求委托给该服务。请从 v1/controllers/ 中操作类型和身份验证
模型相同的现有控制器复制结构;仅复用适用的部分。所有装饰器均来自 @n8n/decorators:
| 装饰器 | 用途 |
|---|---|
@PublicApiController('/base') | 类标记;在 /api/v1/base 挂载路由。 |
@Get/@Post/@Put/@Patch/@Delete('/path') | 路由方法。 |
@ApiKeyScope('res:action') | 检查 API 密钥授权。 |
@ProjectScope/@GlobalScope('res:action') | 检查用户 RBAC 权限。 |
@ApiResponse(status) / @ApiResponse(status, Dto) | 成功状态码 +(可选)输出 DTO;注册器会对返回值调用 .parse() 并剥离多余字段。每条路由只能有一个——第二个 @ApiResponse 会抛出异常。204 不能携带 DTO 或二进制主体,否则会抛出异常。对于二进制响应主体(以 { body, headers? } 形式返回),请使用 @ApiResponse(status, { mediaType, description?, headers? })——参见二进制响应主体。 |
@ApiErrorResponse(status) | 声明额外记录的非 2xx 状态码(例如 404、409)。需要多个时可叠加使用。400/401/403 会自动添加(分别对应存在请求体或查询参数、始终添加、存在 @ApiKeyScope)——无需自行声明。 |
@ApiSummary(text) / @ApiDescription(text) / @ApiTags([...]) | OpenAPI 摘要/描述/标签。无论传入顺序如何,@ApiTags 都会按字母顺序排序。它们都是可选的,但每条实际路由都应添加。 |
@Query / @Body / @Param('name') | 通过 Z.class DTO/路径参数绑定并验证。默认情况下,@Body 使用 JSON;@Body({ mediaType: 'multipart/form-data', uploadLimits }) 则接收 multipart/form-data 请求体——参见请求体媒体类型。 |
@Licensed('feat') | 根据单个 BooleanLicenseFeature 对路由进行门控;PublicApiControllerRegistry 会运行自己的许可证中间件(在身份验证/@ApiKeyScope/@ProjectScope 或 @GlobalScope 之后、处理程序之前),并对未经许可的请求返回 403。只接受一个功能——如果门控条件是任一/全部组合(例如 LicenseState.isProvisioningLicensed(),即 feat:saml 或 feat:oidc),@Licensed 无法表达;应像当前内部的 provisioning.controller.ee.ts/role-mapping-rule.controller.ee.ts 一样,在处理程序中手动检查(失败时抛出 ForbiddenError)。 |
授权(容易出错)
@ApiKeyScope(API 密钥获授的权限)与@ProjectScope/@GlobalScope(用户可执行的操作)彼此独立。当模型两者都需要时,请同时使用。- 将每个路径参数命名为
{resource}Id(例如workflowId、credentialId、projectId等)——绝不要使用通用的:id/{id}。这是 Public API 的 命名约定:它能让 API 自我说明,并为类型化 SDK 代码生成提供真实的参数名, 而不是id。@ProjectScope也会原样读取req.params,不会将id重新映射—— 它按确切的键名解析授权(workflowId、credentialId、projectId、dataTableId、 等),因此在@ProjectScope路由上使用通用的id往往会直接失败;@GlobalScope或未限定作用域的路由不会以相同方式失败,但仍应遵循此约定。 @ApiKeyScope接受字符串、{ anyOf: [...] }或{ allOf: [...] }——绝不能 使用裸数组。该作用域必须存在于权限注册表(@n8n/permissions中的API_KEY_RESOURCES)里;scope-parity.test.ts会在作用域无对应项时失败。
DTO
- 明确构建公共响应结构;不要返回 ORM 实体并依赖
@ApiResponse剥离字段来隐藏内容。 - 将输出 DTO 视为允许列表。重新检查嵌套关系、所有权字段、令牌和加密值。
- 输出 DTO 限制的是返回哪些字段,而不是这些字段可以包含哪些值。注册表会根据 DTO
解析处理程序的返回值,因此不符合架构的值会导致
500。架构应足够宽松,以容纳 现有行中可能包含的任何内容。 - 根据路由已加载的关系构建响应,而不是根据实体类型。TypeORM 关系需要显式选择, 因此,同一实体上的两个路由可以返回不同的结构。
- 输入 DTO 应设置为严格模式,避免静默接受未知字段或不完整字段:
Z.class(shape, { strict: true })。 - 密钥:绝不返回真实密钥;应使用资源的哨兵值/占位符(或省略)。参见更新和只写密钥。
列表端点(游标分页)
请复制 tags.public.controller.ts 中的游标流程。输入 DTO 接受
limit: publicApiPaginationSchema.limit 和 cursor: z.string().optional() —
从架构中单独取出 limit,绝不要展开整个 publicApiPaginationSchema
(它还导出了 offset,而 offset 绝不能作为 Public API 查询参数)。使用共享分页服务中的
decodeCursor / encodeNextCursor;游标是不透明的;返回 { data, nextCursor }(绝不要返回裸数组),
最后一页的 nextCursor: null;无效游标会返回 400。按原样保留现有端点的游标语义——但
offset 参数属于应移除的缺陷,不是应保留的契约。详情请参阅:
列表端点和游标分页。
接线检查清单
v1/controllers/<feature>.public.controller.ts+ 在v1/controllers/index.ts中添加副作用导入。- 在
@n8n/api-types中添加公共 DTO + 从 barrel(src/dto/)导出。 - 权限注册表中存在
@ApiKeyScope值。 - 不要手动编写 OpenAPI 路径或控制器路由的
x-required-scope——生成器(v1/openapi-gen/generate.ts)会根据你的装饰器(@ApiSummary/@ApiDescription/@ApiTags/@ApiKeyScope/@ApiResponse/@ApiErrorResponse)生成它们。运行完整的pnpm build,并提交重新生成的handlers/<feature>/spec/paths/*.generated.yml片段和openapi.decorator-routes.generated.yml—— 如果它们过期,generated-spec-drift.test.ts会导致 CI 失败。修改控制器后,仅运行pnpm run build:data并不足够:它会基于已编译的dist/运行生成器,因此除非先运行了tsc,否则新建或修改的控制器不会显示出来。 - 如果路由在
@ApiTags中使用了新标签,请在v1/openapi.yml的tags:数组中声明该标签。为其设置x-displayName和一个指向父标签的x-parent。子标签按字母顺序排列。 GitBook 会根据显示名称生成页面 URL,因此修改现有的x-displayName前,请确认文档 URL 不受影响。 如果未声明标签或标签顺序错误,openapi-tags.test.ts会导致测试失败。 - 将路由添加到
packages/nodes-base/nodes/N8n/n8n-api-coverage.json。 - 测试。
测试
始终覆盖:正常路径、输入验证失败、缺少 API 密钥权限范围、RBAC 拒绝。
优先在 packages/cli/test/integration/public-api/ 中覆盖业务路径(真实 HTTP + DB);模拟服务的单元
测试不能替代它。添加适用的用例(游标分页、
未找到/冲突、不包含敏感字段、凭证保留/替换、迁移契约)——参见测试矩阵。参照最接近的现有测试。