New to Claude Skills? Learn how to install them →

n8n-io on GitHub

n8n Public API

Free

Streamline your API endpoint management with n8n.

by n8n-io200.1k stars on n8n-io/n8n
2 views
Updated Aug 10, 2026
Get this skill

Free · Opens the source repo

What n8n Public API does

The n8n Public API skill provides a structured approach to managing API v1 endpoints within the n8n framework. It focuses on the creation, migration, and updating of public API endpoints using the @PublicApiController. This skill is particularly useful for developers working on the n8n platform, as it enforces a set of rules and best practices that ensure the integrity and consistency of API design. By adhering to these guidelines, developers can avoid common pitfalls and produce reliable, maintainable code.

The skill operates under a clear set of non-negotiable rules that dictate how new endpoints should be structured and how they interact with existing services. For example, it mandates that public controllers must never directly call internal endpoints, thereby maintaining a separation of concerns between public and internal API surfaces. This separation is crucial for security and maintainability, ensuring that public-facing APIs do not inadvertently expose internal logic or data.

In addition to its structural guidelines, the n8n Public API skill includes specific requirements for input and output data transfer objects (DTOs), ensuring that all API responses are predictable and well-documented. The use of cursor-based pagination for listing endpoints is another key feature, which enhances performance and user experience by allowing efficient data retrieval. This skill is designed for developers who are building or maintaining APIs within the n8n ecosystem, providing them with the tools and rules necessary to create robust and scalable public APIs.

Overall, the n8n Public API skill is an essential resource for developers looking to implement best practices in API development, ensuring that their endpoints are not only functional but also adhere to the standards set forth by the n8n framework.

When to use it

Use this skill when developing or updating public API endpoints in the n8n framework to ensure best practices are followed.

When not to use it

This skill may not be suitable for projects outside the n8n ecosystem or for developers unfamiliar with its conventions.

What you can build with it

Creating a New Public Endpoint

Use the n8n Public API skill to set up a new public API endpoint by following the prescribed structure and rules.

Migrating Legacy Endpoints

Leverage this skill to migrate existing legacy endpoints to the new public API format without altering their public contracts.

Ensuring API Compliance

Utilize the skill to ensure that all new public API endpoints comply with n8n's standards and best practices.

How to install n8n Public API

View source

1. Install with the skills CLI

npx skills add n8n-io/n8n/public-api --agent claude-code

2. Or install it manually

Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.

Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs

Inside SKILL.md

Written by n8n-io

Public API v1

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.

Non-negotiable rules

  • New endpoints are @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).
  • Public API and internal REST are separate HTTP surfaces. A public controller never calls an internal controller/endpoint; both reuse the same service.
  • Controllers and handlers delegate to a service — never import a repository or Container.get(…Repository) (no-repository-in-public-api-handler).
  • Input/output go through DTOs from @n8n/api-types; every JSON route declares @ApiResponse(Dto).
  • Register each controller via a side-effect import in v1/controllers/index.ts (public-api-controllers.test.ts fails otherwise).
  • Don't add business logic to legacy express-openapi-validator (EOV) handlers.
  • Migrating a legacy endpoint must not change its public contract.

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.

Team defaults

  • List endpoints: cursor-based pagination (internal API uses both cursor- and page-based — don't copy an internal endpoint's model).
  • Updates: full-object PUT, not PATCH. A successful GET body should be acceptable as a PUT body for the same resource (round-trip), aside from server-managed/immutable fields.
  • Strict input DTOs; output DTOs are an allowlist of public fields.
  • Never return real secrets/tokens in responses or error details — mask with the resource's sentinel/placeholder (or omit). Echoing that sentinel on PUT means keep; any other value replaces. Detail: Updates and write-only secrets.
  • "Test connection/config" endpoints validate the request body (test-before-save).

Architecture

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.

Before editing

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.
  • Decorators in 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.
  • The OpenAPI generator (reads the decorators above, no hand-written YAML needed for a controller route): v1/openapi-gen/generate.ts, v1/openapi-gen/decorator-routes.ts.
  • Pagination helpers: v1/shared/services/pagination.service.ts (decodeCursor, encodeNextCursor).
  • DTOs: packages/@n8n/api-types/src/dto/.
  • Gating tests: v1/__tests__/public-api-controllers.test.ts, v1/__tests__/scope-parity.test.ts, v1/openapi-gen/__tests__/generated-spec-drift.test.ts.
  • The internal controller for this resource and its neighboring functional tests.

Declaring a controller

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:

DecoratorUse
@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')Gates the route on a single BooleanLicenseFeature; PublicApiControllerRegistry runs its own license middleware (after auth/@ApiKeyScope/@ProjectScope

Authorization (easy to get wrong)

  • @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 path param what the resolver expects (workflowId, credentialId, projectId, dataTableId, …). A generic id often fails.
  • @ApiKeyScope takes a string, { anyOf: [...] }, or { allOf: [...] } — never a 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.

DTOs

  • Build the public response shape explicitly; don't return an ORM entity and lean on @ApiResponse stripping to hide fields.
  • Treat the output DTO as an allowlist. Re-check nested relations, ownership fields, tokens, and encrypted values.
  • Make input DTOs strict so unknown/partial fields aren't silently accepted.
  • Secrets: never return a real secret; use the resource's sentinel/placeholder (or omit). See Updates and write-only secrets.

List endpoints (cursor pagination)

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.

Wiring checklist

  1. v1/controllers/<feature>.public.controller.ts + side-effect import in v1/controllers/index.ts.
  2. Public DTO in @n8n/api-types + export from the barrel (src/dto/).
  3. @ApiKeyScope value exists in the permissions registry.
  4. Don't hand-write the OpenAPI path or x-required-scope for a controller route — 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.ymlgenerated-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.
  5. Add the route to packages/nodes-base/nodes/N8n/n8n-api-coverage.json.
  6. Tests.

Testing

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.

More detail (reference.md)

Frequently asked questions about n8n Public API

Similar skills