Adds or updates n8n Public API v1 endpoints, OpenAPI specs, and handler wiring. Use when creating public API handlers, registering paths in openapi.yml, or adding OpenAPI tags. Always use @PublicApiController for new endpoints (API-37+).
Public API lives under packages/cli/src/public-api/v1/.
Always use @PublicApiController for new endpoints (API-37+). Do not add new business logic to express-openapi-validator handlers. Controllers use the same decorator style as internal @RestController, mounted at /api/v1 with API-key auth and public errors via PublicApiControllerRegistry.
Legacy (existing endpoints only): express-openapi-validator handlers under handlers/; OpenAPI path specs are YAML under each handler's spec/ directory.
When you touch a legacy endpoint, migrate it to @PublicApiController rather than extending the eov handler. Do not mix data-access styles within a new feature.
Convergence is at the service layer, not HTTP. Public and internal stay as two sibling routes over one shared service — neither calls the other:
GET /rest/tags → TagsController ┐ JWT auth, internal shape
├─→ TagService
GET /api/v1/tags → TagsPublicController ┘ API-key auth, public DTOReference: v1/controllers/tags.public.controller.ts (GET /tags).
@n8n/api-types — input query/body + output resource DTO
(distinct from internal shapes when they diverge). Use Z.class so the
registry can validate/parse. Wrap list payloads as objects (e.g. { data: [...] }),
same as TagListPublicDto / WorkflowVersionHistoryListPublicDto.v1/controllers/<feature>.public.controller.ts
@PublicApiController('/tags')
export class TagsPublicController {
constructor(private readonly tagService: TagService) {}
@Get('/')
@ApiKeyScope('tag:list')
@ApiResponse(TagListPublicDto)
async getTags(_req, _res, @Query q: ListTagsQueryDto) { /* call service */ }
}@Get/@Post/@Body/@Query/@Param/@GlobalScope/@ProjectScope as-is.@ProjectScope on workflow/credential routes, name the path param
workflowId / credentialId (or projectId / dataTableId) — the
registry passes req.params to userHasScopes, which does not remap id.@ApiKeyScope — string, or { anyOf } / { allOf } (no bare arrays).@ApiResponse(Dto) — registry .parse()s the return value (strips
undeclared fields). Shape is provisional until API-39 (doc-gen).packages/cli/src/public-api/v1/controllers/index.ts (re-exported via
public-api/index.ts) so metadata is registered before
PublicApiControllerRegistry.activate. Name the file
*.public.controller.ts — public-api-controllers.test.ts fails CI if a
controller file is missing from that barrel or lives outside controllers/.scope-parity.test.ts
(handlers/<feature>/spec/paths/…, $ref in openapi.yml). Set
operationId, x-required-scope matching @ApiKeyScope. Do not set
x-eov-operation-id / x-eov-operation-handler — those are legacy eov-only;
scope-parity and discover read @ApiKeyScope from the controller by matching
method + path.packages/nodes-base/nodes/N8n/n8n-api-coverage.json.Only for maintaining endpoints not yet migrated:
handlers/<feature>/<feature>.handler.ts
x-eov-operation-id.publicApiScope() or
apiKeyHasScopeWithGlobalScopeFallback() (they tag the scope-enforcement
middleware with __apiKeyScope) and add matching x-required-scope in the
path YAML; use none for endpoints without an API-key scope (see
scope-parity.test.ts).@n8n/api-types DTOs, call Container.get(SomeService),
map errors — do not reach into repositories or duplicate business logic.handlers/<feature>/spec/paths/<path>.yml
tags: [<TagName>] matching a top-level tag in openapi.yml.$ref entry under paths: in openapi.yml.packages/cli/src/public-api/types.ts
when the handler needs typed query/body params.packages/nodes-base/nodes/N8n/n8n-api-coverage.json.tags array in openapi.ymlWhen adding or editing the top-level tags: array in
packages/cli/src/public-api/v1/openapi.yml, keep entries sorted
alphabetically by name. Insert the new tag in order; do not append to the end.
tags:
- name: Audit
description: Operations about security audit
- name: CommunityPackage
description: Operations about community packages
# … remaining tags in A→Z order by namescope-parity.test.ts asserts this ordering — CI fails if tags drift out of
sort order.
v1/controllers/tags.public.controller.ts, v1/controllers/workflows.public.controller.tspackages/cli/src/public-api/public-api-controller.registry.tshandlers/insights/, handlers/variables/, handlers/folders/,
handlers/projects/, handlers/data-tables/, handlers/workflows/handlers/n8n-packages/ (see also
packages/cli/src/modules/n8n-packages/CLAUDE.md)bcbea0a
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.