
n8n:public-api
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.
- Installs
- 83
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/n8n-io/n8n --skill n8n:public-api- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads n8n:public-api automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
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
@PublicApiControllerclasses underv1/controllers/, one*.public.controller.tsper feature. - 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.tsfails otherwise). - 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
- Write code that acts as its own documentation. The schema, the decorator, and the test should make the rule clear on their own without a comment.
- List endpoints: cursor-based pagination (internal API uses both cursor- and page-based — don't copy an internal endpoint's model).
- Pagination args are always
offsetandlimit— on service methods, handler calls, and repository methods you add. Neverskip/take(TypeORM names). Translate toskip/takeonly inside a repository, at the TypeORMfindcall. The public query string is stillcursor+limit;offsetis the decoded cursor field passed into the service, never a client-facing param. - Updates: full-object
PUT, notPATCH. A successfulGETbody should be acceptable as aPUTbody 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
PUTmeans 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 fromtags.public.controller.ts(list + cursor) orworkflows.public.controller.ts(@Param+@ProjectScope), andindex.tsfor 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:
| 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 or a binary body — throws. For a binary response body (returned as { body, headers? }), use @ApiResponse(status, { mediaType, description?, headers? }) — see Binary response bodies. |
@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. @Body is JSON by default; @Body({ mediaType: 'multipart/form-data', uploadLimits }) takes a multipart/form-data body instead — see Request body media types. |
@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.- Name every path param
{resource}Id(e.g.workflowId,credentialId,projectId, …) — never a generic:id/{id}. This is the Public API's naming convention: it keeps the API self-documenting and gives typed SDK codegen a real argument name instead ofid.@ProjectScopealso readsreq.paramsas-is and does not remapid— it resolves authorization by exact key name (workflowId,credentialId,projectId,dataTableId, …), so a genericidon a@ProjectScoperoute often fails outright; a@GlobalScopeor unscoped route won't fail the same way, but still follow the convention. @ApiKeyScopetakes a string,{ anyOf: [...] }, or{ allOf: [...] }— never a bare array. The scope must exist in the permissions registry (API_KEY_RESOURCESin@n8n/permissions);scope-parity.test.tsfails on an orphan scope.
DTOs
- Build the public response shape explicitly; don't return an ORM entity and lean
on
@ApiResponsestripping to hide fields. - Treat the output DTO as an allowlist. Re-check nested relations, ownership fields, tokens, and encrypted values.
- An output DTO restricts which fields you return, not which values they may hold.
The registry parses the handler's return value against it, so a value the schema
rejects becomes a
500. Keep the schema loose enough for anything an existing row may contain. - Build the response from the relations the route loaded, not from the entity type. TypeORM relations are opt-in, so two routes over the same entity can return different shapes.
- Make input DTOs strict so unknown/partial fields aren't silently accepted:
Z.class(shape, { strict: true }). - 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. The input DTO takes
limit: publicApiPaginationSchema.limit plus cursor: z.string().optional() —
pick limit off the schema, never spread the whole publicApiPaginationSchema
(it also exports offset, which must never be a Public API query param). Use
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 cursor semantics as-is — but an offset param is a
defect to remove, not a contract to preserve. Detail:
List endpoints and cursor pagination.
Wiring checklist
v1/controllers/<feature>.public.controller.ts+ side-effect import inv1/controllers/index.ts.- Public DTO in
@n8n/api-types+ export from the barrel (src/dto/). @ApiKeyScopevalue exists in the permissions registry.- Don't hand-write the OpenAPI path or
x-required-scopefor a controller route — the generator (v1/openapi-gen/generate.ts) builds it from your decorators (@ApiSummary/@ApiDescription/@ApiTags/@ApiKeyScope/@ApiResponse/@ApiErrorResponse). Run the fullpnpm buildand commit the regeneratedhandlers/<feature>/spec/paths/*.generated.ymlfragment(s) andopenapi.decorator-routes.generated.yml—generated-spec-drift.test.tsfails CI if they're stale.pnpm run build:dataalone is not enough after touching a controller: it runs the generator against the already-compileddist/, so a new/changed controller silently doesn't show up unlesstscran first. - Add the route to
packages/nodes-base/nodes/N8n/n8n-api-coverage.json. - 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)
- List endpoints and cursor pagination
- Updates and write-only secrets
- Test-before-save endpoints
- Errors
- Testing matrix
- Verifying a migration
- CI and merging
FAQ
What does the n8n:public-api skill do?
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.
How do I install n8n:public-api?
Run npx skills add https://github.com/n8n-io/n8n --skill n8n:public-api in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/n8n:public-api/ (personal) or .claude/skills/n8n:public-api/ (project).
Which AI agents can use n8n:public-api?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is n8n:public-api safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains n8n:public-api?
It is published by n8n-io in n8n-io/n8n. It has 83 recorded installs.
More from n8n-io/n8n
create-pr
n8n-io/n8n
Creates GitHub pull requests with properly formatted titles that pass the check-pr-title CI validation. Use when creating PRs, submitting changes for review, or when the user says /pr or asks to create a pull request.
content-design
n8n-io/n8n
Product content designer for UI copy. Use when writing, reviewing, or auditing user-facing text: button labels, error messages, tooltips, empty states, modal copy, placeholder text, confirmation dialogs, onboarding flows, or i18n strings. Also use when the user says /copy, /content, or /ux-copy.
n8n-conventions
n8n-io/n8n
Quick reference for n8n patterns. Full docs /AGENTS.md
n8n:conventions
n8n-io/n8n
Quick reference for n8n patterns. Full docs /AGENTS.md
reproduce-bug
n8n-io/n8n
Reproduce a bug from a Linear ticket with a failing test. Expects the full ticket context (title, description, comments) to be provided as input.
linear-issue
n8n-io/n8n
Fetch and analyze Linear issue with all related context. Use when starting work on a Linear ticket, analyzing issues, or gathering context about a Linear issue.
Popular backend & apis skills
find-skills
vercel-labs/skills
Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
entra-app-registration
microsoft/azure-skills
Guides Microsoft Entra ID app registration, OAuth 2.0 authentication, and MSAL integration. USE FOR: create app registration, register Azure AD app, configure OAuth, set up authentication, add API permissions, generate service principal, MSAL example, console app auth, Entra ID setup, Azure AD authentication. DO NOT USE FOR: Key Vault secrets (use azure-keyvault-expiration-audit), general Azure resource security guidance.
google-agents-cli-adk-code
google/agents-cli
This skill should be used when the user wants to "write agent code", "build an agent with ADK", "add a tool", "create a callback", "define an agent", "use state management" — in a project that needs ADK (Agent Development Kit) API patterns and code examples. It provides a quick reference for agent types, tool definitions, orchestration patterns, callbacks, state management, the graph Workflow API, and reference recipes to study. Do NOT use for scaffolding (use google-agents-cli-scaffold) or deployment (use google-agents-cli-deploy).
embedded-captions
heygen-com/hyperframes
Add captions or subtitles to an existing single-subject talking-head video without editing the footage. Use for plain verbatim captions, cinematic captions embedded behind the subject, VFX captions, “炸/特效/酷炫字幕,” or a named identity from the 35-style catalog. Route by visual identity, not by backend engine. The quiet anchor rail is the default; embed every word only when the user explicitly wants a fully cinematic treatment. The workflow runs locally end to end, including transcription and subject matting; split multi-shot footage before applying it.
entra-agent-id
microsoft/azure-skills
Provision Microsoft Entra Agent Identity Blueprints, BlueprintPrincipals, and per-instance Agent Identities via Microsoft Graph, and configure OAuth 2.0 token exchange (fmipath, OBO, cross-tenant) including the Microsoft Entra SDK for AgentID sidecar. USE FOR: Agent Identity Blueprint, BlueprintPrincipal, agent OAuth, fmipath token exchange, agent OBO, Workload Identity Federation for agents, polyglot agent auth, Microsoft.Identity.Web.AgentIdentities. DO NOT USE FOR: standard Entra app registration (use entra-app-registration), Microsoft Foundry agent authoring (use microsoft-foundry).
neon
neondatabase/agent-skills
Overview of Neon, a complete set of cloud backend primitives around Lakebase Postgres: Auth, Object Storage, Functions, the AI Gateway, and Realtime. Start here to choose Neon for undecided login, files, APIs, and LLM calls, set up the CLI or MCP server, and follow the branch-first workflow. Use when building an app or backend on Neon, or when "Neon" or "Lakebase Postgres" is mentioned. Child skill neon-postgres wins for an existing DATABASEURL, SQL, schema, inspect, or search. Child skill neon-auth wins for login, users, sessions, identity routing, and Managed Better Auth setup. Child skill neon-realtime wins for live queries in reactive apps and agents. Also use for object storage, S3, buckets, serverless functions, function triggers, cron, AI gateway, LLM calls, realtime, logs, Loki, Grafana, observability, postgres, database, backend, Claimable Neon, neon.new, or a no-signup database.