Agent Skillsn8n-io/n8n › n8n:public-api

n8n:public-api

GitHub

规范 n8n Public API v1 端点的开发、迁移与更新,涵盖控制器结构、DTO 定义、分页、认证及测试要求。

.agents/skills/public-api/SKILL.md n8n-io/n8n

触发场景

创建新的 Public API 端点 迁移现有内部接口至公共 API 更新或重构 Public API 代码

安装

npx skills add n8n-io/n8n --skill n8n:public-api -g -y
更多选项

非标准路径

npx skills add https://github.com/n8n-io/n8n/tree/master/.agents/skills/public-api -g -y

不安装直接使用

npx skills use n8n-io/n8n@n8n:public-api

指定 Agent (Claude Code)

npx skills add n8n-io/n8n --skill n8n:public-api -a claude-code -g -y

安装 repo 全部 skill

npx skills add n8n-io/n8n --all -g -y

预览 repo 内 skill

npx skills add n8n-io/n8n --list

SKILL.md

Frontmatter
{
    "name": "n8n:public-api",
    "description": "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."
}

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

  • 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 offset and limit — on service methods, handler calls, and repository methods you add. Never skip/take (TypeORM names). Translate to skip/take only inside a repository, at the TypeORM find call. The public query string is still cursor + limit; offset is the decoded cursor field passed into the service, never a client-facing param.
  • 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:

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') 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 of id. @ProjectScope also reads req.params as-is and does not remap id — it resolves authorization by exact key name (workflowId, credentialId, projectId, dataTableId, …), so a generic id on a @ProjectScope route often fails outright; a @GlobalScope or unscoped route won't fail the same way, but still follow the convention.
  • @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.
  • 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

  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)

版本历史

  • fe0fad5 当前 2026-09-23 10:21
  • 770b97c 2026-08-29 06:27

    移除 workflow history 列表接口的 offset 查询参数支持

  • c31d0e5 2026-08-20 19:08

同 Skill 集合

.agents/skills/community-pr-readiness-check/SKILL.md
.agents/skills/content-design/SKILL.md
.agents/skills/conventions/SKILL.md
.agents/skills/create-agent-builder-eval/SKILL.md
.agents/skills/create-community-node-lint-rule/SKILL.md
.agents/skills/create-instance-ai-eval/SKILL.md
.agents/skills/create-issue/SKILL.md
.agents/skills/create-pr/SKILL.md
.agents/skills/create-skill/SKILL.md
.agents/skills/db-migrations/SKILL.md
.agents/skills/design-system/SKILL.md
.agents/skills/experiments/SKILL.md
.agents/skills/gh-stack/SKILL.md
.agents/skills/human-like-code-review/SKILL.md
.agents/skills/linear-issue/SKILL.md
.agents/skills/loom-transcript/SKILL.md
.agents/skills/nathan/SKILL.md
.agents/skills/node-add-oauth/SKILL.md
.agents/skills/protect-endpoints/SKILL.md
.agents/skills/reproduce-bug/SKILL.md
.agents/skills/spec-driven-development/SKILL.md
.agents/skills/telemetry/SKILL.md
.agents/skills/ui-design/SKILL.md
.claude/plugins/n8n/skills/setup-mcps/SKILL.md
.opencode/skills/setup-mcps/SKILL.md
packages/@n8n/cli/skills/n8n-cli/SKILL.md
packages/@n8n/instance-ai/skills/agent-builder/SKILL.md
packages/@n8n/instance-ai/skills/config-evals/SKILL.md
packages/@n8n/instance-ai/skills/credential-recipe-research/SKILL.md
packages/@n8n/instance-ai/skills/credential-setup-with-computer-use/SKILL.md
packages/@n8n/instance-ai/skills/debugging-executions/SKILL.md
packages/@n8n/instance-ai/skills/instance-awareness/SKILL.md
packages/@n8n/instance-ai/skills/n8n-docs-assistant/SKILL.md
packages/@n8n/instance-ai/skills/planned-task-runtime/SKILL.md
packages/@n8n/instance-ai/skills/planning/SKILL.md
packages/@n8n/instance-ai/skills/post-build-flow/SKILL.md
packages/@n8n/instance-ai/skills/data-table-manager/SKILL.md
packages/@n8n/instance-ai/skills/intent-recognition/SKILL.md
packages/@n8n/instance-ai/skills/model-selection/SKILL.md
packages/@n8n/instance-ai/skills/one-off-operations/SKILL.md
packages/@n8n/instance-ai/skills/progressive-building/SKILL.md
packages/@n8n/instance-ai/skills/workflow-builder/SKILL.md

元信息

文件数
0
版本
fe0fad5
Hash
af4cfb1b
收录时间
2026-08-20 19:08

首页 - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-23 15:21
浙ICP备14020137号-1