Iurii RoguliaIurii Rogulia
AboutAI EngineeringServicesProjectsAI SkillsStackTestimonialsPhrasesBlog
Contact
Iurii ships.

Iurii Rogulia, senior full-stack software engineer. Professionally building software since 2001.

Think of a numberThink of a birthday
Hourly ratesChecklistsPrivacy PolicyCookie Policy

Business

TMI Iurii Rogulia
VAT ID: FI29845875
DUNS: 368664211
Lappeenranta, Finland 🇫🇮

  1. Home
  2. /
  3. Blog
  4. /
  5. n8n Custom Node Development: Verified Node, AI Tool

Iurii Builds: n8n Custom Node Development: Verified Node, AI Tool

One flag, usableAsTool, makes n8n generate an agent-facing copy of your node. What that does to the way you write descriptions, and what the verification review asks for.

October 28, 2026· 9 min read

n8n custom node development: how usableAsTool builds an AI agent Tool variant, why your descriptions are its contract, and what verification checks.

Stack

n8nTypeScriptNode.js

Libraries

n8n-workflow

Services

GitHub Actionsnpm

Topics

AI Agent ToolDeveloper ToolsIntegrationsOpen Source
n8n Custom Node Development: Verified Node, AI Tool

I published n8n-nodes-vatnode, a verified n8n community node for EU VAT validation. One line in the node class, usableAsTool: true, also makes n8n generate an agent-facing Tool variant of it. That line is easy to add and easy to under-think, because it changes who reads your code. In a normal node the reader is a person looking at a canvas. In the tool variant, the reader of your descriptions is a model.

This article is about two things: what that flag does, as far as the types and n8n’s loader code show, and what the path to a verified node looks like when you build for it from the start. One limit up front: I have not run this node inside an agent or evaluated what an agent does with it. This covers how the tool variant is built, not agent behaviour.

What the Flag Does

usableAsTool is not described on the docs pages I checked (node base files, file structure, verification guidelines, submission). The evidence is elsewhere:

  • The n8n-workflow types declare usableAsTool?: true | UsableAsToolDescription, commented as ‘whether the node will be wrapped for tool-use by AI Agents’.
  • The @n8n/node-cli template guide says to set it to true to allow use as an agent tool, and to leave it off if the node works heavily with binary data, which tools don’t support.
  • In n8n’s source, createAiTools (packages/cli/src/tool-generation/ai-tools.ts) takes every node with a truthy usableAsTool and builds a copy: Tool appended to the name, inputs cleared, the output set to AiTool. It injects a required toolDescription string parameter that defaults to the node’s description. If the node has a resource or operation property, it also adds a descriptionType option, automatic or manual. Credentials are copied across.

The code I read has no condition on whether the node is built in or a community package. I have not checked every code path that could add one, and I did not verify that the Tool variant is available on a given instance; that may depend on its configuration.

The text a model sees comes from strings you wrote for a human dropdown: the node description, each operation’s description and action, each parameter’s description. Those strings are now your tool contract. I haven’t verified how the automatic description type composes them, so I treat all of them as if they will be read.

Designing Operations for a Reader That Can Spend Money

The node has two resources and four operations. Two of them are the interesting pair, because they answer the same question at different prices:

options: [
  {
    name: 'Validate',
    value: 'validate',
    description:
      'Verify a VAT number against the official EU VIES register. Requires an API key and spends one request from your quota.',
    action: 'Validate a VAT number',
  },
  {
    name: 'Check Format',
    value: 'checkFormat',
    description:
      'Check a VAT number against its country pattern. Runs offline: no API key, no network call, no quota.',
    action: 'Check the format of a VAT number',
  },
],

Cost is written into the description, in plain words, on each operation. A person scanning the dropdown benefits. So does anything choosing between the two from text alone. I did the same split in the MCP server for the same API, for the same reason: a caller who only needs a cheap answer shouldn’t be pushed toward the expensive one.

This is a mitigation, not a guarantee. A tool variant of a quota-spending operation exposes a quota-spending operation to an agent. What I did is state the cost where the model reads it, and make the free path complete on its own: the credential is only required for validate, so Check Format and both rate operations work with no key attached.

credentials: [
  {
    name: 'vatnodeApi',
    required: true,
    displayOptions: {
      show: { resource: ['vat'], operation: ['validate'] },
    },
  },
],

The node-level description is Validate EU VAT numbers against VIES and look up EU VAT rates, which says nothing about cost. The default toolDescription is that string. If you ship a spending operation, check what your default tool description actually says, and consider whether it should say more. I have not tested which of these strings a model weighs.

Errors a Reader Can Act On

A tool call that fails with a stack trace gives a model nothing to correct. Two small decisions apply to people and models alike.

First, parameters typed as strings are not always strings. An expression like {{ $json.vatId }} resolves to undefined on an item without that field:

function toStringParam(value: unknown): string {
  return value === undefined || value === null ? "" : String(value);
}

In Validate, the empty string then hits an explicit check and throws The VAT number is empty, which is a message the caller can act on, instead of a TypeError from .toUpperCase().

Second, input errors and upstream errors are different types, and the loop isolates failures per item:

} catch (error) {
  if (this.continueOnFail()) {
    returnData.push({
      json: { error: (error as Error).message },
      pairedItem: { item: i },
    });
    continue;
  }
  // Input problems are ours, not the API's – keep them out of NodeApiError.
  if (error instanceof NodeOperationError) {
    throw new NodeOperationError(this.getNode(), error.message, { itemIndex: i });
  }
  throw new NodeApiError(this.getNode(), error as JsonObject, { itemIndex: i });
}

pairedItem keeps the link from each output to its input row. The NodeOperationError versus NodeApiError split tells the user whether their input or the upstream failed.

One more cost detail, in the credential. n8n runs the credential test when you save or test a credential, so a test that validates a real number spends quota each time. The test sends the reserved number XX0000001 instead:

// XX0000001 is the reserved test VAT number: it never reaches VIES and never
// spends quota, so the test only proves that the key is accepted.
test: ICredentialTestRequest = {
  request: { baseURL: "https://api.vatnode.dev", url: "/v1/vat/XX0000001" },
  rules: [
    { type: "responseCode", properties: { value: 401, message: "The vatnode API key is invalid" } },
    {
      type: "responseCode",
      properties: {
        value: 403,
        message: "This vatnode API key is not allowed to validate VAT numbers",
      },
    },
  ],
};

The Verification Path

Verification is a review by n8n, and what it checks is public in the submission and verification guidelines pages. The ones that shaped this build:

  • No runtime dependencies. The format check needs a pattern per country. Hardcoding a pattern for each of 45 countries (plus an EL alias for Greece) creates a second source of truth, and a dependency is not allowed. So eu-vat-rates-data is a devDependency, and scripts/generate-patterns.mjs writes the patterns into countries.generated.ts at build time. The published package carries the patterns and nothing to install. The only other dependency, n8n-workflow, is a peer dependency.
  • One service per package, MIT license, matching repo and author. The npm repository URL has to point at the public GitHub repo, and the author has to match.
  • Scaffolded with n8n-node. Strongly suggested rather than required. The scaffold gives you the lint rules, the release script and the n8n block in package.json, including "strict": true.
  • Provenance from GitHub Actions. From 1 May 2026, n8n does not accept verified nodes published from a local machine. The publish workflow runs on a version tag, with the permissions it needs and no more:
on:
  push:
    tags: ["*.*.*", "v*.*.*"]
jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read

The workflow publishes from GitHub Actions with npm provenance. The first release used a granular npm token; from the second release the package has been published through npm Trusted Publishing. publishConfig.provenance is true. Releases need @n8n/node-cli 0.23.0 or later.

  • A README with usage, authentication details and example workflows. The repo ships three importable templates: validating a number from a form, deciding reverse charge before invoicing, and re-checking customer numbers on a schedule.
  • Submission through the n8n Creator Portal. The review applies to the version you submit.

Limits and Trade-offs

  • Verification is a review, and review can reject. n8n’s page says it may reject nodes that compete with its paid features. Build the checklist in, but don’t plan a launch around the outcome.
  • The no-dependencies rule is a real constraint. Anything you’d normally import has to be generated, inlined or written. For patterns, generation works. For something like a parsing library it may not.
  • usableAsTool puts your node in front of a caller you can’t see. For a read-only free operation that’s harmless. For a spending or destructive one, the description is the only guard in this design. I haven’t evaluated whether it’s enough.
  • The flag is wrong for nodes that work heavily with binary data, per the template guide.

What to Check Before You Submit

  1. Package name is n8n-nodes-* or @scope/n8n-nodes-*, with the n8n-community-node-package keyword.
  2. Nodes and credentials are listed in the n8n attribute of package.json.
  3. npm run lint passes, and npx @n8n/scan-community-package n8n-nodes-yourpackage is clean.
  4. No runtime dependencies. Generate at build time what you need.
  5. MIT license; repository URL and author match the public GitHub repo.
  6. Published by a GitHub Actions workflow with provenance, not from your laptop.
  7. Every operation and parameter has a description written for a reader who can’t ask a follow-up, and operations that cost something say so.
  8. Empty and missing inputs produce readable errors; continueOnFail and pairedItem are handled.
  9. The credential test does not spend anything.
  10. The README has usage, auth and example workflows.

Who This Is For

If you’re building a node for your own API, the points above are the checklist I’d want. If you only need your tool or API to appear as a working step in n8n, you don’t need to learn any of this.


If you have an API that should be a step in n8n, or exposed to an AI agent as a tool with its cost stated plainly, get in touch – that is API integration work.


Further reading:

  • n8n VAT validation node – project page – what the node does and how it was released
  • Building an MCP Server for VAT Validation: Why It’s Two Tools, Not One – the same free-versus-spending split in a different agent interface
  • Caching VAT and FX Rates at Build Time, Not on the Request Path – the build-time data idea in another setting
  • n8n: submit community nodes
  • n8n: verification guidelines
Iurii RoguliaAvailable

API & Integrations

Need an API to be a first-class step in n8n, or a tool exposed to an AI agent with its cost stated plainly? Designing that surface – operations, errors, cost – is part of the integration work I do.

More about this service

Relevant client work

View all projects
n8n Verified Community Node – EU VAT Validation in Workflows
n8n Verified Community Node – EU VAT Validation in Workflows
August 6, 2026
n8n Verified Community Node – EU VAT Validation in Workflows

Verified n8n node for VIES VAT validation and EU rate lookups in a workflow, so onboarding or invoicing acts on a real VAT number, not a well-formed one.

polytypo.dev – Locale-Correct Typography Engine and Spec
polytypo.dev – Locale-Correct Typography Engine and Spec
September 8, 2026
polytypo.dev – Locale-Correct Typography Engine and Spec

Open-source spec and engine for locale-correct typography: curly quotes, dashes, ellipses and no-break spaces in 20+ locales. Live in-browser playground.

vatnode.dev mcp – Official MCP Server for EU VAT Validation
vatnode.dev mcp – Official MCP Server for EU VAT Validation
May 20, 2026
vatnode.dev mcp – Official MCP Server for EU VAT Validation

Open-source MCP server that lets Claude Desktop, Cursor and other MCP clients validate EU VAT numbers and look up rates directly in chat.

What clients say

“

I’d built most of our MVP with Cursor and it looked finished – it compiled, the tests were green, the demo worked. It just wouldn’t survive real users.

Sebastian Falk 🇸🇪

Founder

Stack

Next.jsTypeScript

Topics

Technical DebtAICode ReviewArchitecture
“

We wanted to add an AI feature that turns messy user notes into structured records, but our first attempt returned unpredictable JSON that broke the app half the time.

Bram de Vries 🇳🇱

Product Lead

Stack

Next.jsTypeScript

Services

OpenAI

Topics

AILLMStructured Outputs
“

We migrated our content platform last autumn and assumed everything was fine because the tests passed.

Mathias Sørensen 🇩🇰

CTO

Topics

SEOCI/CDDeveloper ToolsArchitecture

Related articles

How a Typography Pipeline Actually Rewrites Text: Order, Spans, and Fixed Points
September 16, 2026· 25 min
How a Typography Pipeline Actually Rewrites Text: Order, Spans, and Fixed Points

polytypo’s typography engine is a nine-rule pipeline that locates and rewrites text without ever building or reprinting an AST.

Stack

TypeScriptNode.js
Halloween Effects for Any Website, Turned On by One CSS Class
August 28, 2026· 10 min
Halloween Effects for Any Website, Turned On by One CSS Class

halloween.js adds Halloween decorations to any website – one class flips them on, and a real-calendar season window turns them off automatically.

Stack

TypeScript
Publishing One Package to Five Registries with GitHub Actions
March 27, 2026· 10 min
Publishing One Package to Five Registries with GitHub Actions

How to publish one dataset to npm, PyPI, Go Module, RubyGems, and Packagist with GitHub Actions – architecture, versioning, and per-ecosystem gotchas.

Stack

TypeScriptPythonPHPGoRuby
Building an MCP Server for VAT Validation: Why It’s Two Tools, Not One
September 18, 2026· 9 min
Building an MCP Server for VAT Validation: Why It’s Two Tools, Not One

Building the vatnode MCP server meant deciding whether format-checking and live VIES validation should be one tool or two.

Stack

TypeScriptNode.js
Caching VAT and FX Rates at Build Time, Not on the Request Path
September 11, 2026· 9 min
Caching VAT and FX Rates at Build Time, Not on the Request Path

Why a Next.js e-commerce project fetches EUR/VAT reference data once per build instead of once per request or never.

Stack

Node.jsTypeScript