>- Adds, migrates, or updates n8n Public API v1 endpoints with @PublicApiController — public DTOs, API-key and RBAC scopes, cursor pagination, OpenAPI + coverage wiring, and tests. Use when working under packages/cli/src/public-api/v1/ or when exposing an existing service through /api/v1.
npx skills add https://github.com/n8n-io/n8n --skill n8n:public-api
Public API v1 lives in packages/cli/src/public-api/v1/, mounted at /api/v1
with API-key auth and public error formatting via PublicApiControllerRegistry
(packages/cli/src/public-api/public-api-controller.registry.ts).
Two rule tiers: invariants (never break) and team defaults (follow unless
an existing public contract forces otherwise). When this skill and the code
disagree on a detail, the code wins — so open the files below. That is a reason to
check the code, not license to drop a team default.
@PublicApiController classes under v1/controllers/, one*.public.controller.ts per feature. A controller is a class — never
export = (the legacy tuple style; require-public-api-controller flags it).
never calls an internal controller/endpoint; both reuse the same service.
Container.get(…Repository) (no-repository-in-public-api-handler).
@n8n/api-types; every JSON route declares@ApiResponse(Dto).
v1/controllers/index.ts(public-api-controllers.test.ts fails otherwise).
express-openapi-validator (EOV) handlers.These are n8n-local-rules ESLint rules (see packages/cli/eslint.config.mjs)
and can't be silenced inline (no-public-api-guardrail-disable). The off
allowlist there covers pre-existing legacy files only — it's shrink-only, don't
add to it.
page-based — don't copy an internal endpoint's model).
PUT, not PATCH. A successful GET body should beacceptable as a PUT body for the same resource (round-trip), aside from
server-managed/immutable fields.
resource's sentinel/placeholder (or omit). Echoing that sentinel on PUT
means keep; any other value replaces. Detail:
Updates and write-only secrets.
Public and internal are sibling routes over one shared, HTTP-agnostic service;
neither calls the other.
GET /rest/tags → TagsController ┐ JWT auth, internal shape
├─→ TagService
GET /api/v1/tags → TagsPublicController ┘ API-key auth, public DTO
Reuse the service behavior. Reuse a DTO only when public and internal contracts
are intentionally identical; otherwise make a public-specific DTO that doesn't
depend on a UI-oriented internal shape.
Open these — they are the source of truth, not this skill:
v1/controllers/ — copy structure from tags.public.controller.ts (list +cursor) or workflows.public.controller.ts (@Param + @ProjectScope), and
index.ts for the barrel.
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.
needed for a controller route): v1/openapi-gen/generate.ts,
v1/openapi-gen/decorator-routes.ts.
v1/shared/services/pagination.service.ts(decodeCursor, encodeNextCursor).
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.
A controller is a class marked @PublicApiController('/base') that injects the
shared service via its constructor and delegates to it. Copy the shape from an
existing controller in v1/controllers/ with the same operation type and auth
model; reuse only what applies. Decorators, all from @n8n/decorators:
| Decorator | Use |
|---|---|
| @PublicApiController('/base') | Class marker; mounts routes at /api/v1/base. |
| @Get/@Post/@Put/@Patch/@Delete('/path') | Route method. |
| @ApiKeyScope('res:action') | API-key grant check. |
| @ProjectScope/@GlobalScope('res:action') | User RBAC check. |
| @ApiResponse(status) / @ApiResponse(status, Dto) | Success status + (optional) output DTO; registry .parse()s + strips the return value. Exactly one per route — a second @ApiResponse throws. 204 can't carry a DTO — throws. |
| @ApiErrorResponse(status) | Declares an additional documented non-2xx status (e.g. 404, 409). Stack multiple for more than one. 400/401/403 are added automatically (body/query present, always, and @ApiKeyScope present, respectively) — don't declare those yourself. |
| @ApiSummary(text) / @ApiDescription(text) / @ApiTags([...]) | OpenAPI summary/description/tags. @ApiTags sorts alphabetically regardless of the order you pass. All optional but expected on every real route. |
| @Query / @Body / @Param('name') | Bind + validate via a Z.class DTO / path param. |
| @Licensed('feat') | Not yet enforced for @PublicApiController routes — PublicApiControllerRegistry doesn't read licenseFeature (only the internal @RestController registry does). If the endpoint gates an EE feature, check the license manually in the handler (Container.get(License).isLicensed(LICENSE_FEATURES.X), throwing ForbiddenError on failure) instead of relying on this decorator alone. |
@ApiKeyScope (what the API key is granted) and @ProjectScope/@GlobalScope(what the user may do) are independent. Use both when the model needs both.
@ProjectScope reads req.params as-is and does not remap id — name the pathparam what the resolver expects (workflowId, credentialId, projectId,
dataTableId, …). A generic id often fails.
@ApiKeyScope takes a string, { anyOf: [...] }, or { allOf: [...] } — nevera bare array. The scope must exist in the permissions registry
(API_KEY_RESOURCES in @n8n/permissions); scope-parity.test.ts fails on an
orphan scope.
on @ApiResponse stripping to hide fields.
fields, tokens, and encrypted values.
(or omit). See Updates and write-only secrets.
Copy the cursor flow from tags.public.controller.ts. Use publicApiPaginationSchema
plus decodeCursor / encodeNextCursor from the shared pagination service; the
cursor is opaque; return { data, nextCursor } (never a bare array) with
nextCursor: null on the last page; an invalid cursor is a 400. Preserve an
existing endpoint's pagination as-is. Detail:
List endpoints and cursor pagination.
v1/controllers/<feature>.public.controller.ts + side-effect import inv1/controllers/index.ts.
@n8n/api-types + export from the barrel (src/dto/).@ApiKeyScope value exists in the permissions registry.x-required-scope for a controllerroute — the generator (v1/openapi-gen/generate.ts) builds it from your
decorators (@ApiSummary/@ApiDescription/@ApiTags/@ApiKeyScope/
@ApiResponse/@ApiErrorResponse). Run the full pnpm build and commit
the regenerated handlers/<feature>/spec/paths/*.generated.yml fragment(s)
and openapi.decorator-routes.generated.yml —
generated-spec-drift.test.ts fails CI if they're stale. `pnpm run
build:data` alone is not enough after touching a controller: it runs
the generator against the already-compiled dist/, so a new/changed
controller silently doesn't show up unless tsc ran first.
packages/nodes-base/nodes/N8n/n8n-api-coverage.json.Always cover: happy path, input-validation failure, missing API-key scope, RBAC
denial. Prefer covering the business path in
packages/cli/test/integration/public-api/ (real HTTP + DB); mocked-service unit
tests don't replace that. Add the cases that apply (cursor pages,
not-found/conflict, no sensitive fields, credential keep/replace, migration
contract) — see Testing matrix. Match the nearest
existing tests.
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling
Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification
Take n8n-io/n8n:public-api from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.