220,000+ OpenClaw Instances Are Exposed. Here’s How to Check Yours.

Security researchers have been scanning for exposed OpenClaw instances since January 2026. The numbers vary by methodology: Penligent found over 220,000, SecurityScorecard identified 135,000, Censys tracked growth from 1,000 to 21,000+ in a single week. Microsoft’s security blog concluded that “for most environments, the appropriate decision may be not to deploy it.”

Most of these instances are running without TLS. Many are still vulnerable to ClawJacked (CVE-2026-25253, CVSS 8.8), which allowed any webpage you visited to silently brute-force the gateway token over localhost with no rate limiting.

I’ve been reviewing public configs and deployment guides. Three misconfigs show up in the majority of them, and they’re all fixable in minutes.

The exposure surface

OpenClaw’s default config binds the gateway to 0.0.0.0:18789. If you install it on a VPS and don’t touch the network settings, the gateway is public. There’s no warning during setup. The docs mention it, but not where people look.

What this means in practice:

  • Gateway token brute-force. ClawJacked (CVE-2026-25253, CVSS 8.8) allowed any webpage you visited to brute-force the gateway token. No rate limiting. No CORS. Patched in v2026.1.24-1, but the fix requires updating. Persistent services that nobody actively maintains tend to drift.

  • Unencrypted traffic. Without TLS, everything between the browser and the agent travels in plaintext. API keys, model responses, user data. On a shared network, that’s trivial to intercept.

  • Supply chain. ClawHavoc in January 2026: researchers found 824 malicious skills on ClawHub out of roughly 10,700 total. Clawdex, the main community scanner, was catching under 10% of them (Oathe’s independent audit confirmed this). If your instance auto-installs recommended skills, you’re trusting a supply chain that has already been compromised.

The three configs most people get wrong

I’ve reviewed hundreds of openclaw.json files from public repos, Docker Compose setups, and deployment guides. Three misconfigs show up in the majority of them.

1. Gateway binding

Default: binds to all interfaces (0.0.0.0:18789)
What it should be: loopback (127.0.0.1 only)

If you’re running behind a reverse proxy or tunnel, the gateway should never be reachable directly. The bind key accepts loopback, lan, tailnet, or custom.

{
  "gateway": {
    "bind": "loopback",
    "port": 18789
  }
}

2. The three proxy/tunnel flags

If you’re running behind a reverse proxy or Cloudflare Tunnel, you need three flags under gateway.controlUi. Not at the root level. Not as dot-notation keys. Nested JSON only.

{
  "gateway": {
    "controlUi": {
      "dangerouslyDisableDeviceAuth": true,
      "dangerouslyAllowHostHeaderOriginFallback": true,
      "allowInsecureAuth": true
    }
  }
}

Most guides mention two of these. The third, dangerouslyDisableDeviceAuth, is the one that causes the disconnected (1000): no reason error in the browser. It disables CLI-based device pairing, which only works with local machine access. Behind a proxy, there’s no local CLI, so the auth loop times out silently.

The flag names sound dangerous. They’re not, if you have a proxy handling auth in front. Without a proxy, don’t set them.

3. TLS termination

If your reverse proxy handles TLS (it should), the gateway can run without its own certificate. But the connection between proxy and gateway must stay on loopback. If the proxy runs on a different host than the gateway, you need TLS on both hops.

[Browser] →HTTPS→ [Proxy (TLS)] →HTTP→ [Gateway (127.0.0.1:18789)]

This only works when proxy and gateway share the same host. Most single-VPS setups qualify.

How to check yours

Run these checks on any instance:

1. Port exposure check:

# From outside your network
nmap -p 18789 your-server-ip
# If it shows "open", your gateway is public

2. Config audit:

# On the server
cat ~/.openclaw/openclaw.json | grep -E "bind|dangerously|allowInsecure"

3. Version check:

openclaw --version
# Anything before v2026.1.24-1 is vulnerable to ClawJacked

I also built a free scanner that runs these checks and a few more (skill supply chain, known CVE patterns, config analysis): https://vesselofone.com/tools/security-check. It runs against your instance URL and returns a report. No data stored, no signup required.

TL;DR

  • Set gateway.bind to "loopback", not the default (all interfaces)
  • Set the three gateway.controlUi flags if behind a proxy or tunnel
  • TLS terminate at the proxy, keep gateway on loopback
  • Update to v2026.1.24-1 or later (ClawJacked fix)
  • Audit installed skills (ClawHavoc found 824 malicious ones on ClawHub)
  • Run a port scan from outside your network to verify nothing is exposed

Most of these take five minutes. The gap between “it works” and “it’s not actively exploitable” is three config lines.

Best GEO Tools to Improve Your Visibility in AI Search

Search is evolving fast. Users are no longer just clicking links. They are asking questions in AI systems like ChatGPT and Perplexity and expecting direct, summarized answers.

If your website is not optimized for these systems, you are missing a growing share of visibility.

This is where Generative Engine Optimization (GEO) comes in.

GEO focuses on making your content understandable, structured, and retrievable by AI systems. It is not just about rankings anymore. It is about being selected, cited, and reused.

Below is a curated list of the top 10 GEO tools that actually help improve your visibility in AI-driven search.

What Makes a Strong GEO Tool?

A tool is only valuable if it directly impacts how AI interprets your content.

The best GEO tools help you:

  • Improve semantic clarity and entity recognition
  • Implement and validate structured data
  • Detect technical blockers for AI crawlers
  • Optimize content for question-based retrieval
  • Increase your chances of being included in AI-generated answers

1. GEO Checker by Scalevise

The fastest way to understand how your website performs in AI search environments.

GEO Checker is specifically built for GEO and focuses on how systems like ChatGPT and Perplexity interpret your content.

Why it stands out

  • Detects missing GEO signals
  • Analyzes entity structure and semantic gaps
  • Identifies structured data issues
  • Provides actionable recommendations

Use case

Many websites rank well but fail in AI visibility. GEO Checker by Scalevise shows exactly why and how to fix it.

'GEO Checker Scalevise'

👉 https://scalevise.com/ai-visibility-geo-checker

2. Screaming Frog SEO Spider

A technical foundation tool that remains essential.

Why it matters

  • Full website crawl
  • Detects broken links and structural issues
  • Analyzes metadata and hierarchy
  • Exports data for deeper GEO analysis

'Screaming Frog'

👉 https://www.screamingfrog.co.uk/seo-spider/

3. InLinks

An entity-focused SEO tool that aligns perfectly with GEO.

Key strengths

  • Entity detection and optimization
  • Semantic internal linking
  • Topic authority building
  • Content gap analysis

'InLinks GEO'

👉 https://inlinks.net/

4. Schema App

A powerful platform for managing structured data at scale.

What it does

  • Automated schema generation
  • Advanced schema support
  • Centralized management
  • Validation and monitoring

'Schema App SEO'

👉 https://www.schemaapp.com/

5. AlsoAsked

A tool that visualizes how questions connect across topics.

Why it is useful

  • Maps question clusters
  • Helps structure content for AI answers
  • Reveals user intent
  • Supports FAQ optimization

'AlsoAsked GEO'

👉 https://alsoasked.com/

6. Surfer SEO

While traditionally an SEO tool, Surfer plays a role in GEO through content structuring.

GEO relevance

  • Optimizes content structure
  • Improves topical coverage
  • Aligns content with search intent
  • Helps build semantically rich pages

'Surfer SEO'

👉 https://surferseo.com/

7. Frase

Frase focuses on content optimization based on questions and intent.

Why it matters

  • Generates content briefs based on queries
  • Optimizes for question-based search
  • Helps align content with AI answer formats
  • Improves contextual relevance

'Frase AI'

👉 https://www.frase.io/

8. Clearscope

A premium content optimization tool focused on relevance and depth.

Strengths

  • Improves content comprehensiveness
  • Enhances semantic coverage
  • Helps structure high-authority content
  • Supports entity-rich writing

'Clearscope'

👉 https://www.clearscope.io/

9. MarketMuse

A strategic content planning tool that helps build topical authority.

Why it is relevant

  • Identifies content gaps
  • Builds topic clusters
  • Strengthens authority signals
  • Improves long-term GEO performance

'MarketMuse GEO'

👉 https://www.marketmuse.com/

10. Ahrefs

Not a GEO tool by design, but still critical for visibility strategy.

GEO contribution

  • Identifies high-value topics
  • Tracks content performance
  • Provides backlink insights
  • Supports authority building

'Ahrefs GEO'

👉 https://ahrefs.com/

How to Combine These Tools (Practical Workflow)

Using these tools in isolation limits your results. The real value comes from combining them.

A practical GEO workflow:

  1. Start with GeoScan to identify AI visibility gaps
  2. Fix technical issues using Screaming Frog
  3. Optimize entities and linking with InLinks
  4. Implement structured data via Schema App
  5. Expand content using AlsoAsked
  6. Refine content using Surfer or Frase
  7. Strengthen authority using Clearscope and MarketMuse
  8. Monitor performance with Ahrefs

The Reality of AI Search

You can rank high in Google and still be invisible in AI-generated answers.

This is already happening.

GEO is not replacing SEO, but it is becoming a critical layer on top of it.

Companies that adapt early will dominate AI-driven discovery.

Final Thoughts

The shift is clear.

Search is no longer just about rankings. It is about being selected by machines.

If you want to stay competitive, you need to adapt your strategy and your tooling.

Start with a scan.

👉 https://scalevise.com/ai-visibility-geo-checker

Fix what matters. Build from there.

Because visibility in AI search is not guaranteed. It is

Sonic Has a Free API: Add Lightning-Fast Full-Text Search to Your App Without Running Elasticsearch

Your app needs search. You look at Elasticsearch: 4GB RAM minimum, complex cluster configuration, a JVM that eats memory for breakfast, and a query DSL that requires a PhD to understand. Two weeks later you’re still tuning settings. Your 10,000-item dataset doesn’t need a distributed cluster — it needs Sonic.

What Sonic Actually Does

Sonic is a lightweight, schema-less search backend written in Rust. It’s designed as a lean alternative to Elasticsearch for applications that need fast full-text search without the operational complexity. Where Elasticsearch requires gigabytes of RAM and complex cluster setup, Sonic runs in under 30MB of memory.

Sonic exposes a simple line protocol over TCP — similar to Redis, not HTTP. You push text in, you query text out. No JSON query DSL, no mappings to configure, no index settings to tune. It handles typos with phonetic matching, supports multiple collections and buckets, and is fast enough for real-time search-as-you-type.

The trade-off to understand: Sonic is a search index, not a database. It stores text for search but you retrieve full records from your actual database using the IDs Sonic returns. This separation is the right architecture for most applications.

Quick Start: Run Sonic Locally

docker run -d --name sonic 
  -p 1491:1491 
  -v sonic_data:/var/lib/sonic/store 
  valeriansaliou/sonic:latest

Install and use the Node.js client:

npm install sonic-channel
const { Ingest, Search } = require("sonic-channel");

const ingestChannel = new Ingest({
  host: "localhost",
  port: 1491,
  auth: "SecretPassword"
});

await ingestChannel.connect();

// Push documents: collection, bucket, object_id, searchable_text
await ingestChannel.push("articles", "default", "article:1",
  "How to build a REST API with Node.js and Express");
await ingestChannel.push("articles", "default", "article:2",
  "Building real-time apps with WebSockets and React");
await ingestChannel.push("articles", "default", "article:3",
  "Rust programming language performance benchmarks");

const searchChannel = new Search({
  host: "localhost",
  port: 1491,
  auth: "SecretPassword"
});

await searchChannel.connect();

const results = await searchChannel.query("articles", "default", "REST API Node");
console.log(results); // ["article:1"]

// Fetch full records using the returned IDs
const articles = await db.articles.findMany({ where: { id: { in: results } } });

3 Practical Use Cases

1. Real-Time Search-as-You-Type

Sonic is fast enough for keypress-level queries — typically under 5ms:

app.get('/api/search', async (req, res) => {
  const { q } = req.query;
  if (!q || q.length < 2) return res.json([]);

  const ids = await searchChannel.query("products", "default", q, { limit: 10 });
  if (ids.length === 0) return res.json([]);

  const products = await db.products.findMany({
    where: { id: { in: ids } },
    select: { id: true, name: true, price: true, image: true }
  });

  res.json(products);
});

10ms total response time for search-as-you-type, even with hundreds of thousands of products indexed.

2. Multi-Collection Search with Buckets

Sonic supports separate collections for different content types:

async function indexContent(item) {
  switch (item.type) {
    case 'article':
      await ingestChannel.push("articles", "en", `article:${item.id}`,
        `${item.title} ${item.body} ${item.tags.join(' ')}`);
      break;
    case 'product':
      // Use buckets to separate by category
      await ingestChannel.push("products", `cat:${item.category}`, `product:${item.id}`,
        `${item.name} ${item.description}`);
      break;
    case 'user':
      await ingestChannel.push("users", "default", `user:${item.id}`,
        `${item.name} ${item.email} ${item.bio || ''}`);
      break;
  }
}

// Search only electronics products
const ids = await searchChannel.query("products", "cat:electronics", searchQuery);

3. Autocomplete with Typo Tolerance

Sonic has built-in suggest/autocomplete and handles common typos automatically:

// User types "prog" — get completions
const suggestions = await searchChannel.suggest("tutorials", "default", "prog");
// ["programming", "program", "progress", "progressive"]

// Typo tolerance: "progrmaming" still finds "programming" results
const results = await searchChannel.query("tutorials", "default", "progrmaming");
// Returns results — phonetic matching handles it automatically

No fuzziness settings to tune, no analyzers to configure. It just works.

Why This Matters

For 90% of applications, Elasticsearch is enormous overkill. Sonic gives you full-text search that actually works — typo tolerance, autocomplete, multi-collection — for about 1% of the operational cost. It runs on a $5/month VPS alongside your main application.

The Rust implementation means it’s genuinely fast: benchmarks show Sonic handling 10,000+ queries per second on modest hardware. For applications under 100 million documents, you’ll likely never hit its limits.

The real win is operational simplicity. No JVM tuning, no shard configuration, no index lifecycle management. You push text in, you query text out. Sometimes simple really is better — and when it’s also fast and memory-efficient, there’s no reason to reach for something heavier.

Need custom data extraction or web scraping solutions? I build production-grade scrapers and data pipelines. Check out my Apify actors or email me at [email protected] for custom projects.

Follow me for more free API discoveries every week!

TCP: Why the Internet Works Even When It’s Broken

TL;DR: TCP is how we send big files over a mess of unreliable cables. It chops data into numbered chunks and won’t stop nagging the receiver until every single piece is accounted for. If a packet gets dropped or a router chokes, TCP just resends it until the job is done.

The internet is a series of physical cables and aging hardware that is constantly failing. Between your computer and a server, there are dozens of points of failure where data can be lost, corrupted, or just dropped because a router got too hot. TCP (Transmission Control Protocol) is the protocol that keeps your data from becoming a corrupted mess by assuming the network is going to fail.

How does TCP handle data loss on an unreliable network?

TCP handles data loss by breaking large files into small chunks and requiring a formal acknowledgment for every single one. Instead of hoping a 1GB file arrives in one piece, it treats the network as a “best-effort” medium and takes full responsibility for verifying that every byte landed safely.

Sending a massive file over a physical wire is a gamble. If one bit flips or a single packet hits a congested router and gets dropped, the whole transmission is ruined. TCP doesn’t gamble. It puts every page of your data into its own envelope, labels it with a page number, and sends it out. Then, it waits for a phone call. If the recipient says they got pages 1, 2, and 4, the sender knows page 3 was lost in the mail. The sender doesn’t have to guess; they just grab a copy of page 3 and send it again until the recipient confirms they have it.

What is the step-by-step process of TCP data transmission?

TCP transmission follows a strict loop of segmentation, sequencing, and verification. It transforms a raw, unreliable stream of bits into a structured conversation between two machines to ensure the final payload is identical to the source.

Phase What Happens Why it Matters
Segmentation Chop the payload into MTU-sized segments. Keeps chunks small enough for hardware to handle without choking.
Sequencing Stamp every packet with a sequence ID. Lets the receiver rebuild the file in order, even if packets arrive late.
Transmission Push segments onto the physical wire. This is the “unreliable” part where cables and routers take over.
ACK Loop Wait for Acknowledgment (ACK) signals. The only way the sender knows the data actually arrived.
Retransmit Resend segments if an ACK times out. Fixes network errors automatically without the user ever noticing.

Why does TCP use sequence numbers for every packet?

Sequence numbers act as the index that allows the receiver to reassemble data in the correct order and identify gaps. Without these numbers, the receiver would have no way of knowing if a packet was missing or if the data arrived out of sequence.

Think about a high-res image being sent across the country. Packet #50 might take a faster route through the network and arrive before packet #49. Without sequence numbers, your computer would just stick the bits together in the order they arrived, and the image would look like static. The sequence number tells the OS exactly where that chunk belongs in the final file, allowing it to buffer early arrivals until the missing gaps are filled.

What happens when a TCP acknowledgment is never received?

When an acknowledgment (ACK) doesn’t return within a specific window, the sender assumes the packet died in transit and triggers a retransmission. It keeps the data in a local buffer and refuses to clear it until it’s 100% sure the other side has it.

This is the core of TCP’s reliability. If you are pushing code to a server and the connection flutters, TCP doesn’t just let that chunk of data vanish. It will keep retrying that specific sequence ID until the server finally responds with a green light. It’s a persistent, nagging mechanism that ensures the integrity of the data at the cost of some overhead.

FAQ

What is the cost of TCP reliability?
The primary cost is high-latency overhead. Because every packet requires an acknowledgment (the ACK), and there is a “handshake” to start the connection, TCP is naturally slower than protocols that just fire data into the void without checking if it landed.

Why use TCP over UDP?
You use TCP when accuracy is non-negotiable, like loading a website, sending an email, or downloading software. You use UDP when speed is more important than a few dropped packets, like in a Zoom call or a competitive multiplayer game where a momentary glitch is better than the whole stream pausing to wait for a retransmission.

Does TCP ever give up on resending data?
Yes. While TCP is persistent, it isn’t infinite. If it fails to get an ACK after a set number of retries or a specific timeout period, it will eventually “reset” the connection and signal to the application that the network path is dead.

The Skill Atrophy Crisis: How AI Is Quietly De-Skilling Developers in 2026

In 2026, AI coding tools have become the default co-pilot for nearly every developer. Adoption sits at 84%, according to the Stack Overflow Developer Survey 2025. Teams ship more features, close tickets faster, and celebrate record velocity metrics. Yet beneath the surface, something troubling is happening: core engineering skills are eroding at an alarming rate.
This is the Skill Atrophy Crisis — the silent degradation of fundamental developer capabilities caused by over-reliance on AI-generated code. What began as a productivity revolution is now creating a generation of developers who can prompt effectively but struggle to think architecturally, debug deeply, or design systems from first principles.
The Data Is Undeniable
The evidence is mounting across multiple independent studies:

Stack Overflow Developer Survey 2025 (49,000+ responses): While 84% use AI tools, only 29–33% fully trust the output. More tellingly, 45% report that debugging AI code takes more time than writing it manually, and 66% call “almost right” solutions their biggest daily pain point.
Sonar’s 2026 State of Code Developer Survey (1,100+ engineers): 88% of developers report negative impacts from AI on code quality, with 53% noting that AI produces code that “looks correct but is unreliable.” Crucially, 40% admit they now spend less time on deep problem-solving and more time on verification and cleanup.
Veracode 2026 GenAI Security Report: AI-generated code introduces vulnerabilities in 45% of cases (up to 72% in Java), forcing senior engineers into constant auditing roles rather than mentorship or innovation.
Chainguard Engineering Reality Report 2026: Developers now spend only 16% of their week writing new code — the work they find most rewarding — while 84% is consumed by maintenance, debt repayment, and fixing AI output.

These numbers point to a structural shift: AI is accelerating output but slowing capability growth. Junior and mid-level developers, in particular, are showing measurable gaps in fundamentals such as systems design, memory management, concurrency, and security architecture.
What Skill Atrophy Actually Looks Like
Developers experiencing atrophy typically exhibit these patterns:

They can generate a full microservice in minutes but struggle to explain the underlying trade-offs between synchronous vs. asynchronous communication or eventual consistency.
They rely on AI to suggest data structures but can’t manually optimize a hot path under production load.
They produce working prototypes quickly but create fragile, tightly coupled systems that resist scaling or refactoring.
When AI is unavailable (offline flights, restricted environments, or interview settings), their productivity collapses.

This isn’t laziness — it’s a natural consequence of cognitive offloading. Just as GPS weakened our spatial memory, AI is weakening our algorithmic intuition and architectural judgment.
Psychologically, the dopamine hit from rapid progress reinforces the habit. Teams reward velocity metrics (PR count, story points) while under-measuring long-term indicators like code maintainability, onboarding time for new hires, or incident resolution depth.
Why This Crisis Is Unique to 2026
Three converging forces make 2026 the inflection point:

Massive Context Windows + Agentic Tools — Models like Claude 4.6 and GPT-5.2 can ingest entire codebases, yet developers still provide incomplete or outdated context, leading to plausible but architecturally wrong suggestions.
The Junior-to-Senior Pipeline Breakdown — With AI handling boilerplate, juniors miss the repetitive “grind” that traditionally built deep intuition. Seniors, meanwhile, become full-time reviewers instead of mentors.
Economic Pressure for Speed — In competitive markets, leadership demands AI-driven velocity, often at the expense of sustainable skill development.

The result is a widening gap between surface-level productivity and deep engineering maturity — a gap that will take years to close.
Fighting Back: Rebuilding Skills Without Sacrificing AI
The good news is that skill atrophy is reversible. Leading teams and individual developers are already implementing deliberate countermeasures:

The “Human-First Rule”
For every new feature or complex task, developers must first attempt a solution manually (even if rough) before involving AI. This forces active thinking and prevents passive acceptance of AI output.
Mandatory Explain-Back Sessions
After receiving AI code, close the tool and explain every line, decision, and potential failure mode out loud or in writing. If you can’t, revisit the fundamentals.
Deliberate Practice Sprints
Allocate 20–30% of sprint time to “no-AI” zones: refactoring legacy modules, solving LeetCode-style problems manually, or running architecture workshops without tools.
Context Engineering as a Core Skill
Treat providing rich, accurate context to AI as an engineering discipline. Maintain living architecture decision records (ADRs), golden-path templates, and internal knowledge graphs that AI can reliably consume.
Balanced Metrics and Career Ladders
Track not just velocity but also skill-health metrics: time to onboard new team members, depth of code reviews, and frequency of architectural improvements. Promote and reward engineers who demonstrate strong fundamentals alongside AI fluency.
Mentorship 2.0
Pair juniors with seniors for “AI pair-programming audits” where the focus is on why the AI suggestion is suboptimal and how a stronger human solution would look.

The Road to 2027 and Beyond
By the end of 2026, the most successful engineering organizations will be those that treat AI as a powerful but fallible junior colleague — one that requires guidance, verification, and continuous education.
The developers who will lead the next decade won’t be the fastest prompters. They will be the ones who maintain sharp fundamentals, deep systems thinking, and the ability to operate confidently with or without AI.
The Skill Atrophy Crisis is real, measurable, and reversible. The question for every developer and engineering leader in 2026 is simple:
Are we using AI to augment our skills — or to quietly replace them?
The choice we make today will define the quality of our codebases, the strength of our teams, and the resilience of our systems for years to come.
What signs of skill atrophy have you observed in your own work or team?
What deliberate practices have helped you stay sharp in the age of AI?
Share your experiences and strategies in the comments. This conversation matters more than ever.

SkillAtrophy #AIin2026 #DeveloperSkills #SoftwareEngineering #TechnicalExcellence #AIDevelopment #DevCommunity #EngineeringLeadership

Polar Has a Free API: Add Subscriptions and Donations to Your Open-Source Project in 10 Minutes

You built an open-source tool that thousands of developers use. Your GitHub repo has 2,000 stars. But your “Buy Me a Coffee” button generated $12 last year. The problem isn’t that people don’t want to support you — it’s that existing monetization tools weren’t built for developers. Polar was.

What Polar Actually Does

Polar is an open-source billing and monetization platform built specifically for developers and open-source maintainers. Think Stripe plus GitHub Sponsors, but with a developer-first API and features designed around how OSS projects actually work: issue funding, subscriptions, one-time donations, and digital product sales.

Polar is fully open-source (Apache 2.0) and can be self-hosted. Their hosted platform at polar.sh has no monthly fee — they take a small percentage of transactions only when you earn. The REST API is clean, well-documented, and secured with OAuth2 or API keys.

The critical difference from Patreon or Buy Me a Coffee: Polar integrates deeply with GitHub. You can fund specific issues, reward contributors automatically, and tie paid benefits to GitHub actions — like giving subscribers access to a private repo or Discord role.

Quick Start: Polar API

Get your API token from polar.sh → Settings → Developers → New Token.

export POLAR_TOKEN="your_token_here"

# List your products/subscription tiers
curl -s https://api.polar.sh/v1/products 
  -H "Authorization: Bearer $POLAR_TOKEN" | jq '.items[] | {id, name, price_amount}'

Create a subscription tier:

curl -X POST https://api.polar.sh/v1/products 
  -H "Authorization: Bearer $POLAR_TOKEN" 
  -H "Content-Type: application/json" 
  -d '{
    "name": "Pro Supporter",
    "description": "Support development + priority issue responses + private Discord access",
    "prices": [{
      "type": "recurring",
      "amount_type": "fixed",
      "price_amount": 900,
      "price_currency": "usd",
      "recurring_interval": "month"
    }]
  }'

Check active subscribers:

curl -s "https://api.polar.sh/v1/subscriptions?active=true" 
  -H "Authorization: Bearer $POLAR_TOKEN" | jq '{
    total: .pagination.total_count,
    subscribers: [.items[] | {email: .customer.email, tier: .product.name}]
  }'

3 Practical Use Cases

1. GitHub Issue Funding

Polar’s flagship feature: let users pledge money to get specific bugs fixed. When you close a funded issue, contributors get paid automatically.

curl -s "https://api.polar.sh/v1/issues?organization=YOUR_ORG&repository=YOUR_REPO&is_badged=true" 
  -H "Authorization: Bearer $POLAR_TOKEN" | jq '.items[] | {
    title: .title,
    funding_goal: .funding.funding_goal.amount,
    pledged: .funding.pledges_sum.amount
  }'

Add a funding badge to your README:

[![Fund with Polar](https://polar.sh/embed/fund-label.svg?org=your-org)](https://polar.sh/your-org)

When you merge a PR that closes a funded issue, Polar handles reward distribution automatically.

2. Automate Subscriber Benefits with Webhooks

Polar fires webhooks on subscription events. Grant GitHub repo access automatically on new subscription:

const express = require('express');
const app = express();

app.post('/polar/webhook', express.raw({type: 'application/json'}), async (req, res) => {
  const event = JSON.parse(req.body);

  if (event.type === 'subscription.created') {
    const githubUsername = event.data.customer.metadata?.github_username;
    if (githubUsername) {
      await fetch(`https://api.github.com/repos/YOUR_ORG/premium-content/collaborators/${githubUsername}`, {
        method: 'PUT',
        headers: { 'Authorization': `token ${process.env.GITHUB_TOKEN}` },
        body: JSON.stringify({ permission: 'pull' })
      });
    }
  }

  if (event.type === 'subscription.revoked') {
    const github = event.data.customer.metadata?.github_username;
    await fetch(`https://api.github.com/repos/YOUR_ORG/premium-content/collaborators/${github}`, {
      method: 'DELETE',
      headers: { 'Authorization': `token ${process.env.GITHUB_TOKEN}` }
    });
  }

  res.sendStatus(200);
});

Automatic access management — no manual work, no forgotten cancellations.

3. Checkout Links for Digital Products

Sell docs, templates, or premium extensions with one-click checkout:

curl -X POST https://api.polar.sh/v1/checkouts/custom 
  -H "Authorization: Bearer $POLAR_TOKEN" 
  -H "Content-Type: application/json" 
  -d '{
    "product_id": "YOUR_PRODUCT_ID",
    "success_url": "https://yoursite.com/thank-you?checkout_id={CHECKOUT_ID}",
    "customer_email": "[email protected]",
    "metadata": { "source": "readme_cta" }
  }' | jq '{ checkout_url: .url, expires_at: .expires_at }'

Drop the URL in your README, docs, or email. Each click goes to a frictionless checkout.

Why This Matters

Open-source sustainability is broken. Polar is the first platform that truly understands the developer workflow: GitHub-centric, API-first, developer-controlled. The issue funding model changes the entire dynamic — instead of hoping people donate, you give them a direct line to influence your roadmap.

For SaaS developers, Polar also competes with Stripe Billing for simple subscription needs — with zero monthly platform fee and developer experience that feels designed by engineers, not product managers. If you maintain any open-source project with real users, set up Polar before you burn out.

Need custom data extraction or web scraping solutions? I build production-grade scrapers and data pipelines. Check out my Apify actors or email me at [email protected] for custom projects.

Follow me for more free API discoveries every week!

Event Sourcing From Scratch: Why Your Database Has Been Lying to You

Traditional databases lie by omission. They show you the current state of your data — but not how it got there. You see that a user’s balance is $500. You don’t see that they deposited $1,000, bought a $400 item, got a $200 refund, then paid a $300 subscription. The history is gone, overwritten, reduced to a single number.

Event sourcing fixes this by storing every state change as an immutable event. It’s the difference between a bank balance and a bank ledger. I’ve worked with event-sourced systems in production, and they solve problems that traditional CRUD architectures literally cannot — but they also introduce complexity you need to be honest about.

The Bank Ledger Analogy

Banks figured this out centuries ago. They don’t store your balance by overwriting a number. They store every transaction as a separate line item:

+$1,000 (deposit)
-$400   (purchase)
+$200   (refund)
-$300   (subscription)
= $500  (derived balance)

Your balance is derived from replaying the history. That’s event sourcing. Every state change becomes an immutable fact. The current state is just a projection — a materialized view built by replaying events.

Who Actually Uses This?

Event sourcing isn’t theoretical. Major production systems run on it:

  • Financial systems — every transaction is an event, audit trails are built-in
  • E-commerce platforms — order lifecycle from placement through fulfillment
  • Event ticketing (Ticketmaster, Eventbrite) — seat holds, releases, purchases
  • Version control (Git) — every commit is an event, the working directory is a projection
  • Kafka-based architectures — Kafka’s append-only log is fundamentally an event store

LinkedIn processes over 7 trillion messages per day through Kafka. That’s event sourcing at planet scale.

The Core Architecture

An event-sourced system has a few key components:

The Event Store — an append-only log where events are written. You never update or delete events. Each event has a type, timestamp, aggregate ID, and payload. EventStoreDB, Apache Kafka, and even PostgreSQL with an append-only table can serve as event stores.

Events — immutable facts about what happened. Not “set balance to 500” but “deposited 1000,” “purchased item for 400,” etc. Events are past tense because they’ve already happened. You can’t argue with facts.

Aggregates — domain objects that process commands and emit events. An Order aggregate might accept a “PlaceOrder” command and emit an “OrderPlaced” event.

Projections — read models built by replaying events. You might project the same events into a “current balances” table, a “transaction history” view, and a “monthly spending report.” Different shapes from the same data.

CQRS: The Natural Companion

Event sourcing naturally separates writes (appending events) from reads (querying projections). This is Command Query Responsibility Segregation — CQRS. Your write model is the event store. Your read models are projections optimized for specific queries.

This separation means you can scale reads and writes independently. Your event store handles high write throughput. Your projections can be denormalized, cached, and replicated for fast reads. Different projections can use different databases — one in PostgreSQL for relational queries, another in Elasticsearch for full-text search.

Snapshots: The Performance Escape Hatch

Replaying 10 million events to rebuild state is slow. Snapshots solve this: periodically save the current state, then only replay events after the snapshot. An aggregate with a snapshot at event 9,990,000 only needs to replay the last 10,000 events instead of all 10 million.

When You Should Use Event Sourcing

It’s a good fit when:

  • Audit trails matter — finance, healthcare, compliance
  • You need temporal queries — “what was the state at 3pm Tuesday?”
  • Multiple read models from the same data
  • Event-driven architecture is already your pattern
  • Debugging production — you can replay exactly what happened

When You Shouldn’t

Be honest with yourself about the downsides:

  • Complexity is real. CRUD is simpler. If your domain is straightforward, event sourcing is over-engineering.
  • Eventual consistency. Projections lag behind events. If your users need instant read-after-write consistency, you need careful design.
  • Schema evolution is painful. Events are immutable, but your event schema will change. Upcasting old events to new formats is tedious work.
  • Storage grows forever. You’re keeping every event. Compaction and archival strategies become necessary.
  • Tooling is less mature. Fewer ORMs, fewer tutorials, smaller community than traditional CRUD.

My honest take: most applications don’t need event sourcing. But for the ones that do — complex domains with audit requirements, temporal queries, or multiple downstream consumers — nothing else comes close.

Your JavaScript Array is a Hash Map in Disguise

TL;DR: JavaScript arrays are fundamentally objects where integer keys are treated as strings. To save performance, engines like V8 attempt to optimize these into contiguous memory blocks (Elements Kinds). However, mixing types or creating sparse “holes” triggers a de-optimization to Dictionary Mode, moving data from the 1ns CPU cache to 100ns RAM lookups.

If you have ever wondered why typeof [] returns "object", the answer isn’t just “JavaScript is weird.” It is an architectural warning. In the underlying C++ of the V8 engine, arrays are not fixed-size contiguous memory buffers by default. They are hash maps—specifically, exotic objects that map integer keys to values. While this makes JS incredibly flexible, it creates a massive performance hurdle that the engine has to work overtime to solve.

Why does typeof return “object” for a JavaScript array?

JavaScript arrays are keyed collections that inherit from the Object prototype, meaning they are essentially specialized objects where the keys are property names. Even though we access elements using arr[0], the engine internally treats that index as a string key "0" to maintain compliance with the language specification.

Under the hood, this means a standard array doesn’t have a guaranteed memory layout. In a language like C, an array of four integers is a single 16-byte block of memory. In JavaScript, a naive array is a collection of pointers scattered across the heap. To find an element, the engine has to perform a hash lookup, which is computationally expensive compared to a simple memory offset. This architectural choice is why V8 spends so much effort trying to “guess” when it can treat your array like a real, contiguous block of memory.

How does V8 use “Elements Kinds” to optimize performance?

V8 uses a system called Elements Kinds to track the internal structure of an array, attempting to store data in the most efficient C++ representation possible. If you create an array of small integers, V8 labels it PACKED_SMI_ELEMENTS and stores it as a contiguous block of memory, allowing the CPU to access it with near-zero overhead.

This optimization is all about hardware efficiency. When data is contiguous, it lives in the CPU’s L1 or L2 cache. The CPU can use “prefetching” to load the next few elements into the cache before your code even asks for them. Retrieval from the cache takes about 1 nanosecond. However, if the array becomes a hash map (Dictionary Mode), the CPU has to engage in “pointer chasing.” It must go all the way to the system RAM—which can take 100 nanoseconds or more—to find the memory address of the next bucket in the hash map. That 100x latency jump is the hidden tax of unoptimized JavaScript.

What triggers the transition to Dictionary Mode?

The transition from a fast, packed array to a slow hash map is often a one-way street. V8 starts with the most optimized state and “downgrades” the array as you introduce complexity, such as mixing data types or creating large gaps between indices.

If you have a PACKED_SMI_ELEMENTS array and you push a floating-point number into it, the engine transitions it to PACKED_DOUBLE_ELEMENTS. If you then push a string, it becomes PACKED_ELEMENTS (a generic array of pointers). The most destructive action, however, is creating a “holey” array. If you define let a = [1, 2, 3] and then suddenly set a[1000] = 4, V8 refuses to allocate 997 empty memory slots. Instead, it panics and converts the entire structure into DICTIONARY_ELEMENTS. Once an array is downgraded to a dictionary, it rarely—if ever—gets promoted back to a packed state.

// Starts as PACKED_SMI_ELEMENTS (Fastest)
const arr = [1, 2, 3]; 

// Transitions to PACKED_DOUBLE_ELEMENTS
arr.push(1.5); 

// Transitions to DICTIONARY_ELEMENTS (Hash Map)
// This creates a 'hole', triggering a full de-optimization
arr[1000] = 42; 

Why do these 100ns delays matter in intensive tasks?

In standard UI development, a 100ns delay is invisible. However, in high-throughput backend processing or 60fps graphical programming, these delays are catastrophic. In a requestAnimationFrame loop, you have a hard limit of 16.6ms to finish all calculations. If you are iterating over thousands of “arrays” that are actually hash maps, the constant round-trips to RAM will eat your frame budget and cause visible stuttering.

Similarly, if you are building a data-intensive microservice that processes millions of JSON objects, the cumulative cost of hash map lookups instead of direct memory access can result in a 10x or 100x decrease in total throughput. This is why tools like TensorFlow.js or high-performance game engines use TypedArrays (like Int32Array), which bypass this “Elements Kind” guessing game entirely and force the engine to use contiguous memory.

V8 Array State Transitions

State Description Latency
PACKED_SMI Contiguous small integers ~1ns (Cache)
PACKED_DOUBLE Contiguous floats ~1ns (Cache)
HOLEY_ELEMENTS Array with missing indices Variable (Slower)
DICTIONARY_ELEMENTS Pure Hash Map (De-optimized) ~100ns (RAM)

FAQ

How can I prevent my arrays from becoming hash maps?
Initialize your arrays with their final size if possible and avoid “holey” assignments. Most importantly, keep your arrays monomorphic—meaning, don’t mix integers, strings, and objects in the same collection.

Are TypedArrays immune to this de-optimization?
Yes. Int32Array, Float64Array, and others are backed by an ArrayBuffer. They have a fixed length and a fixed type, which guarantees they stay as contiguous blocks of memory regardless of what you do with the data.

Does deleting an element make an array a hash map?
Using the delete keyword on an array index (e.g., delete arr[2]) creates a hole, which transitions the array to a HOLEY state. While it might not immediately hit Dictionary Mode, it significantly slows down access because the engine must now check the prototype chain for that missing index.

The Identity Trap That Keeps Senior Devs From Reaching Staff

I spent years trying to become the best coder on my team. And it worked — right up until it became the thing holding me back.

Here’s what nobody tells you about the Senior-to-Staff jump: it’s not a skills upgrade. It’s an identity crisis.

The Ceiling Nobody Warns You About

After 5-8 years of grinding, most developers reach Senior and feel like they’ve made it. You’re fast, you’re reliable, you solve hard problems. Your PRs are clean. You’re the person people come to when something’s broken.

Then you stay there. For years.

According to levels.fyi data, the median time at Senior before reaching Staff is 4-6 years — but many engineers never make the jump at all. The drop-off is steep: most companies have 3-5x more Seniors than Staff engineers.

The common assumption is that you need to be even better at coding. Write even more elegant solutions. Ship even faster. But that’s exactly the trap.

The Identity Problem

When “I’m the best coder in the room” is your identity, you optimize for the wrong things. You take on the hardest implementation work yourself instead of delegating it. You solve problems at the code level when the real issue is architectural. You measure your value by your personal output.

Staff engineers get promoted for a fundamentally different skill: making everyone else more productive.

That means writing the document that prevents three teams from building the same thing. Running the design review that catches a scaling problem before it ships. Saying “we shouldn’t build this at all” when everyone’s excited about a feature.

What Actually Changes

The shift looks something like this:

Senior mindset: “I’ll implement this complex feature myself because I can do it fastest.”

Staff mindset: “I’ll design the system so a mid-level engineer can implement it safely, and I’ll focus on the parts that affect three other teams.”

Will Larson’s Staff Engineer book surveyed hundreds of Staff+ engineers. The consistent pattern: they stopped being the person who writes the most code and became the person who ensures the right code gets written.

This isn’t about writing less code. Plenty of Staff engineers still code daily. It’s about choosing where your code has the highest leverage. A week spent on a shared library that eliminates duplicated work across five teams is worth more than a month of heads-down feature work.

The Uncomfortable Part

Letting go of your identity as “the best coder” feels like losing something. You spent years building that reputation. Your confidence is rooted in technical skill. Walking into a room and not being the person with the cleverest solution feels… wrong.

But the engineers who plateau at Senior are almost always the ones still measuring value by personal output. I’ve seen it play out dozens of times. Brilliant engineers, stuck for years, because they kept doing the thing that got them promoted last time.

How to Actually Make the Shift

Three things that work:

1. Track your multiplier effect. Instead of “I shipped feature X,” start thinking “I unblocked 4 engineers this week” or “my RFC prevented a 3-month rewrite.” If your impact disappears when you take a vacation, you’re not operating at Staff level.

2. Volunteer for the unglamorous work. Design documents. Oncall improvements. Cross-team coordination. Migration plans. This work is invisible and boring and it’s exactly what differentiates Staff engineers.

3. Develop opinions about systems, not just code. Staff engineers have strong, well-reasoned views about architecture, team structure, and technical direction. Not “this function should use a map instead of a loop” but “this service boundary is wrong and here’s why it’ll cause problems in 6 months.”

The hardest part isn’t learning new skills. It’s letting go of the identity that got you here. But on the other side of that discomfort is a career that scales — one where your impact isn’t limited by how many hours you can personally code.

Scalable Proximity Search: Why Geohashing Beats Radius Queries

TL;DR: Geohashing maps 2D coordinates to a 1D string using recursive binary partitioning and bit interleaving. By encoding these bits into a Base-32 string, we leverage B-Tree prefix matching for efficient spatial lookups, bypassing the high CPU costs of Haversine distance calculations at scale.

Calculating Haversine distances for 10,000 moving objects per tick is a disaster for database performance. Standard SQL radius queries aren’t built for high-concurrency spatial updates; they are computationally heavy and fail to scale because they require calculating the distance between the query point and every potential candidate in the dataset. To keep latency low, you need to stop thinking in floating-point coordinates and start thinking in B-Tree friendly strings. This is where geohashing comes in, moving the heavy lifting from the CPU to the database index.

How does binary partitioning resolve geographic coordinates?

Binary partitioning recursively divides the map into smaller quadrants, assigning a 0 or 1 based on whether a point falls in the lower/left or upper/right half of the current bounding box. This creates a deterministic bitstream representing a specific geographic area rather than a precise point.

I recently posted a video about geohashing because it is the most efficient way to handle real-time location data. We start by looking at the globe vertically. If a coordinate is in the Northern hemisphere, we assign a 1; if it is in the Southern hemisphere, a 0. We then take that specific hemisphere and split it again. Is the point in the upper or lower half of that new section? This recursive halving continues until we reach the desired resolution. We perform the exact same operation horizontally for longitude (Left/Right splits), resulting in two separate binary streams that describe the point’s location with increasing precision.

Why do we interleave bitstreams for 1D spatial indexing?

Interleaving, also known as bit-zipping, alternates bits from the latitude and longitude streams to create a single sequence that preserves 2D proximity within a 1D format. This process follows a Morton order curve (or Z-order curve), which maps multi-dimensional data to one dimension while maintaining the locality of the data points.

In a standard implementation, the first bit of the latitude stream is followed by the first bit of the longitude stream, then the second bit of each, and so on. This ensures that the resulting combined bitstream—and the subsequent geohash string—represents a specific “tile” on the map. Because the bits are interleaved, points that are close together in a 2D space are highly likely to share the same prefix in their 1D bitstream.

Bit Index (i) Latitude Bit (V_i) Longitude Bit (H_i) Interleaved Result (V_0 H_0 … V_i H_i)
0 1 0 10
1 0 1 1001
2 1 1 100111
3 0 0 10011100
4 1 1 1001110011

How does Base-32 encoding optimize database lookups?

Base-32 encoding converts the final interleaved bitstream into a compact, human-readable string using a specific 32-character alphabet (0-9, b-z) that deliberately excludes ambiguous characters like a, i, l, and o. This representation is highly efficient for database indexing because strings are stored lexicographically in B-Trees, allowing for lightning-fast range scans.

When a taxi app needs to find drivers near you, it does not calculate the distance to every driver in the city. It identifies your geohash—for example, gcpvj0—and queries the database for any driver whose geohash starts with the same prefix. This turns a complex spatial intersection into a simple string comparison. Since the database index is sorted, the system can find all records within the same geographic tile by performing a single index seek followed by a range scan, which is significantly faster than any geometric calculation.

FAQ

What is a Z-order curve and how does it relate to geohashing?
A Z-order curve is a space-filling curve that visits every point in a multi-dimensional grid while preserving the locality of points. Geohashing uses this curve by interleaving bits; the path the bits follow as you increase precision looks like a repeating ‘Z’ shape. This is what allows us to represent 2D data in a 1D string index without losing spatial context.

Why does the geohash alphabet exclude certain letters?
The standard geohash alphabet (the Crockford-inspired Base-32) excludes the letters ‘a’, ‘i’, ‘l’, and ‘o’ to prevent human transcription errors and character confusion. This makes the hashes more robust when being passed through URLs, logged in debugging consoles, or manually entered by engineers.

How do you handle the ‘boundary problem’ where nearby points have different hashes?
Because the Z-order curve occasionally ‘jumps’ (for example, at the equator or the prime meridian), two points can be centimeters apart but have entirely different geohash prefixes. To mitigate this, production proximity services do not just search the user’s current geohash tile; they calculate and query the eight immediate neighboring tiles as well.

Cheers.