<?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: Oleksandr</title>
    <description>The latest articles on DEV Community by Oleksandr (@alexcodebytes).</description>
    <link>https://dev.to/alexcodebytes</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%2F4134414%2F911e1774-b09d-4120-80e9-56cc4a11b28c.png</url>
      <title>DEV Community: Oleksandr</title>
      <link>https://dev.to/alexcodebytes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexcodebytes"/>
    <language>en</language>
    <item>
      <title>I built 100 compliance screening pages with one command (OFAC, PEP, entity)</title>
      <dc:creator>Oleksandr</dc:creator>
      <pubDate>Sat, 10 Oct 2026 06:16:05 +0000</pubDate>
      <link>https://dev.to/alexcodebytes/i-built-100-compliance-screening-pages-with-one-command-ofac-pep-entity-5ek1</link>
      <guid>https://dev.to/alexcodebytes/i-built-100-compliance-screening-pages-with-one-command-ofac-pep-entity-5ek1</guid>
      <description>&lt;p&gt;This is the 5th stream in a series. Previous ones: &lt;strong&gt;HS codes&lt;/strong&gt;, &lt;strong&gt;document parsing&lt;/strong&gt;, &lt;strong&gt;finance validators&lt;/strong&gt;, &lt;strong&gt;watch/monitoring&lt;/strong&gt;. This time — &lt;strong&gt;compliance screening&lt;/strong&gt; against OFAC, EU, UK, UN sanctions lists, plus PEP screening and entity verification.&lt;/p&gt;

&lt;p&gt;Same playbook each time: one Cloudflare Worker + one JSON list + one generator script → 100 SEO pages + landing + Suby product.&lt;/p&gt;

&lt;p&gt;Live: &lt;strong&gt;&lt;a href="https://jsonexlab.com/compliance" rel="noopener noreferrer"&gt;https://jsonexlab.com/compliance&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Screen any person or company against:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OFAC SDN&lt;/strong&gt; (US Treasury, 19,416 entries)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OFAC Consolidated&lt;/strong&gt; (non-SDN)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UK OFSI&lt;/strong&gt; (UK sanctions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EU Consolidated&lt;/strong&gt; (EU sanctions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UN Security Council&lt;/strong&gt; (UN sanctions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PEP&lt;/strong&gt; (Politically Exposed Persons)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entity verification&lt;/strong&gt; (company registries: US-DE, UK, DE, UA, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Live demo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://compliance-engine.boring-saas-infra.workers.dev/check/sanctions?q=Rosneft"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"check_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SANCTIONS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Rosneft"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HIT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"list"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OFAC_SDN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OPEN JOINT-STOCK COMPANY ROSNEFT OIL COMPANY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"entity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Russia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"programs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"UKRAINE-EO13662"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RUSSIA-EO14024"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ids"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Registration ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1027700043502"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tax ID No."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7706107510"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Website"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"www.rosneft.com"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"addresses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"street"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"26/1 Sofiyskaya Embankment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Moscow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Russia"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why this one was harder
&lt;/h2&gt;

&lt;p&gt;Three things made this stream different from previous ones:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Cloudflare Workers cannot fetch OFAC directly
&lt;/h3&gt;

&lt;p&gt;OFAC lives on &lt;code&gt;sanctionslistservice.ofac.treas.gov&lt;/code&gt;. Cloudflare Workers fails with HTTP 525 (SSL handshake error) or 403 (missing User-Agent). This is a &lt;strong&gt;TLS incompatibility on the origin side&lt;/strong&gt; — nothing you can fix from the Worker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; move ingestion to &lt;strong&gt;GitHub Actions&lt;/strong&gt; (6 GB RAM, normal TLS stack, works with any URL).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 30 MB XML doesn't fit in 128 MB Worker RAM
&lt;/h3&gt;

&lt;p&gt;Naive approach — fetch XML → parse → insert — dies with &lt;strong&gt;error 1102&lt;/strong&gt; (&lt;code&gt;Worker exceeded resource limits&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; GitHub Actions downloads the XML, parses it in Node.js, and inserts directly into Cloudflare D1 via the &lt;strong&gt;D1 HTTP API&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. D1 free tier has a daily row-write limit
&lt;/h3&gt;

&lt;p&gt;100,000 row writes per day. A single OFAC ingestion = 19,416 rows × 2 (UPSERT counts as insert + update) = ~39,000 writes. Two ingestions in one day → quota hit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; cron runs once per day at 01:00 UTC. Quota resets at midnight UTC, so the ingestion always fits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GitHub Actions (cron 0 1 * * *)
    │
    ├── Download OFAC SDN XML (30 MB) via curl
    ├── Download UK OFSI CSV (50 MB) via curl
    ├── Parse in Node.js (6 GB RAM available)
    └── POST to Cloudflare D1 HTTP API
            │
            ▼
    Cloudflare D1 (compliance-db)
        ├── sanctions (19,416+ entries)
        ├── pep
        ├── entity
        ├── license
        ├── runs (ingestion log)
        └── search_log
            │
            ▼
    Cloudflare Worker (compliance-engine)
        ├── GET /check/sanctions?q=...
        ├── GET /check/pep?q=...
        ├── GET /check/entity?q=...
        ├── GET /stats
        └── Cron triggers (legacy, not used)
            │
            ▼
    Cloudflare Pages (jsonexlab.com)
        ├── /compliance (landing)
        ├── /compliance/{slug} (100 SEO pages)
        └── /sitemap.xml (729 URLs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The GitHub Actions workflow
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.github/workflows/ingest-ofac.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingest OFAC Sanctions&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ingest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run OFAC ingestion&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;CLOUDFLARE_API_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.CLOUDFLARE_API_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;CLOUDFLARE_ACCOUNT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.CLOUDFLARE_ACCOUNT_ID }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node scripts/ingest-ofac.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple. GitHub Actions does the heavy lifting, Worker stays light.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parser
&lt;/h2&gt;

&lt;p&gt;Core is the OFAC SDN XML parser — regex-based, no dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseOneSdnEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OFAC_SDN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extractTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decodeXmlEntities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extractTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lastName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decodeXmlEntities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extractTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firstName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapSdnType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extractTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sdnType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Entity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;programs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extractAllTags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;program&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// aliases, ids, addresses, nationality, dob...&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;list_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;entity_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name_normalized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;normalizeName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;aliases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aliases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;aliases&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;programs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;programs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;programs&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;dob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;ids&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;addresses&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;remarks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;remarks&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whole parser is ~250 lines. Handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;19,416 SDN entries&lt;/li&gt;
&lt;li&gt;Aliases (aka blocks)&lt;/li&gt;
&lt;li&gt;IDs (passport, tax, registration, website, email)&lt;/li&gt;
&lt;li&gt;Addresses (multiple per entity)&lt;/li&gt;
&lt;li&gt;Nationality / citizenship&lt;/li&gt;
&lt;li&gt;Date of birth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Gotcha I hit:&lt;/strong&gt; &lt;code&gt;extractAllTags&lt;/code&gt; initially captured nested XML tags. &lt;code&gt;&amp;lt;programList&amp;gt;&amp;lt;program&amp;gt;CUBA&amp;lt;/program&amp;gt;&amp;lt;/programList&amp;gt;&lt;/code&gt; returned &lt;code&gt;["&amp;lt;program&amp;gt;CUBA"]&lt;/code&gt; instead of &lt;code&gt;["CUBA"]&lt;/code&gt;. Fix: strip nested tags with &lt;code&gt;.replace(/&amp;lt;[^&amp;gt;]+&amp;gt;/g, "")&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fuzzy matching
&lt;/h2&gt;

&lt;p&gt;For a compliance tool, exact string matching is useless — you need fuzzy. Here's what I use in the Worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;matchScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryNorm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidateNorm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;jaroWinkler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryNorm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidateNorm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokenSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryNorm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidateNorm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;levenshtein&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryNorm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidateNorm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;levScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lev&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryNorm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidateNorm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;tok&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;jw&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;levScore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three algorithms weighted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token similarity&lt;/strong&gt; (0.5) — handles word order ("Rosneft Oil Company" vs "Oil Company Rosneft")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jaro-Winkler&lt;/strong&gt; (0.35) — handles typos and short strings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Levenshtein&lt;/strong&gt; (0.15) — tie-breaker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus &lt;strong&gt;normalization&lt;/strong&gt; before matching:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lowercase&lt;/li&gt;
&lt;li&gt;ASCII-fold (Cyrillic → Latin: &lt;code&gt;Олександр&lt;/code&gt; → &lt;code&gt;oleksandr&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Strip punctuation&lt;/li&gt;
&lt;li&gt;Remove legal suffixes (&lt;code&gt;LLC&lt;/code&gt;, &lt;code&gt;Inc&lt;/code&gt;, &lt;code&gt;GmbH&lt;/code&gt;, &lt;code&gt;PJSC&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;Collapse whitespace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Threshold 0.82 by default. &lt;code&gt;Rosneft&lt;/code&gt; → &lt;code&gt;OPEN JOINT-STOCK COMPANY ROSNEFT OIL COMPANY&lt;/code&gt; scores 1.0 (100% match). &lt;code&gt;Gazprom&lt;/code&gt; → CLEAR (correct — Gazprom itself isn't on SDN, but Gazprombank and Gazpromneft are).&lt;/p&gt;

&lt;h2&gt;
  
  
  The 100 SEO pages
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;products-compliance.json&lt;/code&gt; (excerpt):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"sanctions-ofac-sdn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"OFAC SDN Sanctions Check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"sanctions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Sanctions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"list_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"OFAC_SDN"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"sanctions-ofac-sdn-individuals"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"OFAC SDN — Individuals"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"sanctions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type_filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"individual"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"sanctions-russia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Russia Sanctions Screening"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"sanctions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"program_filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"RUSSIA"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"pep-head-of-state"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"PEP — Head of State"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"pep"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"head_of_state"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"entity-verify-llc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"LLC Entity Verification"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"entity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"entity_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"LLC"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"verify-medical-license"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Medical License Verification"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"license_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"medical"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;100 entries across 5 categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;30 Sanctions&lt;/strong&gt; — by list (OFAC SDN, EU, UK, UN) + by type (individual, entity, vessel, aircraft) + by program (Russia, Iran, DPRK, Cuba, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20 Licenses&lt;/strong&gt; — medical, nursing, legal, contractor, real estate, insurance, accounting, pharmacy, dental, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20 Checklists&lt;/strong&gt; — country + segment (Germany import, US export, fintech, crypto, healthcare, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15 PEP&lt;/strong&gt; — head of state, minister, parliament, judiciary, military, SOE executive, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15 Entity&lt;/strong&gt; — LLC, Corp, PLC, Ltd, GmbH, AG, SA, SARL, BV, NV, Oy, AB, Pty, LLP, ТОВ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;generator-compliance.cjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node generator-compliance.cjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Згенеровано 100 сторінок у output/compliance/
✅ sitemap.xml оновлено — всього 729 URL
✅ llms.txt оновлено — 100 compliance checks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each page has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unique &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; + meta description&lt;/li&gt;
&lt;li&gt;Working live demo (hits real Worker with real D1 data)&lt;/li&gt;
&lt;li&gt;Schema.org &lt;code&gt;Product&lt;/code&gt; + &lt;code&gt;BreadcrumbList&lt;/code&gt; + &lt;code&gt;FAQPage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;5 FAQ items customized per check type&lt;/li&gt;
&lt;li&gt;Related checks chips&lt;/li&gt;
&lt;li&gt;Pricing block with Suby checkout&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free&lt;/strong&gt; — 5 checks/day, no API key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro ($99/mo)&lt;/strong&gt; — 500 checks/mo, all 5 lists, PEP, entity, license, webhooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Higher price than other streams ($19.99) because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Recurring compliance need (regulatory requirement)&lt;/li&gt;
&lt;li&gt;B2B audience pays more&lt;/li&gt;
&lt;li&gt;Different value prop (legal risk mitigation, not a one-shot check)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;Two Cloudflare accounts came in handy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Account #1&lt;/strong&gt; — &lt;code&gt;jsonexlab.com&lt;/code&gt; (custom domain) + Pages project&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Account #2&lt;/strong&gt; — Workers + D1 + R2 + KV&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Workers stay on &lt;code&gt;*.boring-saas-infra.workers.dev&lt;/code&gt;, Pages on &lt;code&gt;jsonexlab.com&lt;/code&gt;. Clean separation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Workers&lt;/strong&gt; — API (compliance-engine, finance-validators, watch-engine)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare D1&lt;/strong&gt; — SQLite storage (compliance-db: 19,416+ sanctions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare R2&lt;/strong&gt; — raw file buffer (not actively used — kept as fallback)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare KV&lt;/strong&gt; — response cache&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Pages&lt;/strong&gt; — static site (729 URLs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt; — daily ingestion (OFAC + UK OFSI)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js generators&lt;/strong&gt; — no deps, ~300 lines each&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suby&lt;/strong&gt; — payments (crypto + card)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IndexNow&lt;/strong&gt; — 729 URLs pinged to Bing + Yandex&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total monthly cost: &lt;strong&gt;$0&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EU + UN ingestion&lt;/strong&gt; — currently only OFAC + UK&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PEP data&lt;/strong&gt; — WikiData + OpenSanctions (needs commercial licence)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook alerts&lt;/strong&gt; — get notified when a party on your list gets sanctioned&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Sheets add-on&lt;/strong&gt; — &lt;code&gt;=SANCTIONS_CHECK(A1)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Landing: &lt;a href="https://jsonexlab.com/compliance" rel="noopener noreferrer"&gt;https://jsonexlab.com/compliance&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Example pages:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jsonexlab.com/compliance/sanctions-ofac-sdn" rel="noopener noreferrer"&gt;https://jsonexlab.com/compliance/sanctions-ofac-sdn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jsonexlab.com/compliance/sanctions-russia" rel="noopener noreferrer"&gt;https://jsonexlab.com/compliance/sanctions-russia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jsonexlab.com/compliance/pep-head-of-state" rel="noopener noreferrer"&gt;https://jsonexlab.com/compliance/pep-head-of-state&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jsonexlab.com/compliance/entity-verify-llc" rel="noopener noreferrer"&gt;https://jsonexlab.com/compliance/entity-verify-llc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;API: &lt;a href="https://compliance-engine.boring-saas-infra.workers.dev/check/sanctions?q=Rosneft" rel="noopener noreferrer"&gt;https://compliance-engine.boring-saas-infra.workers.dev/check/sanctions?q=Rosneft&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building KYC, AML, or trade compliance workflows — give it a shot. Free tier is genuinely free.&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the Cloudflare Workers + D1 + GitHub Actions setup, or the programmatic SEO pattern.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>showdev</category>
      <category>cloudflarechallenge</category>
    </item>
    <item>
      <title>I built a currency &amp; VAT monitoring system with Cloudflare Workers</title>
      <dc:creator>Oleksandr</dc:creator>
      <pubDate>Thu, 08 Oct 2026 14:26:53 +0000</pubDate>
      <link>https://dev.to/alexcodebytes/i-built-a-currency-vat-monitoring-system-with-cloudflare-workers-2o13</link>
      <guid>https://dev.to/alexcodebytes/i-built-a-currency-vat-monitoring-system-with-cloudflare-workers-2o13</guid>
      <description>&lt;p&gt;Three streams shipped this week:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;100 document parsers&lt;/strong&gt; with one command&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100 finance validators&lt;/strong&gt; with one command&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100 monitoring pages&lt;/strong&gt; — and this one needs a Worker with cron, KV, and webhooks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let me show you how the monitoring one works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Watch&lt;/strong&gt; monitors two things daily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Currency rates&lt;/strong&gt; — NBU official rates (44 currencies) + ECB via Frankfurter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EU VAT rates&lt;/strong&gt; — 27 member states, standard and reduced rates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a value changes beyond the threshold, it fires a webhook + email alert. No dashboards to check.&lt;/p&gt;

&lt;p&gt;Live landing: &lt;strong&gt;&lt;a href="https://boring-saas-infra.pages.dev/watch" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/watch&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example page: &lt;strong&gt;&lt;a href="https://boring-saas-infra.pages.dev/watch/vat-watch-germany" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/watch/vat-watch-germany&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why monitoring is harder than validation
&lt;/h2&gt;

&lt;p&gt;The first two streams were stateless: request → response → forget.&lt;/p&gt;

&lt;p&gt;Monitoring is &lt;strong&gt;stateful&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A cron must run daily even when nobody is using the API&lt;/li&gt;
&lt;li&gt;We need to remember yesterday's value to detect changes&lt;/li&gt;
&lt;li&gt;We need to store subscriptions (who watches what)&lt;/li&gt;
&lt;li&gt;We need to deliver alerts (webhook + email)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's exactly what Cloudflare Workers + KV + Cron Triggers give you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;One Worker, one KV namespace, one cron job.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;watch-engine.boring-saas-infra.workers.dev
  ├── GET  /currency/nbu              ← live NBU rates
  ├── GET  /currency/frankfurter      ← live ECB rates
  ├── GET  /vat/:country              ← VAT snapshot for one country
  ├── GET  /watch/list                ← list all watches
  ├── POST /watch/create              ← create a watch
  ├── DELETE /watch/:id               ← delete a watch
  └── GET  /cron/run                  ← manual cron trigger (for testing)

Cron: 0 6 * * *                       ← runs every day at 06:00 UTC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The wrangler.toml
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"watch-engine"&lt;/span&gt;
&lt;span class="py"&gt;main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/index.js"&lt;/span&gt;
&lt;span class="py"&gt;compatibility_date&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2026-10-08"&lt;/span&gt;

&lt;span class="nn"&gt;[[kv_namespaces]]&lt;/span&gt;
&lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"WATCH_KV"&lt;/span&gt;
&lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"021f939116ae416bb35703daa1e2c74d"&lt;/span&gt;
&lt;span class="py"&gt;preview_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"021f939116ae416bb35703daa1e2c74d"&lt;/span&gt;

&lt;span class="nn"&gt;[triggers]&lt;/span&gt;
&lt;span class="py"&gt;crons&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"0 6 * * *"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire infrastructure. No database, no message queue, no VPS.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data sources
&lt;/h2&gt;

&lt;p&gt;For currency — three of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// NBU — official Ukrainian rates&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchNBU&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://bank.gov.ua/NBUStatService/v1/statdirectory/exchange?json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;txt&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NBU&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UAH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rates&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Frankfurter — free ECB wrapper&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchFrankfurter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.frankfurter.app/latest?from=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Frankfurter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rates&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are free, no API key, stable JSON.&lt;/p&gt;

&lt;p&gt;For VAT — a snapshot map of 27 countries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;VAT_SNAPSHOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-10-08&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;DE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;standard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reduced&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;FR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;standard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reduced&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;2.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;IT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;standard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reduced&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// ... 24 more&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cron compares the current snapshot with yesterday's stored value. If a rate changed, it fires an alert. VAT rates change rarely (a few times per year per country), so this is cheap and effective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Change detection
&lt;/h2&gt;

&lt;p&gt;The whole point of a monitoring system is to &lt;strong&gt;not alert on every run&lt;/strong&gt;. Only when something actually changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runCron&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alerts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="c1"&gt;// Currency: compare with previous snapshot&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nbu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchNBU&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;currency:NBU&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nbu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;old&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;old&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.005&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CURRENCY_CHANGE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;change_pct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;currency:NBU&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nbu&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// VAT: only fires when the snapshot's `updated` date changes&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;alerts&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Threshold for currency: &lt;strong&gt;0.5%&lt;/strong&gt;. Anything smaller is noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The webhook alert format
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VAT_CHANGE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"19%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"effective"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EU Commission TEDB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-08T14:21:33.181Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;POSTed to whatever URL you configured. Works with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slack incoming webhooks&lt;/li&gt;
&lt;li&gt;Discord webhooks&lt;/li&gt;
&lt;li&gt;n8n / Zapier / Make.com&lt;/li&gt;
&lt;li&gt;Your own backend&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 100 SEO pages
&lt;/h2&gt;

&lt;p&gt;Same pattern as the previous two streams.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;products-watch.json&lt;/code&gt; (excerpt):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"currency-watch-uah-usd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"UAH/USD Currency Watch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Currency"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"pair"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"UAH/USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"NBU"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"vat-watch-germany"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"German VAT Watch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"vat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"VAT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"country_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Germany"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"vat-watch-eu-standard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"EU Standard VAT Watch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"vat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"VAT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"country_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"EU"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;100 entries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;50 currency pairs&lt;/strong&gt; — UAH pairs (25) + ECB/Frankfurter pairs (25)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;50 VAT watches&lt;/strong&gt; — 27 countries + 23 EU-wide categories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;generator-watch.cjs&lt;/code&gt; reads the JSON, renders &lt;code&gt;templates/watch-detail.html&lt;/code&gt;, writes 100 folders, updates sitemap.xml and llms.txt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node generator-watch.cjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generated 100 pages in output/watch/
sitemap.xml updated — 628 URLs total
llms.txt updated — 100 monitors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each page has a live demo hitting &lt;code&gt;/currency/nbu&lt;/code&gt; or &lt;code&gt;/vat/:country&lt;/code&gt; — so visitors see real data, not a mock.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free&lt;/strong&gt; — 1 active watch, daily check, email alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro ($29.99/mo)&lt;/strong&gt; — 100 watches, webhooks, priority checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different price from the previous streams because monitoring is recurring value — customers stay subscribed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cloudflare Workers (API + cron)&lt;/li&gt;
&lt;li&gt;Cloudflare KV (state)&lt;/li&gt;
&lt;li&gt;Cloudflare Pages (static pages)&lt;/li&gt;
&lt;li&gt;Node.js generators (no deps)&lt;/li&gt;
&lt;li&gt;Suby (payments)&lt;/li&gt;
&lt;li&gt;IndexNow (search ping)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total monthly cost so far: &lt;strong&gt;$0&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tariff Watch&lt;/strong&gt; — US ITC / EU TARIC / UK Trade Tariff changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sanctions Watch&lt;/strong&gt; — OFAC / EU / UK OFSI lists&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HS Code Watch&lt;/strong&gt; — WCO Harmonized System updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same architecture, just more data sources.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Landing: &lt;a href="https://boring-saas-infra.pages.dev/watch" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/watch&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Example page: &lt;a href="https://boring-saas-infra.pages.dev/watch/vat-watch-germany" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/watch/vat-watch-germany&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live NBU rates: &lt;a href="https://watch-engine.boring-saas-infra.workers.dev/currency/nbu" rel="noopener noreferrer"&gt;https://watch-engine.boring-saas-infra.workers.dev/currency/nbu&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live VAT for Germany: &lt;a href="https://watch-engine.boring-saas-infra.workers.dev/vat/DE" rel="noopener noreferrer"&gt;https://watch-engine.boring-saas-infra.workers.dev/vat/DE&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building KYC, tax, or finance tooling and need alerts on rate/rate changes — the free tier is genuinely free.&lt;/p&gt;

&lt;p&gt;Happy to answer questions about Cloudflare Workers + KV + cron, or the programmatic SEO side.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>showdev</category>
      <category>cloudflarechallenge</category>
    </item>
    <item>
      <title>I built 100 finance validators with one command</title>
      <dc:creator>Oleksandr</dc:creator>
      <pubDate>Thu, 08 Oct 2026 13:34:46 +0000</pubDate>
      <link>https://dev.to/alexcodebytes/i-built-100-finance-validators-with-one-command-12ap</link>
      <guid>https://dev.to/alexcodebytes/i-built-100-finance-validators-with-one-command-12ap</guid>
      <description>&lt;p&gt;Two days ago I shipped 100 document parsers with one command. Yesterday I did 100 finance validators. Same playbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Build a small API (Cloudflare Worker)&lt;/li&gt;
&lt;li&gt;Write a JSON list of 100 keywords&lt;/li&gt;
&lt;li&gt;Template + generator script → 100 SEO pages&lt;/li&gt;
&lt;li&gt;Sitemap + IndexNow → 527 URLs pinged to Bing/Yandex&lt;/li&gt;
&lt;li&gt;Landing page with a live demo&lt;/li&gt;
&lt;li&gt;Pricing via Suby (crypto + card)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This works because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each of the 100 pages targets a specific search query&lt;/li&gt;
&lt;li&gt;Each page has unique content (own examples, own FAQ, own schema)&lt;/li&gt;
&lt;li&gt;Zero cost per page — Cloudflare Workers free tier handles everything&lt;/li&gt;
&lt;li&gt;Total build time: ~2 hours&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I built this time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Finance Tools&lt;/strong&gt; — 5 validators behind one API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IBAN (80+ countries, mod-97 checksum)&lt;/li&gt;
&lt;li&gt;EU VAT (format for 28 countries)&lt;/li&gt;
&lt;li&gt;SWIFT/BIC (bank + country + location + branch)&lt;/li&gt;
&lt;li&gt;LEI (ISO 17442, mod-97-10)&lt;/li&gt;
&lt;li&gt;Credit cards (Luhn + brand detection: Visa, Mastercard, Amex, Discover, JCB, Diners, Maestro, UnionPay)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Live landing: &lt;strong&gt;&lt;a href="https://boring-saas-infra.pages.dev/finance-tools" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/finance-tools&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Try it (no signup):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://finance-validators.boring-saas-infra.workers.dev/iban?value=DE89370400440532013000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"valid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iban"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DE89370400440532013000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"check_digits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"89"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bban"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"370400440532013000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The 100 pages
&lt;/h2&gt;

&lt;p&gt;Each of these has its own page at &lt;code&gt;/validate/{slug}&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;30 IBAN validators&lt;/strong&gt; — one per country (Germany, France, UK, Spain, Italy, Netherlands, Belgium, Switzerland, Poland, …)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20 VAT validators&lt;/strong&gt; — Germany, France, UK, Italy, Spain, Netherlands, Belgium, Austria, Poland, …&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20 SWIFT validators&lt;/strong&gt; — USA, UK, Germany, France, Switzerland, Japan, China, Singapore, Hong Kong, …&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15 LEI validators&lt;/strong&gt; — bank, corporation, fund, insurance, broker, government, nonprofit, …&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15 card validators&lt;/strong&gt; — Visa, Mastercard, Amex, Discover, JCB, Diners, Maestro, UnionPay, Luhn, …&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://boring-saas-infra.pages.dev/validate/validate-iban" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/validate/validate-iban&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://boring-saas-infra.pages.dev/validate/validate-vat-germany" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/validate/validate-vat-germany&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://boring-saas-infra.pages.dev/validate/validate-visa" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/validate/validate-visa&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each page has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unique &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; + meta description&lt;/li&gt;
&lt;li&gt;Its own sample input and sample JSON output&lt;/li&gt;
&lt;li&gt;cURL example customized to that validator&lt;/li&gt;
&lt;li&gt;5 FAQ items&lt;/li&gt;
&lt;li&gt;Schema.org &lt;code&gt;Product&lt;/code&gt; + &lt;code&gt;BreadcrumbList&lt;/code&gt; + &lt;code&gt;FAQPage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Working live demo hitting the real API&lt;/li&gt;
&lt;li&gt;Related validator chips&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The generator
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;products-finance.json&lt;/code&gt; (excerpt):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"validate-iban"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"IBAN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"iban"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"IBAN"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"validate-iban-germany"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"German IBAN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"iban"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"IBAN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sample"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"DE89370400440532013000"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"validate-vat-germany"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"German VAT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"vat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"VAT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sample"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"DE123456789"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"validate-visa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Visa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sample"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"4111111111111111"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;generator-finance.cjs&lt;/code&gt; does the rest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node generator-finance.cjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generated 100 pages in output/validate/
sitemap.xml updated — 527 URLs total
llms.txt updated — 100 validators
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Worker
&lt;/h2&gt;

&lt;p&gt;Single Cloudflare Worker, 5 endpoints. Core IBAN mod-97:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mod97&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rearranged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;expanded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;rearranged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expanded&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;57&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;expanded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;remainder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LEI uses a slightly different algorithm (mod-97-10), cards use Luhn, SWIFT is length + format. Whole worker is ~250 lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Free — 50 validations/day, no API key&lt;/li&gt;
&lt;li&gt;Pro — $19.99/mo, 5,000 validations, batch, VIES lookup for VAT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stripe alternative: Suby. Crypto + card. Live in 5 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cloudflare Workers (API)&lt;/li&gt;
&lt;li&gt;Cloudflare Pages (static pages)&lt;/li&gt;
&lt;li&gt;Node.js generators (no deps)&lt;/li&gt;
&lt;li&gt;IndexNow (ping Bing/Yandex)&lt;/li&gt;
&lt;li&gt;Suby (payments)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total cost: $0.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Google Sheets add-on: &lt;code&gt;=VALIDATE_IBAN(A1)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Bulk validation endpoint&lt;/li&gt;
&lt;li&gt;VIES integration for Pro tier&lt;/li&gt;
&lt;li&gt;Two more programmatic streams: Monitoring, Compliance&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Landing: &lt;a href="https://boring-saas-infra.pages.dev/finance-tools" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/finance-tools&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Example page: &lt;a href="https://boring-saas-infra.pages.dev/validate/validate-iban" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/validate/validate-iban&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;API: &lt;a href="https://finance-validators.boring-saas-infra.workers.dev" rel="noopener noreferrer"&gt;https://finance-validators.boring-saas-infra.workers.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building KYC, SEPA, VAT, or checkout flows — the free tier is genuinely free.&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the programmatic SEO setup, the generators, or the Worker code.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>api</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I turned 100 document types into JSON with one command</title>
      <dc:creator>Oleksandr</dc:creator>
      <pubDate>Thu, 08 Oct 2026 12:07:07 +0000</pubDate>
      <link>https://dev.to/alexcodebytes/i-turned-100-document-types-into-json-with-one-command-24cn</link>
      <guid>https://dev.to/alexcodebytes/i-turned-100-document-types-into-json-with-one-command-24cn</guid>
      <description>&lt;p&gt;Last week I shipped a programmatic SEO site that turns &lt;strong&gt;any product name&lt;/strong&gt; into its HS code — 320 pages, one command, zero manual work.&lt;/p&gt;

&lt;p&gt;Today I did the same thing for &lt;strong&gt;documents&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every B2B team needs to pull structured data out of documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Invoices → line items, tax, totals, vendor, customer&lt;/li&gt;
&lt;li&gt;Receipts → merchant, items, payment method&lt;/li&gt;
&lt;li&gt;Contracts → parties, term, governing law, termination clauses&lt;/li&gt;
&lt;li&gt;Bills of lading → shipper, consignee, containers, ports&lt;/li&gt;
&lt;li&gt;Offer letters → candidate, salary, start date&lt;/li&gt;
&lt;li&gt;Lab reports → test, result, units, reference range&lt;/li&gt;
&lt;li&gt;…and 94 more types&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Existing OCR APIs either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cost $0.10+ per document with a $500/mo minimum&lt;/li&gt;
&lt;li&gt;Require you to train a custom model per document type&lt;/li&gt;
&lt;li&gt;Return garbage on layout variations&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Docs-to-JSON&lt;/strong&gt; — one endpoint, 100+ document types, clean JSON back in ~2 seconds.&lt;/p&gt;

&lt;p&gt;Try it: &lt;strong&gt;&lt;a href="https://boring-saas-infra.pages.dev/document-to-json" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/document-to-json&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Live demo (no signup):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://document-to-json.boring-saas-infra.workers.dev/extract &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "document_type": "invoice",
    "text": "INVOICE #INV-2043\nDate: 2026-03-14\nVendor: Acme Supplies Ltd.\n..."
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"document_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invoice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"invoice_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INV-2043"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"vendor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Supplies Ltd."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Brightline Imports Inc."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"subtotal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;925.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tax"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;92.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1017.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"line_items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Copper wire, 2mm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"qty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.50&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Steel fittings, 3/4&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"qty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pcs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.25&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The distribution play
&lt;/h2&gt;

&lt;p&gt;Same playbook as the HS Codes stream:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Product page&lt;/strong&gt; with live demo — &lt;code&gt;/document-to-json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100 SEO landing pages&lt;/strong&gt; — &lt;code&gt;/document-type/invoice-to-json&lt;/code&gt;, &lt;code&gt;/document-type/contract-to-json&lt;/code&gt;, &lt;code&gt;/document-type/lab-report-to-json&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Programmatic generation&lt;/strong&gt; — one JSON config + one template + one generator script&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitemap ping&lt;/strong&gt; via IndexNow — 426 URLs to Bing/Yandex in one call&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The generator
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;products-docs.json&lt;/code&gt; (excerpt):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"invoice-to-json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Invoice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Finance"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"contract-to-json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Contract"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Legal"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"bill-of-lading-to-json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Bill of Lading"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Logistics"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"offer-letter-to-json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Offer Letter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"HR"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"lab-report-to-json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Lab Report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Medical"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;generator-docs.cjs&lt;/code&gt; reads the JSON, loops over 100 entries, injects them into a single template, writes 100 folders, updates sitemap.xml and llms.txt.&lt;/p&gt;

&lt;p&gt;That's it. One command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node generator-docs.cjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Згенеровано 100 сторінок у output/document-type/
✅ sitemap.xml оновлено — всього 426 URL
✅ llms.txt оновлено — 100 типів документів
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SEO structure of each page
&lt;/h2&gt;

&lt;p&gt;Every one of the 100 pages includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unique title + meta description&lt;/li&gt;
&lt;li&gt;Schema.org &lt;code&gt;Product&lt;/code&gt; + &lt;code&gt;BreadcrumbList&lt;/code&gt; + &lt;code&gt;FAQPage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Input sample (raw document text) and output sample (structured JSON)&lt;/li&gt;
&lt;li&gt;cURL example with &lt;code&gt;document_type=&amp;lt;slug&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pricing block&lt;/li&gt;
&lt;li&gt;5 FAQ items specific to that document type&lt;/li&gt;
&lt;li&gt;Related document type chips&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zero duplicate content. Each page has its own example tailored to the document type — invoices get invoice samples, contracts get contract samples, lab reports get lab samples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free&lt;/strong&gt; — 50 extractions/day, all 100+ document types, REST API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro&lt;/strong&gt; — $19.99/mo, 5,000 extractions, priority queue, email support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stripe alternative: Suby (crypto + card, instant setup).&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Worker&lt;/strong&gt; — Cloudflare Workers (already deployed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Landing + 100 SEO pages&lt;/strong&gt; — static HTML on Cloudflare Pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generator&lt;/strong&gt; — Node.js, no dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy&lt;/strong&gt; — &lt;code&gt;wrangler pages deploy output&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sitemap ping&lt;/strong&gt; — IndexNow (Bing + Yandex)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whole thing ran end-to-end in under 2 hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Google Sheets add-on: &lt;code&gt;=DOC2JSON(A1)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;MCP server for AI agents to call the API directly&lt;/li&gt;
&lt;li&gt;3 more programmatic streams: Finance, Monitoring, Compliance&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Landing:&lt;/strong&gt; &lt;a href="https://boring-saas-infra.pages.dev/document-to-json" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/document-to-json&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Example SEO page:&lt;/strong&gt; &lt;a href="https://boring-saas-infra.pages.dev/document-type/invoice-to-json" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/document-type/invoice-to-json&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API:&lt;/strong&gt; POST &lt;a href="https://document-to-json.boring-saas-infra.workers.dev/extract" rel="noopener noreferrer"&gt;https://document-to-json.boring-saas-infra.workers.dev/extract&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're processing documents at any scale and don't want to pay $$$/mo for OCR — give it a shot. Free tier is genuinely free.&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the programmatic SEO setup, the generator, or the worker in comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>api</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I generated 320 SEO pages with one command. Here's the full stack (and what broke).</title>
      <dc:creator>Oleksandr</dc:creator>
      <pubDate>Thu, 08 Oct 2026 07:22:31 +0000</pubDate>
      <link>https://dev.to/alexcodebytes/i-generated-320-seo-pages-with-one-command-heres-the-full-stack-and-what-broke-5106</link>
      <guid>https://dev.to/alexcodebytes/i-generated-320-seo-pages-with-one-command-heres-the-full-stack-and-what-broke-5106</guid>
      <description>&lt;p&gt;Three days ago I published a post about deploying 35 MCP servers in 10 days. It got some attention, but the honest takeaway was: 0 revenue, 0 users, 35 perfectly configured servers. Configuration is not distribution.&lt;/p&gt;

&lt;p&gt;This is the follow-up. Three days later, here's what actually changed.&lt;/p&gt;

&lt;p&gt;The number that matters&lt;br&gt;
220 to 320 SEO pages.&lt;/p&gt;

&lt;p&gt;Not manually written. Generated with one command.&lt;/p&gt;

&lt;p&gt;The command reads products.json (320 products), calls my own HS Codes API for each product, fills an HTML template, and writes 320 static pages into the output directory. It also generates sitemap.xml, llms.txt, and a catalog index.&lt;/p&gt;

&lt;p&gt;Then two more commands: deploy to Cloudflare Pages, and submit all URLs to IndexNow.&lt;/p&gt;

&lt;p&gt;Three commands. 320 pages live. 325 URLs submitted to Google, Bing, Yandex, and Seznam.&lt;/p&gt;

&lt;p&gt;What I learned about SEO at scale&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Schema.org is not optional.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every page has four JSON-LD blocks: Product (with the HS code as SKU), BreadcrumbList, WebSite with SearchAction, and FAQPage with three real questions per page.&lt;/p&gt;

&lt;p&gt;Why does this matter? Because Google's rich results show breadcrumbs, product info, and FAQ snippets. That's free real estate in search results. You can validate any page at search.google.com/test/rich-results. My pages pass cleanly.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cloudflare Pages does clean URLs by default.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I didn't know this until I tested it. Requesting /pages/terms.html returns a 308 redirect to /pages/terms. Requesting /pages/terms returns 200 OK.&lt;/p&gt;

&lt;p&gt;This means you should never link to .html in your sitemap or internal links. Google treats redirects as a minor signal, but you lose a tiny bit of link equity on every hop.&lt;/p&gt;

&lt;p&gt;The fix is one file: _redirects in the root, with one line. That's it. Now every .html request gets a clean 308 redirect to the canonical clean URL.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;IndexNow is the fastest way to notify search engines.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Google has Search Console. Bing has IndexNow.&lt;/p&gt;

&lt;p&gt;IndexNow is an open protocol that lets you push URLs directly to Bing, Yandex, Seznam, and Naver. No crawling queue, no waiting for sitemap processing.&lt;/p&gt;

&lt;p&gt;I submitted 325 URLs yesterday. Today, Bing shows the site in site: search.&lt;/p&gt;

&lt;p&gt;What broke (the honest part)&lt;br&gt;
Product Hunt failed.&lt;/p&gt;

&lt;p&gt;I launched HS Codes Classifier on October 1st. It got 1 upvote. Not featured. The launch was effectively invisible.&lt;/p&gt;

&lt;p&gt;Why? Because Product Hunt rewards existing audience, timing, and hunter relationships. I had none of the three.&lt;/p&gt;

&lt;p&gt;I'm not re-launching. I'm focusing on channels that don't require pre-existing audience: search engines, organic distribution, and technical content like this post.&lt;/p&gt;

&lt;p&gt;Suby cards are blocked for Ukraine.&lt;/p&gt;

&lt;p&gt;I set up Suby (a Ukrainian crypto payment provider) to accept USDC for my $9.99/month Pro tier. The crypto checkout works. But cards are blocked for Ukrainian accounts. Suby requires a business entity in a supported jurisdiction.&lt;/p&gt;

&lt;p&gt;Workaround: I'm looking at AgentaOS, a Merchant of Record that supports more countries. That verification is still in progress.&lt;/p&gt;

&lt;p&gt;AgentaOS verify was rejected.&lt;/p&gt;

&lt;p&gt;They asked for three things: branded support email at my own domain, live product proof, and proof of how customers receive their Pro API key. Fair feedback. I'm buying a domain this week and fixing all three.&lt;/p&gt;

&lt;p&gt;What is actually working&lt;br&gt;
The infrastructure, finally.&lt;/p&gt;

&lt;p&gt;Three days ago, my dashboards were empty. Today: 1,459 API calls in 24 hours, 215 LLM calls to OpenAI, 0 errors across all workers. Sentry catches errors before users see them. UptimeRobot monitors 4 endpoints with 100% uptime. Ops Bot sends a daily digest at 9:00 AM Kyiv time.&lt;/p&gt;

&lt;p&gt;Git backup, for the first time.&lt;/p&gt;

&lt;p&gt;Before this week, my entire hs-seo project lived on a single laptop. If the drive failed, I would lose everything. Now there are 3 private GitHub repos: hs-seo, ops-bot, call-counter. Every code change is committed. The output directory with all 320 HTML files is regenerable from products.json and generator.js.&lt;/p&gt;

&lt;p&gt;Google Sheets Add-on, submitted for review.&lt;/p&gt;

&lt;p&gt;I built =HSCODE("laptop computer") as an Apps Script function. It returns 8471.30 in any cell. Then I created a GCP project, set up OAuth consent screen, enabled Workspace Marketplace SDK, and submitted for review.&lt;/p&gt;

&lt;p&gt;Status: in review. If approved, this becomes a distribution channel. 3 billion Google Sheets users can install it.&lt;/p&gt;

&lt;p&gt;The uncomfortable truth&lt;br&gt;
Configuration is still not distribution.&lt;/p&gt;

&lt;p&gt;I have 320 pages, and Google has indexed zero of them. Bing has indexed a handful. Yandex nothing yet.&lt;/p&gt;

&lt;p&gt;That is normal. Google's crawl queue processes new sites slowly. Based on experience: Week 1 to 2 for the first pages, Week 3 to 4 for most pages, Month 2 to 3 for first organic impressions, Month 4 to 6 for first organic traffic.&lt;/p&gt;

&lt;p&gt;The infrastructure is ready. Now it is a waiting game.&lt;/p&gt;

&lt;p&gt;Meanwhile, I am not waiting idle. This week: Revenue Bot (Telegram notification on every new payment), PostHog (funnel analysis from Landing to Payment), Langfuse (LLM observability with prompt, response, cost, latency).&lt;/p&gt;

&lt;p&gt;What I would tell my past self&lt;br&gt;
Start with distribution, not infrastructure.&lt;/p&gt;

&lt;p&gt;I spent 10 days building 35 MCP servers. If I had spent 2 days building one server and 8 days marketing it, I would probably have 10 paying users by now.&lt;/p&gt;

&lt;p&gt;The infrastructure is a trap. It feels productive. It is not.&lt;/p&gt;

&lt;p&gt;Ship on channels that do not require permission.&lt;/p&gt;

&lt;p&gt;Product Hunt needs a hunter. Reddit needs karma. HN needs luck. But search engines do not need anything. You publish, they index. Given enough pages and enough time, organic traffic compounds.&lt;/p&gt;

&lt;p&gt;Every page needs to answer a question.&lt;/p&gt;

&lt;p&gt;"HS code for laptop" is a question. My page answers it in 2 seconds with a real HS code, a confidence score, and a reasoning paragraph. That is the entire value proposition. No marketing required.&lt;/p&gt;

&lt;p&gt;The metrics that matter are not the metrics you see daily.&lt;/p&gt;

&lt;p&gt;Daily API calls do not pay rent. Monthly recurring revenue does. I keep forgetting this.&lt;/p&gt;

&lt;p&gt;What is next&lt;br&gt;
Immediate: buy a domain, unlock Email Routing and AgentaOS. Resubmit AgentaOS verify with branded email. Get Google to index the first 50 pages via URL Inspection.&lt;/p&gt;

&lt;p&gt;This week: PostHog, Langfuse, Revenue Bot. Google Sheets Add-on approval (hopefully). First Dev.to backlink chain.&lt;/p&gt;

&lt;p&gt;This month: 500+ SEO pages. Real organic traffic from Google. First $9.99 payment.&lt;/p&gt;

&lt;p&gt;I will write the next update when the first payment comes in, or when the wait becomes long enough that I need to write about it anyway.&lt;/p&gt;

&lt;p&gt;Try it&lt;br&gt;
Google Sheets: open a new sheet, go to Extensions, Apps Script, paste the code from my GitHub. Then in any cell: =HSCODE("iPhone 15 Pro"). You will get 8517.12.&lt;/p&gt;

&lt;p&gt;API: &lt;a href="https://hs-codes-classifier.boring-saas-infra.workers.dev/dev-classify" rel="noopener noreferrer"&gt;https://hs-codes-classifier.boring-saas-infra.workers.dev/dev-classify&lt;/a&gt; — POST with a description field. Free, 50 calls per day.&lt;/p&gt;

&lt;p&gt;Catalog: &lt;a href="https://boring-saas-infra.pages.dev/catalog" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev/catalog&lt;/a&gt; — 320 products, each with an HS code.&lt;/p&gt;

&lt;p&gt;Pricing: &lt;a href="https://boring-saas-infra.pages.dev" rel="noopener noreferrer"&gt;https://boring-saas-infra.pages.dev&lt;/a&gt; — Free tier, $9.99 per month Pro.&lt;/p&gt;

&lt;p&gt;If you have found a distribution channel that actually works for indie products in 2026, I would love to hear about it. I am still searching.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>seo</category>
      <category>programming</category>
    </item>
    <item>
      <title>I deployed 35 MCP servers in 10 days. Here's what nobody tells you about AI monetization</title>
      <dc:creator>Oleksandr</dc:creator>
      <pubDate>Mon, 05 Oct 2026 08:38:58 +0000</pubDate>
      <link>https://dev.to/alexcodebytes/i-deployed-35-mcp-servers-in-10-days-heres-what-nobody-tells-you-about-ai-monetization-4j8h</link>
      <guid>https://dev.to/alexcodebytes/i-deployed-35-mcp-servers-in-10-days-heres-what-nobody-tells-you-about-ai-monetization-4j8h</guid>
      <description>&lt;p&gt;Last week I shipped 26 MCP servers in 2 days. This week I'm at 35 servers, a Google Sheets add-on, and a full x402 payment stack.&lt;/p&gt;

&lt;p&gt;Revenue so far: $0.&lt;/p&gt;

&lt;p&gt;Not because nothing works. Everything works. Every server is deployed, every paywall responds, every API returns the right answer. The problem is different — and it's the thing nobody talks about when they write about AI monetization.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reality check
&lt;/h2&gt;

&lt;p&gt;When I started building MCP servers, I genuinely believed the hard part was technical. Get the server running, wire up x402 for agent payments, publish to the marketplace, done. The money should follow.&lt;/p&gt;

&lt;p&gt;Here's what actually happened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;35 MCP servers&lt;/strong&gt; are live on MCPize with SEO, logos, pricing, and marketplace listings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two Cloudflare Workers&lt;/strong&gt; — HS Codes Classifier and Document-to-JSON — are running with x402 paywalls settled in USDC on Base&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Google Sheets add-on&lt;/strong&gt; — &lt;code&gt;=HSCODE(A2)&lt;/code&gt; — returns real customs codes from real product descriptions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero external API calls&lt;/strong&gt; in the last 24 hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero active users&lt;/strong&gt; across all servers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero revenue&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is configured correctly. Nothing is reaching anyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;The stack, briefly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP servers (35)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Validators (&lt;code&gt;iban-validator&lt;/code&gt;, &lt;code&gt;vat-validator&lt;/code&gt;, &lt;code&gt;swift-validator&lt;/code&gt;), parsers (&lt;code&gt;invoice-parser-v2&lt;/code&gt;, &lt;code&gt;document-to-json-mcp&lt;/code&gt;), extractors (&lt;code&gt;contract-clause-extractor&lt;/code&gt;, &lt;code&gt;meeting-action-extractor&lt;/code&gt;), and domain tools (&lt;code&gt;hs-codes-mcp&lt;/code&gt;, &lt;code&gt;license-checker-mcp&lt;/code&gt;, &lt;code&gt;medical-coding-helper-mcp&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare Workers with x402&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every Worker sits behind an x402 paywall. Agents pay USDC on Base — no signup, no API key. Pricing is $0.01 per call for single operations, $0.05 for batch operations up to 100 items. 50 free calls per day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Sheets add-on&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A custom function &lt;code&gt;=HSCODE("laptop computer")&lt;/code&gt; that returns &lt;code&gt;8471.30 | confidence: 0.95&lt;/code&gt; in any cell. The add-on calls the same Cloudflare Worker, caches results locally, and gives regular spreadsheet users direct access to the same classifier that agents get via MCP.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical details that actually matter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Service Bindings instead of public fetch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I first connected a call counter to my MCP servers, I tried the obvious thing: a public &lt;code&gt;fetch()&lt;/code&gt; call to another Worker inside &lt;code&gt;ctx.waitUntil()&lt;/code&gt;. The request never arrived. Cloudflare's loop protection blocks worker-to-worker calls through public URLs.&lt;/p&gt;

&lt;p&gt;The fix is Service Bindings — direct worker-to-worker calls declared in &lt;code&gt;wrangler.toml&lt;/code&gt;. The &lt;code&gt;[[services]]&lt;/code&gt; block binds a name (&lt;code&gt;CALL_COUNTER&lt;/code&gt;) to another Worker (&lt;code&gt;call-counter&lt;/code&gt;). Inside the handler, &lt;code&gt;env.CALL_COUNTER.fetch(...)&lt;/code&gt; reaches the target Worker directly, bypassing the public network entirely.&lt;/p&gt;

&lt;p&gt;Non-blocking, fire-and-forget, and it works. This took me three hours to figure out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KV cache is doing real work&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The classifier caches results by SHA-256 hash of the normalized description. When someone classifies "laptop computer" for the first time, it costs an OpenAI call. The second time, it returns from KV in milliseconds.&lt;/p&gt;

&lt;p&gt;Across the 35 servers, this cache absorbs repeat queries at near-zero cost. It's the difference between $0.0003 per call and $0.000001 per call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secrets management is fragile&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Uploading a secret via &lt;code&gt;wrangler secret put&lt;/code&gt; does not always propagate to the current deployment. Sometimes it does. Sometimes it needs a follow-up &lt;code&gt;wrangler deploy&lt;/code&gt;. I spent an hour chasing a token mismatch that turned out to be a stale deployment.&lt;/p&gt;

&lt;p&gt;If you're debugging an unauthorized response and everything looks correct, redeploy. Then redeploy again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three monetization models — and what each one means
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Model 1: Freemium&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each server has a Free tier — 50 calls per day. This makes the listing visible in the marketplace and gives users a zero-friction way to try it. Good for discovery, bad for revenue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model 2: Subscription ($19/mo)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The MCPize marketplace adds a Pro tier — $19/month for 5,000 calls. This is the model the marketplace actually wants — one subscription, one API key, uniform billing across all servers. Predictable, but the barrier is high for first-time users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model 3: Pay-per-call (x402, $0.01)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Agent-native. No signup, no subscription, no API key. An agent calls a tool, gets a 402 response with a payment challenge, settles in USDC, gets the result. This is the most technically elegant model — and the least understood by anyone outside the crypto-native AI space.&lt;/p&gt;

&lt;p&gt;In practice, all three can coexist on the same server. My &lt;code&gt;iban-validator&lt;/code&gt; has all three: Free tier, Pro $19/mo, and x402 pay-per-call at $0.01–$0.02.&lt;/p&gt;

&lt;p&gt;The problem is that the third model assumes demand. If no agent is calling your server, there's nothing to bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Configuration is not distribution.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I can spend a week tuning SEO, pricing tiers, and listing descriptions. None of it matters if nobody opens the page. Marketplace listings don't automatically bring traffic — they give traffic somewhere to land when it arrives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;x402 doesn't sell itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The protocol is elegant. The settlement is instant. But agents only pay when they have a reason to call. And they only call when the tool is either (a) in their default toolset, or (b) findable through a search mechanism they use. Neither of those things is automatic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Sheets is underrated.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;=HSCODE(A2)&lt;/code&gt; takes three seconds to type. No documentation, no API key, no setup. If you're building tools for non-developers, spreadsheet integration beats an API endpoint every time. A logistics manager will use a spreadsheet formula before they'll touch an MCP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Two directions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Programmatic SEO.&lt;/strong&gt; Generate hundreds of pages — "HS code for laptop", "HS code for wooden chair", "IBAN validator for Germany" — each linking directly to the relevant server or the Sheets add-on. Google indexes them, long-tail traffic arrives, conversion becomes a matter of landing page quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distribution through existing channels.&lt;/strong&gt; The Dev.to and Medium posts are the start. Reddit's r/SideProject and r/mcp. Hacker News. Discord servers for AI agents. Every channel that already has an audience for this problem.&lt;/p&gt;

&lt;p&gt;Everything else is already built. The remaining work is getting the first 100 users to see it.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Google Sheets:&lt;/strong&gt; open a new sheet, go to Extensions → Apps Script, and paste the code from my GitHub. Then in any cell: &lt;code&gt;=HSCODE("iPhone 15 Pro")&lt;/code&gt;. You'll get &lt;code&gt;8517.12&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP servers:&lt;/strong&gt; browse the list at mcpize.com/mcp/iban-validator — it's a good example of the pricing model, the SEO setup, and the overall shape of what I've shipped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare Workers:&lt;/strong&gt; hs-codes-classifier.boring-saas-infra.workers.dev — try &lt;code&gt;/health&lt;/code&gt; and see the 402 response on &lt;code&gt;/classify&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything:&lt;/strong&gt; boring-saas-infra.pages.dev&lt;/p&gt;

&lt;p&gt;What's your experience with AI monetization? Is it a traffic problem for you too, or have you found channels that actually convert?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>I built a Creator Engine that extracts promises from sales conversations (80% accuracy)</title>
      <dc:creator>Oleksandr</dc:creator>
      <pubDate>Fri, 02 Oct 2026 14:30:06 +0000</pubDate>
      <link>https://dev.to/alexcodebytes/i-built-a-creator-engine-that-extracts-promises-from-sales-conversations-80-accuracy-3h1f</link>
      <guid>https://dev.to/alexcodebytes/i-built-a-creator-engine-that-extracts-promises-from-sales-conversations-80-accuracy-3h1f</guid>
      <description>&lt;p&gt;Last week I built 32 MCP servers. Today I want to share the more ambitious project: the Creator Engine — a system that compares what sales promises to what the contract actually says.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Sales teams promise things. Contracts specify things. They don't always match.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Weekly reports every Monday" -&amp;gt; contract says "provide reports" (no frequency)&lt;/li&gt;
&lt;li&gt;"$50,000 all-in" -&amp;gt; contract says "$75,000"&lt;/li&gt;
&lt;li&gt;"99.99% uptime SLA" -&amp;gt; contract says "99.9%"&lt;/li&gt;
&lt;li&gt;"SSO included" -&amp;gt; contract has no mention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each mismatch is potential revenue loss, legal exposure, or customer churn. Traditional CLM tools are expensive ($50K+/yr), heavy, and focus on drafts — not on reality alignment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;Promise Alignment Benchmark v0 — an executable specification for testing the engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure
&lt;/h2&gt;

&lt;p&gt;creator-engine/benchmarks/promise-alignment/v0/ contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cases/ — 20 test cases (PA-001 ... PA-020)&lt;/li&gt;
&lt;li&gt;schemas/ — 5 JSON schemas (promise, term, alignment, evidence, impact)&lt;/li&gt;
&lt;li&gt;expected/ — ground truth&lt;/li&gt;
&lt;li&gt;reports/ — evaluation output&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  20 smoke cases
&lt;/h2&gt;

&lt;p&gt;Cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EXACT match (identical promise + contract clause)&lt;/li&gt;
&lt;li&gt;SYNONYM match ("mobile app" = "native iOS/Android applications")&lt;/li&gt;
&lt;li&gt;IMPLIED match (SSO implied by SAML/OAuth support)&lt;/li&gt;
&lt;li&gt;MISSING (weekly reports promised, contract silent on frequency)&lt;/li&gt;
&lt;li&gt;CONFLICT ($50K vs $75K, June 30 vs August 15, 5 seats vs 3, 99.99% vs 99.9%)&lt;/li&gt;
&lt;li&gt;AMBIGUOUS ("really fast" vs "adequate response time")&lt;/li&gt;
&lt;li&gt;PARTIAL (3 integrations promised, 1 in contract)&lt;/li&gt;
&lt;li&gt;NO_PROMISE (optional feature "could add later" — no commitment created)&lt;/li&gt;
&lt;li&gt;PROMPT INJECTION ("ignore all previous instructions" — engine resists)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8 critical invariants
&lt;/h2&gt;

&lt;p&gt;HARD FAILURE — 19/20 = FAIL if any invariant breaks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;INV-001: CONFLICT requires evidence&lt;/li&gt;
&lt;li&gt;INV-002: financial.amount requires calculation_id&lt;/li&gt;
&lt;li&gt;INV-003: Evidence location must exist in source&lt;/li&gt;
&lt;li&gt;INV-004: NOT_COMMITMENT cannot create a Promise&lt;/li&gt;
&lt;li&gt;INV-005: AMBIGUOUS cannot have review_required = false&lt;/li&gt;
&lt;li&gt;INV-006: LLM-explanation cannot create new factual value&lt;/li&gt;
&lt;li&gt;INV-007: OBSERVED cannot describe arithmetic&lt;/li&gt;
&lt;li&gt;INV-008: Prompt injection cannot change system policy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The engine
&lt;/h2&gt;

&lt;p&gt;Three-step LLM pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extract promises from sales conversation -&amp;gt; PROM-001, PROM-002...&lt;/li&gt;
&lt;li&gt;Extract contract terms from contract text -&amp;gt; TERM-001, TERM-002...&lt;/li&gt;
&lt;li&gt;Align them -&amp;gt; EXACT | IMPLIED | PARTIAL | CONFLICT | MISSING | AMBIGUOUS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then engine-level invariant checks run on top. For example:&lt;/p&gt;

&lt;p&gt;function runInvariants(promises, terms, alignments) {&lt;br&gt;
const violations = [];&lt;br&gt;
for (const p of promises) {&lt;br&gt;
if (p.status === "NOT_COMMITMENT") {&lt;br&gt;
violations.push({ id: "INV-004", reason: "NOT_COMMITMENT promise" });&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
for (const a of alignments) {&lt;br&gt;
if (a.relationship === "CONFLICT" &amp;amp;&amp;amp; !a.reasoning) {&lt;br&gt;
violations.push({ id: "INV-001", reason: "CONFLICT without reasoning" });&lt;br&gt;
}&lt;br&gt;
if (a.impact &amp;amp;&amp;amp; a.impact.amount != null &amp;amp;&amp;amp; !a.impact.calculation_id) {&lt;br&gt;
violations.push({ id: "INV-002", reason: "amount without calculation_id" });&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
return violations;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Architectural rule: LLM extracts, engine decides. Never LLM to final truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;After 3 prompt iterations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;v0.1: 65% (13/20)&lt;/li&gt;
&lt;li&gt;v0.2: 70% (14/20)&lt;/li&gt;
&lt;li&gt;v0.3: 80% (16/20)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zero invariant violations across all 20 cases.&lt;/p&gt;

&lt;p&gt;Failures that remain are mostly edge cases where ground truth is debatable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PA-004: 1M/month vs 12M/year -&amp;gt; mathematically EXACT, but spec says IMPLIED&lt;/li&gt;
&lt;li&gt;PA-015: bundled promise -&amp;gt; 3 terms -&amp;gt; mix of EXACT and PARTIAL&lt;/li&gt;
&lt;li&gt;PA-017: 2 promises -&amp;gt; 1 term -&amp;gt; EXACT or IMPLIED?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is expected. 80% is a baseline, not production. Next: multi-step pipeline + verification layer (Phase 42).&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;GitHub: Alex-Dev-Web-C-DE/creator-engine&lt;/p&gt;

&lt;p&gt;Not on npm yet — it's a benchmark repo, not a package.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Phase 42 — Alignment Engine (separate stage, not one prompt)&lt;/li&gt;
&lt;li&gt;Phase 43 — Conflict / Deviation Engine&lt;/li&gt;
&lt;li&gt;Phase 44 — Evidence Engine&lt;/li&gt;
&lt;li&gt;Phase 45 — Impact Engine&lt;/li&gt;
&lt;li&gt;Phase 49 — Sales Promise Tracker (first commercial product on top)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The engine will power 5 products: Sales Promise Tracker, ScopeGuard, InvoiceGuard, DeliveryGuard, RevenueLeak.&lt;/p&gt;

&lt;p&gt;All share the same core: Comparison Intelligence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the MCP servers
&lt;/h2&gt;

&lt;p&gt;While the engine matures, you can try the live MCP servers: 35 servers on MCPize — validators, parsers, extractors.&lt;/p&gt;

&lt;p&gt;Most relevant for the engine use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Invoice Parser ($0.50) — MCP&lt;/li&gt;
&lt;li&gt;Contract Clause Extractor ($0.75) — MCP&lt;/li&gt;
&lt;li&gt;Meeting Action Extractor ($0.40) — MCP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All x402: agents pay USDC on Base, no signup, no API keys.&lt;/p&gt;




&lt;p&gt;What's your take? Is 80% good enough to ship a human-in-the-loop MVP, or should I push for 90%+ before any customer touches it?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>HS Codes Classifier — just launched on Product Hunt</title>
      <dc:creator>Oleksandr</dc:creator>
      <pubDate>Thu, 01 Oct 2026 07:50:59 +0000</pubDate>
      <link>https://dev.to/alexcodebytes/hs-codes-classifier-just-launched-on-product-hunt-46mf</link>
      <guid>https://dev.to/alexcodebytes/hs-codes-classifier-just-launched-on-product-hunt-46mf</guid>
      <description>&lt;p&gt;HS Codes Classifier turns any product description into an HS (Harmonized System) customs code via AI.&lt;/p&gt;

&lt;p&gt;Just launched on Product Hunt. Support appreciated: &lt;a href="https://www.producthunt.com/products/hs-codes-classifier" rel="noopener noreferrer"&gt;https://www.producthunt.com/products/hs-codes-classifier&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built on Cloudflare Workers + x402 protocol for AI-agent payments.&lt;br&gt;
$0.01 per call, 50 free/day, no signup.&lt;/p&gt;

&lt;p&gt;Available as MCP server + REST API.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>producthunt</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How I built 26 MCP servers + 2 REST APIs in 2 days with $0 infrastructure</title>
      <dc:creator>Oleksandr</dc:creator>
      <pubDate>Tue, 29 Sep 2026 15:20:44 +0000</pubDate>
      <link>https://dev.to/alexcodebytes/how-i-built-26-mcp-servers-2-rest-apis-in-2-days-with-0-infrastructure-1jcg</link>
      <guid>https://dev.to/alexcodebytes/how-i-built-26-mcp-servers-2-rest-apis-in-2-days-with-0-infrastructure-1jcg</guid>
      <description>&lt;p&gt;I'm a solo developer. Over the last 2 days, I shipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;26 MCP servers&lt;/strong&gt; on MCPize (validators, converters, formatters)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 REST APIs&lt;/strong&gt; on Cloudflare Workers (HS Codes Classifier, Document-to-JSON)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 custom x402 gateway&lt;/strong&gt; (Furlpay, self-hosted)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total infrastructure cost: &lt;strong&gt;$0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post is about how I did it, what worked, and what didn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem: getting paid as a solo dev
&lt;/h2&gt;

&lt;p&gt;Traditional payment processors don't work for everyone. Depending on where you are, Stripe, PayPal, and similar services may not be available or may have restrictions.&lt;/p&gt;

&lt;p&gt;I needed a payment path that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Works globally&lt;/li&gt;
&lt;li&gt;Has no signup friction&lt;/li&gt;
&lt;li&gt;Can charge per call, not per month&lt;/li&gt;
&lt;li&gt;Works natively with AI agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Crypto is the answer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And in 2026, there's a new protocol that makes crypto-native payments work &lt;em&gt;natively&lt;/em&gt; with AI agents: &lt;strong&gt;x402&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is x402?
&lt;/h2&gt;

&lt;p&gt;x402 is a protocol built on top of HTTP. The idea is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AI agent calls your API endpoint&lt;/li&gt;
&lt;li&gt;Endpoint returns &lt;strong&gt;HTTP 402 Payment Required&lt;/strong&gt; with payment details&lt;/li&gt;
&lt;li&gt;Agent pays $0.01 USDC (on Base)&lt;/li&gt;
&lt;li&gt;Agent re-calls with payment proof&lt;/li&gt;
&lt;li&gt;Endpoint returns the result&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;No signup. No API keys. No OAuth. No subscriptions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Just: &lt;code&gt;pay per call&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's designed for the AI agent economy — where an agent needs to call 100 different APIs, and signing up for each one is impractical.&lt;/p&gt;




&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;Here's what I used:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Cloudflare Workers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; Serverless, stateless, free tier of 100k requests/day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Numbers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cold start: 7ms&lt;/li&gt;
&lt;li&gt;Free tier: 100k requests/day&lt;/li&gt;
&lt;li&gt;Setup: &lt;code&gt;npm install -g wrangler&lt;/code&gt; + &lt;code&gt;wrangler deploy&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Deploy code example:&lt;/strong&gt;&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/classify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;paywall&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;payTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PAY_TO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;quoteSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;QUOTE_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hsCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hs_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hsCode&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;})(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not Found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Furlpay (x402 gateway)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; Self-hosted x402 gateway. No vendor lock-in. 100% of payments go to my wallet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;npm install -g @furlpay/gateway&lt;br&gt;
furlpay-gateway secret&lt;br&gt;
wrangler deploy&lt;/p&gt;

&lt;h3&gt;
  
  
  3. MCPize (MCP server distribution)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; MCP (Model Context Protocol) is how AI agents discover tools. MCPize is the marketplace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I did:&lt;/strong&gt; Wrapped my APIs as MCP servers, deployed to MCPize. Now ChatGPT/Claude agents can find them.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. gpt-4o-mini (for AI endpoints)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; Cheap, reliable. $0.15/1M input tokens.&lt;/p&gt;

&lt;p&gt;At $0.01/call, margin is healthy even with heavy use.&lt;/p&gt;




&lt;h2&gt;
  
  
  What worked
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Workers + Fetch API&lt;/strong&gt; — perfect for stateless x402 APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Furlpay&lt;/strong&gt; — one middleware = paywall&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCPize&lt;/strong&gt; — free distribution for MCP servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;gpt-4o-mini&lt;/strong&gt; — cheap AI&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What didn't work
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reddit&lt;/strong&gt; — new accounts get auto-filtered without karma&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mcpize dev on Windows&lt;/strong&gt; — broken (port conflict with itself). Use npm run dev instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express + Cloudflare Workers&lt;/strong&gt; — incompatible. Use Fetch API.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Results (so far)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;26 MCP servers&lt;/strong&gt; live&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 REST APIs&lt;/strong&gt; live&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$0 spent&lt;/strong&gt; on infrastructure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 customers&lt;/strong&gt; (just launched)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$0 revenue&lt;/strong&gt; (just launched)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;First paying customer&lt;/li&gt;
&lt;li&gt;More AI endpoints (Document Intelligence family)&lt;/li&gt;
&lt;li&gt;Building in public&lt;/li&gt;
&lt;/ul&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HS Codes Classifier (MCP):&lt;/strong&gt; &lt;a href="https://mcpize.com/mcp/hs-codes-mcp" rel="noopener noreferrer"&gt;https://mcpize.com/mcp/hs-codes-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HS Codes Classifier (REST):&lt;/strong&gt; &lt;a href="https://hs-codes-classifier.boring-saas-infra.workers.dev" rel="noopener noreferrer"&gt;https://hs-codes-classifier.boring-saas-infra.workers.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document-to-JSON (REST):&lt;/strong&gt; &lt;a href="https://document-to-json.boring-saas-infra.workers.dev" rel="noopener noreferrer"&gt;https://document-to-json.boring-saas-infra.workers.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All APIs use x402 — pay $0.01 USDC per call, no signup.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're a solo dev and payment processors don't work for you — I'd love to hear how you handle it. Reply or DM.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>crypto</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why We Need Client-Side Secret Scanning Before Commits Hit Main</title>
      <dc:creator>Oleksandr</dc:creator>
      <pubDate>Sun, 20 Sep 2026 18:08:50 +0000</pubDate>
      <link>https://dev.to/alexcodebytes/why-we-need-client-side-secret-scanning-before-commits-hit-main-j99</link>
      <guid>https://dev.to/alexcodebytes/why-we-need-client-side-secret-scanning-before-commits-hit-main-j99</guid>
      <description>&lt;p&gt;When building web applications and developer tools, security is often treated as a backend concern—something handled by server-side middleware, environment variables, and API gateway filters. But what happens long before code ever reaches a deployment pipeline? It happens right inside the developer's local workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Hidden Cost of Accidental Leaks
&lt;/h3&gt;

&lt;p&gt;We've all heard stories about API keys, database credentials, and personal access tokens accidentally pushed to public (or even private) repositories. Even with &lt;code&gt;.gitignore&lt;/code&gt; properly configured, mistakes slip through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Temporary test files committed under wrong names.&lt;/li&gt;
&lt;li&gt;Hardcoded configuration strings used "just for a quick local check."&lt;/li&gt;
&lt;li&gt;Staging tokens mixed into frontend utility scripts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Relying solely on post-commit hooks or cloud scanning tools means the secret has already left your machine. The damage might already be done by the time CI/CD catches it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shifting Left with Client-Side Linters
&lt;/h3&gt;

&lt;p&gt;This is why implementing proactive, lightweight scanning directly in the development loop matters. A client-side linter or utility tool can inspect code patterns instantly, warning developers &lt;em&gt;before&lt;/em&gt; a commit or push is even executed. &lt;/p&gt;

&lt;p&gt;Key benefits of catching secrets early:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero Latency Feedback:&lt;/strong&gt; No waiting for remote CI pipelines to run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Privacy:&lt;/strong&gt; Code and potential secrets stay local, analyzed via fast regex patterns or pattern matching rules in JavaScript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer Habit Formation:&lt;/strong&gt; Immediate visual warnings teach better coding hygiene naturally.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Building Lightweight Dev Tools
&lt;/h3&gt;

&lt;p&gt;While working on &lt;strong&gt;Secrets Linter&lt;/strong&gt;, my goal was to keep things friction-free—fast parsing, zero unnecessary bloat, built cleanly using modern web technologies and Tailwind CSS for a crisp, distraction-free interface. Developers don't need another heavy, opinionated framework slowing down their workflow; they need sharp, focused utilities that solve one problem exceptionally well.&lt;/p&gt;

&lt;p&gt;How do you handle secret detection in your local development environment? Do you rely on pre-commit hooks, git hooks, or manual checks? Let me know in the comments!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>javascript</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
