<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Juan Cantón Rodríguez</title>
    <description>The latest articles on DEV Community by Juan Cantón Rodríguez (@srcanton).</description>
    <link>https://dev.to/srcanton</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4018882%2F95e3fdf9-a4f3-4923-9c54-45039a38b44c.png</url>
      <title>DEV Community: Juan Cantón Rodríguez</title>
      <link>https://dev.to/srcanton</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/srcanton"/>
    <language>en</language>
    <item>
      <title>I didn't write my n8n node's 323 operations. I scraped the docs and generated them.</title>
      <dc:creator>Juan Cantón Rodríguez</dc:creator>
      <pubDate>Tue, 07 Jul 2026 08:24:16 +0000</pubDate>
      <link>https://dev.to/srcanton/i-didnt-write-my-n8n-nodes-323-operations-i-scraped-the-docs-and-generated-them-1pjn</link>
      <guid>https://dev.to/srcanton/i-didnt-write-my-n8n-nodes-323-operations-i-scraped-the-docs-and-generated-them-1pjn</guid>
      <description>&lt;p&gt;Last month my n8n community node for &lt;a href="https://francodesystems.com/open-source/n8n-nodes-holded" rel="noopener noreferrer"&gt;Holded&lt;/a&gt; — the ERP used by 100,000+ Spanish SMEs — got the &lt;strong&gt;verified&lt;/strong&gt; badge and became &lt;a href="https://n8n.io/integrations/holded/" rel="noopener noreferrer"&gt;the only Holded integration on n8n.io&lt;/a&gt;.&lt;br&gt;
A&lt;br&gt;
It covers &lt;strong&gt;42 resources and 323 operations&lt;/strong&gt; of the Holded API v2, plus a trigger node for 58 webhook events.&lt;/p&gt;

&lt;p&gt;Here's the part that matters: I hand-wrote almost none of it. This is the story of shipping a huge API surface as a solo dev by treating the node as a &lt;em&gt;compiler target&lt;/em&gt; instead of a codebase.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why bother with a node for a Spanish ERP?
&lt;/h2&gt;

&lt;p&gt;Spanish SMEs are migrating to n8n from Make and Zapier for a very unglamorous reason: &lt;strong&gt;per-operation pricing&lt;/strong&gt;. The moment you automate invoicing properly — say, an ecommerce store pushing 1,000+ orders a month into accounting — a per-task bill stops making sense. Self-hosting also plays well with GDPR anxiety.&lt;/p&gt;

&lt;p&gt;The blocker was that n8n had zero Holded connectivity. Everyone was gluing HTTP Request nodes to a quirky API. That's the gap.&lt;/p&gt;
&lt;h2&gt;
  
  
  The math that forbids hand-writing
&lt;/h2&gt;

&lt;p&gt;Holded's API v2 is big: invoices, contacts, products, stock, CRM funnels, projects, payroll, remittances… When I mapped it, I got &lt;strong&gt;591 endpoints across 42 resources&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An n8n node describes every operation declaratively: resource options, operation options, every parameter with its type, defaults, and &lt;code&gt;displayOptions&lt;/code&gt; controlling when it appears in the UI. Written by hand, that's maybe 60–80 lines per operation. Times 323.&lt;/p&gt;

&lt;p&gt;That's a ~25,000-line file I would have to &lt;em&gt;maintain by hand&lt;/em&gt; every time Holded touches their API. No.&lt;/p&gt;
&lt;h2&gt;
  
  
  The pipeline: scrape → spec → generate
&lt;/h2&gt;

&lt;p&gt;So the repo has a &lt;code&gt;spec/&lt;/code&gt; folder that most n8n nodes don't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec/
├── scrape-v2.py        # scrapes the official API docs
├── v2-endpoints.json   # 72,000 lines: every endpoint, param, type
└── gen-v2.py           # 888 lines that write the node for me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;scrape-v2.py&lt;/code&gt;&lt;/strong&gt; walks Holded's API reference and dumps every endpoint — path, method, params, body schema — into &lt;code&gt;v2-endpoints.json&lt;/code&gt; (~72k lines of structured truth).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gen-v2.py&lt;/code&gt;&lt;/strong&gt; compiles that spec into &lt;code&gt;V2Generated.ts&lt;/code&gt;: &lt;strong&gt;22,000 lines&lt;/strong&gt; of n8n property descriptors — every resource, operation, and field, with correct &lt;code&gt;displayOptions&lt;/code&gt; wiring.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;173-line dispatcher&lt;/strong&gt; routes &lt;code&gt;(resource, operation)&lt;/code&gt; pairs to generic HTTP execution. The actual &lt;code&gt;.node.ts&lt;/code&gt; shell is 355 lines.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The generated file opens with the only comment that matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AUTO-GENERATED by spec/gen-v2.py from spec/v2-endpoints.json.&lt;/span&gt;
&lt;span class="c1"&gt;// Do not edit by hand — regenerate with `python3 spec/gen-v2.py`.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Holded changes their API, I re-scrape, re-generate, diff, release. Maintenance went from "career" to "afternoon".&lt;/p&gt;

&lt;h2&gt;
  
  
  What codegen can't do (and what I still wrote by hand)
&lt;/h2&gt;

&lt;p&gt;Generation gets you &lt;em&gt;coverage&lt;/em&gt;. It does not get you &lt;em&gt;good UX&lt;/em&gt;. Three things stayed human:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Contact resource is fully handwritten.&lt;/strong&gt; It's the resource everyone touches first, and the generated version was technically correct but clumsy — search deserves first-class &lt;code&gt;email&lt;/code&gt; / &lt;code&gt;vatNumber&lt;/code&gt; / &lt;code&gt;phone&lt;/code&gt; fields, not a generic filter collection. High-traffic paths earn hand-polish; the long tail stays generated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Naming and grouping.&lt;/strong&gt; A dropdown with 42 resources is only usable if names match what users see in Holded's UI, not what the API paths say.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;n8n's lint rules.&lt;/strong&gt; &lt;code&gt;eslint-plugin-n8n-nodes-base&lt;/code&gt; has strong opinions (alphabetized options, description formats, casing). The generator had to learn them — fixing lint output &lt;em&gt;in the generator&lt;/em&gt; instead of in the generated file is the whole trick of this approach.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting the "verified" badge
&lt;/h2&gt;

&lt;p&gt;n8n's verification reviews your code before the node becomes installable on n8n Cloud (community nodes otherwise need self-hosting). What it costs you: strict linting, no runtime dependencies, real docs, and review iterations measured in days-to-weeks.&lt;/p&gt;

&lt;p&gt;What you get: distribution. Verified nodes show up in the in-app node panel for every n8n Cloud user, plus &lt;a href="https://n8n.io/integrations/holded/" rel="noopener noreferrer"&gt;a public integration page&lt;/a&gt;. For a niche B2B tool, that's the difference between "GitHub repo with 12 stars" and "discoverable by every n8n user who types your product's name".&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways if you're building a big-API node
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;If the API has &amp;gt;50 operations, don't type them. Generate them.&lt;/strong&gt; A one-day scraper plus a one-week generator beats months of typing and years of drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spec first.&lt;/strong&gt; The intermediate JSON is the asset — the TS is disposable output. I can regenerate for a future n8n API version, or even for a different platform, from the same spec.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand-write the top 10% by traffic.&lt;/strong&gt; Generated long tail, polished hot paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teach the generator the linter's opinions.&lt;/strong&gt; Never fix generated files by hand, not even once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhooks are half the value.&lt;/strong&gt; The trigger node (18 objects / 58 events: &lt;code&gt;invoice.create&lt;/code&gt;, &lt;code&gt;stock.update&lt;/code&gt;, …) is what turns an ERP from "destination" into "event source".&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;n8n&lt;/strong&gt;: Settings → Community Nodes → &lt;code&gt;@francodesystems-npm/n8n-nodes-holded&lt;/code&gt; (or find "Holded" directly on n8n Cloud)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/francodesystems/n8n-nodes-holded" rel="noopener noreferrer"&gt;github.com/francodesystems/n8n-nodes-holded&lt;/a&gt; (MIT)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs, coverage matrix and use cases&lt;/strong&gt;: &lt;a href="https://francodesystems.com/open-source/n8n-nodes-holded" rel="noopener noreferrer"&gt;francodesystems.com/open-source/n8n-nodes-holded&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;If you're migrating between Holded API v1 and v2, I wrote up &lt;a href="https://francodesystems.com/blog/holded-api-v1-v2-trampas-migrar" rel="noopener noreferrer"&gt;the 8 traps that will actually bite you&lt;/a&gt; (Spanish).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ready-to-import workflow templates (Shopify → invoices, Stripe → paid invoices, overdue-invoice chasing…) are landing on my n8n creator profile — I'll update this post with the links as they're approved.&lt;/p&gt;

&lt;p&gt;Questions about the codegen pipeline or the verification process — ask away in the comments. 🇪🇸&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>opensource</category>
      <category>automation</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
