<?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: Lightning Developer</title>
    <description>The latest articles on DEV Community by Lightning Developer (@lightningdev123).</description>
    <link>https://dev.to/lightningdev123</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%2F2757052%2F987f57b6-be53-4d74-9893-755596ff93c5.png</url>
      <title>DEV Community: Lightning Developer</title>
      <link>https://dev.to/lightningdev123</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lightningdev123"/>
    <language>en</language>
    <item>
      <title>Run OpenCode from Any Browser: The Definitive Pinggy Setup Guide</title>
      <dc:creator>Lightning Developer</dc:creator>
      <pubDate>Thu, 12 Nov 2026 12:52:39 +0000</pubDate>
      <link>https://dev.to/lightningdev123/run-opencode-from-any-browser-the-definitive-pinggy-setup-guide-2bhf</link>
      <guid>https://dev.to/lightningdev123/run-opencode-from-any-browser-the-definitive-pinggy-setup-guide-2bhf</guid>
      <description>&lt;p&gt;Are you tired of being tied to your desk just to use your AI coding agent? Imagine you are on a train, at a coffee shop, or using a tablet at home, and you need to check on a long-running refactor, start a new session, or continue where you left off. With &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; and OpenCode, you can access a full terminal on your remote machine from any browser, without setting up an SSH server, opening a port, or assigning a public IP address. This guide walks through everything you need to know, from initial setup to advanced session management, security best practices, and troubleshooting.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/l8GJAfvGIBU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Browser Terminal?
&lt;/h2&gt;

&lt;p&gt;Developers often need to reach a powerful workstation or server from a lightweight device. Traditional solutions like SSH require the target machine to expose a port and have a public IP, which is not always possible behind NAT, CGNAT, or strict corporate firewalls. You could use a VPN, but that adds complexity and can be blocked by network administrators. &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; solves this problem by having your machine make a single outbound connection to the &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; dashboard. Your browser talks to the dashboard, and the dashboard relays keystrokes through that connection to a shell on your machine. This is a form of remote terminal that works regardless of your network topology.&lt;/p&gt;

&lt;p&gt;The beauty of this approach is that there is no inbound connection. The &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; CLI, running as a background agent, keeps a persistent WebSocket connection to the Pinggy servers. When you open a terminal tab in the &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; dashboard, the browser sends input events to the server, which forwards them over the existing connection to a pseudo-terminal on your machine. The output is streamed back in real time. The result is a responsive terminal in your browser that feels local, even across continents.&lt;/p&gt;

&lt;p&gt;For developers working with AI coding agents, this is particularly useful. AI tools like OpenCode often run long tasks, generate large files, and need to execute tests. You do not want to keep a laptop lid open and a terminal session active just to watch a progress bar. With a browser terminal, you can start a task, close the tab, and check back later. The process continues because it is running on your remote machine, not in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Pinggy Devices Work
&lt;/h2&gt;

&lt;p&gt;Pinggy Devices is a feature that arrived in Pinggy CLI v0.6.2 on November 2, 2026. The CLI runs on your machine as an agent and maintains one outbound connection to the dashboard. That connection is authenticated with a device token, which is bound to your Pinggy account. When you open a browser terminal, the Pinggy front end communicates with the agent through a relay that is similar to a WebSocket bridge. The agent accepts the connection and spawns a shell process (usually /bin/bash or /bin/zsh) on the host operating system.&lt;/p&gt;

&lt;p&gt;The system is designed to work behind NAT and CGNAT because the agent only initiates outbound connections. There is no need to configure port forwarding on your router. The agent periodically sends keepalive pings to detect dropped connections. If your internet goes offline and comes back, the agent should reconnect automatically, unless the network change is too severe or the machine goes to sleep.&lt;/p&gt;

&lt;p&gt;Since v0.6.4, released on November 5, the agent runs in the background by default. This is a significant improvement because it means you can close the terminal window that launched the agent, and the device remains online. The background agent is managed as a user-level daemon, so it does not require administrator privileges. It stores its configuration in your user configuration directory, typically ~/.config/pinggy/ on Linux and macOS, or %USERPROFILE%\.config\pinggy\ on Windows.&lt;/p&gt;

&lt;p&gt;The device token is stored in device.json within that directory. Permissions are set to read-only for the current user, preventing other local accounts from stealing the token. The agent never logs the actual keystrokes or terminal content. Instead, it forwards the rendered output to the dashboard, which means the dashboard cannot inject commands or extract data from the terminal. It is a pure remote display protocol, similar to how a VNC client works but optimized for text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding OpenCode's Client-Server Model
&lt;/h2&gt;

&lt;p&gt;OpenCode 2 is not just a single binary that runs in the foreground. It is split into a client and a server. The opencode command you type is a terminal client that connects to a background server. This server, which starts automatically when you first run OpenCode, is responsible for managing sessions, permissions, and tool execution. It listens on 127.0.0.1:49374 on macOS and Linux. This design is what makes OpenCode an ideal companion for a browser terminal.&lt;/p&gt;

&lt;p&gt;When you open a shell through Pinggy and run opencode, the client attaches to the existing background server. If your browser tab crashes, the shell might be terminated, but the server stays alive. Your active session continues running in the background. You can later open a new shell, resume the session, and see exactly where you left off. This separation is powerful, especially for long-running AI code generation tasks that might take several minutes. You can even close the browser entirely and come back hours later to find the task completed.&lt;/p&gt;

&lt;p&gt;The background server also centralizes permissions. When OpenCode wants to run a command or modify a file, it asks the user through the TUI. But when you are running in a browser terminal, you interact with the TUI as usual. The server enforces the same permission rules even if the client disconnects and reconnects. This means a long-running task will not accidentally execute a command that you did not approve. You can also revoke permissions through the TUI before disconnecting.&lt;/p&gt;

&lt;p&gt;OpenCode is MIT-licensed and has gathered an impressive 225,629 GitHub stars as of November 9, 2026. The project has rapidly evolved, and v2 introduced a more robust plugin system and a new TUI. The background server model was added specifically to support scenarios like this, where the client and server can be separated across time and space.&lt;/p&gt;

&lt;p&gt;In my test run on November 9, I used an Apple Silicon MacBook with Pinggy CLI 0.6.4 and OpenCode 2.2.6, released on November 6. I added the machine using the dashboard's token command. Most screenshots in this guide come from that run, and a few were taken on November 10 in the dashboard's dark theme. Two images come from our Claude Code guide. All tokens, emails, hostnames, and usernames are placeholder values. Plan numbers and limits were checked against the Pinggy Devices documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we dive into the setup, let us make sure you have everything needed. You need a machine where your code lives. This could be a MacBook, a Linux server, a Windows PC, or even a Raspberry Pi. The Pinggy CLI supports all of these platforms. You also need a modern web browser, such as Chrome, Firefox, Safari, or Edge. Finally, you need an OpenCode installation. The setup process takes about five minutes, and most of it is just running two install scripts and signing in with your email.&lt;/p&gt;

&lt;p&gt;Network requirements are minimal. The machine must be able to reach the internet and maintain an outbound connection to the Pinggy servers. The browser, likewise, needs internet access and a WebSocket-capable connection. There is no need for a static IP, router configuration, or VPN. This is one of the biggest advantages of using &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; instead of traditional remote access tools.&lt;/p&gt;

&lt;p&gt;You should also have a &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; account, which you can create for free during the sign-in process. If you plan to use OpenCode with custom AI providers, make sure you have your API keys ready. For local models, you need a tool like Ollama installed on the machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Pinggy CLI and Sign In
&lt;/h2&gt;

&lt;p&gt;The first step is to install the Pinggy CLI on the machine you want to reach from the browser. This is a simple one-liner. On macOS and Linux, open a terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -fsSL https://pinggy.io/install.sh &lt;span class="o"&gt;|&lt;/span&gt; sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, open PowerShell and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;irm&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;pinggy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ps1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;iex&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The install script does not require sudo. It downloads the CLI, places it in your user directory, and then runs the login flow. You will be prompted to enter your email address, after which Pinggy sends a six-digit verification code. Type that code into the terminal. Alternatively, you can click the "Approve sign in" button in the email, but only if the page displays the same device code that your terminal printed. This prevents phishing and ensures the request is legitimate.&lt;/p&gt;

&lt;p&gt;A quick note on typo awareness: some users recieve the code in their spam folder, so check there if you do not see it within a minute. The code expires after a short window, and you can request a new one if needed.&lt;/p&gt;

&lt;p&gt;If you do not have a Pinggy account, the email address you provide automatically creates a free account. The machine is then added to your account with a device name that matches its hostname. You can change this name later from the dashboard. The device entry shows the hostname, OS, CPU, and memory, which helps you identify the machine when you have multiple devices.&lt;/p&gt;

&lt;p&gt;After successful sign-in, the CLI prints a link and a QR code to the device's page in the dashboard. Starting with CLI version 0.6.4, the agent runs in the background by default. This means the CLI process exits but the agent continues running, maintaining the outbound connection. You can close the terminal window and the device stays online. After a reboot, the agent does not start automatically. You will need to run pinggy device connect again. Since your token is saved, it will not ask for credentials again. If you have an older CLI version, the agent might run in the foreground. Simply run the install script again to update.&lt;/p&gt;

&lt;p&gt;If you already have a &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; account and want to add a new machine, go to the Devices page in the dashboard and click "Add device." Give the machine a name. The dashboard will show a command that includes a unique device token. You can run this command directly on the machine. This method does not require email or a verification code, which makes it perfect for servers or machines that you only reach through a script. The exact command looks like this, with a placeholder token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pinggy device connect --token YOUR_DEVICE_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a machine that already has the CLI installed, this command will connect immediately. It might also check for updates and inform you if a newer version is available. If the machine was already registered as a device on another account, the CLI will say so and leave the old device in the other dashboard, offline. You can remove the old device from the previous account if you have access to it.&lt;/p&gt;

&lt;p&gt;One common question is whether you can run the agent alongside another &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; tunnel. The answer is yes, they are independent features. You can have an existing localhost tunnel active while the device agent runs in the background. The device agent uses a different authentication flow and does not interfere with your tunnel configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Install OpenCode on the Machine
&lt;/h2&gt;

&lt;p&gt;Now that your device is connected to Pinggy, you need to install OpenCode. You have two options: run the install command in a local terminal on the machine, or wait until you have opened a browser shell in Step 3 and run it there. The latter is particularly useful if you want to set up a new machine completely from your tablet or phone.&lt;/p&gt;

&lt;p&gt;The easiest installation method is the official installer. Run this in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -fsSL https://opencode.ai/v2/install &lt;span class="o"&gt;|&lt;/span&gt; bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs the OpenCode 2 binary. If you already have OpenCode 1 installed, the v2 installer will replace the old binary. That is exactly what happened on my MacBook, where version 1.3.10 was replaced by OpenCode 2.2.6. If you installed OpenCode 1 through a package manager, it is recommended to uninstall it first to avoid conflicts. The v2 installer preserves your existing configuration, including your default model. However, v1 plugins do not run in OpenCode 2, so you may need to find replacements.&lt;/p&gt;

&lt;p&gt;Alternative installation methods include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm install -g @opencode/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and, on macOS with Homebrew, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew install anomalyco/tap/opencode-v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both methods work well. The npm package is particularly convenient if you already have Node.js installed, but the standalone installer is more self-contained. Windows package managers are not yet supported. Instead, the OpenCode documentation provides a standalone Windows build that you can download and run. OpenCode 1 also works in the browser terminal. If you still have a v1 installation and want to keep it, that is fine. In fact, here is what OpenCode 1.3.10 looked like in the Pinggy terminal tab, running with a local Ollama model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcy4dadpixg73q46awyud.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcy4dadpixg73q46awyud.webp" alt="Blog Image" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, the rest of this guide focuses on OpenCode 2 because its background server architecture provides seamless session persistence. For the best remote experience, I highly recommend upgrading to v2. If you need to migrate, copy any custom configuration from ~/.config/opencode/ to the new location that v2 uses. The installer usually handles this, but it is good to verify.&lt;/p&gt;

&lt;p&gt;One of the first questions people ask is whether they need an account to use OpenCode. You do not. The free tier includes access to several free models, such as Big Pickle, DeepSeek V4.1 Flash Free, and GLM-5.3-Flash Free. You can start coding immediately with these models. That said, OpenCode stores your data when you use free models. If you are working with proprietary or sensitive code, connect your own provider. Use the /connect command inside the OpenCode TUI, or run opencode auth login from the shell. API keys can be pasted easily into the browser terminal. For providers that use OAuth or browser-based sign-in, it is a good idea to authenticate while you are still at the machine, before you start relying on remote sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Open the Browser Terminal and Run OpenCode
&lt;/h2&gt;

&lt;p&gt;With both Pinggy and OpenCode installed, it is time to open the terminal in your browser. Go to the Pinggy Devices page and click on your device's name. The Overview tab shows whether the device is online, along with its hostname, operating system, CPU, and memory. The screenshot below shows the dark theme of the dashboard, which is easy on the eyes for late-night coding sessions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo0h4eybeo97a6dvlahno.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo0h4eybeo97a6dvlahno.webp" alt="Blog Image" width="799" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the Terminal tab and then click "New shell." A new terminal tab will open, connected directly to a shell on your machine. This shell runs as the same user that ran the pinggy command. That means it has access to your user's files, git configuration, and saved provider logins. This is critical because OpenCode needs to see your SSH keys, your project directories, and your API tokens.&lt;/p&gt;

&lt;p&gt;Once the shell is up, navigate to your project and start OpenCode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/code/storefront   &lt;span class="c"&gt;# your own project folder&lt;/span&gt;
opencode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time you run OpenCode 2.2.6 after an update, it may show a "What's new" dialog. You can close it by pressing Esc. In my case, OpenCode opened on the local Ollama model from my existing configuration. I wanted to use Big Pickle, so I pressed Ctrl+X, then M, typed "big pickle" and pressed Enter. The Ctrl+X key sequence went through the browser terminal without any issues.&lt;/p&gt;

&lt;p&gt;Then I asked OpenCode to write a test file, specifically "Write cart.test.js with three node:test tests for cart.js." Big Pickle wrote the file and ran the tests in 11.3 seconds. The latency was barely noticeable, even though the browser tab was communicating through the Pinggy relay. The entire experience felt just like a local terminal.&lt;/p&gt;

&lt;p&gt;Once you are inside OpenCode, the usual keybinding s work. Here are a few commands to help you get started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/models to switch models. This opens an interactive picker where you can browse all available models, including local ones.&lt;/li&gt;
&lt;li&gt;/connect to add a provider. You can enter an API key or choose an OAuth provider.&lt;/li&gt;
&lt;li&gt;! at the start of an empty prompt to run a shell command directly. This is handy for quick file checks.&lt;/li&gt;
&lt;li&gt;Ctrl+P to open the command palette. From there, you can run actions like session list, continue, or toggle permissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also use Ctrl+X and then M to select a model, as I did. The browser terminal supports all standard terminal key combinations, including Ctrl+C to interrupt, Ctrl+L to clear, and Ctrl+R to search shell history.&lt;/p&gt;

&lt;p&gt;One tip: if you are using an iPad or tablet, make sure you have a keyboard connected. The browser terminal works with touch, but the AI coding TUI is much easier with a physical keyboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Resume Sessions Later
&lt;/h2&gt;

&lt;p&gt;One of the best parts of this setup is that closing the browser tab does not kill your shell. The Terminal tab in the Pinggy dashboard lists all shells that are still running on the device. Each shell has its own tab, and the list shows the program currently running in each. Clicking on a shell reopens it with the screen redrawn. This is incredibly useful when you accidentally close a tab or need to switch devices.&lt;/p&gt;

&lt;p&gt;There is, however, a timeout limit. A dashboard shell will end after 30 minutes with no activity, and it will definitely end after 8 hours, regardless of activity. When the shell ends, the OpenCode client running inside it also ends. But because OpenCode's background server holds the session, you can simply open a new shell and resume.&lt;/p&gt;

&lt;p&gt;To test this, I deliberately hung up the shell from the machine's side, which is exactly what a timeout does. The OpenCode client exited, but the session was still alive. In a new shell, I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/code/storefront     &lt;span class="c"&gt;# sessions belong to a project folder&lt;/span&gt;
opencode session list    &lt;span class="c"&gt;# list sessions; it opens in a pager, press q to leave&lt;/span&gt;
opencode -c              &lt;span class="c"&gt;# continue the last session&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output of session list looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ses_e40039520ffev5jRWwjsz0uEAl  Three node:test tests for cart.js       11/9/2026, 4:36:04 PM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then opencode -c opened the session exactly where I had left off. You can also open a specific session by ID with opencode -s . Inside the TUI, /sessions (or Ctrl+X, then L) lists all sessions as well.&lt;/p&gt;

&lt;p&gt;What about work in progress? I ran an experiment. In a local terminal, I killed the opencode client eight seconds into a task, using the same hang-up signal that a closing shell would send. The background server kept running, and the file that OpenCode was writing appeared about 16 seconds later, with no client attached. This means you can start a long task, close the browser, go for a walk, and come back later to see the result. As long as the machine stays awake and connected, the task will complete.&lt;/p&gt;

&lt;p&gt;There is a subtle edge case. If the shell times out due to inactivity, the OpenCode client receives a SIGHUP. The client might not have time to flush its output buffer, but the background server handles all file writes and tool executions. Therefore, you will not lose any generated files. The only thing you lose is the in-memory UI state, which includes the scrollback and any unsubmitted prompt. The session history is preserved on disk.&lt;/p&gt;

&lt;p&gt;Another useful feature is the ability to run multiple shells simultaneously. With the Pro plan, you can have up to three shells per device. For example, you can run OpenCode in one shell, a dev server in another, and tail logs in a third. Each shell is an independent process group, so commands do not interfere with each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Session Management
&lt;/h2&gt;

&lt;p&gt;OpenCode's session system is more powerful than it first appears. Sessions are tied to a project folder. If you run opencode in a subdirectory, it will still find sessions from the parent folder. You can use session IDs to programmatically continue a session, which is useful for scripts and automation. For example, you can write a shell alias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;oc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'cd ~/code/storefront &amp;amp;&amp;amp; opencode -c'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it quick to resume your last session from any shell. You can also archive old sessions to keep the list clean. The /sessions command has an archive option.&lt;/p&gt;

&lt;p&gt;One recurring issue is that sessions can become orphaned if the server crashes. The server stores session metadata and file changes incrementally, so you rarely lose data. If you ever need to kill a hung session, you can restart the OpenCode server with opencode service restart. This will not delete sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Local Models with Ollama
&lt;/h2&gt;

&lt;p&gt;One of the best ways to get low-latency AI assistance in a remote terminal is to run a local model. Ollama is a popular tool for running open-source language models on your own hardware. To install Ollama on the remote machine, use the install script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -fsSL https://ollama.com/install.sh &lt;span class="o"&gt;|&lt;/span&gt; sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, pull a model such as qwen2.5-coder:14b or llama3.1:8b:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull qwen2.5-coder:14b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start the Ollama server. It runs as a background service by default. In OpenCode, you can select Ollama as the provider and pick the model you pulled. The interaction happens over the localhost server, so even the browser terminal will see negligible latency. This setup is particularly useful when you are using a remote machine with a powerful GPU. You can leave the GPU at home and use a thin client elsewhere. However, make sure the model fits in memory. The qwen2.5-coder:14b model needs about 10 GB of RAM. If the machine has 16 GB or more, you are fine.&lt;/p&gt;

&lt;p&gt;Ollama also supports multiple models and can switch on demand. You can use /models inside OpenCode to list them. The first request will load the model into memory, which might take a few seconds. Subsequent requests are fast. If you notice slow performance, check the CPU and memory usage with htop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Pinggy Agent as a Service
&lt;/h2&gt;

&lt;p&gt;If you want the Pinggy device to be always available, especially on a server, you should run the agent as a service. On Linux, you can create a systemd user unit. First, make sure the agent is not already running. Then create the unit file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mkdir -p ~/.config/systemd/user
cat &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.config/systemd/user/pinggy-device.service &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;EOF
&lt;span class="o"&gt;[&lt;/span&gt;Unit&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Pinggy Device Agent
&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network-online.target
&lt;span class="nv"&gt;Wants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network-online.target

&lt;span class="o"&gt;[&lt;/span&gt;Service&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%h/.pinggy/pinggy device connect --foreground
&lt;span class="nv"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on-failure
&lt;span class="nv"&gt;RestartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5

&lt;span class="o"&gt;[&lt;/span&gt;Install&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default.target
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then enable and start it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl --user daemon-reload
systemctl --user &lt;span class="nb"&gt;enable&lt;/span&gt; --now pinggy-device.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure lingering is enabled for your user so the service starts at boot without you logging in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loginctl enable-linger &lt;span class="nv"&gt;$(&lt;/span&gt;whoami&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup ensures that the Pinggy agent is always running and reconnects automatically after a crash or reboot. On macOS, you can do something similar with a launchd LaunchAgent. On Windows, use Task Scheduler.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenCode Configuration Tips
&lt;/h2&gt;

&lt;p&gt;OpenCode stores its configuration in ~/.config/opencode/. You can edit the opencode.json file to set default models, theme, and permission policies. For example, to set a default provider and model:&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="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"big-pickle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"opencode"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also define custom prompts and agent behaviors in a prompts/ directory. This is helpful when you have recurring tasks, such as "create a commit message" or "write unit tests." The configuration files are plain text, so you can version them with git. If you are using OpenCode through a browser terminal, you might want to increase the terminal font size for readability. You can do that in the TUI settings.&lt;/p&gt;

&lt;p&gt;Permission policies are particularly important in a remote environment. You can configure OpenCode to ask for permission only once per session, or to auto-approve certain commands like npm test and git status. This reduces the number of interruptions while you are away from the keyboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenCode's Own Remote Access, and Why a Shell
&lt;/h2&gt;

&lt;p&gt;OpenCode has its own remote access feature. The command opencode pair prints a one-time link and a QR code to its web UI. This link is valid for 5 minutes. With opencode pair --remote, you can pair from anywhere through an OpenTunnel address. This option exposes your OpenCode server to the internet. It is end-to-end encrypted and protected by a password, and OpenCode asks for confirmation before turning it on. On a phone, the OpenCode web UI or mobile app is arguably nicer than a TUI in a browser tab. If you only need to interact with OpenCode, this might be enough.&lt;/p&gt;

&lt;p&gt;But a Pinggy shell is a whole terminal, not just an OpenCode session. You can restart the OpenCode service, run git pull, restart a dev server, or even run Claude Code in a seperate tab. Nothing about the OpenCode server is exposed to the internet. The shell is just a terminal. This is especially valuable if you want to inspect system logs, edit configuration files, or perform maintenance tasks in the same environment. Our Claude Code guide covers the same setup for Claude Code, and you can run both tools side by side. The screenshot below shows Claude Code 2.1.318 running in the same Pinggy terminal tab, from the Claude Code guide.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmueva905ohl6wviy7fqg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmueva905ohl6wviy7fqg.webp" alt="Blog Image" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having a full terminal also means you can use tools like htop, vim, tmux, or any other command-line utility. You can start a local web server and test it by opening another tunnel. The possibilities are endless. If you are a sysadmin, you can even use the browser terminal to manage nginx configuraton files, restart services, and read logs, all without leaving the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free vs Pro
&lt;/h2&gt;

&lt;p&gt;The browser terminal is available on every Pinggy plan, including the free one. The free plan includes one device and one concurrent shell. That is enough for a single developer with one machine. The Pro plan costs $2.50 per month if billed annually, or $3 per month if billed monthly, as of November 3, 2026. It raises the limits to 25 devices, three concurrent shells per device, and ten open shells per seat. Three shells is a comfortable number for most workflows: one for OpenCode, one for the dev server, and one for git operations. For a more detailed breakdown, check the Pinggy pricing page.&lt;/p&gt;

&lt;p&gt;If you are collaborating with a small team, each seat gets its own device and shell limits. You can also share devices across the team if you all sign in to the same account, though that raises security concerns that we will address shortly. The Pro plan also includes priority support and higher bandwidth limits. If you are using the browser terminal for heavy output, such as test suites that print thousands of lines, you will appreciate the increased throughput.&lt;/p&gt;

&lt;p&gt;One thing to note is that the free plan's single shell can be a limitation if you want to run OpenCode and a dev server concurrently. You can work around this by using tmux inside the single shell, but the Pro plan's three shells are more ergonomic. You can always upgrade or downgrade at any time, and the pricing is transparent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Best Practices
&lt;/h2&gt;

&lt;p&gt;When you give remote access to your development environment, security becomes paramount. The device token is stored in a file called device.json under ~/.config/pinggy/. The file is readable only by your user account. Pinggy documents that the agent never logs what you type. The Activity tab in the dashboard lists device events, including source IP addresses, so you can monitor for suspicious access.&lt;/p&gt;

&lt;p&gt;Here is the important part: anyone who is signed in to your Pinggy account can open a shell on your devices. And since the shell runs as your user, that person has access to your source code, your provider keys, and your entire user environment. Therefore, you must enable two-factor authentication (2FA) on your Pinggy account immediately. Do not share screenshots of the dashboard's connect commands, because they contain the device token. Treat those tokens like SSH private keys.&lt;/p&gt;

&lt;p&gt;If you need to revoke access, you have two options. You can delete the device from the dashboard, which severs the connection and prevents it from connecting again with the old token. Or you can rotate the device's token from the dashboard. The local pinggy device remove command only forgets the token on your machine; it does not revoke it remotely. Make sure you understand the difference.&lt;/p&gt;

&lt;p&gt;For an extra layer of security, consider using a dedicated service account for background agents, or limit the permissions of the user that runs the Pinggy CLI. On Linux, you can use systemd to run the agent under a separate user with limited access. On macOS, you might want to use the built-in firewall and app sandboxing. If you are working with highly sensitive code, you can also combine &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; with your own authentication proxy, but that is probably overkill for most developers.&lt;/p&gt;

&lt;p&gt;Another good practice is to set a short screen lock timeout on the machine that runs the agent. If an attacker gains physical access to the machine, they could read the device token and use it to access your Pinggy account. Full disk encryption is also recommended. The agent itself does not store any plaintext credentials beyond the device token and your shell environment variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;Even with a smooth setup, you might encounter issues. Here are some common problems and how to resolve them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terminal tab is greyed out
&lt;/h3&gt;

&lt;p&gt;The most likely cause is that the Pinggy CLI on your machine is too old to support the browser terminal. The terminal tab becomes available in CLI version 0.6.2. If you have an older version, run the install command again to update the CLI. The command is the same one you used for installation.&lt;/p&gt;

&lt;h3&gt;
  
  
  This machine already runs as device  in the background
&lt;/h3&gt;

&lt;p&gt;If you try to run pinggy device login or pinggy device connect --foreground while a background agent is already running, you will see this message. Stop the background agent first with pinggy device stop. Then you can run the foreground command. This is a common mistake when you are testing multiple authentication methods. The background agent already holds the connection, so the foreground command cannot establish a second one.&lt;/p&gt;

&lt;h3&gt;
  
  
  opencode -c shows an error about "ses"
&lt;/h3&gt;

&lt;p&gt;When you run opencode -c in a folder that has no OpenCode sessions, you might see an error like Expected a string starting with "ses" followed by an empty session. This happens because you are in the wrong directory. In a fresh browser shell, you start in your home directory, not in your project folder. You need to cd into the project folder first. Alternatively, open a session by its ID with opencode -s .&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenCode can't reach its server
&lt;/h3&gt;

&lt;p&gt;If the OpenCode client cannot connect to the background server, you will see a connection error. Run opencode service status to check the server's state. If it is stuck, use opencode service restart. This usually resolves the issue. If the server is not running at all, starting a new opencode command should launch it automatically. Sometimes the server's port, 127.0.0.1:49374, is occupied by a different process. You can check with lsof or netstat.&lt;/p&gt;

&lt;h3&gt;
  
  
  Input lag or slow key response
&lt;/h3&gt;

&lt;p&gt;The browser terminal should feel snappy, but if you notice latency, check your network connection. The &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; relay is optimized for low-latency, but a poor internet connection on either end can cause delays. You can also try using a wired connection on the machine side, or switching browsers. Some browser extensions might interfere with WebSocket connections, so try disabling them temporarily.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clipboard issues
&lt;/h3&gt;

&lt;p&gt;Copying and pasting large amounts of text between your local machine and the browser terminal can sometimes be flaky. Use the terminal's own copy and paste shortcuts. In most browser terminals, Ctrl+Shift+C and Ctrl+Shift+V work. For very large pastes, consider echoing the text into a file first. If you are using a Mac, Cmd+C and Cmd+V may work instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  The machine goes offline unexpectedly
&lt;/h3&gt;

&lt;p&gt;If the device shows as offline in the dashboard, the agent might have crashed or the network connection was lost. Check if the machine is awake and has internet access. You can restart the agent by running pinggy device connect again. If the machine rebooted, the agent will not start automatically, so you need to launch it manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Permission denied when running opencode
&lt;/h3&gt;

&lt;p&gt;The shell user may not have execute permission for the OpenCode binary, or the binary may be in a directory that is not in the PATH for the shell that Pinggy starts. Use an absolute path like ~/.opencode/bin/opencode or add the directory to your shell profile. You can also reinstall OpenCode with the official installer, which handles PATH configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Device code" mismatch
&lt;/h3&gt;

&lt;p&gt;During login, the email shows a device code that should match the code displayed in your terminal. If they do not match, do not approve the access. This is a safeguard against session hijacking. The code changes on every attempt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance and Reliability
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; agent maintains a single outbound connection, which is remarkably stable. In my tests, it survived network changes and sleeps on the laptop, though a reboot will obviously disconnect. The connection uses TLS, so all data is encrypted in transit. The relay servers are distributed, but the latency depends on the physical distance between your browser, the Pinggy relay, and your target machine. For most development tasks, the latency is negligible.&lt;/p&gt;

&lt;p&gt;To get the best performance, close unnecessary browser tabs and keep your machine awake. On macOS, use caffeinate if you want to prevent sleep during a long task. On Linux, you can adjust the power settings. You might also want to use a tool like tmux inside the shell to create persistent sessions that survive shell disconnects. Although OpenCode's server provides session persistence, tmux gives you a more general solution for any process.&lt;/p&gt;

&lt;p&gt;Network quality matters. If you are on a cellular connection, you may experience occasional jitter. The terminal protocol is text-based, so the bandwidth usage is low, but packet loss can cause lag. Try to maintain a stable Wi-Fi or Ethernet connection on both sides. If the machine is in a region with high latency, you can still use OpenCode comfortably, but the initial render of a large scrollback might take a second or two.&lt;/p&gt;

&lt;p&gt;Another performance factor is the model you choose. Local models running on the machine, like those served by Ollama, do not depend on internet speed and can be very fast. Cloud models, on the other hand, require streaming iterations over the internet, and the browser terminal adds a tiny overhead. In practice, the overhead is less than 10%, which is not noticeable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;The combination of Pinggy and OpenCode is perfect for several scenarios.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Road warrior developer&lt;/strong&gt;: You are working from a tablet or a borrowed laptop. You connect to your powerful desktop at home and run OpenCode exactly as if you were sitting in front of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server maintenance and AI assistance&lt;/strong&gt;: You have a headless server that hosts your application. You can run OpenCode on the server to refactor code, inspect logs, and run tests, all from a browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporary workstations&lt;/strong&gt;: You are at a client site and need to access a development environment behind their strict firewall. The client machine initiates the outbound connection, so no firewall changes are required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pair programming across locations&lt;/strong&gt;: Two developers can sign in to the same Pinggy account and open separate shells on the same device. They can even run tmux and attach to the same session for collaborative debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost efficiency&lt;/strong&gt;: Instead of carrying a heavy laptop around, you can use a thin client or a Chromebook and offload all computation to your main machine.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let me give you a concrete example. I often leave my MacBook Pro at home, plugged into a dock with multiple monitors. When I am traveling, I use an iPad Pro with a keyboard. I open the Pinggy dashboard, start a terminal shell, and run opencode. The iPad is now a full development environment. I can also use the local Ollama model to avoid sending code to third-party servers, which is useful for confidential projects.&lt;/p&gt;

&lt;p&gt;For teams, the ability to access shared servers remotely from a browser can reduce the need for VPNs. A QA engineer can open a shell on the staging server and run OpenCode to debug an issue, while a backend developer is simultaneously connected to the same device through another shell. This is much simpler than coordinating SSH keys and jump hosts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing with Alternative Remote Dev Tools
&lt;/h2&gt;

&lt;p&gt;There are other ways to access a remote terminal, such as SSH with port forwarding, VS Code Remote, or tools like ngrok. SSH is secure and reliable, but it requires the target machine to be reachable, which is often not the case behind CGNAT. VS Code Remote is excellent for editor integrations, but it is heavier and still requires a tunnel for clients. Pinggy provides a lightweight, browser-based terminal that works everywhere and does not require agent installation on the client, only on the host. It does not offer a full IDE, but for a terminal-based AI coding tool like OpenCode, that is all you need.&lt;/p&gt;

&lt;p&gt;OpenTunnel, mentioned earlier, is great for exposing a specific service to the internet, but it exposes the service itself. With Pinggy, you are not exposing any service; you are exposing a login-protected terminal endpoint. This is a fundamental difference.&lt;/p&gt;

&lt;p&gt;We should also mention that Pinggy supports SSH access through a different feature. If you prefer a native SSH terminal, you can configure Pinggy to provide an SSH host. However, for browser-based access, the Devices terminal is the simplest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls and How to Avoid Them
&lt;/h2&gt;

&lt;p&gt;Even experienced developers can stumble when setting up a remote terminal. Below are some mistakes I have seen, and how to avoid them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting to update the CLI&lt;/strong&gt;: The browser terminal feature is relatively new. Always use the latest Pinggy CLI to get the most up-to-date features and security fixes. Re-run the install script periodically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using the wrong shell&lt;/strong&gt;: The dashboard terminal spawns the user's default shell. If you have customized your shell with a startup script that expects a TTY or specific environment variables, make sure the script does not hang. Some shell profiles display messages that can confuse the terminal emulator. Keep your profile scripts robust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assuming the background agent starts after reboot&lt;/strong&gt;: It does not. You must add it to your startup items if you want it to persist. On macOS, you can use launchd. On Windows, use Task Scheduler. On Linux, use systemd user units.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Running heavy AI tasks on a weak machine&lt;/strong&gt;: The browser terminal is just a front end. The actual AI processing happens on the remote machine. Make sure that machine has enough CPU, memory, and GPU resources. Otherwise, tasks will be slow even with a fast connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not securing your provider API keys&lt;/strong&gt;: If you connect a provider and save the API key in OpenCode's config, anyone with access to your Pinggy shell can use it. Limit the damages by using short-lived keys or a dedicated billing account.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need to keep the Pinggy CLI running in the foreground?
&lt;/h3&gt;

&lt;p&gt;No, starting from version 0.6.4, the agent runs in the background by default. It will persist until the machine reboots. If you want to stop it, use &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; device stop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use the free plan for this?
&lt;/h3&gt;

&lt;p&gt;Yes, the free plan includes one device and one shell. That is sufficient to run OpenCode and do meaningful work. If you need multiple shells or more devices, consider upgrading to Pro.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does OpenCode 2 work with all of my existing plugins?
&lt;/h3&gt;

&lt;p&gt;No, v1 plugins are not compatible with v2. You will need to migrate them or find alternatives. The core functionality, including model selection and tool execution, is built-in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I run OpenCode on a Windows machine?
&lt;/h3&gt;

&lt;p&gt;Yes, OpenCode has a standalone Windows build. The Pinggy CLI also works on Windows through PowerShell. The browser terminal works exactly the same way. The background server listens on a local port, and the client connects as usual.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it safe to leave the browser terminal tab open?
&lt;/h3&gt;

&lt;p&gt;It is as safe as any terminal session. However, you should always lock your screen when away from your device, and enable 2FA on your Pinggy account. The terminal shell has full access to your user account, so treat it with the same caution as a local terminal.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if my machine goes to sleep?
&lt;/h3&gt;

&lt;p&gt;If the machine goes to sleep, the &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; agent will disconnect. When it wakes up, it may reconnect automatically, depending on your settings. OpenCode's server should resume, but if the machine was asleep for a long time, you might need to restart the agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use multiple Pinggy tunnels and the device agent at the same time?
&lt;/h3&gt;

&lt;p&gt;Yes, the features are independent. The device agent does not interfere with localhost tunnels. You can have one terminal for OpenCode and another tunnel for your web app simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the browser terminal support tmux and other GUI tools?
&lt;/h3&gt;

&lt;p&gt;It supports terminal-based tools, including tmux, vim, htop, and most command-line applications. It does not render graphical X11 applications, but there is no need for that in this context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;Let us summarize the entire setup in a few commands. First, install Pinggy and sign in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -fsSL https://pinggy.io/install.sh &lt;span class="o"&gt;|&lt;/span&gt; sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install OpenCode 2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -fsSL https://opencode.ai/v2/install &lt;span class="o"&gt;|&lt;/span&gt; bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the Pinggy Devices page, click your device, go to the Terminal tab, and start a new shell. Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/code/storefront
opencode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To resume a previous session later, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;opencode -c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is all there is to it. You now have a full OpenCode environment that follows you anywhere, without any port forwarding, SSH daemons, or public IP addresses. The only requirement is that your development machine stays powered on and connected to the internet.&lt;/p&gt;

&lt;p&gt;We hope this guide helps you become more productive and flexible. Whether you are a solo developer, a remote worker, or a team lead, the ability to run AI coding tools from any browser is a game changer. If you run into problems, refer to the official documentation and leave a comment below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pinggy.io/blog/run_opencode_remotely_from_any_browser/" rel="noopener noreferrer"&gt;Run OpenCode Remotely from Any Browser with Pinggy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pinggy.io/docs/devices/" rel="noopener noreferrer"&gt;Pinggy Devices documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opencode.ai/docs" rel="noopener noreferrer"&gt;OpenCode documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opencode</category>
      <category>pinggy</category>
      <category>remote</category>
      <category>ai</category>
    </item>
    <item>
      <title>Port 0: The Magic Number That Asks Everyone to Choose</title>
      <dc:creator>Lightning Developer</dc:creator>
      <pubDate>Tue, 10 Nov 2026 13:44:21 +0000</pubDate>
      <link>https://dev.to/lightningdev123/port-0-the-magic-number-that-asks-everyone-to-choose-42p3</link>
      <guid>https://dev.to/lightningdev123/port-0-the-magic-number-that-asks-everyone-to-choose-42p3</guid>
      <description>&lt;p&gt;Every networking developer eventually runs into a strange inconsistency. You can bind a socket to port 0 and the operation succeeds. But if you try to connect to port 0, the connection is refused. The same number appears in SSH remote forwarding commands like &lt;code&gt;ssh -R0:localhost:3000 free.pinggy.io&lt;/code&gt;, and it somehow tells the remote server to choose a free port. Port 0 is reserved, meaning it is not supposed to exist on the wire, yet it is one of the most useful values in the entire sockets API. This post explains what port 0 actually means, why the kernel and the SSH server treat it as a request to pick a port, and how you can use this trick in real development workflows.&lt;/p&gt;

&lt;p&gt;I ran a few small experiments on a Linux 6.18 box to see where that convention holds, where it breaks, and what it has to do with tunnels. All output shown below is real unless it says otherwise. &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; relies on this exact behaviour for its TCP tunnels, so understanding port 0 helps you understand how tools like this work under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Here is what you need to remember from this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the sockets API, &lt;code&gt;bind(0)&lt;/code&gt; asks the kernel for a free ephemeral port. You read the port back with &lt;code&gt;getsockname()&lt;/code&gt;. On Linux the default pool is 32768-60999.&lt;/li&gt;
&lt;li&gt;On the wire, port 0 is reserved. TCP has no use for it, and trying to connect to it fails. UDP has one oddity: RFC 768 lets a sender put 0 in the source port when it does not want a reply.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ssh -R0:...&lt;/code&gt; uses the same trick on a remote machine. The SSH protocol lets the client ask the server to pick the listening port and report it back, which is how a &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; TCP tunnel gets its port.&lt;/li&gt;
&lt;li&gt;The practical rule: ask for port 0 when you do not care which port you get, then read the real port back before you tell anyone else about it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What bind(0) Does
&lt;/h2&gt;

&lt;p&gt;Binding a socket normally means listen on this exact port. Passing 0 changes the question to give me any free one. The kernel picks a port from its ephemeral range, binds the socket to it, and you ask the socket afterwards what it got.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.python.org" rel="noopener noreferrer"&gt;Python&lt;/a&gt; shows it in four lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&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="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getsockname&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;The output on my machine was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;('127.0.0.1', 60681)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it again and you get a diferent number. The second socket I opened got 44627. &lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; does the same through &lt;code&gt;listen(0)&lt;/code&gt;:&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="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'net'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&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="nf"&gt;listen&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="s1"&gt;'127.0.0.1'&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'listening on'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&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="n"&gt;port&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;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;listening on 44909
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The range comes from a sysctl. On the machine I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cat /proc/sys/net/ipv4/ip_local_port_range
&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;32768   60999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the Linux default. IANA's suggested ephemeral range in RFC 6335 is 49152-65535, and Windows and the BSDs follow that one, so the same program lands in a different part of the port space depending on the operating system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx60jn0fteab0i69w3twy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx60jn0fteab0i69w3twy.webp" alt="Blog Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The port number exists only after &lt;code&gt;bind&lt;/code&gt; returns, so the program has to read it back. Why bother? Because hard-coded ports collide. Test suites that start a server per test, CI jobs that share a runner, and two dev servers that both want 3000 all trip over each other. Letting the kernel choose means nobody collides, since the kernel will not hand out a port that is already bound.&lt;/p&gt;

&lt;p&gt;There is a catch: the port is only free at the moment you bind. Code that picks a port with &lt;code&gt;bind(0)&lt;/code&gt;, closes the socket, and then passes the number to another process has a race. Something else can take it in between. Keep the socket open and hand the process the socket, or have the server itself listen on 0 and report the port.&lt;/p&gt;

&lt;p&gt;Inside the kernel, &lt;code&gt;bind(0)&lt;/code&gt; triggers the ephemeral port allocator. The allocator scans the local port range, skips ports already in use by other sockets or reserved by listening services, and returns the first available candidate. Different kernels have different allocation strategies. Linux tends to allocate ports sequentially and wrap around, which makes large scale systems predictable. &lt;a href="https://www.freebsd.org" rel="noopener noreferrer"&gt;FreeBSD&lt;/a&gt; and macOS use a random offset to make it harder for remote attackers to guess the next port. This matters for security because predictable ephemeral ports can make certain spoofing attacks easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why You Can't Connect to Port 0
&lt;/h2&gt;

&lt;p&gt;The sockets convention is a local trick. Port 0 is still marked reserved in the &lt;a href="https://www.iana.org" rel="noopener noreferrer"&gt;IANA&lt;/a&gt; registry, and TCP does not use it. If you try to connect to it, you get an instant failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;
&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&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;ConnectionRefusedError: [Errno 111] Connection refused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A UDP &lt;code&gt;sendto()&lt;/code&gt; to port 0 is rejected before it leaves the machine, with &lt;code&gt;EINVAL&lt;/code&gt; (Invalid argument) on my run. So 0 is a valid argument to &lt;code&gt;bind&lt;/code&gt; and an invalid destination for everything else.&lt;/p&gt;

&lt;p&gt;The nuance is the UDP source port. RFC 768 calls the source port optional: it indicates the port of the sending process, and if it is not used, a value of zero is inserted. In practice it means do not reply to me. It is legal on the wire, and some firewalls drop it anyway. The IETF mailing lists have argued for years over whether reserved means forbidden or just unassigned; one FreeBSD thread covers the firewall angle, and an Austin Group bug report covers whether POSIX should specify the &lt;code&gt;bind&lt;/code&gt; behaviour at all. POSIX itself does not say much on this subject.&lt;/p&gt;

&lt;p&gt;For SCTP the rule is stricter: RFC 4960 section 3.1 forbids 0 as a source or destination port.&lt;/p&gt;

&lt;p&gt;The takeaway: nobody should be listening on port 0 and nobody should be dialing it, and no real service will recieve your packets if you do. When you see traffic to port 0 in a firewall log, it is almost always a probe or fragments that lost their port information, not a service.&lt;/p&gt;

&lt;p&gt;At the packet level, a TCP header with source or destination port 0 is not generated by normal kernels. The TCP stack treats port 0 as an invalid destination and returns &lt;code&gt;ECONNREFUSED&lt;/code&gt; without sending a SYN. That is why the failure is immediate. UDP is different. The kernel checks the destination port locally and rejects the &lt;code&gt;sendto&lt;/code&gt; call with &lt;code&gt;EINVAL&lt;/code&gt; before the packet hits the wire. Destination port 0 is therefore not a network problem, it is a local policy decision built into the protocol stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Other Port 0: ssh -R0
&lt;/h2&gt;

&lt;p&gt;If you have used &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt;, you have probably typed this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh -p 443 -R0:localhost:3000 free.pinggy.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;0&lt;/code&gt; in &lt;code&gt;-R0&lt;/code&gt; is the remote port to listen on, and it is the same idea as &lt;code&gt;bind(0)&lt;/code&gt;. The format is &lt;code&gt;-R [bind_address:]port:host:hostport&lt;/code&gt;, so &lt;code&gt;-R0:localhost:3000&lt;/code&gt; reads as listen on any free port on the server, and send what arrives to &lt;code&gt;localhost:3000&lt;/code&gt; on my machine.&lt;/p&gt;

&lt;p&gt;The SSH protocol has this built in. In RFC 4254, section 7.1, the client sends a &lt;code&gt;tcpip-forward&lt;/code&gt; request, and if the port to bind is 0 and the client asked for a reply, the server allocates the next available unprivileged port and returns it. The client then knows where the forward lives.&lt;/p&gt;

&lt;p&gt;With a plain &lt;a href="https://www.openssh.com" rel="noopener noreferrer"&gt;OpenSSH&lt;/a&gt; server, you can see it directly. This is OpenSSH's own behaviour, not something that is specific to Pinggy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Allocated port 41873 for remote forward to localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OpenSSH prints that line on stderr when the server picks the port. With a Pinggy HTTP tunnel the port is hidden behind a hostname, so you get a URL such as &lt;code&gt;https://abc123.a.pinggy.link&lt;/code&gt;. For a TCP tunnel you get the host and port itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh -p 443 -R0:localhost:22 tcp@free.pinggy.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pinggy prints a &lt;code&gt;tcp://&lt;/code&gt; address with a port the server allocated, and that port is what a friend types into their SSH client. With &lt;code&gt;-R0&lt;/code&gt; the Pinggy server picks the public port and tells the client which one it got.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmndcir7ua8xrldn01lex.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmndcir7ua8xrldn01lex.webp" alt="Blog Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key detail is that SSH does not use the TCP port 0 destination mechanism. The SSH protocol is a control protocol with its own message types. Port 0 inside the SSH request is interpreted by the SSH server as a special value, not as a network destination. This is why port 0 can mean pick one for me in SSH even though it is invalid on the wire.&lt;/p&gt;

&lt;p&gt;When the server receives the &lt;code&gt;tcpip-forward&lt;/code&gt; request, it begins listening on the requested interface. If the requested port is 0, it asks its own ephemeral port allocator for a free number. The server then sends a success response that contains the allocated port. The client can use that number later when it opens data channels. The same pattern appears in tools like &lt;code&gt;ssh -R&lt;/code&gt; and in tunnelling services like Pinggy.&lt;/p&gt;

&lt;p&gt;I could not run the two Pinggy commands from the sandbox I wrote this in, because the sandbox has no &lt;code&gt;ssh&lt;/code&gt; client. The output of those commands is therefore untested here, but the command forms match the Pinggy documentation.&lt;/p&gt;

&lt;p&gt;Port 0 is not limited to remote forwarding. You can also use it with &lt;code&gt;-L&lt;/code&gt; to make the local SSH client choose a free local listening port. The client prints a message similar to the remote one. The important idea is that port 0 is a signal to the SSH machinery, not a literal port number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Both Zeros in One Workflow
&lt;/h2&gt;

&lt;p&gt;Here is a worked example: a &lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; dev server picks its own port, and a tunnel points at whatever port it got.&lt;/p&gt;

&lt;p&gt;First, create &lt;code&gt;server.js&lt;/code&gt;:&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="n"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&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="s1"&gt;'127.0.0.1'&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&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="n"&gt;port&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;Run the server in one terminal and read the port it prints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node server.js
&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;44909
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start teh tunnel from another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh -p 443 -R0:localhost:44909 free.pinggy.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap &lt;code&gt;44909&lt;/code&gt; for the port your server printed. Two kinds of pick a port for me are now stacked: the kernel chose &lt;code&gt;44909&lt;/code&gt; locally, and the Pinggy server chooses the public side. Neither number was in your config, and nothing had to be free in advance.&lt;/p&gt;

&lt;p&gt;The main thing to watch is the local race from earlier. If you script this, have the server write the port to a file after it is listening, and start the tunnel after the file exists.&lt;/p&gt;

&lt;p&gt;For a more robust script, &lt;code&gt;server.js&lt;/code&gt; can write the port itself:&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="n"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fs'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&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="s1"&gt;'127.0.0.1'&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'port.txt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&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="n"&gt;port&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the shell script can wait for the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node server.js &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
&lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -s port.txt &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; sleep 0.1&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;cat port.txt&lt;span class="o"&gt;)&lt;/span&gt;
ssh -p 443 -R0:localhost:&lt;span class="nv"&gt;$PORT&lt;/span&gt; free.pinggy.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents the race because the server is already listening when the file is created. The tunnel also uses its own 0, so the public port is dynamic.&lt;/p&gt;

&lt;p&gt;When you need a predictable public port for clients, a dynamic port can be inconvenient. Some tunnel services let you reserve a subdomain or a fixed port. Pinggy supports fixed ports on certain plans, but the free flow is designed around the zero trick.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing It on Your Own Machine
&lt;/h2&gt;

&lt;p&gt;Three quick checks, all of which work without root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s1"&gt;'PY'&lt;/span&gt;
import socket
s &lt;span class="o"&gt;=&lt;/span&gt; socket.socket&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
s.bind&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;, 0&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
print&lt;span class="o"&gt;(&lt;/span&gt;s.getsockname&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;1&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
PY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cat /proc/sys/net/ipv4/ip_local_port_range
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On macOS, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also inspect the ephemeral range on Linux with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sysctl net.ipv4.ip_local_port_range
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should match &lt;code&gt;/proc/sys/net/ipv4/ip_local_port_range&lt;/code&gt;. Finally, to see which sockets are listening on your machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsof -iTCP -sTCP:LISTEN -P -n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the Python snippet several times and watch the number change. Then try &lt;code&gt;nc 127.0.0.1 0&lt;/code&gt;. The copy of netcat on my box rejected it before touching the network, printing &lt;code&gt;nc: port number too small: 0&lt;/code&gt;, so the Python connection test above is a better way to demonstrate the failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real World Patterns and Gotchas
&lt;/h2&gt;

&lt;p&gt;The port 0 trick is not just an academic curiosity. It appears in many production tools and workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker and Containers
&lt;/h3&gt;

&lt;p&gt;Inside a container, &lt;code&gt;listen(0)&lt;/code&gt; works exactly as it does on a host. The container kernel allocates an ephemeral port, and the process can read it back. However, if you publish a container port to the host, you generally need a known port for service discovery. &lt;a href="https://www.docker.com" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;'s &lt;code&gt;-P&lt;/code&gt; flag publishes every exposed port to a random host port, which is a similar idea but not directly related to port 0. If you need a random host port with a fixed container port, you can run &lt;code&gt;docker run -p 127.0.0.1::8080 ...&lt;/code&gt; and let Docker choose a host port.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI Pipelines
&lt;/h3&gt;

&lt;p&gt;CI systems often run multiple test jobs on the same physical machine. A test suite that binds to a fixed port can fail with &lt;code&gt;EADDRINUSE&lt;/code&gt; when two jobs run at the same time. Using port 0 removes this class of failure. The challenge is that the test framework has to expose the chosen port. Some frameworks read it from the server object, while others require you to inspect the logs. Many load balancers and sidecars also need a predictable port, so the zero trick is most valuable in isolated environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hot Reload and Dev Servers
&lt;/h3&gt;

&lt;p&gt;Modern development servers can use port 0 to avoid the infamous &lt;code&gt;EADDRINUSE&lt;/code&gt; error. The terminal prints the real port after startup. This is particularly useful when you have multiple projects open at the same time and you do not want to remember which port belongs to which project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ephemeral Port Exhaustion
&lt;/h3&gt;

&lt;p&gt;Every time you bind to 0, you consume one entry from the ephemeral port range. If you do this many times without closing sockets, you can exhaust the available pool. This can happen in test suites that create thousands of sockets in a short time. On Linux, the default range contains roughly 28,000 ports. A busy server that also makes outbound connections shares the same pool. Use &lt;code&gt;ss -s&lt;/code&gt; or &lt;code&gt;netstat -s&lt;/code&gt; to monitor socket usage and port allocation. If you see failures like &lt;code&gt;EADDRNOTAVAIL&lt;/code&gt;, you may have run out of ephemeral ports.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firewalls and Security
&lt;/h3&gt;

&lt;p&gt;Dynamic ports make firewall rules harder to write. If you know that a service always listens on port 3000, you can allow TCP 3000 in your security group. With port 0, you have no fixed port to allow, so you must either allow a wide range or use a service mesh that handles registration. For SSH tunnels, the remote side picks the port, which is fine for temporary sharing but impractical for a production API endpoint. A tunnel service like Pinggy manages the public port for you, so you do not need to update firewall rules on your laptop.&lt;/p&gt;

&lt;h3&gt;
  
  
  IPv6
&lt;/h3&gt;

&lt;p&gt;The zero trick works with IPv6 as well. You can bind a socket to &lt;code&gt;[::1]:0&lt;/code&gt; and the kernel selects a port from the same ephemeral range. The address family does not change the semantics of port 0. The same &lt;code&gt;ss&lt;/code&gt; or &lt;code&gt;netstat&lt;/code&gt; commands will show the assigned port.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orchestrators and Kubernetes
&lt;/h3&gt;

&lt;p&gt;Kubernetes and other orchestrators usually do not allow port 0 in container port definitions. The port is part of the pod spec and must be a valid integer between 1 and 65535. A process inside the container can still bind to port 0 and later register itself with a service discovery agent, but the container port metadata cannot say 0. This can be a source of confusion when moving from a local test with port 0 to a deployment manifest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Port 0 Across Languages
&lt;/h2&gt;

&lt;p&gt;The port 0 convention is part of the sockets API, so every language that exposes sockets supports it. Here are a few examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  Go
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://go.dev" rel="noopener noreferrer"&gt;Go&lt;/a&gt;, &lt;code&gt;net.Listen&lt;/code&gt; accepts port 0 and the listener address contains the assigned port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;"net"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;main&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="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1:0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Addr&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TCPAddr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Port&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;
  
  
  Ruby
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.ruby-lang.org" rel="noopener noreferrer"&gt;Ruby&lt;/a&gt; uses &lt;code&gt;TCPServer.new&lt;/code&gt; with port 0:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'socket'&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TCPServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&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="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rust
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://www.rust-lang.org" rel="noopener noreferrer"&gt;Rust&lt;/a&gt;, you call &lt;code&gt;local_addr&lt;/code&gt; to discover the port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;TcpListener&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&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="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TcpListener&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1:0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unwrap&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="nf"&gt;println!&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="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local_addr&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="nf"&gt;unwrap&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="nf"&gt;port&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of these use the same kernel feature. The language does not change the meaning of port 0; it only changes how you read the assigned port.&lt;/p&gt;

&lt;h2&gt;
  
  
  Port 0 in Web Frameworks
&lt;/h2&gt;

&lt;p&gt;The same idea applies to high level web frameworks. &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;Express&lt;/a&gt; in Node.js accepts &lt;code&gt;0&lt;/code&gt; as the port parameter:&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="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'express'&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="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello'&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="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&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="p"&gt;(&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="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&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="n"&gt;port&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;In Python, the standard library &lt;code&gt;http.server&lt;/code&gt; can run on port 0 if you create the server object directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BaseHTTPRequestHandler&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BaseHTTPRequestHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end_headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;'hello'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;httpd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&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="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server_address&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;httpd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle_request&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 kernel assigns the port only after the server socket is created. The framework hands you the address object so you can discover the port.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Misunderstandings About Port 0
&lt;/h2&gt;

&lt;p&gt;There are several confusing ideas about port 0 that show up in developer discussions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Port 0 Means Disabled
&lt;/h3&gt;

&lt;p&gt;Some configuration files use port 0 to mean do not listen. That is an application-level convention. In the sockets API, port 0 always means kernel, choose. A server program may treat 0 differently from the raw socket API, so check the documentation before assuming.&lt;/p&gt;

&lt;h3&gt;
  
  
  Port 0 Is a Wildcard
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;bind()&lt;/code&gt;, port 0 is not a wildcard. The wildcard address is &lt;code&gt;0.0.0.0&lt;/code&gt; for IPv4 and &lt;code&gt;::&lt;/code&gt; for IPv6. Port 0 is a request to allocate a concrete port. After the call, the port is no longer zero.&lt;/p&gt;

&lt;h3&gt;
  
  
  Port 0 Can Be Connected To
&lt;/h3&gt;

&lt;p&gt;Some developers think that if a process binds to port 0, another process can connect to port 0 and reach it. That is false. The bound socket has a real ephemeral port, not port 0. A connection to port 0 is invalid and will fail before it reaches any listener.&lt;/p&gt;

&lt;h3&gt;
  
  
  Port 0 Is a Secret Port
&lt;/h3&gt;

&lt;p&gt;Port 0 is reserved, not secret. If someone tells you to connect to port 0, they are probably talking about a dynamic port that needs to be discovered. Do not hard-code it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting and Common Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why does bind(0) return the same port twice?
&lt;/h3&gt;

&lt;p&gt;If you close the first socket and then bind a second one immediately, the kernel may reuse the same port because it is the first free port in the sequence. This is not a bug. The kernel has no memory that you do not want the same number twice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I put port 0 in a URL?
&lt;/h3&gt;

&lt;p&gt;No. A URL like &lt;code&gt;http://localhost:0/&lt;/code&gt; is invalid for practical purposes. Browsers ignore the port or reject it. Port numbers in URLs must be between 1 and 65535.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does port 0 work with UDP servers?
&lt;/h3&gt;

&lt;p&gt;Yes. You can call &lt;code&gt;bind()&lt;/code&gt; on a UDP socket with port 0 if you want the kernel to pick a source port. This is useful for clients that need a unique source port. The server will see a nonzero source port in the packet header after the kernel replaces the 0.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens if a TCP SYN arrives with destination port 0?
&lt;/h3&gt;

&lt;p&gt;It should not happen. If it does, the receiving stack will almost certainly drop it. Some network monitoring tools may flag it as abnormal traffic. There is no service registered on port 0, so there is nothing to handle it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I get the assigned port in a shell script?
&lt;/h3&gt;

&lt;p&gt;If your server prints the port to stdout, capture the output. If it does not, write the port to a file. The file-based approach is safer because it avoids the race where the port is read before the server is listening.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can ssh -R0 fail?
&lt;/h3&gt;

&lt;p&gt;Yes. The remote SSH server must allow remote forwarding. OpenSSH's &lt;code&gt;AllowTcpForwarding remote&lt;/code&gt; or &lt;code&gt;PermitOpen&lt;/code&gt; settings can block it. The server may also have a restricted port range. If port 0 is not allowed, you will see an error such as &lt;code&gt;remote port forwarding failed for listen port 0&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do some packets show source port 0 in UDP?
&lt;/h3&gt;

&lt;p&gt;RFC 768 permits a sender to use source port 0 when a reply is not wanted. This happens in some network boot and discovery protocols. Many firewalls treat source port 0 as suspicious and drop the packet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a way to avoid the race when using bind(0)?
&lt;/h3&gt;

&lt;p&gt;The safest approach is to keep the listening socket open in the same process that needs the port. If another process must know the port, pass the socket file descriptor over the process boundary using file descriptor passing. Alternatively, have the listening process write the port to a file or publish it to a service registry after the socket is bound.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Port 0 is not a port you use. It is a request. Locally it means kernel, choose. Remotely it means server, choose. In both cases the real number only shows up in the reply. If you write servers or test harnesses, &lt;code&gt;listen(0)&lt;/code&gt; and a read-back cures most port clashes. If you tunnel, remember that the &lt;code&gt;0&lt;/code&gt; in &lt;code&gt;-R0&lt;/code&gt; is why you did not have to pick a public port at all.&lt;/p&gt;

&lt;p&gt;Understanding port 0 makes socket programming feel less magical. The reserved number is not a destination; it is a placeholder for the allocator. Once you understand that, you can read the real port back and move on with confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pinggy.io/blog/port_0_the_port_that_does_not_exist/" rel="noopener noreferrer"&gt;Port 0: The Port That Doesn't Exist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc768" rel="noopener noreferrer"&gt;RFC 768: User Datagram Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc4254" rel="noopener noreferrer"&gt;RFC 4254: The Secure Shell (SSH) Connection Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc4960" rel="noopener noreferrer"&gt;RFC 4960: Stream Control Transmission Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6335" rel="noopener noreferrer"&gt;RFC 6335: Internet Assigned Numbers Authority (IANA) Procedures for the Management of the Service Name and Transport Protocol Port Number Registry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>networking</category>
      <category>ssh</category>
      <category>tcp</category>
      <category>tunneling</category>
    </item>
    <item>
      <title>Run Claude Code From Any Browser: A Pinggy Devices Guide</title>
      <dc:creator>Lightning Developer</dc:creator>
      <pubDate>Mon, 09 Nov 2026 13:30:36 +0000</pubDate>
      <link>https://dev.to/lightningdev123/run-claude-code-from-any-browser-a-pinggy-devices-guide-598c</link>
      <guid>https://dev.to/lightningdev123/run-claude-code-from-any-browser-a-pinggy-devices-guide-598c</guid>
      <description>&lt;p&gt;Access to a real terminal from any browser is one of those superpowers that sounds too good to be true. You keep your workstation or server where it is, you leave a small agent running, and then from a laptop, tablet, or even a borrowed computer you open a browser tab and get a shell on that machine. &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; makes this workflow simple with its Devices feature. In this guide I will show you how to combine &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy Devices&lt;/a&gt; with &lt;a href="https://claude.com/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; so you can run AI coding sessions remotely, inspect logs, restart services, and keep long tasks alive even after you close the browser.&lt;/p&gt;

&lt;p&gt;The core idea is straightforward. You install the &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy CLI&lt;/a&gt; on a machine, connect to the Pinggy dashboard with one outbound connection, and then use that browser dashboard to open a terminal. Once the terminal exists, you can run &lt;code&gt;claude&lt;/code&gt; or any other command line tool you would normally use locally. There is no SSH server, no open inbound port, and no public IP address to configure. For developers who work with AI agents, this removes a whole category of network headaches.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/FNj9WTlHllY" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: AI Coding Agents Are Tied to Your Machine
&lt;/h2&gt;

&lt;p&gt;Claude Code and similar coding agents are command line tools. They are great when you are sitting in front of your development machine. They can read your repository, edit files, run tests, commit code, and interact with APIs using your saved credentials. But once you step away, they become unreachable. Laptops sleep, office networks block inbound traffic, home routers use NAT, and cloud VMs may be locked down with security groups. You may want to start a long refactoring task and then check on it from another room or from a different city. The default tools, SSH and remote control features, have limitations.&lt;/p&gt;

&lt;p&gt;SSH requires a server listening on port 22, a reachable IP address, and often a number of firewall changes. If the machine is behind a hotel router, a cellular connection, or a strict corporate firewall, inbound SSH is not an option. You can use a reverse tunnel, but tunnels need to be started, monitored, and sometimes restarted. Claude Code has a Remote Control mode, but it is for attaching to an already running &lt;code&gt;claude&lt;/code&gt; process, and it requires someone or something to start that session first. Also, depending on your account tier, Remote Control may not be available at all.&lt;/p&gt;

&lt;p&gt;This is where the terminal in browser model shines. The machine makes one outbound connection. The browser connects to a dashboard over HTTPS. The dashboard relays keystrokes and terminal output. No inbound network configuration is needed. In the rest of this post, I will walk through the complete setup using Pinggy Devices, then run Claude Code from a browser terminal, and cover the production details that matter when you trust a remote AI agent with real work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Pinggy Devices Actually Is
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy Devices&lt;/a&gt; is a feature of Pinggy that turns any machine with the Pinggy CLI into a device you can manage from the &lt;a href="https://dashboard.pinggy.io" rel="noopener noreferrer"&gt;Pinggy dashboard&lt;/a&gt;. The feature appeared in Pinggy CLI v0.6.2 on November 2, 2026. I tested the workflow with Pinggy CLI 0.6.3 and Claude Code 2.1.318 on an Apple Silicon MacBook. The limits and pricing details mentioned in this article are from the &lt;a href="https://pinggy.io/docs/devices/" rel="noopener noreferrer"&gt;Pinggy Devices docs&lt;/a&gt; and the &lt;a href="https://pinggy.io/pricing" rel="noopener noreferrer"&gt;Pinggy pricing page&lt;/a&gt;, checked on November 3, 2026.&lt;/p&gt;

&lt;p&gt;Unlike a traditional tunnel, Pinggy Devices does not expose a port on the public internet by default. Instead, the CLI registers the machine with your account and keeps a persistent channel to the dashboard. From the dashboard, you can open a shell on that machine. Each shell runs as the same user who started the Pinggy agent. That is a crucial detail: if you run &lt;code&gt;pinggy device connect&lt;/code&gt; as your normal user, the browser terminal will have your home directory, your Git configuration, your SSH keys, your environment variables, and your Claude Code login. It is effectively you sitting at the machine, just rendered through a browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Architecture Works
&lt;/h2&gt;

&lt;p&gt;Let us look at the moving parts before we jump into commands. Pinggy Devices is not a magic portal into your laptop. It is a relay between a browser based terminal client and a shell process on your machine. Here is the flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Pinggy CLI on your machine establishes an outbound connection to the Pinggy infrastructure.&lt;/li&gt;
&lt;li&gt;The CLI authenticates itself with a device token, which is stored locally after the first successful connect.&lt;/li&gt;
&lt;li&gt;The Pinggy dashboard, which your browser loads over HTTPS, sends your keystrokes to the relay.&lt;/li&gt;
&lt;li&gt;The relay forwards those keystrokes through the existing outbound connection to the CLI agent on the machine.&lt;/li&gt;
&lt;li&gt;The agent starts or attaches to a shell, runs the commands, and streams the resulting terminal output back to the browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the connection is initiated by the machine, there is no need for a public IP, port forwarding, or a DMZ. The outbound connection can use standard HTTPS ports, which means it can pass through most firewalls and proxy environments. The browser side only needs access to the Pinggy dashboard, which is a normal website. This architecture is similar to remote support tools, but it is designed for developers who need full terminal access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you begin, make sure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A machine you can physically access at least once, such as a laptop, desktop, or VM.&lt;/li&gt;
&lt;li&gt;A user account on that machine with permission to run the Pinggy CLI. You do not need root.&lt;/li&gt;
&lt;li&gt;An email address for the Pinggy account. If you do not already have one, a new free account will be created when you enter the email and verification code.&lt;/li&gt;
&lt;li&gt;A browser that can reach the Pinggy dashboard. Chrome, Firefox, Safari, and Edge all work.&lt;/li&gt;
&lt;li&gt;Enough disk space and memory to run both the CLI and Claude Code. Claude Code is a Node.js application, and its typical install requires a few hundred megabytes.&lt;/li&gt;
&lt;li&gt;Optionally, npm and Node.js if you are on Alpine or another musl based Linux distribution, because the standard install script may not work there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The install script does not require sudo. It installs the CLI into your home directory and runs the login flow automatically. On Windows, the PowerShell script uses &lt;code&gt;irm&lt;/code&gt; and &lt;code&gt;iex&lt;/code&gt;. On Alpine, the official docs recommend &lt;code&gt;npm install -g pinggy&lt;/code&gt; instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install the Pinggy CLI and Sign In
&lt;/h2&gt;

&lt;p&gt;The first command is simple. On Linux and macOS, open a terminal on the machine you want to reach and run:&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;-fsSL&lt;/span&gt; https://pinggy.io/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script will download the Pinggy CLI, put it in a location on your &lt;code&gt;PATH&lt;/code&gt;, and start the login process. On Windows, use PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://pinggy.io/install.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the CLI asks for your email, enter the address you want to associate with the Pinggy account. It will send a six digit verification code. Type that code in the terminal. You may also be shown a link with an &lt;code&gt;Approve sign in&lt;/code&gt; button. Only approve the login if the page on your screen displays the same code that the terminal printed. This prevents account takeovers from a mistyped email.&lt;/p&gt;

&lt;p&gt;After the code is accepted, the CLI creates a device on your Pinggy account. The device name is usually the hostname of your machine. It appears on the Pinggy Devices page in the dashboard. If the machine was already added, the dashboard shows a &lt;code&gt;Connect this device&lt;/code&gt; card on the Overview tab. That card contains two commands that already include your device token.&lt;/p&gt;

&lt;p&gt;The first command works for machines that do not have the CLI installed:&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;-fsSL&lt;/span&gt; https://pinggy.io/install.sh | &lt;span class="nv"&gt;DEVICE_AGENT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;TOKEN&amp;gt; sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second command is for machines where the CLI is already installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pinggy device connect &lt;span class="nt"&gt;--token&lt;/span&gt; &amp;lt;TOKEN&amp;gt; &lt;span class="nt"&gt;--manage&lt;/span&gt; dashboard.pinggy.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--manage&lt;/code&gt; flag tells the agent which dashboard host it should connect to. The documentation for Pinggy Devices lists the same command without &lt;code&gt;--manage&lt;/code&gt;, and it uses the default dashboard host. Using an explicit &lt;code&gt;--manage&lt;/code&gt; is useful when you have multiple environments or when your company runs a custom relay. The token based flow is important for headless machines because you do not need to type an email or a verification code. You can install and connect in one step.&lt;/p&gt;

&lt;p&gt;After the first successful connect, the CLI saves the token on the machine. From then on, you only need &lt;code&gt;pinggy device connect&lt;/code&gt; to reconnect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Keep the Device Running in the Background
&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;pinggy device login&lt;/code&gt; or &lt;code&gt;pinggy device connect&lt;/code&gt; for the first time, the agent stays in the foreground. It prints connection status, and it keeps running until you press Ctrl+C. For a quick test, that is fine. For real use, you want the agent to survive after you close the terminal. Stop the foreground agent, then reconnect in teh background with the &lt;code&gt;-b&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pinggy device connect &lt;span class="nt"&gt;-b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the machine does not have a saved token yet, include the token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pinggy device connect &lt;span class="nt"&gt;--token&lt;/span&gt; &amp;lt;TOKEN&amp;gt; &lt;span class="nt"&gt;-b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-b&lt;/code&gt; flag tells the CLI to run as part of the Pinggy daemon. The daemon is a lightweight background process that starts when you first run a background connect command. It is not the same as a cron job or a systemd service. It outlives the terminal that started it, but it does not survive a reboot. After the machine restarts, you must run &lt;code&gt;pinggy device connect -b&lt;/code&gt; again to bring the device back online.&lt;/p&gt;

&lt;p&gt;To stop the background agent, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pinggy device stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to stop the entire Pinggy daemon and all connected devices, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pinggy daemon stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the agent is stopped, the Pinggy dashboard shows the device as offline. If you try to open a terminal, the Terminal tab will be unusable until the device reconnects. You can also remove the device token from the local machine with &lt;code&gt;pinggy device remove&lt;/code&gt;, but that only forgets the token locally. It does not delete the device from your account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Open the Terminal and Run Claude Code
&lt;/h2&gt;

&lt;p&gt;Now the fun part. Go to the &lt;a href="https://dashboard.pinggy.io" rel="noopener noreferrer"&gt;Pinggy dashboard&lt;/a&gt;, open the Devices page, and click the device you just connected. The Overview tab shows useful information about the machine: hostname, operating system, CPU, memory, and live system usage. This is handy for quickly checking whether the machine is under load before you start a big AI assisted refactor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnz0cp4o0ogtrwdhyshk7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnz0cp4o0ogtrwdhyshk7.webp" alt="Blog Image" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above the system stats, you may see buttons for &lt;code&gt;Terminal&lt;/code&gt;, &lt;code&gt;Activity&lt;/code&gt;, and other tabs. Click Terminal and then click &lt;code&gt;New shell&lt;/code&gt;. A new shell tab opens. Each shell gets its own tab, so you can have multiple terminals open at the same time, depending on your plan. From the browser, you can use the terminal almost exactly as you would use a local terminal. Typing, keyboard shortcuts, and terminal resizing work through the browser based client.&lt;/p&gt;

&lt;p&gt;Let me show you a typical Claude Code workflow. You can change into your project directory and start the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/code/storefront
claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time you run &lt;code&gt;claude&lt;/code&gt; in a browser shell, it may ask you to log in. If Claude Code is not installed yet, install it from the same browser shell:&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;-fsSL&lt;/span&gt; https://claude.ai/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, you should log in while you are still physically at the machine, because the first launch of Claude Code tries to open a browser window on the local machine. Claude Code has a fallback that prints a login URL and asks you to copy it, but I have not personally tested that fallback through the Pinggy dashboard terminal. If you want to be safe, complete the Claude Code login before you leave home.&lt;/p&gt;

&lt;p&gt;Once Claude Code is running, the browser terminal behaves like a normal terminal. You can describe a task in natural language, approve file edits, run tests, and watch the output. The terminal redraws quickly enough for interactive work, and the dashboard keeps the session alive as long as you keep the shell active.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmueva905ohl6wviy7fqg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmueva905ohl6wviy7fqg.webp" alt="Blog Image" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is what a simple Claude Code session looks like inside a Pinggy browser terminal. The shell on the right is running on the remote machine, while the browser is only responsible for drawing the terminal screen and capturing keystrokes.&lt;/p&gt;

&lt;p&gt;Claude Code can also be used for non interactive tasks. You can ask it to generate a commit message, run the test suite, or fix a lint error. The browser terminal streams all of it back to you in real time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjkrc86x0t4tswbe15t53.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjkrc86x0t4tswbe15t53.webp" alt="Blog Image" width="799" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After Claude Code finishes a task, it prints a summary of the files it changed and the commands it ran. You can review the output, type a follow up prompt, or switch to another shell tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Run Long Tasks in the Background
&lt;/h2&gt;

&lt;p&gt;A dashboard shell has two time limits. If the shell has no activity for 30 minutes, it ends. If the shell is open for 8 hours, it ends regardless of activity. This is intentional; it prevents forgotten browser tabs from leaving orphaned processes running forever. But it creates a problem for long Claude Code tasks, like running the test suite and fixing all failing tests in &lt;code&gt;src/billing&lt;/code&gt;, or migrating a codebase from JavaScript to TypeScript. Those tasks can take longer than 30 minutes, and you might not be typing while they run. If the shell ends, the process running inside it may also end.&lt;/p&gt;

&lt;p&gt;To handle long tasks, Claude Code has a background mode. You can start a session that is supervised by a seperate supervisor process, not by the terminal. The command looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--bg&lt;/span&gt; &lt;span class="s1"&gt;'run the test suite and fix the failing tests in src/billing'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Claude Code 2.1.317, running a short test prompt returned immediately and printed something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting background service...

backgrounded · 5dc70293

  claude agents             list sessions
  claude attach 5dc70293    open in this terminal
  claude logs 5dc70293      show recent output
  claude stop 5dc70293      stop this session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important thing is that a &lt;code&gt;--bg&lt;/code&gt; session is not attached to the shell that started it. It runs in its own supervisor process, so it keeps working even if the dashboard shell closes. You can close the browser tab, go for a walk, and come back later. From any later shell, you can list all your sessions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can reattach to a specific session with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude attach 5dc70293
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can view recent output without attaching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude logs 5dc70293
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you can stop a session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude stop 5dc70293
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To detach from an attached session, press the left arrow key on an empty prompt, or press Ctrl+Z. A background session runs in its own Git worktree under &lt;code&gt;.claude/worktrees/&lt;/code&gt;. This isolates the changes it makes from your main working tree until you merge them. The session survives sleep mode, but it stops if the machine shuts down. If you expect to run long tasks overnight, make sure the machine is plugged in and not set to hibernate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free vs Pro: What You Actually Get
&lt;/h2&gt;

&lt;p&gt;The Pinggy browser terminal is available on every plan, including the Free plan. The differences are in the number of devices and concurrent terminals. The following numbers come from the &lt;a href="https://pinggy.io/pricing" rel="noopener noreferrer"&gt;Pinggy pricing page&lt;/a&gt; and the &lt;a href="https://pinggy.io/docs/devices/" rel="noopener noreferrer"&gt;Pinggy Devices docs&lt;/a&gt;, as checked on November 3, 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Devices&lt;/th&gt;
&lt;th&gt;Terminals per device&lt;/th&gt;
&lt;th&gt;Terminals open across all devices&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;$2.50/month billed annually, $3/month billed monthly&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10 per seat&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Free plan is enough for one home machine and one terminal. If you only need to run one Claude Code session from your laptop while you are away, that is fine. Once you start using multiple shells, the Pro plan becomes much more comfortable. Three shells per device means you can keep one terminal for Claude Code, one for a development server, and one for Git commands or log watching.&lt;/p&gt;

&lt;p&gt;Pro also includes persistent tunnels, custom domains, and teams, and it comes with a 7 day free trial. For larger organizations, Enterprise offers custom pricing and unlimited devices and terminals. If you are trying to decide whether Free is enough, start with Free and only upgrade when you find yourself constantly closing and reopening shells.&lt;/p&gt;

&lt;h2&gt;
  
  
  How This Compares with Remote Control and SSH
&lt;/h2&gt;

&lt;p&gt;There are two common alternatives for remote access to Claude Code. Both have trade-offs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Code Remote Control
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://claude.com/claude-code" rel="noopener noreferrer"&gt;Claude Code Remote Control&lt;/a&gt; is a feature that lets you attach to an already running Claude Code session. It is useful for pair programming or for checking in on a session that someone else started. However, it requires a Pro, Max, Ultra, Team, or Enterprise subscription. You cannot use it with API keys alone. More importantly, Remote Control only reaches a &lt;code&gt;claude&lt;/code&gt; process that is already running. Someone still has to start &lt;code&gt;claude remote-control&lt;/code&gt; on the machine before you can connect. If the machine is locked or the process dies, Remote Control cannot start a new one for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSH and Reverse Tunnels
&lt;/h3&gt;

&lt;p&gt;SSH is the classic answer. If the machine has a public IP and an SSH server, you can connect from anywhere. But many machines are behind NAT, CGNAT, or strict firewalls. To reach them, you need a tunnel like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-p&lt;/span&gt; 443 &lt;span class="nt"&gt;-R0&lt;/span&gt;:localhost:22 tcp@free.pinggy.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command creates a reverse tunnel through Pinggy's free TCP service, but the public address changes every 60 minutes on the free tier. You would need to keep checking the new address, which is impractical for interactive work. Tunnels are also a separate layer from the terminal; you still need an SSH client on the device you are connecting from.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Pinggy Devices Changes
&lt;/h3&gt;

&lt;p&gt;A Pinggy Devices shell has neither limitation. You can start Claude Code from scratch, restart a development server, run &lt;code&gt;git pull&lt;/code&gt;, or even use a different coding agent like &lt;a href="https://openai.com/codex" rel="noopener noreferrer"&gt;Codex&lt;/a&gt;. The browser terminal has your machine's credentials and filesystem, so it works like a local shell. There is no need to pre-start a special server. The only requirement is that the Pinggy agent is running on the machine.&lt;/p&gt;

&lt;p&gt;If you want a deeper comparison of Claude Code and Codex on virtual machines, Pinggy has a separate guide that covers those setups in detail. The short version is that the device terminal gives you a generic shell, while Remote Control gives you a narrow window into one specific process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;Giving yourself browser access to a machine also means giving anyone who can access your Pinggy account the same power. You should treat the Pinggy dashboard like a root password. The device token is stored in &lt;code&gt;device.json&lt;/code&gt; under &lt;code&gt;~/.config/pinggy/&lt;/code&gt;, and it is readable only by your user. According to the Pinggy Devices docs, the agent never logs what you type. The Activity tab in the dashboard lists events with source IP addresses, so you can see when a device connected and from where.&lt;/p&gt;

&lt;p&gt;The biggest risk is account compromise. Anyone signed in to your Pinggy account can open a shell on your devices, run commands as your user, and start Claude Code with your &lt;a href="https://www.anthropic.com" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt; login. That means they recieve access to your source code, environment variables, cloud credentials, and any API keys in your shell profile. Before you rely on this workflow, enable two factor authentication on your Pinggy account. Also, keep the dashboard's connect commands out of screenshots and chat messages, because they contain the device token. If a token leaks, an attacker can connect a rogue machine to your account or potentially take over your device.&lt;/p&gt;

&lt;p&gt;To cut off a machine, delete the device in the dashboard or rotate its token. Rotating the token invalidates the old one, which forces the legitimate machine to reconnect with a new token. Note that &lt;code&gt;pinggy device remove&lt;/code&gt; only forgets the token locally. It does not revoke the device from the account. Use the dashboard for revocation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;The two most common problems are straightforward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terminal Tab Is Greyed Out
&lt;/h3&gt;

&lt;p&gt;If the Terminal tab is greyed out, the CLI on that machine is too old to support the browser terminal feature. Run the install command again to update the CLI. In most cases, the install script replaces the existing binary and you can reconnect immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  This machine already runs as device id in the background
&lt;/h3&gt;

&lt;p&gt;This error appears when you try to run &lt;code&gt;pinggy device login&lt;/code&gt; or &lt;code&gt;pinggy device connect&lt;/code&gt; in the foreground while a background agent is already holding the connection to the dashboard. Stop it first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pinggy device stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the background agent stops, you can run the foreground command again. If you want the device to stay connected, use &lt;code&gt;pinggy device connect -b&lt;/code&gt; instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Device Does Not Come Back After Reboot
&lt;/h3&gt;

&lt;p&gt;As I mentioned earlier, the background agent does not survive a reboot. If the machine restarts, run &lt;code&gt;pinggy device connect -b&lt;/code&gt; again. To make this automatic, you can add a login item on macOS, a startup script on Windows, or a systemd user service on Linux. The Pinggy CLI does not install a system service by default, so you need to do this yourself if you want always on availability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shell Closes After Inactivity
&lt;/h3&gt;

&lt;p&gt;Remember the 30 minute idle limit. If you step away from the browser terminal and the shell closes, any foreground process started inside it will also end. For long tasks, always use &lt;code&gt;claude --bg&lt;/code&gt; or a process supervisor like &lt;code&gt;tmux&lt;/code&gt; or &lt;code&gt;screen&lt;/code&gt;. You can install &lt;code&gt;tmux&lt;/code&gt; on the remote machine and run &lt;code&gt;tmux&lt;/code&gt; inside the browser terminal. That gives you another layer of protection against shell timeouts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Workflows
&lt;/h2&gt;

&lt;p&gt;The flexibility of a browser terminal means you can do much more than run Claude Code. Here are a few workflows that work well.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Remote Development From a Thin Client
&lt;/h3&gt;

&lt;p&gt;If you have a powerful desktop at home and a lightweight laptop on the road, you can leave the desktop connected with Pinggy and use its terminal from anywhere. Your IDE still runs locally, but the terminal controls the desktop. You can edit files through your IDE if you use a sync tool, or you can install a headless editor in a tmux session and use the browser terminal to do everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Server Maintenance
&lt;/h3&gt;

&lt;p&gt;A cloud VM might only expose SSH through a bastion host. If you do not want to configure extra SSH keys, install Pinggy CLI on the VM and connect with a device token. From the browser, you can update packages, restart services, inspect logs, and run Claude Code to diagnose incidents. The outbound connection means you do not have to change security group rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Multiple Shells for a Complex Task
&lt;/h3&gt;

&lt;p&gt;On the Pro plan, you can open three terminals on the same device at once. You might dedicate one shell to Claude Code, one to a local dev server, and one to Git or &lt;code&gt;docker compose&lt;/code&gt;. Because each shell has its own tab, you can watch all three in the browser and switch between them quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Headless Claude Code Tasks
&lt;/h3&gt;

&lt;p&gt;You can start a Claude Code background session from the browser terminal and then detach. Close the browser tab. The session continues. Later, open a new shell, list the sessions, and attach to see the output. This gives you a very simple remote job queue for AI assisted coding tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Pair Debugging
&lt;/h3&gt;

&lt;p&gt;If you share a Pinggy account with a trusted teammate, both of you can sign in and open a shell on the same device. Since each shell is independent, two people can work on the same machine simultaneously. Be careful with concurrent Git operations in the same repository. You may end up with merge conflicts or index locks. Use separate worktrees or coordinate through Claude Code sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance, Latency, and Practical Limits
&lt;/h2&gt;

&lt;p&gt;A browser terminal is not as instant as a local terminal, but it is close. The Pinggy relay is optimized for terminal traffic, which is low bandwidth and high interactivity. Over a normal internet connection, keystroke latency is usually well under a second. The bigger bottleneck is often the responsiveness of the remote machine itself.&lt;/p&gt;

&lt;p&gt;You can reduce latency by working near the machine's network region and by using a wired connection on the remote side. Wi-Fi with interference can cause jitter, especially if the remote machine is a laptop in sleep mode or on a weak signal. If you plan to run Claude Code for hours, make sure the machine does not go to sleep. In macOS, you can use &lt;code&gt;caffeinate&lt;/code&gt; to prevent sleep while the terminal is active.&lt;/p&gt;

&lt;p&gt;The dashboard terminal renders ANSI colors, box drawing characters, and Unicode output. Most modern browsers handle this well. If you notice text flicker or misaligned output, try a different browser or disable browser extensions that modify the DOM. The terminal client is also compatible with common terminal multiplexers like &lt;code&gt;tmux&lt;/code&gt; and &lt;code&gt;screen&lt;/code&gt;. If a shell ends because of inactivity, &lt;code&gt;tmux&lt;/code&gt; can keep your session alive; the Pinggy shell can reconnect to the same &lt;code&gt;tmux&lt;/code&gt; session in a new browser tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge Cases and Limitations
&lt;/h2&gt;

&lt;p&gt;It is worth being explicit about what this setup does not do.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Pinggy agent must be running. If the machine is off, asleep, or the daemon is stopped, no terminal can connect.&lt;/li&gt;
&lt;li&gt;The free plan has one device and one concurrent terminal. Opening a second shell will fail until the first one is closed.&lt;/li&gt;
&lt;li&gt;A foreground process started in a normal dashboard shell dies when the shell ends. Background sessions started with &lt;code&gt;claude --bg&lt;/code&gt; survive shell termination.&lt;/li&gt;
&lt;li&gt;The device token grants full access to a shell as the user who owns the token. Anyone with access to your Pinggy account has the same power.&lt;/li&gt;
&lt;li&gt;The terminal runs as the user who started the Pinggy agent, not as root. If you need root privileges, you can use &lt;code&gt;sudo&lt;/code&gt; inside the terminal, assuming your user is allowed to.&lt;/li&gt;
&lt;li&gt;The browser terminal is not meant for very high bandwidth applications like streaming video or transferring large files. It is a terminal, not a general purpose tunnel. For file transfers, use a separate tunnel or an SCP session configured through Pinggy.&lt;/li&gt;
&lt;li&gt;The 30 minute idle limit applies to browser shells. Background CLI sessions are managed separately by Claude Code. If you use &lt;code&gt;tmux&lt;/code&gt;, the &lt;code&gt;tmux&lt;/code&gt; session persists even if the browser shell ends.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need a public IP address?
&lt;/h3&gt;

&lt;p&gt;No. The machine only makes an outbound connection to the Pinggy relay. It works behind NAT, CGNAT, and most firewalls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I run Claude Code without installing anything on the remote machine?
&lt;/h3&gt;

&lt;p&gt;No. You need the Pinggy CLI installed on the machine you want to reach. The installation is a single command and does not require sudo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the browser terminal support copy and paste?
&lt;/h3&gt;

&lt;p&gt;Yes. Most browser terminals support clipboard operations through keyboard shortcuts or context menus. The exact behavior depends on your browser. On macOS, Cmd+C and Cmd+V generally work in the terminal's native menu or through the browser's clipboard permission prompts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it safe to leave the Pinggy agent running all the time?
&lt;/h3&gt;

&lt;p&gt;It is safe as long as your account is secured with a strong password and two factor authentication, and as long as the device token stays private. The agent itself only sends terminal keystrokes and output over an outbound TLS connection. It does not expose a generic port.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use this with tools other than Claude Code?
&lt;/h3&gt;

&lt;p&gt;Absolutely. The browser terminal is a full shell. You can run &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;docker&lt;/code&gt;, &lt;code&gt;vim&lt;/code&gt;, &lt;code&gt;htop&lt;/code&gt;, &lt;code&gt;node&lt;/code&gt;, &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;Codex&lt;/code&gt;, or any other command. Claude Code is just one example, and a very useful one for AI driven development.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens if my browser crashes?
&lt;/h3&gt;

&lt;p&gt;The shell on the remote machine stays alive for a while because the relay may not immediately know that the browser disconnected. When you reopen the dashboard and click the Terminal tab, the screen is redrawn to reflect the current state of the shell. If the shell hit the idle timeout, you will see a fresh shell instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does &lt;code&gt;claude --bg&lt;/code&gt; work through the browser terminal?
&lt;/h3&gt;

&lt;p&gt;Yes. You can run &lt;code&gt;claude --bg 'task'&lt;/code&gt; in any shell, including a browser shell. The background process is independent of the shell that launched it. As long as the Pinggy agent is running and the machine is on, the background session continues.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between &lt;code&gt;claude --bg&lt;/code&gt; and &lt;code&gt;tmux&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;claude --bg&lt;/code&gt; is specific to Claude Code and runs the agent in a separate supervised process. &lt;code&gt;tmux&lt;/code&gt; is a general terminal multiplexer that keeps any interactive program alive across terminal disconnects. You can use both together: open a browser shell, start &lt;code&gt;tmux&lt;/code&gt;, run &lt;code&gt;claude&lt;/code&gt; inside &lt;code&gt;tmux&lt;/code&gt;, and then detach from &lt;code&gt;tmux&lt;/code&gt;. Even if the browser shell times out, the &lt;code&gt;tmux&lt;/code&gt; session and Claude Code remain alive. The downside is that attaching to a &lt;code&gt;tmux&lt;/code&gt; session is less convenient than &lt;code&gt;claude attach&lt;/code&gt; for seeing recent output, because you have to reattach to the session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use two factor authentication on your Pinggy account.&lt;/li&gt;
&lt;li&gt;Keep the device token out of logs and screenshots.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;pinggy device connect -b&lt;/code&gt; for long running machines.&lt;/li&gt;
&lt;li&gt;Create a startup job to reconnect after reboot.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;claude --bg&lt;/code&gt; for tasks longer than a few minutes.&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;claude agents&lt;/code&gt; before starting another background session.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;tmux&lt;/code&gt; if you want a persistent interactive shell beyond Claude Code's background mode.&lt;/li&gt;
&lt;li&gt;Keep the remote machine awake with &lt;code&gt;caffeinate&lt;/code&gt; on macOS or a power plan on Windows.&lt;/li&gt;
&lt;li&gt;Monitor the Overview tab to see if the machine is under heavy load.&lt;/li&gt;
&lt;li&gt;Rotate the device token when you stop using a machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;The whole setup can be summarized in three commands. On the machine you want to reach, install and sign in:&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;-fsSL&lt;/span&gt; https://pinggy.io/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the device connected in the background:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pinggy device connect &lt;span class="nt"&gt;-b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the browser terminal, start a Claude Code background task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--bg&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;your task&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open the Pinggy Devices page, click the device, open the Terminal tab, and choose &lt;code&gt;New shell&lt;/code&gt;. You now have a real terminal on that machine, running in a browser, ready for Claude Code or anything else.&lt;/p&gt;

&lt;p&gt;The combination of an outbound only agent and a browser based terminal is a practical answer to a very common developer problem: your best coding environment is stuck on a machine you are not in front of. Pinggy Devices solves that without requiring you to open ports, maintain a VPN, or leave an SSH server exposed. It is simple, close to the metal, and works with the AI coding tools you already use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pinggy.io/blog/run_claude_code_remotely_from_any_browser/" rel="noopener noreferrer"&gt;Run Claude Code Remotely from Any Browser with Pinggy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>pinggy</category>
      <category>claudecode</category>
      <category>terminal</category>
      <category>devtools</category>
    </item>
    <item>
      <title>OpenRouter's Free Model APIs in 2026: Working With Request Limits Instead of Token Caps</title>
      <dc:creator>Lightning Developer</dc:creator>
      <pubDate>Thu, 05 Nov 2026 07:53:03 +0000</pubDate>
      <link>https://dev.to/lightningdev123/openrouters-free-model-apis-in-2026-working-with-request-limits-instead-of-token-caps-413f</link>
      <guid>https://dev.to/lightningdev123/openrouters-free-model-apis-in-2026-working-with-request-limits-instead-of-token-caps-413f</guid>
      <description>&lt;p&gt;Most developers hit the same wall once they start building with large language models. The prototype works, and then the API bill starts tracking every experiment. Frontier models still bill by the token. Claude Opus 5.5 lists at $4 per million input tokens and $20 per million output tokens, and that adds up fast once you are calling it in a loop.&lt;/p&gt;

&lt;p&gt;Search for 'free AI API unlimited tokens' and you will find a pile of posts promising a way around that. The honest version is less exciting but more useful. &lt;a href="https://openrouter.ai/" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt; gives you no-credit-card access to a rotating set of models at $0 per token, but 'unlimited' is not the right word. You get 20 requests per minute and either 50 or 1,000 requests per day, depending on whether you have bought $10 of credits. No token cap, but a very real request cap.&lt;/p&gt;

&lt;p&gt;The free list also turns over fast. In July 2026, Owl Alpha, Tencent Hy3, Qwen3 Coder and gpt-oss-120b were all free on OpenRouter; none of them is free today, and the lineup is led by &lt;a href="https://thinkingmachines.ai/" rel="noopener noreferrer"&gt;Thinking Machines Lab&lt;/a&gt;'s 975-billion-parameter Inkling and &lt;a href="https://www.nvidia.com/" rel="noopener noreferrer"&gt;NVIDIA&lt;/a&gt;'s Nemotron family. Every limit, price and model ID below was checked against OpenRouter's live API and model pages on October 27, 2026.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcbfxhk6lp626uzukypat.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcbfxhk6lp626uzukypat.webp" alt="Blog Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the current free lineup at a glance before we get into the mechanics:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Context (free)&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Thinking Machines Inkling&lt;/td&gt;
&lt;td&gt;975B / 41B active MoE&lt;/td&gt;
&lt;td&gt;1M tokens&lt;/td&gt;
&lt;td&gt;Coding and agent harnesses, text, image and audio input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVIDIA Nemotron 3 Ultra&lt;/td&gt;
&lt;td&gt;550B / 55B active MoE&lt;/td&gt;
&lt;td&gt;1M tokens&lt;/td&gt;
&lt;td&gt;General reasoning, agent orchestration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVIDIA Nemotron 3.5 Super VL&lt;/td&gt;
&lt;td&gt;120B / 12B active MoE&lt;/td&gt;
&lt;td&gt;1M tokens&lt;/td&gt;
&lt;td&gt;Documents, screenshots and video input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Gemma 4&lt;/td&gt;
&lt;td&gt;31B dense / 26B-A4B MoE&lt;/td&gt;
&lt;td&gt;262K tokens&lt;/td&gt;
&lt;td&gt;Multimodal input, function calling, 140+ languages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Poolside Laguna S 2.1&lt;/td&gt;
&lt;td&gt;118B / 8B active MoE&lt;/td&gt;
&lt;td&gt;262K tokens&lt;/td&gt;
&lt;td&gt;Coding agents, going away October 31, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;openrouter/free&lt;/td&gt;
&lt;td&gt;Router over the free models&lt;/td&gt;
&lt;td&gt;200K tokens&lt;/td&gt;
&lt;td&gt;Code that should keep working as models rotate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That table covers the six worth knowing individually. Counting embedding, reranking, speech and 'decision' models, OpenRouter lists 28 model IDs ending in :free right now, 16 of them chat models, and we get to the rest further down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Free Tier: Rate Limits, Not Token Limits
&lt;/h2&gt;

&lt;p&gt;This is the part most 'unlimited tokens' articles get wrong. OpenRouter's free models charge $0.00 per input token and $0.00 per output token, so there is genuinely no token-based billing. What every account does have is a request cap, documented on OpenRouter's &lt;a href="https://openrouter.ai/docs/limits" rel="noopener noreferrer"&gt;limits page&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20 requests per minute, always, regardless of account status&lt;/li&gt;
&lt;li&gt;50 requests per day if you have bought less than $10 of credits in total&lt;/li&gt;
&lt;li&gt;1,000 requests per day once your credit purchases reach $10&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The docs say the daily tier is selected by all-time credits purchased, so it counts what you have bought over the account's lifetime, not your current balance. Once you cross $10, the higher ceiling stays even if your balance later drops to zero. In practice the 1,000 tier starts at 9 credits, one below the threshold, which OpenRouter says absorbs rounding and top-up fees.&lt;/p&gt;

&lt;p&gt;So a 100,000-token conversation costs you one request, same as a two-word prompt, and an agent loop that calls the model dozens of times per task hits the daily ceiling long before any token count matters. Two more things can stop you: &lt;a href="https://www.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt;'s DDoS protection blocks requests that dramatically exceed reasonable usage, and a negative credit balance can block free models too, with 402 errors. That mostly happens if you use paid models alongside free ones and let the balance run dry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking How Many Free Requests You Have Left
&lt;/h2&gt;

&lt;p&gt;OpenRouter reports the daily counter on its key endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://openrouter.ai/api/v1/key &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer $OPENROUTER_API_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes a &lt;code&gt;free_model_daily_requests&lt;/code&gt; object with &lt;code&gt;used&lt;/code&gt;, &lt;code&gt;limit&lt;/code&gt; and &lt;code&gt;remaining&lt;/code&gt; counts for the current UTC day. An agent can check it before a long run instead of discovering the ceiling through 429 'Rate limit exceeded' errors halfway through. It does not report the per-minute limit, though. When you do hit a limit, the 429 response carries &lt;code&gt;X-RateLimit-*&lt;/code&gt; headers describing the one you hit.&lt;/p&gt;

&lt;p&gt;A quick parsing pattern with &lt;code&gt;jq&lt;/code&gt; makes this easier to use in a script:&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;-s&lt;/span&gt; https://openrouter.ai/api/v1/key &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer $OPENROUTER_API_KEY'&lt;/span&gt; | jq &lt;span class="s1"&gt;'.free_model_daily_requests'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you something like &lt;code&gt;{"used": 12, "limit": 50, "remaining": 38}&lt;/code&gt;. You can recieve these values before launching a batch job and stop early if the remaining count is too low. The &lt;code&gt;remaining&lt;/code&gt; field is the one that matters most because it tells you how many requests are still available for the rest of the UTC day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Set Up
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to the OpenRouter keys page and sign up with email or &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. No credit card is needed for free models.&lt;/li&gt;
&lt;li&gt;Generate an API key on that page. You can optionally cap each key with a credit limit.&lt;/li&gt;
&lt;li&gt;Optional, recommended: buy credits. Pay-as-you-go carries a 5.5% platform fee on credit purchases, and $10 of purchases in total is what raises your free-model ceiling to 1,000 requests a day. It is a one-time threshold, not a subscription.&lt;/li&gt;
&lt;li&gt;Call the API. Free models carry a &lt;code&gt;:free&lt;/code&gt; suffix on their model ID, and OpenRouter's &lt;a href="https://openrouter.ai/models?q=:free" rel="noopener noreferrer"&gt;Free Models collection&lt;/a&gt; is the fastest way to see what is available today.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you only plan to use free models, you can stop at step 2 and spend $0. The $10 is purely about the daily ceiling, and it is worth buying once a project becomes something you use every day, because 50 requests disappear fast when you are iterating on prompts. Before you buy, note that OpenRouter's FAQ says OpenRouter reserves the right to expire unused credits a year after purchase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling a Free Model
&lt;/h2&gt;

&lt;p&gt;Because OpenRouter mirrors the &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt; API shape, you can use curl directly or the standard OpenAI Python SDK pointed at a different base URL. Here is the raw HTTP version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://openrouter.ai/api/v1/chat/completions &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer $OPENROUTER_API_KEY'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model": "nvidia/nemotron-3-ultra-550b-a55b:free", "messages": [{"role": "user", "content": "Explain the difference between MoE and dense transformer architectures in two sentences."}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the same call using the official OpenAI Python SDK, just repointed at OpenRouter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://openrouter.ai/api/v1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sk-or-v1-your-key-here&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nvidia/nemotron-3-ultra-550b-a55b:free&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Explain the difference between MoE and dense transformer architectures in two sentences.&lt;/span&gt;&lt;span class="sh"&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&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="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No OpenRouter-specific SDK, no custom client library. If your code already talks to the OpenAI API, changing &lt;code&gt;base_url&lt;/code&gt; and the &lt;code&gt;model&lt;/code&gt; string is the entire migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Free Model IDs and Endpoint Behavior
&lt;/h2&gt;

&lt;p&gt;Every free model has the suffix &lt;code&gt;:free&lt;/code&gt; at the end of its model ID. That suffix is the only thing that makes it free. The base URL is still &lt;code&gt;https://openrouter.ai/api/v1&lt;/code&gt;, the authentication is still the same API key, and the request format is the same as the OpenAI-compatible interface. If you remove the suffix from a model ID, the same model may become a paid listing, so the suffix is not decorative. It is a routing tag that tells OpenRouter which pricing tier to apply.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;openrouter/free&lt;/code&gt; router does not carry the suffix because it is not a model in the traditional sense. It is an alias that resolves to a random free model that supports your request's required features. This is useful for quick experiments, but it makes behavior less predictable. You cannot assume which model will answer, and some free models are much slower than others.&lt;/p&gt;

&lt;p&gt;The Free Models collection on OpenRouter is generated from the same API data that is used for the model list, so it is more reliable than a blog post that was written a month ago. Bookmark that page if you are planning to depend on free models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context Windows and Output Limits
&lt;/h2&gt;

&lt;p&gt;Free models do not all have the same context window. The table above shows that several top models offer 1M tokens on their free endpoints, while others like Gemma 4 stop at 262K tokens. Context window is not the same as output limit. A model with a 1M context can still cap the number of tokens it generates in one response. For example, NVIDIA Nemotron 3 Ultra allows up to 65,536 output tokens on its free endpoint. That is generous, but it is not infinite. If you send a prompt that asks for many separate tasks, you may hit the output cap even though the context window has room.&lt;/p&gt;

&lt;p&gt;When you use the OpenAI SDK, you can control the output size with &lt;code&gt;max_tokens&lt;/code&gt; or with the newer &lt;code&gt;max_completion_tokens&lt;/code&gt; parameter. For an OpenRouter request, &lt;code&gt;max_tokens&lt;/code&gt; is the safer choice because many providers on the platform still use it. If you request more output tokens than the endpoint allows, the provider may reject the request or truncate it depending on the model. The response object includes a &lt;code&gt;finish_reason&lt;/code&gt; field. A &lt;code&gt;finish_reason&lt;/code&gt; of &lt;code&gt;length&lt;/code&gt; means the model reached its token limit before completing the message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nvidia/nemotron-3-ultra-550b-a55b:free&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Write a detailed design document for a rate-limited API gateway.&lt;/span&gt;&lt;span class="sh"&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;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&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="n"&gt;finish_reason&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;length&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Response was truncated by the output token limit.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters more on free models because you do not want to waste a request on a response that gets cut off at the wrong place. If you need longer outputs, retry with a higher max, use a model with a larger output budget, or ask the model to continue from the last sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate Limit Headers and Backoff
&lt;/h2&gt;

&lt;p&gt;When you exceed the per-minute limit or the daily limit, OpenRouter responds with HTTP 429 and a set of &lt;code&gt;X-RateLimit-*&lt;/code&gt; headers. The headers tell you which limit was hit and when it resets. A robust client should read those headers and back off instead of retrying immediately.&lt;/p&gt;

&lt;p&gt;Here is a minimal Python retry loop that respects the reset time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://openrouter.ai/api/v1/chat/completions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Bearer sk-or-v1-your-key-here&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nvidia/nemotron-3-ultra-550b-a55b:free&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello from a rate limited client.&lt;/span&gt;&lt;span class="sh"&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;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;reset_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;X-RateLimit-Reset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reset_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;X-RateLimit-Reset&lt;/code&gt; may be a Unix timestamp. If the header is not present, fall back to a fixed sleep. Do not hammer the endpoint; that can trigger Cloudflare's DDoS protection and get your IP blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Rotation and Rate Limits with Fallback Models
&lt;/h2&gt;

&lt;p&gt;The single most important production tip for the free tier: do not hardcode one free model. Free variants get pulled or repriced without much warning. Between July and late October 2026, &lt;code&gt;qwen/qwen3-coder:free&lt;/code&gt;, &lt;code&gt;openai/gpt-oss-120b:free&lt;/code&gt;, &lt;code&gt;tencent/hy3:free&lt;/code&gt; and &lt;code&gt;nvidia/nemotron-3-super-120b-a12b:free&lt;/code&gt; have all disappeared, and any code pinned to them now fails. OpenRouter's &lt;code&gt;models&lt;/code&gt; array lets you list several candidates in priority order in one request:&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;"models"&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="s2"&gt;"nvidia/nemotron-3-ultra-550b-a55b:free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"google/gemma-4-31b-it:free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"nvidia/nemotron-3.5-lightning:free"&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;"messages"&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;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Summarize this changelog in three bullet points."&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;p&gt;By default any error can send the request to the next model, including context-length errors, moderation flags, rate limiting and provider downtime. You are billed for whichever model answered, and its ID comes back in the response's &lt;code&gt;model&lt;/code&gt; field, so you can log which fallback fired. Keep every entry a &lt;code&gt;:free&lt;/code&gt; model: if the list falls through to a paid one, you pay that model's rate.&lt;/p&gt;

&lt;p&gt;The exact configuraton of your fallback list will depend on what capabilities you need. If you need tool calling or structured outputs, check the model page for each candidate before adding it. Not every free model supports every feature. A router model can help here, but it is not a replacement for a fallback strategy.&lt;/p&gt;

&lt;p&gt;If you would rather not manage a list at all, &lt;code&gt;openrouter/free&lt;/code&gt; is a router that picks a free model at random from the ones that support what your request needs, such as tool calling, structured outputs or image input. It trades predictability for convenience: you do not control which model answers, but your code keeps working as the free lineup changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Free Models Leave OpenRouter
&lt;/h2&gt;

&lt;p&gt;Three months ago the free list looked different. In July, Nemotron 3 Ultra, Gemma 4, Owl Alpha, Tencent Hy3, Qwen3 Coder and gpt-oss-120b were all free. Only the first two still are, and the other four show the different ways a free listing ends.&lt;/p&gt;

&lt;p&gt;Owl Alpha turned out to be &lt;a href="https://www.meituan.com/" rel="noopener noreferrer"&gt;Meituan&lt;/a&gt;'s LongCat-2.0. It was a stealth model, a prerelease checkpoint OpenRouter serves for free under a codename while the lab collects real-world usage. At the end of June, Meituan's LongCat team introduced LongCat-2.0 as the full model behind Owl Alpha: 1.6 trillion parameters, about 48B active per token, and a 1M-token context. The weights are on &lt;a href="https://huggingface.co/" rel="noopener noreferrer"&gt;Hugging Face&lt;/a&gt; under the MIT license, and on OpenRouter &lt;code&gt;meituan/longcat-2.0&lt;/code&gt; costs $0.30/M input and $1.20/M output, currently shown as a discounted price.&lt;/p&gt;

&lt;p&gt;The same pattern occured again this fall. A stealth model called Space Bunny left OpenRouter on October 5 and came back on October 19 as &lt;a href="https://www.minimax.io/" rel="noopener noreferrer"&gt;MiniMax&lt;/a&gt; M3.1 Flash, free for 72 hours and now paid, from $0.143/M input. As of October 27 there is no stealth model on the list, so do not plan around one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tencent.com/" rel="noopener noreferrer"&gt;Tencent&lt;/a&gt; Hy3's free window closed. Tencent had set July 21 as the end date for the free listing, and Hy3 is now paid only, from $0.0825/M input and $0.33/M output through Tencent's own endpoint.&lt;/p&gt;

&lt;p&gt;Qwen3 Coder and gpt-oss-120b lost their &lt;code&gt;:free&lt;/code&gt; variants. Both are still on OpenRouter as paid models: Qwen3 Coder from $0.22/M input, and gpt-oss-120b from $0.03/M input and $0.15/M output across about two dozen providers. At that price gpt-oss is close to free anyway. Smaller free models from July went the same way: the free Llama 3.3 70B, Llama 3.2 3B and Hermes 3 405B variants are gone, and NVIDIA's Nemotron 3 Super lost its free endpoint in late October. DeepSeek still has no free model, though V4.1 Flash is listed at $0.14/M input and $0.42/M output.&lt;/p&gt;

&lt;p&gt;This churn is not a bug. Free endpoints exist to promote labs, gather telemetry, and push adoption. Once a model has enough traction, the free tier goes away. The best defense is to treat the free catalog as a pool of interchangeable endpoints rather than a set of permanent providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Keep Up With Rotation
&lt;/h2&gt;

&lt;p&gt;Because free model lists change quickly, a weekly review is reasonable. Open an issue in your repo or use a scheduled job that queries the model list and filters by &lt;code&gt;:free&lt;/code&gt;. Keep the list in your README so other contributors know not to hardcode a model that might disappear.&lt;/p&gt;

&lt;p&gt;Another practical habit is to add a comment next to each free model ID in your config file with the date it was verified. When the list changes, you know exactly which entries need to be updated. This has saved many debugging sessions.&lt;/p&gt;

&lt;p&gt;If you use a shared team account, tell everyone to check the OpenRouter Free Models collection before they start a new integration. It only takes a minute and it prevents the most common failure mode: a hardcoded model that was removed from the free tier overnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Free Model Catalog Right Now
&lt;/h2&gt;

&lt;p&gt;OpenRouter's free lineup is a moving target: models get added when labs promote a launch, and pulled when the promotion ends. On October 27, 2026, the API lists 28 model IDs ending in &lt;code&gt;:free&lt;/code&gt;: 16 chat models, seven embedding and reranking models, two text-to-speech models and three 'decision' models that return scored answers instead of free text. By last week's usage, OpenRouter's &lt;a href="https://openrouter.ai/models?q=:free" rel="noopener noreferrer"&gt;Free Models page&lt;/a&gt; ranks Nemotron 3 Ultra, Nemotron 3.5 Super VL and Nemotron 3.5 Lightning as the most used. Five of the chat models are worth calling out individually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thinking Machines Inkling (975B-A41B)
&lt;/h3&gt;

&lt;p&gt;Inkling is the biggest model on the free list. &lt;a href="https://thinkingmachines.ai/" rel="noopener noreferrer"&gt;Thinking Machines Lab&lt;/a&gt; released it on July 15, 2026, trained from scratch with the full weights available: a Mixture-of-Experts with 975B total parameters and 41B active, text, image and audio input, and up to 1M tokens of context. The weights are on &lt;a href="https://huggingface.co/" rel="noopener noreferrer"&gt;Hugging Face&lt;/a&gt; under Apache 2.0. On OpenRouter the free endpoint serves the full 1M context, while the paid listing ($1.00/M input, $4.05/M output) tops out at 524K. The lab is frank about where it stands: Inkling is not the strongest overall model available today, open or closed, and it pitches itself as a base for customization.&lt;/p&gt;

&lt;p&gt;Read the notice at the top of the model page before you wire it in. The free endpoint is only available for use with agentic harnesses, and Thinking Machines logs its prompts and outputs to improve its models, after separating them from your account. Use it for coding-agent experiments, not confidential work. Its smaller sibling, &lt;code&gt;thinkingmachines/inkling-small:free&lt;/code&gt; (276B total, 12B active), is free as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  NVIDIA Nemotron 3 Ultra (550B-A55B)
&lt;/h3&gt;

&lt;p&gt;NVIDIA Nemotron 3 Ultra, released June 4, 2026, is one of the two July picks that is still free. It is a 550B-parameter MoE with 55B active per token, built on a hybrid Transformer-Mamba architecture that &lt;a href="https://www.nvidia.com/" rel="noopener noreferrer"&gt;NVIDIA&lt;/a&gt; designed for high-throughput agent pipelines. NVIDIA serves the free endpoint itself, with a 1M-token context and up to 65,536 output tokens. The paid endpoints, from $0.40/M input and $1.76/M output, stop at 262K context, so on this model the free tier is the bigger one. NVIDIA's notice on the page says use of the free endpoint is logged for security purposes and to improve NVIDIA products and services.&lt;/p&gt;

&lt;h3&gt;
  
  
  NVIDIA Nemotron 3.5 Super VL (120B-A12B)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0gjvvmwewiimpivrqmsj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0gjvvmwewiimpivrqmsj.webp" alt="Blog Image" width="799" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nemotron 3.5 Super VL is the newest model in this guide, released October 15, 2026. It is a 120B-parameter MoE with 12B active that takes text, images, documents, GUI screenshots and video and returns text, with a 1M-token context and reasoning switched on by default. You can turn it off or give it a token budget. That makes it the free pick for document extraction, screenshot-driven agents and video understanding. The same NVIDIA logging notice sits on its page. Paid endpoints start at $0.06/M input. For speed over size, &lt;code&gt;nvidia/nemotron-3.5-lightning:free&lt;/code&gt; (30B total, 3B active, text only, 1M context) is the high-throughput option in the same family.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Gemma 4 (31B and 26B-A4B)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://deepmind.google/" rel="noopener noreferrer"&gt;Google DeepMind&lt;/a&gt;'s Gemma 4 family, released April 2, 2026, is the other July pick that is still free, and both variants survived. &lt;code&gt;google/gemma-4-31b-it:free&lt;/code&gt; is the 30.7B dense model: no MoE routing, so it is the slower of the two, but it is the one to reach for when you want the best single answer. &lt;code&gt;google/gemma-4-26b-a4b-it:free&lt;/code&gt; is the MoE sibling, with 25.2B total and 3.8B active parameters, landing close to the 31B model's quality while responding faster. &lt;a href="https://aistudio.google.com/" rel="noopener noreferrer"&gt;Google AI Studio&lt;/a&gt; serves both for free with a 262K context, up to 32,768 output tokens, image and video input, configurable thinking, native function calling and 140+ languages, under the Apache 2.0 license. Unlike the NVIDIA and Thinking Machines pages, Gemma's free page shows no logging notice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Poolside Laguna S 2.1 - Going Away October 31
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fla6ezl9eofxx1b4m00lz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fla6ezl9eofxx1b4m00lz.webp" alt="Blog Image" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Laguna S 2.1 is &lt;a href="https://poolside.ai/" rel="noopener noreferrer"&gt;Poolside&lt;/a&gt;'s coding-agent model: 118B total parameters, 8B active, open-weight under the OpenMDW-1.1 license, with a 262K context on the free endpoint. Poolside reports 70.2% on Terminal-Bench 2.1 and 40.4% on DeepSWE. Those are vendor numbers, but strong ones for a model with 8B active parameters, and of everything on the free list it is the one most directly built for coding agents. That is exactly the problem: the model page says going away October 31, 2026, and the smaller &lt;code&gt;poolside/laguna-xs-2.1:free&lt;/code&gt; (33B total, 3B active) expires the same day. Poolside also says it may use free-tier inputs and outputs to train its models. It is not only the free tier that is ending: the paid listing ($0.09/M input, $0.18/M output, 1M context) shows the same going away date, as does the paid Laguna XS 2.1.&lt;/p&gt;

&lt;p&gt;We are calling it out because free-tier listicles go stale within weeks. After October 31, 2026, check its current listing before you use it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Rest of the Current Free Roster
&lt;/h3&gt;

&lt;p&gt;Beyond those five, the free chat models are Inkling Small; NVIDIA's Nemotron 3.5 Lightning, Nemotron 3 Nano Omni (30B-A3B, with text, image, audio and video input) and the 4B Nemotron 3.5 Content Safety guardrail model; &lt;a href="https://cohere.com/" rel="noopener noreferrer"&gt;Cohere&lt;/a&gt;'s North Mini Code (30B, 3B active), its first agentic coding model; &lt;a href="https://recursive.ai/" rel="noopener noreferrer"&gt;Recursive&lt;/a&gt;'s Flint (40B, 2.5B active), built for coding agents and added on October 21; &lt;a href="https://www.chinatelecomglobal.com/" rel="noopener noreferrer"&gt;China Telecom&lt;/a&gt;'s Xing4.0 29B-A4B; &lt;a href="https://dots.studio/" rel="noopener noreferrer"&gt;Dots Studio&lt;/a&gt;'s Dots3-Note Preview (280B, 16B active), free until December 31, 2026; and &lt;a href="https://www.liquid.ai/" rel="noopener noreferrer"&gt;LiquidAI&lt;/a&gt;'s LFM2.5-2.6B for small, fast jobs. Outside chat, there are free embedding and reranking models from NVIDIA and LiquidAI, and free text-to-speech from &lt;a href="https://fish.audio/" rel="noopener noreferrer"&gt;Fish Audio&lt;/a&gt; and &lt;a href="https://www.heygen.com/" rel="noopener noreferrer"&gt;HeyGen&lt;/a&gt;. HeyGen's free text-to-speech listing ends November 30, 2026.&lt;/p&gt;

&lt;p&gt;For open-weight models you can run on your own hardware instead of depending on a rotating free tier, see the rest of this blog for self-hosting options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Embedding, Reranking and Speech Endpoints
&lt;/h2&gt;

&lt;p&gt;Not every free endpoint is a chat model. OpenRouter also exposes free embeddings and reranking models. The chat completion endpoint is the obvious way to use free AI, but the embedding endpoints are useful for private knowledge bases, semantic search, and retrieval-augmented generation. Most of these models also use the same &lt;code&gt;:free&lt;/code&gt; suffix, and the API shape follows the OpenAI embeddings interface.&lt;/p&gt;

&lt;p&gt;For reranking, you send a query and a list of candidate documents, and the model returns relevance scores. This is useful when you want to filter a large set of results before sending the best ones to a chat model. The free tier on these models is small, but it is enough for experiments and low-traffic retrieval pipelines.&lt;/p&gt;

&lt;p&gt;Speech endpoints are a different story. Free text-to-speech models from Fish Audio and HeyGen let you generate audio from text without paying. Those endpoints consume the same daily free request budget, so a long-form audio generation task can burn through the quota quickly. The quality varies, so test the audio output before using it in a user-facing product.&lt;/p&gt;

&lt;p&gt;'Decision' models on OpenRouter return scored answers instead of free text. They are less common, but they fit into classification and routing workflows where a structured score is more useful than a paragraph.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Free Endpoints Do With Your Prompts
&lt;/h2&gt;

&lt;p&gt;Rate limits are the documented cost of the free tier. The less obvious one is data. Three of the five model pages above say the provider logs free-tier traffic or may train on it: NVIDIA and Thinking Machines log it to improve their models, and Poolside may train on it. That is the deal behind most free inference: you get the tokens, and the lab gets real-world usage.&lt;/p&gt;

&lt;p&gt;OpenRouter lets you opt out, at a price. Your account's privacy settings have separate switches for paid and free models on whether to allow providers that may train on your data, and per OpenRouter's &lt;a href="https://openrouter.ai/docs/features/provider-logging" rel="noopener noreferrer"&gt;provider logging docs&lt;/a&gt;, if you opt out, OpenRouter will not route to providers that train. Every free model in this guide runs on a single provider, so opting out does not move you to a safer host; it takes those models off the table for your account. If a free model works for everyone else but fails for you, check that setting first.&lt;/p&gt;

&lt;p&gt;Privacy compliance is an important part of the decision. If your company has a policy that prohibits sending customer data to providers that may retain it, you need to review each free model's data notice before using it. The free tier is convenient, but it is not a place for secrets, personal data, or proprietary code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Business Model Behind Free Inference
&lt;/h2&gt;

&lt;p&gt;Even though the price is $0, there is a cost. The labs behind these models need real-world usage, feedback, and performance data. Free inference is a form of evaluation at scale. When a lab like NVIDIA or Thinking Machines offers a free endpoint, it is not giving away a commodity. It is using the free tier to stress-test a new architecture, gather quality signals, and build a community of developers who will move to paid infrastructure when the project becomes serious.&lt;/p&gt;

&lt;p&gt;OpenRouter also benefits because free models attract developers to its API, and once they are comfortable, the paid tier is a short step away. The free tier can be seen as an extended trial of both the model and the routing platform.&lt;/p&gt;

&lt;p&gt;This business logic explains why models rotate. A free listing is not a guarantee. It is a promotional window, and the window closes when the lab decides that the promotion has served its purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Tier vs. Paying: What You Actually Give Up
&lt;/h2&gt;

&lt;p&gt;The free tier is not a stripped-down demo. The biggest free models are frontier-scale open-weight releases, and on Inkling and Nemotron 3 Ultra the free endpoint offers a bigger context window than the paid listing. What you give up by staying free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proprietary flagships. Free models on OpenRouter are open-weight releases or temporary promotions. You will not find free access to Claude, GPT or Gemini's flagship tiers.&lt;/li&gt;
&lt;li&gt;Throughput. 50 or 1,000 requests a day is fine for personal projects and prototyping. It is not enough for a production app with real traffic, and OpenRouter itself warns that free models can be rate limited or slower at peak times.&lt;/li&gt;
&lt;li&gt;Privacy. Many free endpoints log or may train on what you send.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you outgrow it, pay-as-you-go charges each provider's list price with no markup on tokens, plus the 5.5% fee when you buy credits. If you already have keys with &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;, &lt;a href="https://www.anthropic.com/" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt; or Google, bring your own key routes through them with no OpenRouter fee on the first $25,000 of list-price inference each month and 5% after that. For gateways beyond OpenRouter, tooling like &lt;a href="https://portkey.ai/" rel="noopener noreferrer"&gt;Portkey&lt;/a&gt; and &lt;a href="https://www.litellm.ai/" rel="noopener noreferrer"&gt;LiteLLM&lt;/a&gt; can help with routing, caching and retries, and our other OpenRouter production posts cover what breaks when you move paid traffic onto it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Codes You Might See
&lt;/h2&gt;

&lt;p&gt;OpenRouter uses standard HTTP status codes, but the meaning can be confusing if you are new to the free tier.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;401&lt;/td&gt;
&lt;td&gt;Invalid API key&lt;/td&gt;
&lt;td&gt;Regenerate the key in your OpenRouter dashboard and update your config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;402&lt;/td&gt;
&lt;td&gt;Negative credit balance&lt;/td&gt;
&lt;td&gt;Add credits; free models can also be blocked by this condition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;Model not found&lt;/td&gt;
&lt;td&gt;Check the model ID and the &lt;code&gt;:free&lt;/code&gt; suffix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;429&lt;/td&gt;
&lt;td&gt;Rate limit hit&lt;/td&gt;
&lt;td&gt;Read the &lt;code&gt;X-RateLimit-*&lt;/code&gt; headers and back off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5xx&lt;/td&gt;
&lt;td&gt;Provider error&lt;/td&gt;
&lt;td&gt;Retry with exponential backoff or use a fallback model&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A 5xx error is different from a rate limit. It usually means the provider had a transient issue, and a fallback model can keep your agent moving. A 429 means you are being throttled, and no amount of fallback switching will help until the reset time arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Realistic Way to Use This
&lt;/h2&gt;

&lt;p&gt;The pattern that holds up over time looks like this: build against the OpenAI-compatible endpoint so you are never locked into one provider's SDK, put two or three free models in a &lt;code&gt;models&lt;/code&gt; fallback array instead of trusting any single one, check &lt;code&gt;free_model_daily_requests&lt;/code&gt; before long runs, and keep confidential data on paid endpoints whose data policy you have read. Treat stealth models and time-limited listings like Laguna S 2.1 as bonus capacity, not something a workflow depends on.&lt;/p&gt;

&lt;p&gt;A practical example of a healthy integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sk-or-v1-your-key-here&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://openrouter.ai/api/v1/chat/completions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;models&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nvidia/nemotron-3-ultra-550b-a55b:free&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;google/gemma-4-31b-it:free&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nvidia/nemotron-3.5-lightning:free&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Explain the tradeoffs of free LLM APIs.&lt;/span&gt;&lt;span class="sh"&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="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This request uses Python's &lt;code&gt;requests&lt;/code&gt; library and passes the fallback array directly. The response will tell you which model handled the call, and you can log it for later analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Free Models in Agent Loops
&lt;/h2&gt;

&lt;p&gt;An agent loop can consume the daily free quota very quickly. Each tool call, each retry, and each intermediate step can generate a new chat completion. A task that takes 20 steps can eat 20 requests in minutes. To make the quota last, consider using a larger context and packing multiple steps into one prompt. When you can, reuse previous completions instead of asking the model to re-read the whole transcript.&lt;/p&gt;

&lt;p&gt;Another approach is to route only the final decision to a large free model and use a smaller model for simpler subtasks. For example, use &lt;code&gt;nvidia/nemotron-3.5-lightning:free&lt;/code&gt; for extraction and &lt;code&gt;nvidia/nemotron-3-ultra-550b-a55b:free&lt;/code&gt; for the final synthesis. The fallback array still applies, but you can choose different fallback lists for different task classes.&lt;/p&gt;

&lt;p&gt;Coding agents like &lt;a href="https://opencode.ai/" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt; ship OpenRouter as a built-in provider, so you can point them at the free tier without writing a custom integration. The same request caps apply, though, so an agent that makes many small calls will still exhaust the daily limit quickly. If you use a coding agent, set a low request budget and fall back to a paid model for long sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Free Usage
&lt;/h2&gt;

&lt;p&gt;The key endpoint gives you the daily usage numbers, but not a historical view. For a long-running project, log the &lt;code&gt;model&lt;/code&gt; field from every response and count requests by model ID. You can also check the OpenRouter activity page to see recent requests and errors across your account. A simple cron job that runs once an hour can hit the key endpoint and write the remaining count to a log file.&lt;/p&gt;

&lt;p&gt;A logging line does not need to be complicated. Capture the timestamp, the selected model, the status code, and the &lt;code&gt;finish_reason&lt;/code&gt;. That is enough to detect when a particular model starts failing or when your fallback order is not working as intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Example: Daily Newsletter Summarizer
&lt;/h2&gt;

&lt;p&gt;Imagine you want a bot that reads a set of tech newsletters every morning and produces a one-paragraph summary. With the free tier, you can do this with a small Python script. The script fetches the articles, truncates each one to the context limit, sends the content to a free model, and stores the summary. A daily run uses maybe 10 to 20 requests, which fits comfortably in the 50 request daily tier.&lt;/p&gt;

&lt;p&gt;First, choose a fallback list with two free models. Second, call the chat completion endpoint. Third, check &lt;code&gt;finish_reason&lt;/code&gt; and store the output. Finally, schedule it with cron. This is a good example because it uses the free tier for a bounded task rather than building a production API that needs thousands of calls per day.&lt;/p&gt;

&lt;p&gt;The same pattern applies to other scheduled jobs: log summarization, test report generation, issue triage, and personal research digests. Keep the scope small, monitor the daily remaining count, and you can get real value from an API that costs nothing per token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is OpenRouter's free tier actually unlimited?
&lt;/h3&gt;

&lt;p&gt;No. The tokens are free, but the requests are capped. The daily cap is 50 or 1,000 requests depending on your all-time credit purchases, and the per-minute cap is always 20. If you stay under those limits, you can send very large prompts and receive very long completions without token billing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why did my free model stop working?
&lt;/h3&gt;

&lt;p&gt;Free listings rotate. Labs use free inference to promote launches, collect telemetry, or test a model under real traffic. When the promotion ends, the model either disappears from the free list or becomes paid. Always have a fallback list and check the OpenRouter model pages before a deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use free models in production?
&lt;/h3&gt;

&lt;p&gt;You can, but only for low-traffic internal tools, personal projects, and experiments. The request caps and the data logging notices make them unsuitable for customer-facing products that need privacy and consistent throughput. For production use, the paid tier or bring-your-own-key route is a better fit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does using a free model with a huge context count as one request?
&lt;/h3&gt;

&lt;p&gt;Yes. A request is one API call, regardless of token count. That is why free models with 1M context are attractive for document processing, but you still have to respect the daily request limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I pay the $10 to unlock 1,000 requests?
&lt;/h3&gt;

&lt;p&gt;If you are using free models every day or iterating on prompts for hours, yes. The $10 is a one-time threshold that raises your free daily ceiling, and it also gives you pay-as-you-go access to paid models if you need them. If you only need a handful of requests for a weekend experiment, skip it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;'Free AI API with unlimited tokens' is a catchy search phrase, but the accurate description is more useful once you understand it: OpenRouter gives you free, per-token-uncapped access to a strong and changing set of open-weight models, gated by a daily request limit rather than a token limit. Right now the lineup includes Inkling, Nemotron 3 Ultra, Nemotron 3.5 Super VL and Gemma 4. Fifty requests a day costs nothing and needs no card; $10 of credits, bought once, raises that to a thousand. The discipline it asks for has not changed since July: build for rotation from day one, read each free endpoint's data notice, and recheck model pages before you rely on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pinggy.io/blog/free_ai_model_apis_unlimited_tokens_openrouter/" rel="noopener noreferrer"&gt;How to get Free AI Model APIs with 'Unlimited' Tokens&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>openrouter</category>
      <category>llm</category>
      <category>api</category>
    </item>
    <item>
      <title>Proactive AI Agents in 2026: Dots, Muse, Grok Bot, Instinct, OpenClaw, and Hermes Compared</title>
      <dc:creator>Lightning Developer</dc:creator>
      <pubDate>Wed, 04 Nov 2026 06:54:06 +0000</pubDate>
      <link>https://dev.to/lightningdev123/proactive-ai-agents-in-2026-dots-muse-grok-bot-instinct-openclaw-and-hermes-compared-129l</link>
      <guid>https://dev.to/lightningdev123/proactive-ai-agents-in-2026-dots-muse-grok-bot-instinct-openclaw-and-hermes-compared-129l</guid>
      <description>&lt;p&gt;Proactive AI agents are the new frontier in assistant design. Traditional chatbots wait for your message and then respond, which makes them easy to reason about. The new generation flips that model. Agents run in the background, wake themselves on schedules or events, and send you notifications before you ask. For developers, this shift is both exciting and a little uncomfortable, because proactivity means an agent can perform actions in the world without a fresh command from you.&lt;/p&gt;

&lt;p&gt;Between August 11 and October 21, 2026, the landscape changed rapidly. xAI shipped &lt;a href="https://x.ai/grok" rel="noopener noreferrer"&gt;Grok Bot&lt;/a&gt;, Meta shipped &lt;a href="https://muse.ai" rel="noopener noreferrer"&gt;Meta Muse&lt;/a&gt;, OpenAI shipped &lt;a href="https://openai.com/dots" rel="noopener noreferrer"&gt;OpenAI Dots&lt;/a&gt;, and &lt;a href="https://www.anthropic.com" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt; previewed Claude Duet. &lt;a href="https://instinct.com" rel="noopener noreferrer"&gt;Instinct&lt;/a&gt;, an agent you interact with through your phone number, was valued at ten billion dollars by October. Open source projects were already ahead on this path. &lt;a href="https://openclaw.ai" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; had 393,525 &lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; stars on October 31, and &lt;a href="https://hermes-agent.nousresearch.com" rel="noopener noreferrer"&gt;Hermes Agent&lt;/a&gt; had 259,065. Both run on your own hardware, which makes them interesting for privacy sensitive workflows.&lt;/p&gt;

&lt;p&gt;In this article, I compare six proactive agents: OpenAI Dots, Meta Muse, Grok Bot, Instinct, OpenClaw, and Hermes Agent. The comparison uses a concrete running example. You want a weekday 8 AM brief that only speaks up when a decision is needed, and you want to know what happens when a new GitHub issue arrives during the day. I verified the claims below on October 31, 2026. The OpenClaw and Hermes commands were tested in clean Docker containers, so the output you see is reproducible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes an AI agent proactive
&lt;/h2&gt;

&lt;p&gt;An agent is proactive when it starts a turn on its own, without a new user message. There are three common trigger types: a schedule, an event, or a self-wake. A schedule is usually a cron expression or a plain English phrase like &lt;code&gt;weekdays at 8am&lt;/code&gt;. An event is a webhook, an email, a GitHub issue, or a Slack mention. A self-wake is the most interesting. It means the agent decides when to check back, or it runs a periodic pass over your data asking whether anything needs attention.&lt;/p&gt;

&lt;p&gt;Self-wake only works if the agent can stay silent when nothing matters. OpenClaw's default heartbeat prompt ends with &lt;code&gt;If nothing needs attention, reply NO_REPLY&lt;/code&gt;. Hermes does the same thing with a &lt;code&gt;[SILENT]&lt;/code&gt; token: if the final response contains that token, the gateway skips delivery. Without a quiet path, a proactive agent would spam you every few hours with low value summaries. That is why the quiet path is one of the first features to check.&lt;/p&gt;

&lt;p&gt;Proactivity also changes the risk model. An unattended agent reads untrusted content, such as email bodies, calendar invites, and issue comments, while holding your credentials. A malicious email could attempt a prompt injection that instructs the agent to read your inbox, send money, or delete files. The six agents in this article handle that danger very differently, so security is a bigger differentiator than model quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proactive AI agents at a glance
&lt;/h2&gt;

&lt;p&gt;Here is a quick comparison. The table captures where each agent runs, what starts it, and what you pay.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Runs on&lt;/th&gt;
&lt;th&gt;Starts work via&lt;/th&gt;
&lt;th&gt;Price and access&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI Dots&lt;/td&gt;
&lt;td&gt;OpenAI cloud computer, optional link to yours&lt;/td&gt;
&lt;td&gt;Self-wake, scheduled tasks, read-only research&lt;/td&gt;
&lt;td&gt;Pro from $100/month, Business Premium, Enterprise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meta Muse&lt;/td&gt;
&lt;td&gt;One Muse Secure VM per user&lt;/td&gt;
&lt;td&gt;Background jobs, unprompted suggestions&lt;/td&gt;
&lt;td&gt;Free tier, Power $20, Maximum $100; US only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok Bot&lt;/td&gt;
&lt;td&gt;One shared computer per account, in Cursor's cloud&lt;/td&gt;
&lt;td&gt;Routines on schedules or Slack, GitHub, email and webhook events&lt;/td&gt;
&lt;td&gt;Beta, included with X and Cursor plans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instinct&lt;/td&gt;
&lt;td&gt;Instinct's own phone and computer&lt;/td&gt;
&lt;td&gt;Texts you first with reminders&lt;/td&gt;
&lt;td&gt;Early access, no public price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenClaw&lt;/td&gt;
&lt;td&gt;Your machine (Node.js Gateway)&lt;/td&gt;
&lt;td&gt;30-minute heartbeat, cron and event automations&lt;/td&gt;
&lt;td&gt;Free, MIT; you pay for the model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hermes Agent&lt;/td&gt;
&lt;td&gt;Your machine (Python gateway)&lt;/td&gt;
&lt;td&gt;Cron jobs, signed webhooks&lt;/td&gt;
&lt;td&gt;Free, MIT; you pay for the model&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One column deserves extra attention: credentials. Hosted agents hold some form of token, either in their cloud or in your browser. Open source agents load your API keys and service tokens on your machine. You should map that out before you connect your Gmail or GitHub. In the sections below, I cover each agent's trigger model, hosting, credentials, and security boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAI Dots: a coordinator that decides when to check back
&lt;/h2&gt;

&lt;p&gt;OpenAI Dots feels like a coordinator rather than a chatbot. A dot lives in OpenAI's cloud with its own computer and browser. It runs on GPT-6 Astra and can pause and resume its own work, so follow-ups do not need a fixed schedule. If you ask for a morning brief in plain words, it schedules that task. The background research is read-only, and any follow-up action must have the applicable permissions. You can attach custom rules like &lt;code&gt;Ask before taking action&lt;/code&gt; for outgoing email.&lt;/p&gt;

&lt;p&gt;OpenAI's documentation does not mention webhook triggers. That means a new GitHub issue will not wake the dot instantly. The dot will notice it the next time it checks your connected apps, which can be minutes or hours later. For a developer this is a meaningful gap if you need event driven automation. Dots are better at coordination. They can hand work to ChatGPT Work and Codex, then chase each subtask until a result is ready. This is a useful pattern for research tasks that need multiple tools.&lt;/p&gt;

&lt;p&gt;Pricing matters. Personal Pro starts at $100 per month and is not available in the EEA, UK, or Switzerland. You get one dot per account, so a power user might feel constrained. Enterprise and Business Premium plans have more room but are aimed at organizations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meta Muse: a second agent holds the keys
&lt;/h2&gt;

&lt;p&gt;Meta Muse has the strongest security story on the hosted side. Every user receives a dedicated Muse Secure VM. The agent runs inside a systemd-nspawn container, and a separate host side component called Sentinel acts as a gate. Meta's documentation says: &lt;code&gt;Muse proposes actions, but only Sentinel can grant permission to perform action.&lt;/code&gt; The agent never touches the real credentials. It holds surrogate tokens, and Sentinel swaps in the actual tokens at the network boundary.&lt;/p&gt;

&lt;p&gt;Payments are handled with single-use Stripe Link cards, and each one needs your approval. This is a genuinely useful safety pattern for an always-on agent, because a hijacked agent cannot spend money without a fresh approval. Meta also makes a reasonable effort to keep the agent's filesystem constrained with systemd-nspawn.&lt;/p&gt;

&lt;p&gt;Muse supports background cron jobs and unprompted suggestions, but there is no visual schedule screen and no event trigger in the user interface. You ask for your morning brief in chat and the agent runs it as a background job. It is available on the web, iOS, Android, and WhatsApp, but only in the United States. Even the free tier requires a payment card.&lt;/p&gt;

&lt;p&gt;One caveat: Meta says staff access to user data is restricted by policy, not technically prevented. The company says a planned &lt;code&gt;Muse Confidential VM&lt;/code&gt; will fix that, but it has not shipped yet. For a developer this is a proof point that hosted agents are fundamentally different from local ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grok Bot: a team of bots on one shared computer
&lt;/h2&gt;

&lt;p&gt;Grok Bot is the most trigger-rich hosted agent in this comparison. xAI launched it in beta on August 11, 2026, and describes it as &lt;code&gt;a team of always-on agents&lt;/code&gt;. A routine can run on a schedule, a Slack mention, GitHub, Linear, Sentry, PagerDuty activity, email to the bot's address, or a webhook. That makes Grok Bot the only hosted agent here that can react to your GitHub issue instantly.&lt;/p&gt;

&lt;p&gt;The catch is the computer. All of your Bots use the same cloud computer, sharing files, browser sessions, and app logins. That means a triage bot that reads a poisoned GitHub issue shares a browser session with a bot that handles your email. A prompt injection in an issue body could try to read a message from your Gmail session. xAI adds an independent Auto Review model for risky actions, and connector tokens are stored on Cursor's backend, but you are still relying on one shared trust boundary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Funaod90a5x11400p73oa.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Funaod90a5x11400p73oa.webp" alt="Blog Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grok Bot runs in &lt;a href="https://cursor.com" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;'s cloud, so it is bundled with X Premium plans and paid Cursor plans. The exact subscription tiers are The X Subscription Premium, Super, and Ultra, plus Cursor paid plans. There is no separate price at the moment. You should think of it as a feature of a broader plan rather than a standalone product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instinct: an agent you text, that texts you first
&lt;/h2&gt;

&lt;p&gt;Instinct is different from all the others because there is no app. You interact with it by text or phone call. The company, Spear Street Technology, was founded by Noah Shinn. Instinct uses a phone and a computer in the same way you do. A hands-on review from October 25, 2026 describes how it checks in, keeps track of ongoing tasks, and reminds you to cancel a subscription. It has since added phone calls, its own email addresses, and group chats.&lt;/p&gt;

&lt;p&gt;Instinct publishes very little about its trigger model. There is no public documentation for schedules, events, or approval workflows, except for purchases. Purchases use one-time Stripe Link cards for amounts you approve. If you want to connect Gmail, read the terms carefully. The revised terms say: &lt;code&gt;You also authorize us to access, copy, collect, and index data from your Connected Services&lt;/code&gt; and &lt;code&gt;You hereby appoint the Services as your agent to enter into agreements, commitments or transactions on your behalf.&lt;/code&gt; Training on your data is on by default, with an opt-out.&lt;/p&gt;

&lt;p&gt;For a developer, Instinct is convenient but opaque. It can be genuinely proactive and the text interface is fun, but you are trusting a commercial service with a wide scope. If you are comfortable with that, it may save you time. If not, self-hosted agents are the better fit. There is also a configuraton detail that may matter: because Instinct is in early access, you cannot inspect its internal schedule or event pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenClaw: a heartbeat on your own Gateway
&lt;/h2&gt;

&lt;p&gt;OpenClaw is the biggest open source project on this list. Version 2026.10.9, released on October 29, runs a Node.js Gateway on your machine. Automations run inside the gateway, so nothing fires while it is down. The gateway is the core pattern. It keeps a scheduler alive, executes jobs, and delivers messages through channels like Telegram.&lt;/p&gt;

&lt;p&gt;The self-wake mechanism is the heartbeat. By default every 30 minutes the heartbeat runs an agent turn and messages your own DM. The checklist for the heartbeat now lives in a &lt;code&gt;monitor scratch&lt;/code&gt; file, not the old &lt;code&gt;HEARTBEAT.md&lt;/code&gt;. You can edit it with &lt;code&gt;openclaw cron scratch &amp;lt;jobId&amp;gt; --set "..."&lt;/code&gt;, and &lt;code&gt;openclaw doctor --fix&lt;/code&gt; will migrate an old setup.&lt;/p&gt;

&lt;p&gt;The morning brief is a separate isolated job. &lt;code&gt;openclaw cron&lt;/code&gt; is an alias for &lt;code&gt;openclaw automations&lt;/code&gt;. The command looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw cron create &lt;span class="s2"&gt;"0 8 * * 1-5"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Check my inbox and calendar. Tell me only what needs a decision today. If nothing does, reply NO_REPLY."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"Morning brief"&lt;/span&gt; &lt;span class="nt"&gt;--tz&lt;/span&gt; &lt;span class="s2"&gt;"Asia/Kolkata"&lt;/span&gt; &lt;span class="nt"&gt;--session&lt;/span&gt; isolated &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--announce&lt;/span&gt; &lt;span class="nt"&gt;--channel&lt;/span&gt; telegram &lt;span class="nt"&gt;--to&lt;/span&gt; &lt;span class="s2"&gt;"123456789"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can inspect your jobs with &lt;code&gt;openclaw cron list --all&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name             Schedule                           Target    Delivery

Heartbeat (dev)  every 30m                          main      not requested

Morning brief    cron 0 8 * * 1-5 @ Asia/Kolka...   isolated  announce -&amp;gt; telegram:123456789 (explicit)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If cron is not second nature, use a cron expression parser. One default I recommend changing is the tool allow list. The newly created job allows every tool with &lt;code&gt;"toolsAllow": ["*"]&lt;/code&gt;, so pass &lt;code&gt;--tools&lt;/code&gt; with a narrower list. The &lt;code&gt;--session isolated&lt;/code&gt; flag is also important; it keeps the brief from reading the heartbeat's scratch memory.&lt;/p&gt;

&lt;p&gt;OpenClaw has a large ecosystem, but that breadth comes with maintenance burden. The OpenClawCVEs tracker counted 543 CVEs and 211 advisories on October 31, 2026. If you expose OpenClaw to the internet, you need a patching routine. Self-hosting means you own the risk.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frvxqr4ej4m1m2a1k29te.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frvxqr4ej4m1m2a1k29te.webp" alt="Blog Image" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hermes Agent: cron jobs and signed webhooks
&lt;/h2&gt;

&lt;p&gt;Hermes Agent is the other major self-hosted player, and for developers it has one of the clearest scheduler designs. Version 0.22.5, released October 23, runs its scheduler inside the gateway and ticks every 60 seconds. You can define schedules in plain English. Failed runs always deliver, so you never miss an alert because of a transient error. There is also a monitor mode with &lt;code&gt;--monitor-url&lt;/code&gt; or &lt;code&gt;--monitor-script&lt;/code&gt; that skips the LLM entirely when a cheap check's output has not changed. This is a nice cost optimization for jobs that poll an API or a website.&lt;/p&gt;

&lt;p&gt;Setting up the morning brief in Hermes is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hermes config &lt;span class="nb"&gt;set &lt;/span&gt;timezone Asia/Kolkata
hermes cron create &lt;span class="s2"&gt;"weekdays at 8am"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Check my inbox and calendar. Tell me only what needs a decision today. If nothing does, reply [SILENT]."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"morning-brief"&lt;/span&gt; &lt;span class="nt"&gt;--deliver&lt;/span&gt; telegram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gateway prints a confirmation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Created job: 115c4cda804e

  Next run: 2026-11-02T08:00:00+05:30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing runs until &lt;code&gt;hermes gateway run&lt;/code&gt; is up. Set the time zone before creating jobs. In our test, changing the time zone after job creation did not move the stored next run. For the model side, you can run Hermes on free &lt;a href="https://openrouter.ai" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt; models if you want to keep costs near zero. This makes Hermes a good choice for experiments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Triggering Hermes from GitHub through a Pinggy tunnel
&lt;/h2&gt;

&lt;p&gt;This is where the developer experience gets concrete. Grok Bot gives you a webhook URL. OpenClaw can poll GitHub with its own automation. Hermes can only recieve webhooks on port 8644 of a machine with no public address, so you need a tunnel. The docs suggest setting &lt;code&gt;WEBHOOK_ENABLED=true&lt;/code&gt; in &lt;code&gt;~/.hermes/.env&lt;/code&gt;, but in v0.22.5 the command &lt;code&gt;hermes webhook subscribe&lt;/code&gt; refused with &lt;code&gt;Webhook platform is not enabled&lt;/code&gt; until the flag was in &lt;code&gt;config.yaml&lt;/code&gt;. Let me walk through the working setup.&lt;/p&gt;

&lt;p&gt;First, enable the webhook platform and subscribe to the GitHub issues event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hermes config &lt;span class="nb"&gt;set &lt;/span&gt;platforms.webhook.enabled &lt;span class="nb"&gt;true

&lt;/span&gt;hermes webhook subscribe github-issues &lt;span class="nt"&gt;--events&lt;/span&gt; &lt;span class="s2"&gt;"issues"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"New issue #{issue.number} in {repository.full_name}: {issue.title}. Triage it and tell me if it needs me today."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--deliver&lt;/span&gt; telegram

hermes gateway run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Subscribing prints a generated HMAC secret and &lt;code&gt;http://localhost:8644/webhooks/github-issues&lt;/code&gt;. In a second terminal, create a secure tunnel with &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-p&lt;/span&gt; 443 &lt;span class="nt"&gt;-R0&lt;/span&gt;:localhost:8644 free.pinggy.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pinggy prints an HTTPS URL ending in &lt;code&gt;.run.pinggy-free.link&lt;/code&gt;. Under your GitHub repository, go to &lt;code&gt;Settings &amp;gt; Webhooks &amp;gt; Add webhook&lt;/code&gt;. Enter the Pinggy URL with &lt;code&gt;/webhooks/github-issues&lt;/code&gt;, choose &lt;code&gt;application/json&lt;/code&gt;, paste the secret, and select only the &lt;code&gt;Issues&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fslkprc6oxr4bgrew8oie.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fslkprc6oxr4bgrew8oie.webp" alt="Blog Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We tested teh path with a signed GitHub-style issues event. Our test gateway had no Telegram bot, so the &lt;code&gt;--deliver telegram&lt;/code&gt; route answered &lt;code&gt;503 {"error": "Admission unavailable; retry this delivery"}&lt;/code&gt;. Connect the delivery channel first. With the route switched to &lt;code&gt;--deliver log&lt;/code&gt;, a valid &lt;code&gt;X-Hub-Signature-256&lt;/code&gt; got HTTP &lt;code&gt;202 {"status": "accepted", ...}&lt;/code&gt; and the gateway logged the rendered prompt. A wrong signature got &lt;code&gt;401 {"error": "Invalid signature"}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A free Pinggy tunnel expires after 60 minutes and gives you a new URL. For a persistent webhook, use the Pro token with a persistent subdomain: &lt;code&gt;&amp;lt;token&amp;gt;@pro.pinggy.io&lt;/code&gt;. Our guide to receiving webhooks on localhost has more details. These steps were tested on October 31, 2026, with Hermes Agent v0.22.5.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose a proactive agent
&lt;/h2&gt;

&lt;p&gt;Choosing one agent depends on where your work lives and how much risk you are willing to take.&lt;/p&gt;

&lt;p&gt;If your daily work lives inside ChatGPT and Codex, choose OpenAI Dots. It is the best coordinator for research tasks and follow-up work. The read-only research default is a good safety boundary. If you are in the US and want cheap errands, Meta Muse is the safest hosted option on paper. Sentinel's credential boundary is a genuinely thoughtful design.&lt;/p&gt;

&lt;p&gt;If you want event-driven routines and are comfortable with a shared computer, Grok Bot is the strongest option. It is the only hosted agent in this list that can react to GitHub instantly. But you should not put secrets in the shared browser session if you can avoid it. If you only want to text with an agent, Instinct is compelling. It feels like an assistant with a phone. Just read the terms first.&lt;/p&gt;

&lt;p&gt;For developers who want to own the infrastructure, OpenClaw has the biggest ecosystem and the largest community. It also has the most CVEs, so you need a patching routine. Hermes Agent has the clearest scheduler and webhook model, and its monitor mode can save money on polling jobs. I would start with Hermes if webhooks are your primary use case, and with OpenClaw if you want broad plugin coverage and a heartbeat style self-wake.&lt;/p&gt;

&lt;p&gt;One more thing to watch: Anthropic's Claude Duet is a US waitlist preview that sends a morning brief and checks your apps every few hours. Google's Gemini Spark is also in the news. Both are worth evaluating when they reach stable access, but neither has a public trigger model yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security checks before you plug in an agent
&lt;/h2&gt;

&lt;p&gt;Whether you choose hosted or self-hosted, run through the same checklist before connecting an account.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What credentials does the agent hold? Hosted agents usually hold tokens server side. Self-hosted agents load locally.&lt;/li&gt;
&lt;li&gt;Can the agent make irreversible changes? Look for approvals on send, pay, delete.&lt;/li&gt;
&lt;li&gt;Does the agent have a quiet path? No &lt;code&gt;NO_REPLY&lt;/code&gt; or &lt;code&gt;[SILENT]&lt;/code&gt; means you will be spammed.&lt;/li&gt;
&lt;li&gt;What events can wake it? Webhooks are fast but expose a public endpoint.&lt;/li&gt;
&lt;li&gt;Is there a session isolation boundary? Grok Bot's shared computer is a warning sign.&lt;/li&gt;
&lt;li&gt;Where is your data stored? Muse is US only, Dots excludes EEA, UK, and Switzerland for Personal Pro.&lt;/li&gt;
&lt;li&gt;What happens when a prompt injection arrives? Test with a fake email or issue before pointing the agent at production accounts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting common issues
&lt;/h2&gt;

&lt;p&gt;The following issues came up during my testing. If you are setting up your own agent, you will probably hit at least one of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Webhook platform not enabled.&lt;/strong&gt; Put &lt;code&gt;platforms.webhook.enabled true&lt;/code&gt; in &lt;code&gt;config.yaml&lt;/code&gt;, not just &lt;code&gt;WEBHOOK_ENABLED&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt;. The command parser reads the YAML config first, and the .env variable was not sufficient in v0.22.5.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Telegram delivery fails.&lt;/strong&gt; The webhook accepted the event, but delivery returned a 503. Connect the delivery channel first. If Hermes cannot reach Telegram, it cannot deliver the rendered prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time zone changes do not move next run.&lt;/strong&gt; Set the timezone before creating a scheduled job. Changing it afterwards left the old next run time in place in our test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free tunnel expires.&lt;/strong&gt; The free Pinggy URL changes every 60 minutes. Use a persistent subdomain with a paid token for any webhook that needs to stay alive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heartbeat messages are too noisy.&lt;/strong&gt; Increase the heartbeat interval or edit the monitor scratch so the agent only replies with &lt;code&gt;NO_REPLY&lt;/code&gt; when nothing needs attention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools allow list is too broad.&lt;/strong&gt; In OpenClaw, the default &lt;code&gt;toolsAllow&lt;/code&gt; is a wildcard. Pass &lt;code&gt;--tools&lt;/code&gt; with a list of allowed tools. A brief only needs calendar, email, and delivery tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I run OpenClaw and Hermes on the same machine?&lt;/strong&gt; Yes. They use different gateways and different ports, so you can run both. You will need to decide which one owns the cron schedules for the same job, otherwise you might get duplicate briefs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the difference between a heartbeat and a cron job?&lt;/strong&gt; A heartbeat is a system-owned periodic pass that asks the agent whether anything needs attention. A cron job is a user-defined scheduled task with a fixed expression. Both are proactive, but a heartbeat is more flexible because the agent can choose to remain silent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do proactive agents work with offline data?&lt;/strong&gt; No. They need an LLM to reason about your data. OpenClaw and Hermes call a model provider like OpenRouter or a local model server. If the model API is down, the job fails or waits for the next run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I prevent prompt injection in a webhook?&lt;/strong&gt; First, verify the webhook signature. In Hermes, the &lt;code&gt;X-Hub-Signature-256&lt;/code&gt; header must match the generated secret. Second, add a prompt instruction that tells the agent to ignore instructions inside the incoming content and only use the content as data. Third, use an allow-list of tools so the agent cannot call arbitrary tool operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the total cost of running OpenClaw?&lt;/strong&gt; The software is free, but you pay for model API usage, a delivery channel, and your machine uptime. For a simple daily brief, the model cost can be less than a dollar per month if you use a small model. For more complex tasks with many tool calls, it can be a few dollars.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Whichever agent you pick, start small. Create one scheduled job that has a quiet path (&lt;code&gt;NO_REPLY&lt;/code&gt;, &lt;code&gt;[SILENT]&lt;/code&gt;, or &lt;code&gt;message me only if a decision is needed&lt;/code&gt;) and one event trigger. Turn on approvals for anything that sends, pays, or deletes. After one week, count how many of its messages you were glad to receive. That number is the real measure of proactivity.&lt;/p&gt;

&lt;p&gt;The field is moving fast. Hosted agents are getting more secure, open source agents are getting easier to deploy, and new entrants like Claude Duet and Gemini Spark will force even better defaults. For developers, the best move is to build a reproducible setup with the agent that matches your own trigger, credential, and privacy requirements. Keep your delivery channel connected, keep your tools allow-listed, and always test with signed payloads before you let the agent talk to your real accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pinggy.io/blog/proactive_ai_agents_compared/" rel="noopener noreferrer"&gt;Proactive AI Agents Compared: Dots vs Muse vs Grok Bot vs Instinct vs Hermes vs OpenClaw (2026)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>webdev</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>Webhook Testing on Localhost: Tunnels, Signatures, and Reliable Replays</title>
      <dc:creator>Lightning Developer</dc:creator>
      <pubDate>Wed, 04 Nov 2026 02:34:00 +0000</pubDate>
      <link>https://dev.to/lightningdev123/webhook-testing-on-localhost-tunnels-signatures-and-reliable-replays-3adn</link>
      <guid>https://dev.to/lightningdev123/webhook-testing-on-localhost-tunnels-signatures-and-reliable-replays-3adn</guid>
      <description>&lt;p&gt;When a service wants to tell your application about an event, it does not wait for you to ask. It sends an HTTP POST to a URL that you registered. A payment clears, a pull request opens, an order ships, and the provider calls your endpoint. This pattern is called a webhook, and it is everywhere.&lt;/p&gt;

&lt;p&gt;The problem is that the URL has to be reachable from the public internet. During development, your application runs on localhost:3000, behind a home router or an office NAT. &lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://stripe.com" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;, and &lt;a href="https://slack.com" rel="noopener noreferrer"&gt;Slack&lt;/a&gt; cannot reach that address. To test webhooks locally, you need two things: a temporary public URL that forwards to your local port, and a handler that only trusts requests that were genuinely signed by the provider.&lt;/p&gt;

&lt;p&gt;This guide follows one example from start to finish. You will receive a GitHub repository webhook in an &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;Express&lt;/a&gt; handler on port 3000 through a &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; tunnel. Stripe, Slack, and &lt;a href="https://www.shopify.com" rel="noopener noreferrer"&gt;Shopify&lt;/a&gt; work the same way, with only the signature details changing. The code and tests in this article were run on &lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; 25.5 with Express 5.3.0 in October 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key points
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One command gives localhost a public URL. &lt;code&gt;ssh -p 443 -R0:localhost:3000 free.pinggy.io&lt;/code&gt; prints an HTTPS address that forwards to port 3000. Free tunnels last 60 minutes and get a new random URL each time. A Pro token keeps one subdomain.&lt;/li&gt;
&lt;li&gt;Verify the signature on the raw bytes. GitHub signs the exact request body with HMAC-SHA256 and sends it in &lt;code&gt;X-Hub-Signature-256&lt;/code&gt;. Read the body with &lt;code&gt;express.raw()&lt;/code&gt;, never &lt;code&gt;express.json()&lt;/code&gt;, and compare with &lt;code&gt;crypto.timingSafeEqual&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Reply fast and expect repeats. GitHub waits 10 seconds and never retries automatically. Shopify waits 5 seconds and retries 8 times over 4 hours. Return a 2XX first, queue the work, and skip delivery IDs you have already handled.&lt;/li&gt;
&lt;li&gt;Replay instead of re-triggering. Pinggy Web Debugger, started with &lt;code&gt;-L4300:localhost:4300&lt;/code&gt;, shows every delivery and can resend it as-is or with edited headers.&lt;/li&gt;
&lt;li&gt;Skip the tunnel when the provider ships a CLI. &lt;code&gt;stripe listen&lt;/code&gt; and &lt;code&gt;gh webhook forward&lt;/code&gt; deliver events to localhost without a public URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How a webhook reaches localhost:3000
&lt;/h2&gt;

&lt;p&gt;Nothing on your router changes. Your laptop makes the first connection outward. An SSH client dials Pinggy on port 443 and asks it to forward a public URL back down that connection. When GitHub POSTs to the URL, the request follows a path: GitHub to Pinggy, then through the SSH connection your laptop opened, then to localhost:3000. The handler's response travels back along the same path.&lt;/p&gt;

&lt;p&gt;Because the laptop starts the connection, this approach also works behind CGNAT. With CGNAT, your router does not have a public IP address, so port forwarding cannot work from the outside. A reverse tunnel does not care. It only needs an outbound connection from your machine to the tunnel server.&lt;/p&gt;

&lt;p&gt;Free Pinggy URLs show a one-time screening page, but only to browsers. Webhook senders, &lt;a href="https://curl.se" rel="noopener noreferrer"&gt;curl&lt;/a&gt;, and HTTP libraries go straight through to your handler. That means a tunnel is safe to use for webhook testing as long as you enforce a signature check.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4epoxfsoe3bo2z1jlco0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4epoxfsoe3bo2z1jlco0.webp" alt="Blog Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why signature verification is non-negotiable
&lt;/h2&gt;

&lt;p&gt;A public tunnel URL is guessable. It may be a random subdomain, but attackers scan subdomains and IP ranges all the time. If your endpoint accepts any request without verifying a signature, anyone can send a fake event. They could trigger a build, mark an order as paid, or spam your logs. Signature verification is the only reliable way to know that a request came from the provider.&lt;/p&gt;

&lt;p&gt;Providers do not all use the same cryptographic primitive, but the pattern is similar. They compute a digest of the request body and a secret, put the digest in a header, and send the request. Your handler recomputes the digest and compares it with the header. If they match, you can trust the body. If they do not, reject the request with &lt;code&gt;401 Unauthorized&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is not a theoretical concern. When you open a tunnel, your local endpoint is on the public internet. Attackers can hit it within minutes. A signature check costs a few lines of code and saves you from a long debugging session.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the signature header
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;X-Hub-Signature-256&lt;/code&gt; header is not just a hex string. It is prefixed with &lt;code&gt;sha256=&lt;/code&gt;. Some older integrations use &lt;code&gt;X-Hub-Signature&lt;/code&gt;, which is the SHA-1 version. GitHub still accepts both for compatibility, but its documentation recommends the SHA-256 header for new integrations. If you only check the legacy header, a delivery signed with SHA-256 would fail. If you only check the SHA-256 header, old tooling will be rejected. For a fresh webhook, you should choose &lt;code&gt;X-Hub-Signature-256&lt;/code&gt; and ignore the legacy header.&lt;/p&gt;

&lt;p&gt;When you construct the expected value, use the same prefix. The code does &lt;code&gt;'sha256=' + ...&lt;/code&gt;, which matches what GitHub sends. Some developers forget the prefix and then wonder why every request returns 401. The header also uses lowercase hex characters. You do not need to normalize case if you compute the digest with &lt;code&gt;digest('hex')&lt;/code&gt;, because Node.js returns lowercase. If you use uppercase, you would need to lowercase before comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trust boundaries and tunnels
&lt;/h3&gt;

&lt;p&gt;Even with a signature check, the tunnel is part of your trust boundary. Anyone who can reach the tunnel URL can send requests to your handler. They will fail the signature check, but the request still reaches your Node.js process. This means you should not rely on the signature check alone to protect against denial-of-service. A malformed request without an &lt;code&gt;X-Hub-Signature-256&lt;/code&gt; header is rejected after a quick length check, but parsing the body and logging it costs CPU and memory.&lt;/p&gt;

&lt;p&gt;Pinggy free tunnels include a one-time browser warning page. This is not a security mechanism for webhooks. It only exists to stop a random person from opening the URL in a browser and seeing your local app. Automated clients, including webhook providers and attackers, skip it. Keep your handler behind a signature check even if you are only testing.&lt;/p&gt;

&lt;p&gt;Source IP restrictions are tempting, but unreliable for GitHub because its IP ranges change. For Stripe, the published IP ranges are more stable, and you could combine an allowlist with the signature. Defense in depth is nice, but the signature is the primary control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Write a handler that checks the signature
&lt;/h2&gt;

&lt;p&gt;GitHub computes an HMAC-SHA256 of the request body using a secret you choose. It sends the hex digest in the &lt;code&gt;X-Hub-Signature-256&lt;/code&gt; header, prefixed with &lt;code&gt;sha256=&lt;/code&gt;. Your handler recomputes that value and compares the two.&lt;/p&gt;

&lt;p&gt;Set up a folder and generate a secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;webhook-demo &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;webhook-demo
npm init &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install &lt;/span&gt;express
openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command uses &lt;a href="https://www.openssl.org/" rel="noopener noreferrer"&gt;OpenSSL&lt;/a&gt; to generate a random 64-character hex string. Copy the output. GitHub will need the same value later. Save the handler as &lt;code&gt;server.mjs&lt;/code&gt;:&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;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:crypto&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&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;GITHUB_WEBHOOK_SECRET&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;seen&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;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&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="s1"&gt;application/json&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="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;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isBuffer&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="nx"&gt;body&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;415&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expected a raw application/json body&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;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&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;received&lt;/span&gt; &lt;span class="o"&gt;=&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Hub-Signature-256&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&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;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;received&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;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bad signature&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;delivery&lt;/span&gt; &lt;span class="o"&gt;=&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-GitHub-Delivery&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;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delivery&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delivery&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;event&lt;/span&gt; &lt;span class="o"&gt;=&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-GitHub-Event&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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="nx"&gt;body&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&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="nx"&gt;delivery&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Listening on http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the server with the secret in the environment. The &lt;code&gt;--watch&lt;/code&gt; flag restarts it when you edit the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GITHUB_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'paste-the-secret-here'&lt;/span&gt; node &lt;span class="nt"&gt;--watch&lt;/span&gt; server.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why &lt;code&gt;express.raw()&lt;/code&gt; matters
&lt;/h3&gt;

&lt;p&gt;The signature covers the exact bytes GitHub sent. &lt;code&gt;express.json()&lt;/code&gt; parses the body into a JavaScript object and then serializes it again when you need the raw string, which can change whitespace, escape characters, or Unicode normalization. The digest from the provider will stop matching. &lt;code&gt;express.raw({ type: 'application/json' })&lt;/code&gt; gives you the body as a Buffer before any parsing. You verify the signature on those raw bytes and only then parse the JSON.&lt;/p&gt;

&lt;p&gt;The length check before &lt;code&gt;crypto.timingSafeEqual&lt;/code&gt; is also important. Node.js throws &lt;code&gt;ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH&lt;/code&gt; when the two buffers differ in size, which happens when the header is missing. Comparing lengths first avoids the exception and avoids leaking the expected length through timing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvtf8guu4nksmwwbj008b.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvtf8guu4nksmwwbj008b.webp" alt="Blog Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Duplicate deliveries
&lt;/h3&gt;

&lt;p&gt;A redelivery keeps the same &lt;code&gt;X-GitHub-Delivery&lt;/code&gt; header. GitHub's documentation states that if you request a redelivery, the delivery ID stays the same. The handler uses an in-memory &lt;code&gt;Set&lt;/code&gt; to skip repeats. This is perfect for a demo. It is wrong for production, because it forgets everything on restart and does not work across multiple instances.&lt;/p&gt;

&lt;p&gt;In a real system, insert the delivery ID into a table with a unique index in the same transaction as the work. If a duplicate arrives, the insert fails with a unique constraint violation and you know the event was already handled. This is the same idempotency strategy used by payment providers and other webhook-heavy services.&lt;/p&gt;

&lt;h3&gt;
  
  
  The reply window
&lt;/h3&gt;

&lt;p&gt;GitHub waits 10 seconds for a 2XX response. If the handler takes longer, GitHub cuts the connection and records the delivery as failed. That is why the handler responds with &lt;code&gt;202 Accepted&lt;/code&gt; as early as possible. Slow operations, such as sending emails or updating a search index, should go to a queue. The code above only logs, but in a real application you would enqueue a background job before returning the response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test the handler locally before GitHub
&lt;/h3&gt;

&lt;p&gt;You can sign a request yourself with &lt;code&gt;openssl&lt;/code&gt; and send it to the handler. This tests the exact same code path that GitHub will use, without involving GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BODY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{"zen":"Design for failure.","hook_id":123}'&lt;/span&gt;
&lt;span class="nv"&gt;SIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sha256=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | openssl dgst &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-hmac&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $NF}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://localhost:3000/webhook &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'X-GitHub-Event: ping'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'X-GitHub-Delivery: test-1'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Hub-Signature-256: &lt;/span&gt;&lt;span class="nv"&gt;$SIG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this in a terminal where &lt;code&gt;GITHUB_WEBHOOK_SECRET&lt;/code&gt; is set to the same value. The first call returns &lt;code&gt;202 Accepted&lt;/code&gt;. Calling the same command again returns &lt;code&gt;200 OK&lt;/code&gt; because the delivery ID repeats. If you change one character in &lt;code&gt;BODY&lt;/code&gt; after computing &lt;code&gt;SIG&lt;/code&gt;, the handler returns &lt;code&gt;401 Unauthorized&lt;/code&gt;. Those three outcomes prove that the handler accepts valid requests, rejects invalid signatures, and deduplicates redeliveries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Open the tunnel
&lt;/h2&gt;

&lt;p&gt;Open a second terminal and point a tunnel at port 3000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-p&lt;/span&gt; 443 &lt;span class="nt"&gt;-R0&lt;/span&gt;:localhost:3000 free.pinggy.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If SSH asks for a password, press Enter. Pinggy prints the tunnel's public URLs, including an HTTPS address shaped like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://uljtt-30-47-152-61.run.pinggy-free.link
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy that URL. You can build a custom command on the Pinggy website as well.&lt;/p&gt;

&lt;p&gt;Free tunnels last 60 minutes and get a new random subdomain every time they start. After a restart, you must paste the new URL into GitHub again. With a Pinggy Pro token, tunnels have no 60-minute limit and you can assign a persistent subdomain from the dashboard. This is helpful when you need a stable webhook URL for several days of development.&lt;/p&gt;

&lt;p&gt;The URL is public while the tunnel is running. The signature check is what keeps out requests that GitHub did not sign. Pinggy can also limit a tunnel to specific source IPs with the &lt;code&gt;w:&lt;/code&gt; option, but GitHub advises against using IP allowlists for webhooks because its IP ranges change from time to time. Trust the signature, not the source address.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing a stable tunnel URL
&lt;/h3&gt;

&lt;p&gt;For a long integration project, a rotating free URL becomes annoying. Every time the tunnel restarts, you have to edit the GitHub webhook settings and wait for the next test event. A persistent subdomain removes that churn. Pinggy's Pro tier gives you a fixed URL that remains the same across tunnel restarts, which is much easier to put in your local &lt;code&gt;.env&lt;/code&gt; file or your webhook configuration.&lt;/p&gt;

&lt;p&gt;There is also a practical benefit for the Web Debugger. If you keep the same tunnel URL, the debugger history accumulates across multiple handler restarts, giving you a record of how the provider's requests evolved. With a new URL each time, you lose that history and have to start over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Point the GitHub webhook at the tunnel
&lt;/h2&gt;

&lt;p&gt;Inside the repository, open Settings, then Webhooks, then Add webhook. Fill in the form:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payload URL: the HTTPS tunnel URL plus &lt;code&gt;/webhook&lt;/code&gt;, for example &lt;code&gt;https://uljtt-30-47-152-61.run.pinggy-free.link/webhook&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Content type: &lt;code&gt;application/json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Secret: the same value you put in &lt;code&gt;GITHUB_WEBHOOK_SECRET&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Events: choose Let me select individual events and tick the events your code handles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Save the webhook. GitHub sends a &lt;code&gt;ping&lt;/code&gt; event immediately. The terminal running &lt;code&gt;node --watch&lt;/code&gt; prints &lt;code&gt;ping&lt;/code&gt; followed by the delivery ID. The Recent deliveries tab on the GitHub webhook page shows the full request, the response status, and a Redeliver button.&lt;/p&gt;

&lt;p&gt;Trigger one of the events you selected, such as pushing a commit if you chose &lt;code&gt;push&lt;/code&gt;. You will recieve the real event in the same way as the ping. At this point, the entire loop is working: GitHub sends to Pinggy, Pinggy forwards over SSH to localhost, and your handler verifies and responds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Inspect and replay deliveries with the Web Debugger
&lt;/h2&gt;

&lt;p&gt;GitHub's delivery log only covers GitHub. Pinggy's Web Debugger shows every request that hits the tunnel, regardless of the sender. To enable it, add one extra flag to the tunnel command. Since a restarted free tunnel gets a new URL, update the payload URL in GitHub after restarting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-p&lt;/span&gt; 443 &lt;span class="nt"&gt;-R0&lt;/span&gt;:localhost:3000 &lt;span class="nt"&gt;-L4300&lt;/span&gt;:localhost:4300 free.pinggy.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:4300&lt;/code&gt; in a browser. Every request appears live with its headers, body, and your handler's response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhhmpwweworj0uhniyydr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhhmpwweworj0uhniyydr.webp" alt="Blog Image" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Web Debugger has two powerful actions. &lt;strong&gt;Replay&lt;/strong&gt; resends a captured delivery exactly as it arrived. This is the quickest way to verify duplicate handling: a replayed GitHub delivery should get &lt;code&gt;200&lt;/code&gt;, not a second &lt;code&gt;202&lt;/code&gt;, as long as the server has not restarted and the in-memory &lt;code&gt;Set&lt;/code&gt; is still populated. &lt;strong&gt;Modify and Replay&lt;/strong&gt; lets you change the method, path, HTTP version, or any header before resending. You can, for example, corrupt the &lt;code&gt;X-Hub-Signature-256&lt;/code&gt; header and confirm that the handler answers &lt;code&gt;401&lt;/code&gt;. That gives you a way to test both success and failure paths without waiting for GitHub to send anything new.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other providers: same handler, different header
&lt;/h2&gt;

&lt;p&gt;The handler structure stays the same across providers. The basic configuraton stays the same. The signature header, the signed string, and the reply window change.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Signature header&lt;/th&gt;
&lt;th&gt;What gets signed&lt;/th&gt;
&lt;th&gt;Reply within&lt;/th&gt;
&lt;th&gt;Retries&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;X-Hub-Signature-256&lt;/code&gt;, &lt;code&gt;sha256=&lt;/code&gt; + hex&lt;/td&gt;
&lt;td&gt;Raw body&lt;/td&gt;
&lt;td&gt;10 seconds&lt;/td&gt;
&lt;td&gt;None automatic; redeliver from UI or API within 3 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Stripe-Signature&lt;/code&gt;, &lt;code&gt;t=...,v1=...&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;timestamp.body&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No documented number, quickly&lt;/td&gt;
&lt;td&gt;Up to 3 days in live mode, 3 times over a few hours in a sandbox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack (Events API)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;X-Slack-Signature&lt;/code&gt;, &lt;code&gt;v0=&lt;/code&gt; + hex&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v0:timestamp:body&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3 seconds&lt;/td&gt;
&lt;td&gt;3 retries, numbered in &lt;code&gt;X-Slack-Retry-Num&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shopify&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;X-Shopify-Hmac-SHA256&lt;/code&gt;, base64&lt;/td&gt;
&lt;td&gt;Raw body&lt;/td&gt;
&lt;td&gt;5 seconds&lt;/td&gt;
&lt;td&gt;8 times over 4 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Stripe and Slack mix a timestamp into the signed string. A handler that checks the timestamp can reject a captured request replayed later. Stripe's official libraries allow a 5 minute tolerance by default. Slack's documentation says to ignore any request more than five minutes off your local time.&lt;/p&gt;

&lt;p&gt;Use the provider's official SDK when one exists. Stripe's Node.js library has &lt;code&gt;constructEvent&lt;/code&gt;, which parses the header, checks the timestamp, and compares signatures in constant time. Writing your own HMAC code for Stripe is more error prone because you also have to validate the timestamp scheme and version. The same advice applies for Slack's signing secret and Shopify's HMAC header.&lt;/p&gt;

&lt;h3&gt;
  
  
  Raw bodies in every framework
&lt;/h3&gt;

&lt;p&gt;The raw body rule applies no matter which framework you use. It just looks different.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;a href="https://fastify.dev" rel="noopener noreferrer"&gt;Fastify&lt;/a&gt;, register a JSON content-type parser with &lt;code&gt;{ parseAs: 'buffer' }&lt;/code&gt; so the route receives a Buffer.&lt;/li&gt;
&lt;li&gt;In a &lt;a href="https://nextjs.org" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; App Router route handler, &lt;code&gt;await request.text()&lt;/code&gt; returns the unparsed body, which is what Next.js's own webhook example uses.&lt;/li&gt;
&lt;li&gt;In &lt;a href="https://hono.dev" rel="noopener noreferrer"&gt;Hono&lt;/a&gt;, &lt;code&gt;await c.req.text()&lt;/code&gt; gives you the body string before any JSON parsing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whatever framework you choose, verify the signature before calling &lt;code&gt;JSON.parse&lt;/code&gt;. If you parse first, you might accidentally mutate the data and break the signature, or you might expose yourself to attacks that exploit the parser before authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retry semantics and backoff
&lt;/h3&gt;

&lt;p&gt;Webhook providers have different retry policies. Some retry with exponential backoff, some retry at fixed intervals, and some do not retry at all. The table above summarizes the most common ones. When a provider does retry, you may see the same event arrive multiple times. Your delivery ID deduplication should use a stable identifier that the provider documents. The raw body is not a good idempotency key because two separate events can have identical bodies.&lt;/p&gt;

&lt;p&gt;Webhooks can also arrive out of order. If your handler processes &lt;code&gt;order.shipped&lt;/code&gt; before &lt;code&gt;order.paid&lt;/code&gt;, your application state may be wrong. Use event timestamps or version fields inside the payload to maintain ordering where it matters. Queue consumers should expect at-least-once delivery, so idempotency is not an option, it is a requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you don't need a tunnel
&lt;/h2&gt;

&lt;p&gt;Some providers ship a CLI that pushes events directly into localhost over an outbound connection. No public URL is needed.&lt;/p&gt;

&lt;p&gt;Stripe's CLI is the most polished example. Install it with one of these commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; @stripe/cli@latest
&lt;span class="c"&gt;# or&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;stripe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then log in and start forwarding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stripe login
stripe listen &lt;span class="nt"&gt;--forward-to&lt;/span&gt; localhost:3000/webhook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI prints a &lt;code&gt;whsec_&lt;/code&gt; signing secret that stays the same between restarts. You can set it as &lt;code&gt;STRIPE_WEBHOOK_SECRET&lt;/code&gt; in your environment and use &lt;code&gt;stripe trigger checkout.session.completed&lt;/code&gt; to fire a test event whenever you want one.&lt;/p&gt;

&lt;p&gt;GitHub has a similar extension for the &lt;a href="https://cli.github.com" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt;. Install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh extension &lt;span class="nb"&gt;install &lt;/span&gt;cli/gh-webhook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use &lt;code&gt;gh webhook forward&lt;/code&gt; to stream repository or organization events to a local URL. Note that only one person can forward a given repository at a time.&lt;/p&gt;

&lt;p&gt;GitHub's documentation also still mentions &lt;a href="https://smee.io" rel="noopener noreferrer"&gt;smee.io&lt;/a&gt;, a relay that streams payloads to a client over Server-Sent Events. Its README warns that channels are not authenticated, so anyone with the channel URL can read the payloads. That makes it acceptable for a quick test, but not for sensitive data.&lt;/p&gt;

&lt;p&gt;Slack apps can avoid a public URL entirely by using Socket Mode. Events arrive over a WebSocket instead of a Request URL. However, Socket Mode apps are not allowed in the public Slack Marketplace, so this only works for internal apps.&lt;/p&gt;

&lt;p&gt;A tunnel stays the fallback for the long tail of providers that have no CLI. There is a separate comparison of provider CLIs versus tunnels for webhook testing, but the pattern is usually the same: if your provider has a CLI, use it; if not, open a tunnel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted inspectors and production gateways
&lt;/h2&gt;

&lt;p&gt;If you only want to see what a provider sends before writing any code, a hosted inspector is faster than a tunnel or CLI. &lt;a href="https://webhook.site" rel="noopener noreferrer"&gt;Webhook.site&lt;/a&gt; gives you a URL instantly. Free URLs accept up to 100 requests, expire after 7 days, and anyone who knows the URL's ID can read what was sent. &lt;a href="https://hookdeck.com" rel="noopener noreferrer"&gt;Hookdeck&lt;/a&gt; has an inspect mode that requires no account and keeps captured requests for 3 days. &lt;a href="https://pipedream.com" rel="noopener noreferrer"&gt;RequestBin&lt;/a&gt; now redirects to &lt;a href="https://pipedream.com" rel="noopener noreferrer"&gt;Pipedream&lt;/a&gt;, so older guides that mention RequestBin are out of date.&lt;/p&gt;

&lt;p&gt;For production, where a missed event can cost money, a receiving gateway is a better choice. Services like &lt;a href="https://www.svix.com" rel="noopener noreferrer"&gt;Svix&lt;/a&gt;, &lt;a href="https://hookdeck.com" rel="noopener noreferrer"&gt;Hookdeck&lt;/a&gt;, or &lt;a href="https://www.getconvoy.io" rel="noopener noreferrer"&gt;Convoy&lt;/a&gt; sit in front of your application and handle buffering, retries, and replay. Convoy is self-hostable under the Elastic License 2.0, so it is source-available rather than open source. A gateway can protect you from provider retry storms and gives you an audit log of every delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production hardening
&lt;/h2&gt;

&lt;p&gt;The demo handler is intentionally small. In production, you should also do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a database-backed deduplication table instead of a &lt;code&gt;Set&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Store the signing secret in a secret manager, not in an environment variable hardcoded in a shell command.&lt;/li&gt;
&lt;li&gt;Add request timeouts and circuit breakers for outbound calls.&lt;/li&gt;
&lt;li&gt;Log the delivery ID, event type, and signature status in a structured format.&lt;/li&gt;
&lt;li&gt;Set up metrics for webhook latency, error rate, and duplicate rate.&lt;/li&gt;
&lt;li&gt;Run the handler behind a reverse proxy that limits request size and rate limits by source IP.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These practices matter because webhook providers can send bursts of events. A simple controller that handles 1 request per second may fail when a provider sends 100 events after a bulk operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling large payloads
&lt;/h3&gt;

&lt;p&gt;Some webhook bodies are large. GitHub repository events can include many commits. Shopify product updates can be several megabytes. &lt;code&gt;express.raw()&lt;/code&gt; buffers the entire body in memory. For most webhooks this is fine. If you expect very large payloads, you need a different strategy. You can stream the request body to disk while computing the HMAC, but you cannot verify the signature until the whole body is read. The provider also has a maximum body size. GitHub limits webhook payloads to 25 MB, and Stripe's limit is lower. For local testing, the default memory limit is fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graceful shutdowns
&lt;/h3&gt;

&lt;p&gt;When the tunnel closes, the handler may still receive in-flight requests. Implement a graceful shutdown that stops accepting new connections, waits for existing requests to finish, and closes the database pool. This is especially important if you are testing delivery semantics with a CLI that retries.&lt;/p&gt;

&lt;h3&gt;
  
  
  A minimal CI workflow for webhook handlers
&lt;/h3&gt;

&lt;p&gt;Once the local curl checks pass, add them to CI. You can write a small test that starts the server, sends a signed request, and asserts on the response. Here is a minimal example using Node's built-in test runner and global fetch:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createHmac&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:crypto&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;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&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;TEST_SECRET&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;opened&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&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;response&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="s1"&gt;http://localhost:3000/webhook&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-github-event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;issues&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-github-delivery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-delivery-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-hub-signature-256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a second test that corrupts the signature and expects &lt;code&gt;401&lt;/code&gt;, and a third that sends the same delivery ID twice and expects the second response to be &lt;code&gt;200&lt;/code&gt;. These three tests will protect you from the most common webhook regressions: missing raw body, broken signature comparison, and duplicate handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Every delivery gets 415
&lt;/h3&gt;

&lt;p&gt;Your handler did not receive raw JSON bytes. Either the GitHub webhook content type is set to &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;, or a global &lt;code&gt;app.use(express.json())&lt;/code&gt; runs before the webhook route and consumes the body first. Mount &lt;code&gt;express.raw()&lt;/code&gt; on the webhook route only, exactly as in the code above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every delivery gets 401
&lt;/h3&gt;

&lt;p&gt;The secret in GitHub and the one in &lt;code&gt;GITHUB_WEBHOOK_SECRET&lt;/code&gt; differ. Common causes are a trailing newline when you pasted the value, a leading or trailing space, or a stray quote. Compare the two values with &lt;code&gt;wc -c&lt;/code&gt; and &lt;code&gt;od -c&lt;/code&gt;, then use the Redeliver button in GitHub to send a fresh delivery.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub reports a timeout or failed delivery
&lt;/h3&gt;

&lt;p&gt;Either the handler took longer than 10 seconds, or the tunnel was down when GitHub sent the request. GitHub does not retry failed deliveries by itself. Fix the cause and click Redeliver in Recent deliveries. Deliveries from the past 3 days are listed there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deliveries stopped after an hour
&lt;/h3&gt;

&lt;p&gt;The free tunnel hit its 60-minute limit. Restart it and update the payload URL in GitHub, or use a Pro token with a persistent subdomain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nothing shows up at all
&lt;/h3&gt;

&lt;p&gt;Check that the payload URL ends with &lt;code&gt;/webhook&lt;/code&gt; and uses the HTTPS address. If the Web Debugger shows no request, GitHub never reached the tunnel. If it shows the request, the problem is on teh local side, in the route or handler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Curl works but GitHub does not
&lt;/h3&gt;

&lt;p&gt;The most common difference is content type. GitHub sends &lt;code&gt;application/json&lt;/code&gt; when configured, but the signature header is case sensitive. Also check whether the secret string contains a newline. &lt;code&gt;openssl rand -hex 32&lt;/code&gt; does not add a newline, but some terminal copy operations do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical debugging tips
&lt;/h2&gt;

&lt;p&gt;When a webhook handler misbehaves, the fastest way to find the problem is to compare what the provider sent with what the handler received. Log the raw body length, the event type, and the signature header value. Log the delivery ID too; support requests are much easier to investigate when you can search by that value.&lt;/p&gt;

&lt;p&gt;You can also add a temporary &lt;code&gt;console.log&lt;/code&gt; before and after &lt;code&gt;JSON.parse&lt;/code&gt; to see whether the body is a Buffer or a string. Many 415 errors come from the route not receiving the raw body because a global JSON parser ran first. A quick log of &lt;code&gt;typeof req.body&lt;/code&gt; and &lt;code&gt;Buffer.isBuffer(req.body)&lt;/code&gt; will tell you which one it is.&lt;/p&gt;

&lt;p&gt;Use the Web Debugger's Modify and Replay feature to test edge cases. Change the &lt;code&gt;Content-Type&lt;/code&gt; header to &lt;code&gt;text/plain&lt;/code&gt;, remove the signature header, or swap the delivery ID to a fresh value. Each test gives you a deterministic response without touching GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Receiving webhooks on localhost comes down to a public URL that forwards to your local port and a handler that refuses anything it cannot verify. Once you get the loop working with GitHub's ping event, turn the curl checks from Step 1 into automated tests. A correctly signed request returns &lt;code&gt;202&lt;/code&gt;, a repeat returns &lt;code&gt;200&lt;/code&gt;, and a wrong signature returns &lt;code&gt;401&lt;/code&gt;. Keep those tests in CI.&lt;/p&gt;

&lt;p&gt;A global JSON parser added months later will turn every real delivery into a &lt;code&gt;415&lt;/code&gt; error. A test that sends a raw body and expects &lt;code&gt;202&lt;/code&gt; catches that regression before production does.&lt;/p&gt;

&lt;p&gt;The same mental model applies to Stripe, Slack, Shopify, and everyone else. Learn the raw body rule, learn the signature header, and learn the retry window. If you can handle GitHub webhooks locally, you can handle the others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pinggy.io/blog/receive_and_test_webhooks_in_localhost/" rel="noopener noreferrer"&gt;How to Receive and Test Webhooks on Localhost&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webhooks</category>
      <category>localhost</category>
      <category>tunneling</category>
      <category>node</category>
    </item>
    <item>
      <title>Proactive AI Agents in 2026: A Practical Comparison of Dots, Muse, Grok Bot, Instinct, OpenClaw, and Hermes</title>
      <dc:creator>Lightning Developer</dc:creator>
      <pubDate>Tue, 03 Nov 2026 05:18:19 +0000</pubDate>
      <link>https://dev.to/lightningdev123/proactive-ai-agents-in-2026-a-practical-comparison-of-dots-muse-grok-bot-instinct-openclaw-2m63</link>
      <guid>https://dev.to/lightningdev123/proactive-ai-agents-in-2026-a-practical-comparison-of-dots-muse-grok-bot-instinct-openclaw-2m63</guid>
      <description>&lt;p&gt;An AI assistant that only answers when you type is easy to reason about, because nothing happens until you ask. The new generation of personal agents intentionally breaks that rule. That changes what you should check before handing one your inbox.&lt;/p&gt;

&lt;p&gt;Between August 11 and October 21, 2026, &lt;a href="https://x.ai" rel="noopener noreferrer"&gt;xAI&lt;/a&gt; shipped Grok Bot, &lt;a href="https://about.meta.com" rel="noopener noreferrer"&gt;Meta&lt;/a&gt; shipped Muse, &lt;a href="https://openai.com" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt; shipped OpenAI Dots, and &lt;a href="https://www.anthropic.com" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt; previewed Claude Duet. All of them are built to keep working after you close the app and to message you first. &lt;a href="https://instinct.com" rel="noopener noreferrer"&gt;Instinct&lt;/a&gt;, an agent you reach by text, was valued at $10 billion by October. The open source side got there earlier: &lt;a href="https://openclaw.ai" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; (393,525 GitHub stars on October 31) runs a heartbeat every 30 minutes by default, and &lt;a href="https://hermes-agent.nousresearch.com" rel="noopener noreferrer"&gt;Hermes Agent&lt;/a&gt; (259,065 stars) runs cron jobs and signed webhooks on your own machine.&lt;/p&gt;

&lt;p&gt;Below, six agents are compared on triggers, hosting, credentials, and price. The running example is a weekday 8 AM brief that speaks up only when a decision is needed, plus a new GitHub issue. Facts were checked, and the OpenClaw and Hermes commands run in clean Docker containers, on October 31.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why proactive agents appeared now
&lt;/h2&gt;

&lt;p&gt;The shift became possible because model latency dropped enough for an agent to run many turns quietly. A generation ago, an AI assistant had to wait for a user prompt because the cost of running a background loop was too high. Now, a frontier model can process an inbox, summarize a calendar, and decide whether to send a message in under a minute. The missing piece was not intelligence. It was permission to start a turn on its own.&lt;/p&gt;

&lt;p&gt;Proactive agents also benefit from better tool integration. They can read Gmail, Slack, GitHub, and calendar APIs through connectors. They can run browser sessions. They can execute code. This makes them useful for real work, but it also expands the attack surface. The industry is still learning how to build safe versions of these systems.&lt;/p&gt;

&lt;p&gt;Another driver is the API-native nature of these agents. Many of them are built on top of the same model providers and use the same webhook primitives that developers already understand. That is why a developer can set up a self-hosted agent with a GitHub webhook in a few minutes. The barrier to entry is much lower than it was a year ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes an AI agent proactive
&lt;/h2&gt;

&lt;p&gt;An agent is proactive when it starts a turn without a new message from you. The trigger can be a schedule, which usually means a cron expression. It can be an event, such as a webhook, a Slack mention, or a push from GitHub. It can also be a self-wake, where the agent decides when to look again or runs a periodic pass that asks whether anything needs attention.&lt;/p&gt;

&lt;p&gt;The self-wake is the new part, and it only works if the agent is allowed to say nothing. OpenClaw's default heartbeat prompt ends with 'If nothing needs attention, reply NO_REPLY'. Hermes skips delivery when a cron job's final response contains [SILENT]. This quiet path is important. If the agent always replies, it becomes spam, and you quickly learn to ignore it. A good proactive agent should send fewer messages, not more.&lt;/p&gt;

&lt;p&gt;The other change is risk. An unattended agent reads untrusted text, like email messages and GitHub issue bodies, with your logins loaded. That changes the threat model. A prompt injection in an email can reach the agent's tools. The agent may not be sophisticated enough to understand that a requested action is malicious. It might send email, create issues, or update records based on text it was never explicitly asked to process. That is why every serious proactive agent now has some kind of approval layer, sandbox, or credential separation.&lt;/p&gt;

&lt;p&gt;There is also a distinction between agent orchestration and agent execution. A proactive agent can be a coordinator that decides what needs to be done and delegates to other agents. Or it can be a single agent that performs the task itself. OpenAI Dots leans toward coordination. Grok Bot is more of a team of agents that share one environment. Hermes and OpenClaw are closer to traditional automation frameworks with an LLM in the loop. Understanding these differences matters because they affect where credentials live and how much damage a compromised agent can do.&lt;/p&gt;

&lt;p&gt;Another important concept is delivery. A proactive agent needs a channel to reach you. Telegram, Slack, email, SMS, and WhatsApp are all common. Some agents use their own notification system. The delivery channel is not just a UX detail. It is also an attack surface. If an attacker can spoof the channel, they can inject messages into the agent's context. Some systems solve this by using signed webhooks and authenticated channels. Others rely on the provider's infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick look at the field
&lt;/h2&gt;

&lt;p&gt;Before comparing the six, it helps to see them side by side. The table below summarizes the main options. It is not exhaustive, but it captures the differences that matter for most developers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Runs on&lt;/th&gt;
&lt;th&gt;Starts work via&lt;/th&gt;
&lt;th&gt;Price and access&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI Dots&lt;/td&gt;
&lt;td&gt;OpenAI cloud computer, optional link to yours&lt;/td&gt;
&lt;td&gt;Self-wake, scheduled tasks, read-only research&lt;/td&gt;
&lt;td&gt;Pro from $100/month, Business Premium, Enterprise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meta Muse&lt;/td&gt;
&lt;td&gt;One Muse Secure VM per user&lt;/td&gt;
&lt;td&gt;Background jobs, unprompted suggestions&lt;/td&gt;
&lt;td&gt;Free tier, Power $20, Maximum $100; US only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok Bot&lt;/td&gt;
&lt;td&gt;One shared computer per account, in Cursor cloud&lt;/td&gt;
&lt;td&gt;Routines on schedules or Slack, GitHub, email and webhook events&lt;/td&gt;
&lt;td&gt;Beta, included with X and Cursor plans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instinct&lt;/td&gt;
&lt;td&gt;Instinct's own phone and computer&lt;/td&gt;
&lt;td&gt;Texts you first with reminders&lt;/td&gt;
&lt;td&gt;Early access, no public price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenClaw&lt;/td&gt;
&lt;td&gt;Your machine (Node.js Gateway)&lt;/td&gt;
&lt;td&gt;30-minute heartbeat, cron and event automations&lt;/td&gt;
&lt;td&gt;Free, MIT; you pay for the model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hermes Agent&lt;/td&gt;
&lt;td&gt;Your machine (Python gateway)&lt;/td&gt;
&lt;td&gt;Cron jobs, signed webhooks&lt;/td&gt;
&lt;td&gt;Free, MIT; you pay for the model&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There are two broad families here. Hosted agents run on the vendor's computer. That means they can work while your laptop is closed. Open source agents run on your own machine, so they only work when the gateway is running. Both families have security tradeoffs. Hosted agents keep your data on someone else's infrastructure. Self-hosted agents require you to secure your own box and handle secrets carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted versus self-hosted tradeoffs
&lt;/h2&gt;

&lt;p&gt;Hosted agents are attractive because they ask for very little technical setup. You click a button, connect an account, and the agent starts running. The provider handles uptime, scaling, patching, and model updates. The downside is that you are trusting the provider with your data. Even with strong encryption in transit, the provider can access your content if it chooses to. Some providers offer enterprise contracts with data processing agreements. Others do not.&lt;/p&gt;

&lt;p&gt;Self-hosted agents require more work. You need a machine that stays on. You need to install the gateway, manage dependencies, and keep it updated. You need to decide how to expose webhook endpoints. You also need to handle your own secrets. In exchange, you have full visibility into what the agent does. You can inspect logs. You can modify the source code. You can add your own security controls.&lt;/p&gt;

&lt;p&gt;There is also a middle path. You can run a self-hosted agent on a cloud VM and let it communicate with the world through a tunnel like &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt;. That gives you the privacy of your own server and the convenience of a public URL. It does not solve the patching problem, but it solves the network problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAI Dots: a coordinator that decides when to check back
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://openai.com" rel="noopener noreferrer"&gt;OpenAI Dots&lt;/a&gt; lives in OpenAI's cloud with its own computer and browser. It runs on GPT-6 Astra and decides when to pause and resume. Follow-ups need no fixed schedule. Its background research is read-only, and follow-up actions need the applicable permissions. You ask for the brief in plain words with a time zone and an end date, and it appears under Scheduled. Actions pass through your custom rules, such as 'Ask before taking action' for outgoing email.&lt;/p&gt;

&lt;p&gt;OpenAI's docs describe no webhook triggers, so the GitHub issue waits until the dot next checks your apps. Its strength is coordination: it hands work to ChatGPT Work and Codex and chases each job. It is a supervisor agent rather than a simple task runner. The dot model is interesting because it can decide to go do research, come back, wait for a while, then continue. This is different from a cron job that fires at a fixed time.&lt;/p&gt;

&lt;p&gt;Personal Pro excludes the EEA, UK and Switzerland, and you get one dot per account. The price is high, so this is aimed at professionals who already live inside OpenAI's ecosystem. In practice, Dots feels like a managed chief of staff. It can prepare briefings, monitor dashboards, and keep an eye on projects. It is not designed for event-driven automation in the same way as Grok Bot.&lt;/p&gt;

&lt;p&gt;One of the things to understand about Dots is that the environment is ephemeral but persistent enough for long-running tasks. It has a browser, a file system, and access to GPT-6 Astra's reasoning. The read-only research mode is a strong safety feature. It means the dot can browse the web and read your connected apps, but it cannot modify anything unless you grant a specific permission for that action. This is a different security tradeoff from giving a bot full access to a shared computer.&lt;/p&gt;

&lt;p&gt;A common question is whether Dots can react to external events. The answer in late 2026 is no, at least not through webhooks. It can poll apps on a schedule, but if an urgent issue arrives at 9:17 AM, you may not hear about it until the dot's next scheduled pass. That is fine for a morning brief, but it is not fine for incident response.&lt;/p&gt;

&lt;p&gt;Another detail is the custom rules engine. You can define rules that require approval before the dot performs certain classes of action. This is a useful control, but it depends on the user writing good rules. If you allow outgoing email without approval, the dot can send email based on its own judgment. You should start with conservative rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meta Muse: a second agent holds the keys
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://muse.ai" rel="noopener noreferrer"&gt;Meta Muse&lt;/a&gt; gives each user a dedicated cloud VM. Meta's security write-up is the most detailed of the six. The agent runs in a systemd-nspawn container, and a separate host-side agent called Sentinel decides what it may do. The key quote from the write-up is 'Muse proposes actions, but only Sentinel can grant permission to perform action.'&lt;/p&gt;

&lt;p&gt;Muse holds only surrogate tokens. Sentinel swaps in real credentials at the network boundary. Payments use single-use &lt;a href="https://stripe.com" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; Link cards, each needing your approval. This is a meaningful design improvement over agents that store your real access tokens inside the agent context. Even if the Muse container is compromised, it does not directly hold your Google or Microsoft credentials. It has to ask Sentinel, which can enforce policy, rate limits, and user consent.&lt;/p&gt;

&lt;p&gt;Meta says Muse runs cron jobs in the background and makes unprompted suggestions, but describes no schedule screen or event triggers. You ask for the brief in chat. It runs on the web, iOS, Android and &lt;a href="https://www.whatsapp.com" rel="noopener noreferrer"&gt;WhatsApp&lt;/a&gt;. It is US only, and needs a card even on the free tier. Meta also says staff access to user data is restricted by policy, not technically prevented, until a planned 'Muse Confidential VM' ships.&lt;/p&gt;

&lt;p&gt;The lack of a visible schedule screen is not a deal breaker for everyone. You can still say 'every weekday at 8 AM, check my inbox and calendar' in natural language, and Muse will set up a background job. The problem is discoverability and control. With no event triggers, you cannot make Muse react to a GitHub issue. You can only ask it to check periodically. For many users, that is enough.&lt;/p&gt;

&lt;p&gt;Muse's pricing tier starts with a free tier, then Power at $20, and Maximum at $100. The VM per user is a strong privacy feature because it prevents cross-tenant inference. But it also means Meta cannot cheaply scale to millions of users. The requirement to add a card on the free tier is likely to reduce abuse.&lt;/p&gt;

&lt;p&gt;A useful mental model for Muse is that the agent is not the principal. The user is the principal, and Sentinel acts as a policy enforcement point. This is similar to how a service account works in cloud security. The agent can propose, but it cannot act without authorization. This separation is worth emulating in your own agent projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grok Bot: a team of bots on one shared computer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq07m5sqy1qbbsp878m86.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq07m5sqy1qbbsp878m86.webp" alt="Blog Image" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://x.ai" rel="noopener noreferrer"&gt;xAI&lt;/a&gt; launched Grok Bot in beta on August 11 as 'a team of always-on agents', with the most explicit triggers of the hosted group. A routine runs on a schedule, at least five minutes apart, or on a &lt;a href="https://slack.com" rel="noopener noreferrer"&gt;Slack&lt;/a&gt; mention, &lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://linear.app" rel="noopener noreferrer"&gt;Linear&lt;/a&gt;, &lt;a href="https://sentry.io" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt; or &lt;a href="https://www.pagerduty.com" rel="noopener noreferrer"&gt;PagerDuty&lt;/a&gt; activity, email to the bot's address, or a webhook. It is the only hosted agent here that reacts to our GitHub issue instantly.&lt;/p&gt;

&lt;p&gt;The catch is the computer. xAI says 'All of your Bots use the same cloud computer, sharing its files, browser sessions, and app logins.' It runs in &lt;a href="https://cursor.com" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; cloud. A triage bot reading a poisoned issue shares your inbox bot's Gmail session. This is a serious security consideration. If one bot is compromised, all bots on the same account share the same environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Funaod90a5x11400p73oa.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Funaod90a5x11400p73oa.webp" alt="Blog Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;xAI adds an independent Auto Review model for risky actions. Connector tokens are kept on Cursor's backend, and passwords are filled by reference. These controls reduce the risk of the shared computer, but they do not eliminate it. The shared computer means that any file written by one bot is visible to another bot. If a workflow requires isolation, Grok Bot is not the right choice.&lt;/p&gt;

&lt;p&gt;Grok Bot comes with The X Subscription's Premium, Super and Ultra plans and paid Cursor plans. It is in beta. The breadth of triggers is impressive. GitHub, Slack, Linear, Sentry, PagerDuty, and generic webhooks cover most developer workflows. For teams that already use these tools, Grok Bot can act as a central automation hub. It can triage issues, respond to incidents, and send summaries to a Slack channel.&lt;/p&gt;

&lt;p&gt;The 'at least five minutes apart' limit is worth noting. You cannot run a cron job every minute. This is probably to prevent abuse and to keep costs manageable. If you need sub-minute scheduling, a self-hosted agent like Hermes or OpenClaw is better.&lt;/p&gt;

&lt;p&gt;The shared computer model also has implications for secrets. Because all bots share browser sessions, a login to a third-party service is available to every bot. The Auto Review model can intercept risky actions, but the model itself is an additional system that must be audited. If you use Grok Bot, you should carefully define which bots can access which tools. The provider's documentation does not currently allow per-bot isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instinct: an agent you text, that texts you first
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://instinct.com" rel="noopener noreferrer"&gt;Instinct&lt;/a&gt; has no app. You text or call it, and it can use a phone and computer in the same way that you do. The company, &lt;a href="https://instinct.com" rel="noopener noreferrer"&gt;Spear Street Technology, Inc.&lt;/a&gt;, was founded by Noah Shinn. A hands-on review from October 25 says it 'checks in, keeps track of ongoing tasks, and takes the initiative to remind you to cancel a subscription.' It has since added phone calls, its own email addresses, and group chats.&lt;/p&gt;

&lt;p&gt;Instinct publishes no trigger model. There are no docs on schedules, events, or approvals beyond purchases. Purchases use one-time Stripe Link cards for amounts you approve. Before connecting Gmail, you should read its terms. The terms were revised August 26, 2026. They include language like 'You also authorize us to access, copy, collect, and index data from your Connected Services' and 'You hereby appoint the Services as your agent to enter into agreements, commitments or transactions on your behalf.' Training on your data is on by default, with an opt-out.&lt;/p&gt;

&lt;p&gt;For a developer, Instinct is the least transparent option. It is also the most consumer friendly. There is no app to install, no dashboard to configure, no API. You just text it. That makes it ideal for non-technical users, but it also makes it hard to audit. If you care about knowing exactly what the agent can do, Instinct is a risk.&lt;/p&gt;

&lt;p&gt;The $10 billion valuation in October shows how much investor interest there is in proactive agents. Even without a public pricing page or a detailed security model, Instinct has captured attention. The service is in early access. Early users report that the agent is good at following up on tasks it has seen in text messages. It can set reminders, monitor ongoing conversations, and send proactive suggestions.&lt;/p&gt;

&lt;p&gt;From an architecture perspective, Instinct likely runs on its own phone and computer infrastructure. The agent has a phone number and can send SMS messages. When you send a message, the agent parses it and may start a task. Later, it can initiate a text to you. This is a different model from a cloud workspace because it is built around the phone as the primary interface.&lt;/p&gt;

&lt;p&gt;One concern is the terms of service. The phrase 'appoint the Services as your agent' is broad. It means Instinct may be able to enter into agreements on your behalf. The use of Stripe Link cards limits financial risk to amounts you approve, but other actions are less constrained. You should not connect critical business accounts until the security model matures.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenClaw: a heartbeat on your own Gateway
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://openclaw.ai" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; v2026.10.9, released October 29, runs a &lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; Gateway on your machine. Automations run inside it, so nothing fires while it is down. The self-wake is the heartbeat. It is a system-owned job that runs an agent turn every 30 minutes by default and messages your own DM. The checklist now lives in a monitor scratch instead of HEARTBEAT.md. You can set it with a command like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw cron scratch &amp;lt;jobId&amp;gt; &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="s1"&gt;'your checklist here'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;openclaw doctor --fix&lt;/code&gt; command migrates from the older HEARTBEAT.md format. This is a good example of how quickly the open source agent space is moving.&lt;/p&gt;

&lt;p&gt;The morning brief is a separate isolated job. &lt;code&gt;openclaw cron&lt;/code&gt; is an alias for &lt;code&gt;openclaw automations&lt;/code&gt;. Here is the full command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw cron create &lt;span class="s1"&gt;'0 8 * * 1-5'&lt;/span&gt; &lt;span class="s1"&gt;'Check my inbox and calendar. Tell me only what needs a decision today. If nothing does, reply NO_REPLY.'&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s1"&gt;'Morning brief'&lt;/span&gt; &lt;span class="nt"&gt;--tz&lt;/span&gt; &lt;span class="s1"&gt;'Asia/Kolkata'&lt;/span&gt; &lt;span class="nt"&gt;--session&lt;/span&gt; isolated &lt;span class="nt"&gt;--announce&lt;/span&gt; &lt;span class="nt"&gt;--channel&lt;/span&gt; telegram &lt;span class="nt"&gt;--to&lt;/span&gt; &lt;span class="s1"&gt;'123456789'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, &lt;code&gt;openclaw cron list --all&lt;/code&gt; shows both jobs. The output looks something like this, trimmed and with a test agent named dev:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name             Schedule                           Target    Delivery
Heartbeat (dev)  every 30m                          main      not requested
Morning brief    cron 0 8 * * 1-5 @ Asia/Kolka...   isolated  announce -&amp;gt; telegram:123456789 (explicit)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A cron expression parser will explain &lt;code&gt;0 8 * * 1-5&lt;/code&gt; if you do not use cron often. The schedule means 8:00 AM from Monday to Friday. The time zone is important because the shell's time zone is not necessarily the one you want. OpenClaw lets you set &lt;code&gt;--tz&lt;/code&gt; directly on the job.&lt;/p&gt;

&lt;p&gt;One default to change immediately. The job allowed every tool because &lt;code&gt;toolsAllow&lt;/code&gt; was set to &lt;code&gt;["*"]&lt;/code&gt;. You should pass &lt;code&gt;--tools&lt;/code&gt; with an allow-list. This reduces the attack surface if the agent's prompt is manipulated. The OpenClawCVEs tracker counted 543 CVEs and 211 advisories on October 31. That number sounds alarming, but it reflects a very active project. Still, self-hosting means you are responsible for patching.&lt;/p&gt;

&lt;p&gt;OpenClaw has a large ecosystem of plugins, automations, and community extensions. It can connect to many messaging platforms, CRMs, file systems, and developer tools. The default heartbeat is the fastest way to get a proactive agent running locally. You need a model API key, and you pay for the tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hermes Agent: cron jobs and signed webhooks
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqd20lihkrnsm7g3ijmf5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqd20lihkrnsm7g3ijmf5.webp" alt="Blog Image" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hermes-agent.nousresearch.com" rel="noopener noreferrer"&gt;Hermes Agent&lt;/a&gt; v0.22.5, released October 23, runs its scheduler inside the gateway. It ticks every 60 seconds. Schedules can be plain English. Failed runs always deliver, and a monitor mode with &lt;code&gt;--monitor-url&lt;/code&gt; or &lt;code&gt;--monitor-script&lt;/code&gt; skips the LLM entirely when a cheap check's output has not changed. That is a useful optimization for health checks.&lt;/p&gt;

&lt;p&gt;Here is the same morning brief in Hermes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hermes config &lt;span class="nb"&gt;set &lt;/span&gt;timezone Asia/Kolkata
hermes cron create &lt;span class="s1"&gt;'weekdays at 8am'&lt;/span&gt; &lt;span class="s1"&gt;'Check my inbox and calendar. Tell me only what needs a decision today. If nothing does, reply [SILENT].'&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s1"&gt;'morning-brief'&lt;/span&gt; &lt;span class="nt"&gt;--deliver&lt;/span&gt; telegram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output shows the job ID and the next run time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Created job: 115c4cda804e
  Next run: 2026-11-02T08:00:00+05:30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing runs until &lt;code&gt;hermes gateway run&lt;/code&gt; is up. Set the time zone first. In our test, changing the time zone afterwards did not move the stored next run. For the model side, you can run Hermes on free &lt;a href="https://openrouter.ai" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt; models. That makes it possible to experiment without paying for an API key.&lt;/p&gt;

&lt;p&gt;Hermes has the clearest scheduler and webhook model for developers. The gateway is a &lt;a href="https://www.python.org" rel="noopener noreferrer"&gt;Python&lt;/a&gt; process, which is easy to extend. The monitor mode is particularly effective. If you have a script that checks whether a service is healthy, you can run it on a schedule and only invoke the LLM when the script reports a change. This keeps token costs low and makes the system more predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Triggering Hermes from GitHub through a Pinggy tunnel
&lt;/h2&gt;

&lt;p&gt;Grok Bot gives you a webhook URL, but Hermes can only recieve webhooks on port 8644 of a machine with no public address. To receive GitHub events, you need to expose that port. This is a common problem for self-hosted agents. A tunnel solves it.&lt;/p&gt;

&lt;p&gt;The docs suggest setting &lt;code&gt;WEBHOOK_ENABLED=true&lt;/code&gt; in &lt;code&gt;~/.hermes/.env&lt;/code&gt;, but in v0.22.5 &lt;code&gt;hermes webhook subscribe&lt;/code&gt; refused with the error 'Webhook platform is not enabled' until the flag was placed in &lt;code&gt;config.yaml&lt;/code&gt;. This is exactly the kind of configuraton detail that can eat an afternoon. The fix is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hermes config &lt;span class="nb"&gt;set &lt;/span&gt;platforms.webhook.enabled &lt;span class="nb"&gt;true
&lt;/span&gt;hermes webhook subscribe github-issues &lt;span class="nt"&gt;--events&lt;/span&gt; &lt;span class="s1"&gt;'issues'&lt;/span&gt; &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s1"&gt;'New issue #{issue.number} in {repository.full_name}: {issue.title}. Triage it and tell me if it needs me today.'&lt;/span&gt; &lt;span class="nt"&gt;--deliver&lt;/span&gt; telegram
hermes gateway run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Subscribing prints a generated HMAC secret and &lt;code&gt;http://localhost:8644/webhooks/github-issues&lt;/code&gt;. In a second terminal, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-p&lt;/span&gt; 443 &lt;span class="nt"&gt;-R0&lt;/span&gt;:localhost:8644 free.pinggy.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; prints an HTTPS URL ending in &lt;code&gt;.run.pinggy-free.link&lt;/code&gt;. Under the repository's Settings, go to Webhooks, then Add webhook. Enter that URL plus &lt;code&gt;/webhooks/github-issues&lt;/code&gt;. Choose &lt;code&gt;application/json&lt;/code&gt;, paste the secret, and select only the Issues event.&lt;/p&gt;

&lt;p&gt;We tested teh path with a signed GitHub-style issues event. Our test gateway had no Telegram bot, so the &lt;code&gt;--deliver telegram&lt;/code&gt; route answered with HTTP 503 and the error &lt;code&gt;{"error": "Admission unavailable; retry this delivery"}&lt;/code&gt;. The lesson is to connect the delivery channel first.&lt;/p&gt;

&lt;p&gt;With the route switched to &lt;code&gt;--deliver log&lt;/code&gt;, a valid &lt;code&gt;X-Hub-Signature-256&lt;/code&gt; got HTTP 202 with &lt;code&gt;{"status": "accepted", ...}&lt;/code&gt; and the gateway logged the rendered prompt. A wrong signature got HTTP 401 with &lt;code&gt;{"error": "Invalid signature"}&lt;/code&gt;. This shows that the HMAC verification works.&lt;/p&gt;

&lt;p&gt;A free tunnel expires after 60 minutes with a new URL, so a saved hook needs a Pro token with a persistent subdomain. The command looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-p&lt;/span&gt; 443 &lt;span class="nt"&gt;-R0&lt;/span&gt;:localhost:8644 &amp;lt;token&amp;gt;@pro.pinggy.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More details are in the Pinggy guide to receiving webhooks on localhost. Steps one through five were tested on October 31, 2026 with Hermes Agent v0.22.5.&lt;/p&gt;

&lt;p&gt;This example matters because it shows how a self-hosted agent can participate in event-driven workflows. You are not limited to cron. With a tunnel, you can expose any local port and receive signed payloads from GitHub. The same pattern works for Stripe, Slack, or any service that sends webhooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security considerations for self-hosted agents
&lt;/h2&gt;

&lt;p&gt;Self-hosting gives you control, but it also gives you responsibility. Your gateway is a target. The default heartbeat in OpenClaw runs every 30 minutes. That means the agent is constantly reading your data, and an attacker who can inject content into a data source may be able to influence the agent. Here are a few practices to follow.&lt;/p&gt;

&lt;p&gt;First, use allow-lists for tools. Do not let the agent use every tool by default. In OpenClaw, pass &lt;code&gt;--tools&lt;/code&gt; with a specific set. In Hermes, configure the gateway to restrict available tools. This limits the damage a prompt injection can do.&lt;/p&gt;

&lt;p&gt;Second, separate job sessions. Use isolated sessions for tasks that read untrusted input. If an email causes the agent to take an action, the action should not affect the rest of your system. OpenClaw has an &lt;code&gt;--session isolated&lt;/code&gt; flag. Hermes has job-level configuration for similar isolation.&lt;/p&gt;

&lt;p&gt;Third, use signed webhooks and verify signatures. GitHub sends an HMAC signature. Hermes verifies it. If you build your own trigger, always verify the payload signature before processing. Also use HTTPS for the tunnel endpoint. Pinggy provides HTTPS by default.&lt;/p&gt;

&lt;p&gt;Fourth, patch often. OpenClaw has a high number of CVEs. This is not a reason to avoid it, but it is a reason to update the gateway frequently. You can automate the update process with a cron job or a CI pipeline.&lt;/p&gt;

&lt;p&gt;Fifth, audit your agent's messages. Start with one job and read every message. Count how many times the agent acted without being asked. If it takes actions you did not expect, tighten permissions. If it sends too many irrelevant messages, refine the prompt with a quiet path.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose a proactive agent
&lt;/h2&gt;

&lt;p&gt;If your work lives in ChatGPT and Codex, pick a dot. It coordinates well and has read-only research. For cheap US errands, Muse's free tier and Sentinel design are the safest hosted bet on paper. Grok Bot suits teams that want event-driven routines and accept one shared computer. Instinct suits people who only want to text, if its terms are acceptable. OpenClaw has the biggest ecosystem but needs frequent patching. Hermes has the clearest scheduler and webhook model for developers.&lt;/p&gt;

&lt;p&gt;Also worth watching are Anthropic's Claude Duet, a US waitlist preview that sends a morning brief and checks your apps every few hours, and &lt;a href="https://www.google.com" rel="noopener noreferrer"&gt;Google&lt;/a&gt;'s Gemini Spark. These two are too new for a direct comparison, but they confirm that proactive agents are becoming a standard feature.&lt;/p&gt;

&lt;p&gt;Think about your delivery channel. An agent that texts you first is useful, but text is asynchronous. A Slack or Telegram message can be more searchable. Email is fine for long-form briefs. Choose the channel that you actually check. If you do not check Telegram, an agent that only publishes to Telegram will be ignored.&lt;/p&gt;

&lt;p&gt;Think about the data the agent can read. Hosted agents read your data on their servers. Self-hosted agents read your data on your machine. Which is more comfortable depends on your threat model. If you handle sensitive client data, self-hosting gives you more control over compliance. If you want zero maintenance, a hosted agent is simpler.&lt;/p&gt;

&lt;p&gt;Think about cost. Hosted agents have monthly subscription prices. Self-hosted agents have model API costs. At high volumes, API costs can exceed a $100/month subscription. At low volumes, self-hosting is much cheaper. You should estimate the number of agent turns per day. A heartbeat every 30 minutes is 48 turns per day. Each turn may include several LLM calls. A simple email check might cost a few cents. A complex research task can cost more.&lt;/p&gt;

&lt;p&gt;Think about trigger flexibility. If you need to react to webhooks, Hermes and Grok Bot are the best choices. If you need fine-grained scheduling with seconds or minutes, Hermes and OpenClaw are better. If you want a coordinator that can decide when to check, Dots is unique. If you want no configuration at all, Instinct is the easiest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;Can I use these agents without giving them access to my email? Yes, to various degrees. OpenClaw and Hermes only access what you configure. Dots requires connecting apps for research. Muse uses Sentinel to gate credentials. Grok Bot shares browser sessions, so be careful. Instinct can access connected services, and its terms are broad.&lt;/p&gt;

&lt;p&gt;Which agent is best for GitHub automation? Grok Bot has native GitHub triggers and is the easiest hosted option. Hermes with a Pinggy tunnel is the best self-hosted option because you can run it on your own machine and verify signatures. OpenClaw also has GitHub integrations, but its default heartbeat is not event-driven.&lt;/p&gt;

&lt;p&gt;Do I need a separate computer to run a self-hosted agent? Not necessarily. A laptop or a small cloud VM is enough for a single user. The gateway is lightweight. The heavy work is done by the LLM API. You do need the machine to be online when you want the agent to run.&lt;/p&gt;

&lt;p&gt;Are these agents safe to connect to my bank? Probably not yet. The only payment-related safety feature among these is the single-use Stripe Link card used by Muse and Instinct. That reduces financial risk. However, no agent is safe if its prompt can be injected with malicious instructions. Keep sensitive financial accounts out of any agent until the security model matures.&lt;/p&gt;

&lt;p&gt;Can I run OpenClaw and Hermes at the same time? Yes, but they cannot share the same delivery channel without careful configuration. Both can send to Telegram, and each has its own gateway. Running both lets you compare them on the same workflows. You should use separate job names and separate sessions.&lt;/p&gt;

&lt;p&gt;How do I make the agent silent when nothing needs attention? Use the explicit quiet path. For OpenClaw, prompt it to reply NO_REPLY. For Hermes, include [SILENT] in the final response. Some hosted agents also have an option to only message when an action is needed, but you should verify that behavior in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Whichever you pick, start with one scheduled job that has a quiet path. The quiet path can be NO_REPLY, [SILENT], or 'message me only if a decision is needed.' Then add one event trigger, with approvals on for anything that sends, pays, or deletes. After a week, count how many of its messages you were glad to get. That number is the real measure of a proactive agent.&lt;/p&gt;

&lt;p&gt;Proactive AI agents are a new category. The differences in security models are more important than the differences in features. A shared computer is risky. A policy enforcement agent is promising. A broad terms of service is a warning. A persistent local gateway is powerful. Choose the model that fits your data, your risk tolerance, and your workflow.&lt;/p&gt;

&lt;p&gt;If you are a developer, the self-hosted options are the easiest to audit. Hermes gives you signed webhooks and a transparent scheduler. OpenClaw gives you a large ecosystem and a heartbeat. Both are free and open source. They require more work, but they give you ownership. In a space that changes this fast, ownership is a real advantage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pinggy.io/blog/proactive_ai_agents_compared/" rel="noopener noreferrer"&gt;Proactive AI Agents Compared: Dots vs Muse vs Grok Bot vs Instinct vs Hermes vs OpenClaw (2026)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>webhooks</category>
    </item>
    <item>
      <title>Anthropic's Junk Rating Forecast Is a Signal for Everyone Building on AI Infrastructure</title>
      <dc:creator>Lightning Developer</dc:creator>
      <pubDate>Sun, 01 Nov 2026 11:40:04 +0000</pubDate>
      <link>https://dev.to/lightningdev123/anthropics-junk-rating-forecast-is-a-signal-for-everyone-building-on-ai-infrastructure-44k6</link>
      <guid>https://dev.to/lightningdev123/anthropics-junk-rating-forecast-is-a-signal-for-everyone-building-on-ai-infrastructure-44k6</guid>
      <description>&lt;p&gt;On October 26, 2026, &lt;a href="https://www.anthropic.com" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt; filed its Form S-1, and for four days the public debate was dominated by valuation, revenue growth, and an $80.7 billion headline loss. Then the &lt;a href="https://www.ft.com" rel="noopener noreferrer"&gt;Financial Times&lt;/a&gt; reported something with more direct consequences for how the company will pay for its future: &lt;a href="https://www.blackstone.com" rel="noopener noreferrer"&gt;Blackstone&lt;/a&gt;, the anchor investor in a $63 billion financing deal for Anthropic's chips, has told investors in that deal that it expects the AI lab to receive a high junk rating after its IPO. This article walks through what that forecast means, why it matters, and what developers and infrastructure buyers should watch in the coming quarters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The forecast that reversed
&lt;/h2&gt;

&lt;p&gt;Blackstone had previously told investors it expected Anthropic to secure an investment-grade rating before 2027. According to the FT, that view has changed. The firm now expects a rating in the double-B range, which is the top tier of speculative grade. &lt;a href="https://www.moodys.com" rel="noopener noreferrer"&gt;Moody's&lt;/a&gt;, &lt;a href="https://www.spglobal.com/ratings" rel="noopener noreferrer"&gt;S&amp;amp;P&lt;/a&gt;, and &lt;a href="https://www.fitchratings.com" rel="noopener noreferrer"&gt;Fitch&lt;/a&gt; have not made an official determination. The forecast is a prediction, not a decision. But predictions from the institution underwriting the debt tend to travel, and this one tells you something about how the people closest to the deal are pricing Anthropic's risk.&lt;/p&gt;

&lt;p&gt;Why did Blackstone change its view? Bankers working with both Anthropic and &lt;a href="https://openai.com" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt; have spent months trying to persuade the major agencies to hand the two AI developers investment-grade ratings once they are public. Blackstone itself had been telling investment partners that Anthropic would get there before the end of the year. The revision came after preliminary guidance from the agencies, pointing toward BB or higher within speculative grade.&lt;/p&gt;

&lt;p&gt;That shift did not happen in isolation. Anthropic has been signing compute deals at a pace that makes the credit math more difficult. Its prospectus describes a plan to spend more than $800 billion on computing infrastructure through 2045. A company that commits to hundreds of billions in future payments is not going to be priced like a mature software business, regardless of how fast revenue is rising. The agencies are saying that out loud, at least in preliminary conversations.&lt;/p&gt;

&lt;p&gt;The sequence matters more than the headline. Bankers have spent months trying to convince the agencies that Anthropic's revenue trajectory, its enterprise retention, and its cash pile should outweigh the still-negative accounting picture. Blackstone's revised guidance shows that those arguments are not yet landing at the rating-committee level. The agencies are not questioning whether the AI buildout is real. They are questioning whether a business with this much committed future spending has yet earned the right to be treated as a low-risk borrower.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a double-B rating actually means
&lt;/h2&gt;

&lt;p&gt;Credit ratings split the corporate world in two. Ratings from AAA down to BBB minus are investment grade. Ratings from BB plus down to D are speculative grade, often called high yield or junk. The boundary is not cosmetic. It decides which institutions are allowed to buy the bonds, how much collateral is required, and how much interest the issuer has to pay.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Band&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Typical issuer profile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AAA to A&lt;/td&gt;
&lt;td&gt;Investment grade&lt;/td&gt;
&lt;td&gt;Large, profitable, low leverage, long track record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BBB plus to BBB minus&lt;/td&gt;
&lt;td&gt;Investment grade&lt;/td&gt;
&lt;td&gt;Stable cash flow, moderate leverage, some cyclicality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BB plus to BB minus&lt;/td&gt;
&lt;td&gt;Speculative grade, top tier&lt;/td&gt;
&lt;td&gt;Growing, leveraged, limited through-cycle history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B plus to B minus&lt;/td&gt;
&lt;td&gt;Speculative grade&lt;/td&gt;
&lt;td&gt;High leverage, thin margins, elevated default risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CCC and below&lt;/td&gt;
&lt;td&gt;Speculative grade&lt;/td&gt;
&lt;td&gt;Distressed or near default&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An issuer with a double-B rating can borrow, but it borrows differently. The pool of buyers narrows to high-yield funds, hedge funds, private credit vehicles, and a sliver of crossover investors. Many insurance companies and pension funds operate under mandates that restrict or forbid meaningful exposure to speculative-grade debt. That is exactly the constraint the FT flagged: a missing investment-grade rating narrows Anthropic's lender base and raises its cost of capital.&lt;/p&gt;

&lt;p&gt;Second-order effects matter as much. Ratings are public signals. A BB rating on Anthropic debt becomes a benchmark that counterparties cite in lease negotiations, in supplier contracts, and in the internal risk models of every company that has extended the lab credit backstops or capacity commitments. If you are running an engineering team that relies on Anthropic APIs, you might not look at the rating directly. The cloud providers and compute brokers that negotiate with Anthropic will. Their financing costs feed into what they charge for capacity, and those costs ultimately shape the price and availability of the models you use.&lt;/p&gt;

&lt;p&gt;There is also a status effect. In the bond market, the difference between BBB minus and BB plus is the difference between a borrower that is eligible for conservative portfolios and one that is not. A company can be operationally strong and still fail that test. The rating agencies are slow to award investment grade to a company with no through-cycle record, even when its quarterly growth numbers look spectacular.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inside the $63 billion deal that made the rating a public question
&lt;/h2&gt;

&lt;p&gt;The rating question became urgent because of how the chip financing was built. On October 26, 2026, &lt;a href="https://www.bankofamerica.com" rel="noopener noreferrer"&gt;Bank of America&lt;/a&gt;, Blackstone, &lt;a href="https://www.citigroup.com" rel="noopener noreferrer"&gt;Citi&lt;/a&gt;, and &lt;a href="https://www.morganstanley.com" rel="noopener noreferrer"&gt;Morgan Stanley&lt;/a&gt; announced a $63 billion financing structure to buy &lt;a href="https://www.broadcom.com" rel="noopener noreferrer"&gt;Broadcom&lt;/a&gt; AI accelerators and networking equipment for lease to Anthropic, executed through a newly formed special purpose vehicle. Deliveries are expected to start in 2027 under a previously announced compute capacity agreement involving Broadcom. The FT later described the transaction as Project North Star, covering Anthropic's lease of 1.5 gigawatts of &lt;a href="https://www.google.com" rel="noopener noreferrer"&gt;Google&lt;/a&gt;-designed chip capacity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7a5f56fbzyw2hvwlgwtm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7a5f56fbzyw2hvwlgwtm.jpg" alt="Blog Image" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The stack is layered, and each layer carries a different rating.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Backing&lt;/th&gt;
&lt;th&gt;Rating signal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Senior (first lien) debt&lt;/td&gt;
&lt;td&gt;About $43.5 billion, roughly 69% of the total&lt;/td&gt;
&lt;td&gt;Broadcom residual value support, absorbing declines in asset value&lt;/td&gt;
&lt;td&gt;Supported by Broadcom's investment-grade profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Junior (second lien) debt&lt;/td&gt;
&lt;td&gt;About $18 billion&lt;/td&gt;
&lt;td&gt;Anthropic lease payments&lt;/td&gt;
&lt;td&gt;BB plus with positive outlook from &lt;a href="https://www.kbra.com" rel="noopener noreferrer"&gt;KBRA&lt;/a&gt;; BB from &lt;a href="https://www.dbrsmorningstar.com" rel="noopener noreferrer"&gt;Morningstar DBRS&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blackstone commitment&lt;/td&gt;
&lt;td&gt;$9 billion of the junior tranche&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;Targeted yield above 10%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Broadcom's role is the part most readers skip. Its residual value guarantee means the senior lenders are not simply betting on Anthropic. They are betting on a chipmaker with an investment-grade rating standing behind the collateral. That structure is why a company with an operating loss could raise $63 billion at all. The senior tranche looks closer to a Broadcom credit than an Anthropic credit. The junior tranche does not, and that is where Blackstone put its money, at a yield that reflects the difference.&lt;/p&gt;

&lt;p&gt;KBRA's BB plus with a positive outlook on the junior debt is a small but real improvement over the stable outlook it assigned to a similar arrangement, Project Big Sky, four months earlier. Read that alongside Blackstone's revised corporate-level forecast and the picture is consistent: the agencies see improvement, but not enough to cross into investment grade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a rating is a financing input, not a report card
&lt;/h2&gt;

&lt;p&gt;It is tempting to treat the rating as commentary on Anthropic's business. It is better understood as an input to a fairly mechanical calculation about how the company funds the next several years. The rating is not a judgment about product quality, and it should not be read as a statement that Anthropic will recieve no more funding.&lt;/p&gt;

&lt;p&gt;The arithmetic runs like this. Anthropic reported $16.29 billion of revenue in the first half of 2026, up from $1.16 billion a year earlier, alongside an operating loss of $693 million and $8.95 billion of compute and infrastructure expense. The $80.7 billion net loss is mostly an accounting event: about $72.4 billion of it came from non-cash fair value changes on convertible notes, derivative liabilities, and similar instruments. As the company's private valuation climbed, the accounting value of those liabilities rose, and accounting rules pushed that increase through the income statement.&lt;/p&gt;

&lt;p&gt;So the operating business is improving while the balance sheet is committing to enormous future payments. Anthropic holds more than $112 billion in cash and cash equivalents, according to the FT, and investors expect it to raise roughly another $100 billion in the IPO. Its S-1 discloses named compute contracts with &lt;a href="https://www.amazon.com" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt;, &lt;a href="https://www.microsoft.com" rel="noopener noreferrer"&gt;Microsoft&lt;/a&gt;, Broadcom, and other providers that add up to roughly $637 billion, and the $800 billion infrastructure plan extends to 2045.&lt;/p&gt;

&lt;p&gt;Capital of that size cannot come from equity alone. Equity raises dilute, and public shareholders are less forgiving than private ones about dilution at scale. Debt is the alternative, and the price of that debt is set by the rating. An investment-grade issuer might borrow at a spread that a double-B issuer pays a multiple of. On a balance sheet carrying tens of billions in lease obligations, a couple of hundred basis points of spread is not a rounding error; it is the difference between funding the next tranche comfortably and funding it reluctantly.&lt;/p&gt;

&lt;p&gt;It also changes the way counterparties view long-term contracts. A compute agreement with an investment-grade tenant gets more favorable payment terms, longer credit periods, and more room to renegotiate capacity. A speculative-grade tenant faces stricter milestones, more escalation clauses, and more frequent true-ups. None of those terms show up in an API changelog, but they determine how aggressively Anthropic can sign new capacity deals and therefore how much inference and training throughput it can offer.&lt;/p&gt;

&lt;p&gt;This is not a report card on the product. It is a pricing signal for the cost of capital. A developer reading the S-1 might assume that a $112 billion cash balance removes all risk. In practice, the cash balance is not enough to cover the forward commitments if the financing markets tighten. The rating matters because it governs how cheaply the remaining gap can be filled.&lt;/p&gt;

&lt;h2&gt;
  
  
  The accounting details behind the $80.7 billion loss
&lt;/h2&gt;

&lt;p&gt;The $80.7 billion net loss looks terrifying at first glance. Most of it is not an operating loss. It is a non-cash mark-to-market adjustment driven by the rising valuation of the company's private convertible notes and derivatives. When a unicorn raises at a higher valuation, the conversion features on earlier notes become more valuable, and accounting standards require the issuer to record that increase as a fair value loss.&lt;/p&gt;

&lt;p&gt;This is one of those cases where generally accepted accounting principles create the opposite of what intuition suggests. As Anthropic became more valuable, its income statement showed a larger loss. The operating business, meanwhile, improved: second-quarter operating income was $974 million, and the first half generated positive operating cash flow of roughly $3.9 billion.&lt;/p&gt;

&lt;p&gt;For engineers who work with financial data, the lesson is to separate recurring operating expenses from non-cash valuation charges. The $8.95 billion of compute and infrastructure expense is real spending. The $72.4 billion fair value adjustment is not a cash outflow. That distinction is central to the credit story because rating agencies focus on the company's ability to generate cash and service debt, not on accounting noise caused by the company's own valuation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bull case the agencies are weighing
&lt;/h2&gt;

&lt;p&gt;It would be wrong to read the junk forecast as a verdict that Anthropic's business is weak. The case for investment grade rests on numbers that are genuinely unusual.&lt;/p&gt;

&lt;p&gt;Revenue grew roughly 1,300% year over year in the first half. Anthropic reported $974 million of operating income in the second quarter of 2026, its first profitable operating quarter in the six periods disclosed in the S-1. It generated positive operating cash flow of roughly $3.9 billion in the first half. Customers spending $100,000 or more on a trailing twelve-month basis grew from over 950 to over 11,900 in a year, and net revenue retention was approximately 900% as of September 30, 2026. A business with that retention profile has a plausible path to covering fixed commitments.&lt;/p&gt;

&lt;p&gt;The 900% net revenue retention figure deserves special attention. It means existing customers are spending roughly nine times as much on Anthropic as they did a year earlier. Very few software businesses ever see that metric. For a credit analyst, it reduces the risk that the revenue base will erode quickly if new customer acquisition slows. It also supports the idea that Anthropic could become a large, profitable company within the next few years.&lt;/p&gt;

&lt;p&gt;That is the argument bankers have been making to the agencies, and it is not frivolous. But it runs into a structural objection that rating committees tend to take seriously: there is no through-cycle history. Investment grade has historically meant a demonstrated ability to service debt during a downturn. Anthropic was founded in 2021. It has never operated through a demand collapse, a pricing war that compressed margins, or a technology shift that made its assets less valuable.&lt;/p&gt;

&lt;p&gt;Moody's made a related point about the broader financing pattern, describing Broadcom as one of the strongest large technology companies while warning that risk would rise if multiple transactions were structured in a short period to meet the sizable funding needs of frontier AI labs. The concern is not any single deal. It is the aggregate. When every major AI lab is trying to lock down datacenter capacity at the same time, the total volume of machine-readable obligations becomes a systemic data point that rating agencies cannot ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  How rating agencies think about AI compute contracts
&lt;/h2&gt;

&lt;p&gt;Understanding the rating means understanding how agencies look at compute contracts. These agreements are not like ordinary SaaS contracts. They involve enormous prepayment structures, residual value guarantees, special purpose vehicles, and delivery milestones that can shift by quarters.&lt;/p&gt;

&lt;p&gt;Agencies tend to focus on four variables.&lt;/p&gt;

&lt;p&gt;The first is debt service coverage, which compares expected cash flow to required principal and interest payments. Anthropic's revenue growth makes this ratio look better today than it did a year ago, but the coverage ratio worsens when analysts add the full amount of committed lease payments into the fixed charge calculation.&lt;/p&gt;

&lt;p&gt;The second is asset coverage. If the chips underlying the lease lose value, how much collateral is left after a default? Broadcom's residual value support is designed to answer that question for the senior lenders. The junior lenders are more exposed to Anthropic's own credit, which is why the junior debt receives a projected BB rating while the senior debt is effectively priced off Broadcom.&lt;/p&gt;

&lt;p&gt;The third is counterparty quality. A compute contract with Google Cloud is not the same as a compute contract with a startup that offers discounted GPUs. The rating agencies can give credit for strong counterparties, but they also worry about concentration risk. Anthropic's dependence on a small number of hardware and cloud providers is a credit negative even when the providers themselves are investment grade.&lt;/p&gt;

&lt;p&gt;The fourth is contract enforceability. Are the payment obligations unconditional? Can Anthropic terminate for convenience? Is capacity guaranteed at a specific date? S-1 filings summarize these agreements, but the actual definitions occupy hundreds of pages. Rating agencies typically assume the worst case if the contracts allow for flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structural objections that keep it out of investment grade
&lt;/h2&gt;

&lt;p&gt;The lack of a through-cycle record is only one objection. There are at least four more that rating committees weigh.&lt;/p&gt;

&lt;p&gt;First, the assets being financed are technology assets with a useful life that is shorter than the debt maturity being proposed. AI accelerators depreciate quickly, and their resale value depends on generations of hardware that have not been released yet. Broadcom's residual value support helps the senior tranche, but it does not eliminate technological obsolescence. If a new interconnect standard or a software optimization reduces demand for current chips, the collateral value may decline faster than the amortization schedule.&lt;/p&gt;

&lt;p&gt;Second, the contracts that back the lease payments are concentrated among a small number of counterparties. Anthropic's compute agreements are with hyperscale cloud providers and specialized GPU clouds. That concentration is operationally important because it gives Anthropic access to capacity, but it is a credit negative because a problem at one counterparty can create a cascading revenue and supply problem.&lt;/p&gt;

&lt;p&gt;Third, the structure uses special purpose vehicles to keep leverage off the operating company's consolidated balance sheet to some degree. SPVs are common in project finance, and they are not inherently risky. But they can make the true aggregate leverage harder to observe. Credit investors have been flagging data center and chip financing structured through SPVs for months, so that leverage does not appear cleanly on any single balance sheet. The rating agencies have to estimate how much of a company's future obligations are embedded in these vehicles and how much recourse the parent actually has.&lt;/p&gt;

&lt;p&gt;Fourth, there is the efficiency question. Many frontier AI companies have publicly discussed inference-time techniques that reduce the number of chips needed for a given workload. If efficiency gains reduce demand for raw compute faster than the contracts assume, the utilization assumptions behind the lease payments could miss. This is not a problem unique to Anthropic, but it is another reason why an agency might hesitate to assign investment grade to a credit that is effectively a bet on a specific hardware roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is left holding the structure
&lt;/h2&gt;

&lt;p&gt;There is a distributional question buried in the rating. When a deal is investment grade, risk lands on insurers, pension funds, and bond funds operating under conservative mandates. When it is speculative grade, risk lands on high-yield specialists and private credit funds, which demand higher yields and, in a stress scenario, are quicker to sell.&lt;/p&gt;

&lt;p&gt;Blackstone's $9 billion commitment in the junior tranche, seeking a yield above 10%, is a clear illustration of how the market prices this. The yield is the compensation for taking Anthropic lease risk without Broadcom's guarantee underneath. If Anthropic's corporate rating comes in at double-B, that junior paper looks appropriately priced. If the corporate rating surprised to the upside, that yield would have been a bargain.&lt;/p&gt;

&lt;p&gt;Broadcom carries the other side of the trade. Its residual value support transfers part of the collateral risk off the lenders and onto a chipmaker with a strong rating. The FT's reporting on Project North Star notes that Broadcom's A-range rating was an important factor in getting the financing done at all. That is a reminder that AI infrastructure debt is rarely a pure bet on one company. It is a web of guarantees in which the strongest balance sheet in the room sets the terms for everyone else.&lt;/p&gt;

&lt;p&gt;Similar reversals have occured across other AI financings in the past year, and the pattern is nearly always the same: an optimistic private forecast, a preliminary agency discussion, and then a correction toward the middle of the credit spectrum. The correction does not mean the deal fails. It means the people providing the capital price it more honestly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to watch between now and pricing
&lt;/h2&gt;

&lt;p&gt;The rating agencies have not spoken. What exists today is preliminary guidance relayed by an anchor investor, and preliminary guidance can move in either direction once audited financials, a price range, and a share count are on the table. A completed IPO that raises close to the reported $100 billion would materially change the liquidity picture the agencies are modeling, and a large equity cushion is exactly the kind of thing that pushes a borderline credit upward.&lt;/p&gt;

&lt;p&gt;Three data points will settle the question faster than any commentary.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The actual rating and outlook from each agency that covers the new debt, including whether any of them assigns a higher rating to the secured tranches than to Anthropic's unsecured obligations.&lt;/li&gt;
&lt;li&gt;The payment schedule behind the compute commitments. A $637 billion contract total says little without knowing how much is due in each year, how much is cancellable, and whether obligations are conditioned on delivery milestones.&lt;/li&gt;
&lt;li&gt;The spread at which Anthropic's first unsecured or lightly secured borrowing prices after listing. That number is the market's own rating, and it will be visible immediately.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The third point is teh one most quantitative developers can start tracking today. Spreads are quoted in basis points, and they move before rating agencies publish anything. If you see a new issue from Anthropic price several hundred basis points above Treasuries, the market has already made its own judgment.&lt;/p&gt;

&lt;p&gt;There is a fourth signal that is less obvious: the behavior of the secured versus unsecured spread. If the market prices the secured tranche at a very tight spread and the unsecured corporate debt at a wide spread, it is telling you that the market trusts the collateral and the counterparty guarantees more than it trusts Anthropic's standalone credit. That kind of split can persist for years and is common for project finance platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical checklist for engineering leaders
&lt;/h2&gt;

&lt;p&gt;For engineering leaders who consume Anthropic models or build tooling around them, the credit story has practical implications. This checklist is a useful way to prepare.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Negotiate reserve capacity in writing. If your workloads depend on a specific model family, ask for reserved throughput and committed service levels. A financially constrained vendor may cut unsupported traffic first.&lt;/li&gt;
&lt;li&gt;Build a model abstraction layer. The ability to route between Anthropic, OpenAI, or open-weights models is no longer a nice-to-have. It is a hedge against vendor pricing changes and capacity constraints.&lt;/li&gt;
&lt;li&gt;Monitor unit economics, not just API price. Track cost per successful request, including retries, latency penalties, and cache hit rates. A price cut can be offset by worse reliability or lower inference efficiency.&lt;/li&gt;
&lt;li&gt;Watch the vendor's balance sheet disclosures. After the IPO, Anthropic will report quarterly. Look for changes in committed capex, lease obligations, and cash burn. Those numbers are leading indicators for API pricing.&lt;/li&gt;
&lt;li&gt;Understand the SPV exposure if you use dedicated GPU instances. If you rent bare metal or dedicated capacity from a provider that financed its fleet with project debt, ask whether the financing includes residual value guarantees from a third party.&lt;/li&gt;
&lt;li&gt;Prepare for stronger rate limits. As Anthropic's cost of capital rises, it will prioritize paid, predictable workloads over speculative traffic. Build retry and fallback logic that respects those limits without causing user-facing failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This checklist is not about predicting a default. It is about reducing the blast radius if the financing environment changes faster than the product roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple model for the spread drag
&lt;/h2&gt;

&lt;p&gt;For developers who want to quantify the impact of a rating change on capital costs, here is a simple model of the annual spread drag between investment-grade and speculative-grade borrowing on a hypothetical $10 billion note. It is intentionally simplified, but it shows why rating categories matter for anyone who models AI capex.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;face_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10_000_000_000&lt;/span&gt;
&lt;span class="n"&gt;years&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;annual_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spread_bps&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;face_value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spread_bps&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ig_spread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;
&lt;span class="n"&gt;hy_spread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;450&lt;/span&gt;

&lt;span class="n"&gt;ig_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;annual_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ig_spread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;hy_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;annual_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hy_spread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;drag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hy_cost&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ig_cost&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Investment grade spread: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ig_cost&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; per year&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;High yield spread: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hy_cost&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; per year&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Annual spread drag: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;drag&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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 output is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Investment grade spread: $150,000,000 per year
High yield spread: $450,000,000 per year
Annual spread drag: $300,000,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single rating notch can create hundreds of millions of dollars in annual interest expense for a company of Anthropic's size. Those costs eventually flow into either the income statement or the prices charged to customers. When you see a company raise prices or reduce free tier limits, this kind of capital cost is often part of the reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions from engineering teams
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Will a junk rating make Anthropic less safe to use?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. The rating is about the company's capital structure, not the security of its API. Anthropic has a large cash balance and a fast growing revenue base. A speculative-grade rating increases the cost of future borrowing, but it does not mean the service will disappear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this affect the Anthropic API pricing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Indirectly. A higher cost of capital puts pressure on the company to improve utilization and pricing discipline. You may see more aggressive tiering, higher prices for low-value traffic, or bigger discounts for committed-use contracts. The effect will show up gradually, not as a single overnight price change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should I do if my company depends heavily on one model provider?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Build redundancy. Use an internal gateway that supports multiple providers. Keep evaluation datasets and prompts in a format that is not tied to one vendor. The goal is not to avoid Anthropic, but to make switching possible without rewriting the entire application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can the rating improve quickly?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. If the IPO raises an unusually large equity cushion and the company continues to grow at triple-digit rates, the agencies could revise the outlook to positive and upgrade within 12 to 24 months. The Blackstone forecast is a snapshot, not a life sentence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I track this going forward?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Follow the rating agency reports, the company's quarterly earnings, and the new issue calendar. The spread on the first corporate bond issue is the single most transparent market signal available.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signal inside the forecast
&lt;/h2&gt;

&lt;p&gt;A junk rating is not a judgment that Anthropic is failing. The company grew revenue by more than a factor of fourteen year over year, turned an operating profit in the second quarter, and sits on a cash pile larger than the entire revenue base of most public software companies. Plenty of excellent businesses have spent years in high yield while they built durable cash flow.&lt;/p&gt;

&lt;p&gt;What the Blackstone reversal signals is narrower and more useful. The credit market is separating the question of whether Anthropic will be a huge business from the question of whether its obligations should be held by conservative institutions. The answer to the first is increasingly yes. The answer to the second, at least for now, is: not yet, and not at investment-grade prices.&lt;/p&gt;

&lt;p&gt;For anyone watching the AI buildout, that distinction is the real story coming out of the IPO. Anthropic's valuation will be set by people buying a growth story. Its borrowing costs will be set by people asking who pays if the growth arrives late. The gap between those two groups, and how it narrows over the next few quarters, will shape how much computing capacity the company can actually commit to. The FT's report is the first clear reading of how wide that gap currently is. For engineers and infrastructure buyers, the practical takeaway is equally clear: monitor the ratings, track the capital costs, and design your AI stack to survive a world where one large vendor has to borrow at junk prices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://productwatch.io/stories/anthropic-expected-to-get-a-high-junk-rating-after-its-ipo-here-is-what-that-actually-changes" rel="noopener noreferrer"&gt;Anthropic Expected to Get a High Junk Rating After Its IPO. Here Is What That Actually Changes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>finance</category>
      <category>infrastructure</category>
      <category>startup</category>
    </item>
    <item>
      <title>Turn OpenAI Dots Into Your Always-On Chief of Staff: A Developer's Guide</title>
      <dc:creator>Lightning Developer</dc:creator>
      <pubDate>Fri, 30 Oct 2026 12:25:29 +0000</pubDate>
      <link>https://dev.to/lightningdev123/turn-openai-dots-into-your-always-on-chief-of-staff-a-developers-guide-4fl6</link>
      <guid>https://dev.to/lightningdev123/turn-openai-dots-into-your-always-on-chief-of-staff-a-developers-guide-4fl6</guid>
      <description>&lt;p&gt;Most AI assistants behave like vending machines. You type a request, get an answer, and nothing else happens until you come back with another prompt. Follow-ups, reminders, and the question 'did that ever get done?' stay your job.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://openai.com" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt; released dots at DevDay on September 29, 2026. A dot is an always-on agent inside &lt;a href="https://chatgpt.com" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt;. It runs on GPT-6 Astra, gets its own cloud computer and browser, and keeps working between conversations. It can inspect your connected apps, delegate long-running work, and message you when something needs a decision. At first glance that overlaps with other agentic products, but its real value is as a chief of staff: something that reads &lt;a href="https://gmail.com" rel="noopener noreferrer"&gt;Gmail&lt;/a&gt;, &lt;a href="https://calendar.google.com" rel="noopener noreferrer"&gt;Google Calendar&lt;/a&gt;, and project threads, notices what slipped, and chases the tasks you hand it until they are actually finished.&lt;/p&gt;

&lt;p&gt;This guide builds that setup with one running example: a solo developer with a personal dashboard app, a hobby game, a newsletter due on Tuesday, and a week on the road. Plan names, limits, and menu labels come from OpenAI's docs and were checked on October 29, 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;A dot is a coordinator, not an errand runner. It lives inside ChatGPT next to your memory, plugins, ChatGPT Work tasks, and Codex threads, and it hands real work off to those specialized tools. It works in two directions. Proactively, it reads your connected apps in read-only mode and tells you what slipped. Reactively, you dump a list on one call and it starts and tracks each task. Custom rules decide what it may do alone, and a short 'message me only when something matters' instruction removes most of the noise.&lt;/p&gt;

&lt;p&gt;Access is narrow for now. Personal accounts need Pro, starting at $100 per month. Business Premium and Enterprise are rolling out, and personal Pro excludes the EEA, UK, and Switzerland. Work and Codex tasks started by a dot count toward those product limits, while conversations with the dot itself do not count toward ChatGPT usage limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  How a dot differs from ChatGPT Work and Codex
&lt;/h3&gt;

&lt;p&gt;A dot is a persistent agent with three things a normal chat does not have.&lt;/p&gt;

&lt;p&gt;First, it has its own cloud computer and browser. That means it can keep working while your laptop is shut. It can open pages, run tools, and perform research independently of your local machine.&lt;/p&gt;

&lt;p&gt;Second, it has memory beyond the current chat. It can use relevant ChatGPT memory, search across your past conversations, and maintain its own notes about your preferences, decisions, and ongoing work. Those notes follow it across the ChatGPT app, &lt;a href="https://slack.com" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;, &lt;a href="https://www.microsoft.com/microsoft-teams" rel="noopener noreferrer"&gt;Microsoft Teams&lt;/a&gt;, and voice calls.&lt;/p&gt;

&lt;p&gt;Third, it can pause and wake itself up. A follow-up check does not need a fixed schedule. The dot can sleep and then resume when it needs to verify something, chase a task, or ask you a question.&lt;/p&gt;

&lt;p&gt;It reaches your apps through ChatGPT plugins. OpenAI listed more than 4,000 plugins at launch, covering Gmail, &lt;a href="https://drive.google.com" rel="noopener noreferrer"&gt;Google Drive&lt;/a&gt;, &lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and many other services. If you connect your computer through the ChatGPT desktop app, the dot can also use local files, code, and apps on that machine, and it can continue existing local Codex tasks.&lt;/p&gt;

&lt;p&gt;The key architectural point is what a dot does with big jobs: it does not do them in its own chat. The docs split the work clearly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it is for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chat&lt;/td&gt;
&lt;td&gt;Answers, explanations, ideas, drafts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://openai.com/chatgpt/work/" rel="noopener noreferrer"&gt;ChatGPT Work&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Delegated tasks like research, analysis, or file creation, now or on a schedule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://openai.com/codex/" rel="noopener noreferrer"&gt;OpenAI Codex&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Building, debugging, testing, and reviewing software&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your dot&lt;/td&gt;
&lt;td&gt;Ongoing responsibilities, next steps as things change, follow-ups between conversations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because a dot delegates, it can split big jobs across background agents that run in parallel. Since an October 9 update, it can also start Codex work and follow up on existing Codex threads. That is the entire case for the dot as a coordinator.&lt;/p&gt;

&lt;p&gt;On paper, &lt;a href="https://about.meta.com" rel="noopener noreferrer"&gt;Meta&lt;/a&gt;'s Muse and &lt;a href="https://x.ai" rel="noopener noreferrer"&gt;xAI&lt;/a&gt;'s &lt;a href="https://grok.com" rel="noopener noreferrer"&gt;Grok Bot&lt;/a&gt; compete with the same idea. Meta's Muse is cheaper for shopping, travel bookings, and forms. xAI's Grok Bot runs teams of always-on bots inside work tools. Neither sits on top of the threads, memory, plugins, and Codex tasks you already have in ChatGPT. If you treat a dot like a one-off errand tool, you ignore the context it is standing next to. Used as a coordinator, it feels different.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F30xf9kbfzpbwx6qofwgj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F30xf9kbfzpbwx6qofwgj.webp" alt="Blog Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How the alternatives compare
&lt;/h3&gt;

&lt;p&gt;Users who follow AI launches will notice that dots are not the only always-on assistant. The comparison depends on where your work already lives.&lt;/p&gt;

&lt;p&gt;Meta's Muse is good at consumer-facing errands. It can handle shopping, travel bookings, and forms, and it has a free tier in the US. It is designed to be cheap and approachable, but it is not built around software project context like existing Codex threads or a personal Obsidian vault.&lt;/p&gt;

&lt;p&gt;xAI's Grok Bot is aimed at running teams of bots inside your work tools. It can complete tasks across Slack, email, and documents, and it feels like a multi-agent workspace. The tradeoff is that it does not share ChatGPT's memory, plugin ecosystem, or Codex task history.&lt;/p&gt;

&lt;p&gt;OpenAI's dot is different because it coordinates the products you already use. It is less independent, but it has more relevant context. In practical terms, a dot can say 'I found a blocking issue in your existing Codex thread, and the research you asked for is done. Here is what I need you to decide.' That mix of delegation and follow-up is hard to replicate in a generic bot team.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Best at&lt;/th&gt;
&lt;th&gt;Limitation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Meta's Muse&lt;/td&gt;
&lt;td&gt;Shopping, travel bookings, forms&lt;/td&gt;
&lt;td&gt;Less useful for software project follow-up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;xAI's Grok Bot&lt;/td&gt;
&lt;td&gt;Running teams of bots in work tools&lt;/td&gt;
&lt;td&gt;Does not share ChatGPT memory and Codex threads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI dot&lt;/td&gt;
&lt;td&gt;Coordinating ChatGPT Work and Codex&lt;/td&gt;
&lt;td&gt;Expensive, one per account, regional limits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What you need before you start
&lt;/h3&gt;

&lt;p&gt;Dots are not available on free plans. The current plan list is short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Personal accounts need Pro 100, Pro 200, or Pro 500, starting at $100 per month.&lt;/li&gt;
&lt;li&gt;You must be over 18.&lt;/li&gt;
&lt;li&gt;Personal Pro excludes the European Economic Area, the United Kingdom, and Switzerland.&lt;/li&gt;
&lt;li&gt;Business Premium is rolling out worldwide.&lt;/li&gt;
&lt;li&gt;Enterprise is rolling out, but a workspace admin must enable dots first.&lt;/li&gt;
&lt;li&gt;Free, Go, and Plus are not on the list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rollout is gradual, so even an eligible account may not see dots immediately. On October 28, a community forum post announced that dots are available in the EU on Business and Enterprise plans, with consumer plans coming very soon. That was confirmed by OpenAI.&lt;/p&gt;

&lt;p&gt;Your first dot is included in the plan. OpenAI says you will be able to add more dots later, but pricing for additional dots has not been announced. Conversations with your dot do not count toward ChatGPT usage limits. Tasks that the dot starts in ChatGPT Work or Codex do count toward those products' limits. Plans also include an allowance for deeper dot work, with extended limits for the first month after launch. OpenAI has not published hard numbers for that allowance, so keep an eye on usage once the first month passes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up your dot
&lt;/h3&gt;

&lt;p&gt;Setup takes only a few minutes, but you need to connect the apps you want the dot to watch. In the running example, that means Gmail, Google Calendar, Google Drive, GitHub, and Slack, plus the laptop where the dashboard app, the game, and the &lt;a href="https://obsidian.md" rel="noopener noreferrer"&gt;Obsidian&lt;/a&gt; vault live.&lt;/p&gt;

&lt;p&gt;Follow these steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create the dot. Open dots in the ChatGPT mobile app, desktop app, or a desktop browser. Mobile web is not supported. Follow the introduction flow and give your dot a name and a look. Its handle starts as @yourname-dot, and naming the dot updates it. For example, a dot named Alfred becomes @yourname-alfred.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect plugins. Add plugins for the apps you want it to use, such as email, calendar, and files. Each plugin works with its connected account and uses permissions that already exist on that account, so review those permissions while you are in the flow. You can skip this step and add plugins later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect your computer. In the ChatGPT app on the computer you want to connect, open the dot's profile, find 'Your computer' under Computers, select 'Allow access', and confirm. The dot can then use local files, code, and Codex tasks on that machine. This only works while the machine is online and the ChatGPT app is open. Only one personal computer can be connected at a time. 'Revoke access' removes it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a channel. On the dot's profile, 'Add' shows contact methods available to you, such as Slack and Microsoft Teams. Texting over iMessage or RCS is rolling out in beta to Pro users in the US. Channels are only a way to talk to the dot. Apps and your computer are connected separately.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After that, write rules before you let the dot near your inbox. That rule-writing step is easy to skip, but it is the most important part of the setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write custom rules before you hand over your inbox
&lt;/h3&gt;

&lt;p&gt;Before a dot can take an action that affects your accounts or shares information, an automatic review checks that action against your instructions, permissions, custom rules, and safety requirements. The action then proceeds, waits for approval, or includes a step you must do yourself. OpenAI uses changing a password as the example of something a dot will always hand off to you.&lt;/p&gt;

&lt;p&gt;Custom rules are how you tune that middle ground. On desktop and web, they live under Settings &amp;gt; Personalization &amp;gt; Custom rules in the Permissions section. On iOS and Android, open the sidebar and select Customize &amp;gt; Custom rules. Each rule is a plain-language description of an action plus one of four handling options.&lt;/p&gt;

&lt;p&gt;A starting set for a chief of staff might look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Handling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Send an email or Slack message to anyone other than me&lt;/td&gt;
&lt;td&gt;Ask before taking action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accept, decline, or move calendar events&lt;/td&gt;
&lt;td&gt;Take action when you say so&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write drafts, update notes, start or continue Codex tasks&lt;/td&gt;
&lt;td&gt;Take action without asking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pay, refund, or change a login&lt;/td&gt;
&lt;td&gt;Hand off to you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two details are easy to miss. Asking for draft replies does not authorize sending them, so 'draft replies to these three emails' leaves the drafts in a draft state. Plugin permissions are a separate layer. From the rules page, open 'Plugins' to see Plugin permissions, and remember that a custom rule never grants access to an app or computer. You have to connect those in the setup stage first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding the right autonomy level
&lt;/h3&gt;

&lt;p&gt;The four handling options are not equally useful for every action. For a chief of staff role, you want low-risk tasks fully automatic and high-risk tasks explicitly approved. Start with the most restrictive option for anything that touches another person or costs money. For notes, drafts, and Codex work, 'Take action without asking' is reasonable if the dot has a clear finish line.&lt;/p&gt;

&lt;p&gt;One useful pattern is to separate 'read' from 'write'. The dot can read your inbox freely, but any draft that goes out should require approval unless you explicitly name the recipient and say send. Another pattern is 'propose, then act': ask the dot to prepare an action and wait for your go signal. The 'Take action when you say so' mode is built for that. The dot does the prep work and then waits. When you are on the road, this reduces back and forth while keeping a human in the loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  The proactive half: what it finds without being asked
&lt;/h3&gt;

&lt;p&gt;Right after setup, a dot introduces itself and uses the context and connected apps to look for places it can help. No prompt is required. For the traveling developer in our example, a first pass might return three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A hotel folio that does not match the booked rate, worth sorting out before checkout.&lt;/li&gt;
&lt;li&gt;A sponsor email that has sat unanswered for two weeks.&lt;/li&gt;
&lt;li&gt;A recording session planned for tomorrow that depends on details nobody has confirmed yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This research is read-only by design. The tools the dot uses for research cannot send messages, change app content, or control your browser or computer. Background agents report what they find to the main dot, which turns it into a suggestion or a question. Any follow-up action goes through the same review and rules we described above.&lt;/p&gt;

&lt;p&gt;The other half of 'proactive' is follow-up. Because a dot pauses and wakes on its own, it keeps checking on things after a conversation ends. That cuts both ways. Suppose you ask the dot to chase a newsletter sponsor for an unpaid invoice. You go back and forth on wording, it sends the message, and half an hour later the dot independently looks at the payment side and tells you the money arrived last week. Useful, and a little embarrasing. The fix is to put the check in the original instruction instead of relying on follow-up: 'Before chasing any invoice, confirm it is still unpaid.'&lt;/p&gt;

&lt;p&gt;Research runs read-only. Only the final step acts, and only through the automatic review plus your custom rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  The reactive half: hand it a whole to-do list on one call
&lt;/h3&gt;

&lt;p&gt;The fastest way to load work onto a dot is to call it. Open the dot's conversation and select the phone button, or press 'Call' on its profile in the desktop app. The screen actually rings like a phone call, which feels like a gimmick the first time. Past that, it is the quickest way to empty your head. You can type while you talk, and ending the call does not stop the work you assigned. Dot-initiated calls are planned for after launch.&lt;/p&gt;

&lt;p&gt;Here is a realistic list that our developer dumps in one call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Research the topic for Tuesday's newsletter and put a full outline into the existing note in the Obsidian vault, complete enough to turn into a draft without missing a key point.&lt;/li&gt;
&lt;li&gt;In the existing Codex thread for the dashboard app, which already pulls Apple Reminders, calendar events, and Obsidian tasks onto one board, make a double-click on a task open a basic Markdown editor inside the app, save edits back to the Obsidian note, and keep an 'Open in Obsidian' button for anything heavier.&lt;/li&gt;
&lt;li&gt;In the game's Codex thread, fix the player getting stuck between walking and climbing on steep slopes, and make god mode give unlimited arrows and faster movement for playtesting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dot reads the plan back, then fans it out. The research goes to a background agent or a ChatGPT Work task, and the outline lands in the vault through the connected computer. The two coding jobs continue existing local Codex tasks on that computer, which means the computer must stay online with ChatGPT open. New coding work can instead run in a Codex cloud environment that you have already set up, with the laptop off. The dot can open each job as its own visible thread, and Activity on the dot's profile in the desktop app shows each task's progress, files, results, and requests for input.&lt;/p&gt;

&lt;p&gt;This is where the dot beats opening three threads yourself. It owns the follow-through. OpenAI's docs say plainly that a completed run does not by itself confirm that the requested result was achieved or delivered, so give it a finish line along with the task. 'Done means the editor saves back to the note and someone has played the god-mode build' is a bar the dot can check, chase, and report on. When it needs a decision or approval, it messages you, and with the ChatGPT app on your phone that arrives as a push notification you can answer from anywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgt8u31bvp7wlyn14f27v.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgt8u31bvp7wlyn14f27v.webp" alt="Blog Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Making delegation reliable
&lt;/h3&gt;

&lt;p&gt;When you hand a dot three tasks in one call, small ambiguities become expensive. The dot can split work, but it cannot read your mind. Use these patterns to reduce wrong results.&lt;/p&gt;

&lt;p&gt;Define done in observable terms. Instead of 'improve the dashboard,' say 'double-clicking a task opens a Markdown editor, edits save back to the Obsidian note, and an Open in Obsidian button appears.' The dot can verify that behavior with its cloud browser and report the result.&lt;/p&gt;

&lt;p&gt;Ask for one summary message. A good instruction is 'When everything is finished, send me a single message with the file paths and the test results.' This prevents a stream of small updates.&lt;/p&gt;

&lt;p&gt;Ask for sources on research. If the newsletter outline has claims or numbers, tell the dot to list sources. OpenAI itself asks you to review important results, so sources should be part of the artifact, not a separate request.&lt;/p&gt;

&lt;p&gt;Set a deadline for each job. 'If the Codex thread is not finished by Friday, tell me what is blocking.' This turns an open-ended background job into a bounded one and gives the dot a reason to wake you.&lt;/p&gt;

&lt;p&gt;Separate new work from existing Codex threads. When you mention 'the existing Codex thread for the dashboard app,' the dot knows to continue that thread. If you just say 'fix the dashboard,' it may start a new thread and miss context. Name the thread and the file path when it matters.&lt;/p&gt;

&lt;p&gt;Use checkpoints. For longer jobs, ask the dot to pause after each milestone and report before starting the next step. That keeps the human loop short and avoids a large batch of wrong work.&lt;/p&gt;

&lt;h3&gt;
  
  
  A day in the life with a dot
&lt;/h3&gt;

&lt;p&gt;To make this concrete, imagine the first morning on the road. The dot has already checked the hotel folio and flagged a rate mismatch. It also noticed that the sponsor email is still unanswered. While you are having coffee, the dot sends one message: 'The hotel folio is wrong, and the sponsor email is two weeks old. Do you want me to draft replies to both?' That is the proactive half working.&lt;/p&gt;

&lt;p&gt;You call the dot and dump the newsletter and coding tasks on it. It starts the research in the background, writes the outline to the Obsidian vault, opens a Codex thread for the dashboard editor, and starts another for the climbing bug. At lunch, the dot reports that the editor task hit a build error and asks whether to try a different approach. You approve. By evening, the outline is in the vault, the dashboard editor is running locally, and the climbing bug is fixed in the god-mode build. The follow-up loop happened without you copying and pasting context between tabs.&lt;/p&gt;

&lt;p&gt;The dot's own activity feed doubles as an audit log. You can see which background agent handled the research, which Codex thread changed, and which files were written. That transparency matters when you are trusting an agent with real work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make it quieter, and more useful
&lt;/h3&gt;

&lt;p&gt;Out of the box, a dot can be chatty. Connect Gmail and it may start telling you about new emails all day, which is noise when your phone already notifies you about every email you recieve. Tell it, in plain words, what you do not want and what you do: 'Do not tell me about new emails. Message me only if a deadline is at risk or you need a decision.' The dot keeps preferences like that in its notes, and you can ask it to confirm what it saved.&lt;/p&gt;

&lt;p&gt;A few more instructions pay off quickly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route by kind. Keep routine progress in ChatGPT and send decisions to Slack. The dot picks the channel based on what you have told it.&lt;/li&gt;
&lt;li&gt;Make check-ins explicit. Recurring work needs a clear schedule. Use a format like 'Every weekday at 8 AM IST for the next four weeks, check my inbox and calendar and send me in Slack only what needs a decision today. Confirm the schedule.' You can later review or cancel it under Scheduled.&lt;/li&gt;
&lt;li&gt;Tell the dot directly when priorities change. Its notes are separate from ChatGPT's saved memory. Changing a saved-memory setting does not necessarily change the notes the dot has already made.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stopping work happens in three places. Pause stops the dot's current main task, but it does not stop every delegated task or cancel future scheduled runs. To stop a delegated task, open it in Activity. To end a recurring one, disable or delete it in Scheduled. None of these undo actions that have already completed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where dots still fall short
&lt;/h3&gt;

&lt;p&gt;Price and region come first. One hundred dollars a month for one agent is a lot if you only want help with email. Meta's Muse has a free tier in the US for errands. Personal Pro accounts in the EEA, UK, and Switzerland cannot get a dot yet, and in the EU only Business and Enterprise workspaces can so far.&lt;/p&gt;

&lt;p&gt;The cloud half and the laptop half behave differently. The cloud computer stays available when the laptop is off, but anything on your machine, such as local Codex tasks or the Obsidian vault, needs that machine online with the ChatGPT app open. The cloud browser keeps its own sessions and does not inherit your logins. Some websites block cloud browsers or ask for extra verification.&lt;/p&gt;

&lt;p&gt;Mistakes are real. OpenAI says dots can make mistakes and asks you to review important results and their sources. The follow-up-after-the-fact problem from the invoice example comes from the dot's fundamental design: it checks things on its own schedule. Usage also needs watching, because Work and Codex tasks started by a dot count toward those products' limits, and the extended limits only cover the first month after launch.&lt;/p&gt;

&lt;p&gt;Finally, you get one dot for now. At launch, OpenAI previewed specialist dots for organizations, each with its own identity, credentials, and system access, starting as focused enterprise pilots. If more dots reach personal plans, the obvious move is one dot per role: inbox, finance, content. That mirrors the bot teams people already run on Grok Bot. Until then, one dot has to cover everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Giving your dot an always-on notes folder with Pinggy
&lt;/h3&gt;

&lt;p&gt;The Obsidian vault in our example has teh same weakness as any local task: the dot can only write the newsletter outline while the laptop is online and ChatGPT is open. If you keep a synced copy of the vault on a machine that never sleeps, such as a mini PC, a NAS, or a Raspberry Pi, you can give the dot its own route to that folder as a custom MCP plugin. A &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; tunnel supplies the public HTTPS URL that ChatGPT needs to reach a box behind a home router.&lt;/p&gt;

&lt;p&gt;Authentication shapes the entire setup. ChatGPT's custom MCP dialog offers OAuth, No authentication, or OAuth or no authentication. OpenAI's auth docs say ChatGPT cannot present custom API keys. If your gateway demands a bearer key, it will lock ChatGPT out along with everyone else. Short of running an OAuth 2.1 server, the practical setup is No authentication with two limits: the server only sees one folder of the vault, and it answers on a long random path instead of /mcp.&lt;/p&gt;

&lt;p&gt;On that box, sync the vault to /srv/vault. &lt;a href="https://syncthing.net" rel="noopener noreferrer"&gt;Syncthing&lt;/a&gt; works well for this. Keep the notes you want the dot to work on, like the newsletter outline, in a Dot folder inside it. Then serve the official filesystem MCP server over Streamable HTTP with &lt;a href="https://www.npmjs.com/package/supergateway" rel="noopener noreferrer"&gt;supergateway&lt;/a&gt;, which wraps a stdio server in an HTTP endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MCP_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/mcp-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 16&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MCP_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
npx &lt;span class="nt"&gt;-y&lt;/span&gt; supergateway &lt;span class="nt"&gt;--stdio&lt;/span&gt; &lt;span class="s2"&gt;"npx -y @modelcontextprotocol/server-filesystem /srv/vault/Dot"&lt;/span&gt; &lt;span class="nt"&gt;--outputTransport&lt;/span&gt; streamableHttp &lt;span class="nt"&gt;--port&lt;/span&gt; 8787 &lt;span class="nt"&gt;--streamableHttpPath&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MCP_PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That serves the &lt;a href="https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem" rel="noopener noreferrer"&gt;server-filesystem&lt;/a&gt; package's 14 tools, including read_text_file, write_file, edit_file, search_files, and the rest, on port 8787 at that random path, and nothing outside /srv/vault/Dot. We ran this on October 29 with supergateway 4.2.1 and server-filesystem 2.1.0. A tools/list request on the random path returned all 14 tools, a request to plain /mcp got a 404, and reading a file elsewhere in the vault returned 'Access denied - path outside allowed directories'.&lt;/p&gt;

&lt;p&gt;Then open the tunnel from the same box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-p&lt;/span&gt; 443 &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;ServerAliveInterval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;60 &lt;span class="nt"&gt;-R0&lt;/span&gt;:localhost:8787 &amp;lt;token&amp;gt;@pro.pinggy.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a Pro token from the Pinggy dashboard, with a persistent subdomain assigned to it. A free tunnel lasts 60 minutes and comes back with a new URL, which breaks the plugin URL you saved in ChatGPT. Free URLs also show a warning page to browser-like clients.&lt;/p&gt;

&lt;p&gt;In ChatGPT on the web, turn on Developer mode if it is not already on. Open Plugins, select the plus button, then Add custom MCP server. Give it a name. Enter https://.a.pinggy.link followed by your random path as the Server URL. Pick No authentication, read the risk warning, select 'I understand and want to continue', choose Create as a plugin, then install it. OpenAI's custom MCP server guide lists the same steps, and Pinggy's guide to building a ChatGPT plugin with MCP shows where the Developer mode switch lives.&lt;/p&gt;

&lt;p&gt;Three caveats apply. First, the random path is a secret inside a URL, not real authentication. Anyone with the full URL can read and write that one folder, so keep sensitive notes out of it and put an OAuth 2.1 layer in front of the gateway before trusting it with more. Second, OpenAI also has a private MCP tunnel option that keeps the server off the public internet entirely, but it requires an OpenAI Platform organization with Tunnels permissions and its tunnel-client running beside the server. Third, OpenAI's dots docs say a dot can use supported plugins installed and enabled for your account. One vendor reports its custom MCP connector working from the dot's first message, but this exact setup has not been tested with a dot by the original guide authors. Add a custom rule for edits to the folder either way.&lt;/p&gt;

&lt;p&gt;The mini PC opens the tunnel outward, so no port is opened on the router and the laptop can stay closed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqdkrdglosb29eigvxdnp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqdkrdglosb29eigvxdnp.webp" alt="Blog Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Security and troubleshooting
&lt;/h3&gt;

&lt;p&gt;When you start using a dot, a few questions tend to come up immediately.&lt;/p&gt;

&lt;p&gt;Why does my dot not appear? The rollout is gradual. Even on an eligible plan, the dot entry point may not show up for a few days. Check that your ChatGPT app is updated and that you are on a supported platform. Mobile web is not supported, so use the mobile app, desktop app, or desktop browser.&lt;/p&gt;

&lt;p&gt;Why did the dot ignore my local files? The connected computer must be online and open in the ChatGPT app. If the laptop is off, anything that depends on local files will fail. Cloud Codex tasks can continue without a local machine, but local paths cannot.&lt;/p&gt;

&lt;p&gt;Can the dot send messages without asking? Only if your custom rules allow it. The default position is to check before actions that affect other people or external accounts. You can set 'Take action without asking' for notes and Codex tasks, but you should keep 'Ask before taking action' for sending messages to third parties.&lt;/p&gt;

&lt;p&gt;How much does this cost in practice? The dot conversation itself does not count toward ChatGPT limits. Work and Codex tasks it starts count toward those product limits. The deeper work allowance has extended limits for the first month, but the numbers are not public. Watch your usage after that.&lt;/p&gt;

&lt;p&gt;What if the dot keeps repeating notifications from my phone? That is the most common complaint. The fix is one clear rule: message me only if a deadline is at risk or you need a decision. Then review the dot's messages after a week and update the rule.&lt;/p&gt;

&lt;p&gt;Can I use this with an open source alternative? Yes, the MCP pattern is vendor neutral. The same supergateway setup can expose any filesystem, and tools like Syncthing make the sync layer portable. The always-on agent itself is tied to ChatGPT for now, but the notes folder architecture is not.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to do next
&lt;/h3&gt;

&lt;p&gt;Start with work you regularly need to check or update, not a one-off request. That is OpenAI's own advice, and it matches how a dot is built. 'Keep the newsletter on schedule' or 'watch the sponsor inbox and tell me what needs a reply' gives the dot something to wake up for. Describe the responsibility, share the sources it needs, and say which decisions need you.&lt;/p&gt;

&lt;p&gt;Then review the dot's work, tell it what needs to change, and only widen the scope once the first job runs well. Give it a week and look at what it actually caught. If most of its messages repeat notifications your phone already sends, tell it so and judge it on follow-ups instead: the invoice it double-checked, the Codex thread it reopened, the outline that was waiting in the vault on Monday morning. That is the job a chief of staff does, and it is the one thing a single chat thread never did.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;A dot works best one layer above the tools you already use. It looks through Gmail, Google Calendar, and Slack with read-only research tools, hands research to ChatGPT Work and code to OpenAI Codex, and keeps following each job until it is done or it needs you. The order of the setup matters more than any individual setting: connect plugins and your computer, write custom rules before it goes near your inbox, give every task a finish line, and tell it in one plain sentence when to stay quiet.&lt;/p&gt;

&lt;p&gt;Whether that is worth $100 per month depends on how much of your week goes into follow-up. If you mostly want errands run and you are in the US, Meta's Muse free tier is the cheaper bet. If you juggle Codex threads, a newsletter, and an inbox that never empties, a dot takes over the tracking you would otherwise keep in your head. Plan around two gaps while you decide: you get one dot per account for now, and anything that runs on your laptop needs the laptop online with ChatGPT open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pinggy.io/blog/use_openai_dots_as_chief_of_staff/" rel="noopener noreferrer"&gt;How to Use OpenAI Dots as a Chief of Staff in ChatGPT&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>openai</category>
      <category>ai</category>
      <category>automation</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>FLUX 3 Max vs. Seedance 2.5: A Practical Field Guide to AI Image-to-Video Models</title>
      <dc:creator>Lightning Developer</dc:creator>
      <pubDate>Wed, 28 Oct 2026 08:39:46 +0000</pubDate>
      <link>https://dev.to/lightningdev123/flux-3-max-vs-seedance-25-a-practical-field-guide-to-ai-image-to-video-models-3ji1</link>
      <guid>https://dev.to/lightningdev123/flux-3-max-vs-seedance-25-a-practical-field-guide-to-ai-image-to-video-models-3ji1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction: The Image-to-Video Model Shortlist
&lt;/h2&gt;

&lt;p&gt;Choosing an AI image-to-video generator used to mean settling for one obvious weakness. The model produced stunning clips, but each render took so long that nobody could iterate. Or the model was fast, but the output looked like a morph between unrelated frames. The latest release cycle changed that calculus. On the &lt;a href="https://artificialanalysis.ai" rel="noopener noreferrer"&gt;Artificial Analysis&lt;/a&gt; image-to-video leaderboard, two models now sit in the leading tier with 1,139 Elo: &lt;a href="https://fal.ai" rel="noopener noreferrer"&gt;FLUX 3 Max&lt;/a&gt; and Seedance 2.5. One of them renders a 10-second clip in 12.45 seconds on &lt;a href="https://fal.ai" rel="noopener noreferrer"&gt;fal&lt;/a&gt;'s benchmark, while the other takes 277.8 seconds. That is not a minor difference. It changes where you can put the model in a product.&lt;/p&gt;

&lt;p&gt;Before going further, keep the data scope in mind. This guide uses October 2026 launch-era scores, pricing, and fal latency measurements. It is a desk-researched comparison, not a hands-on benchmark. The 1,139 Elo figures refer to the launch snapshot. The live Artificial Analysis board, checked on October 24, lists FLUX 3 Max and Seedance 2.5 at 1,138 each, with MiniMax H3 at 1,140. Calculated workload times assume sequential generation and exclude queueing, retries, and review time. Always check current model pricing before budgeting.&lt;/p&gt;

&lt;p&gt;"Best" still depends on the job. A creative agency, a storyboard artist, and a startup watching its bill want different things. So instead of one ranked list, here are six jobs and the model I would hand each one, with the reason and the catch.&lt;/p&gt;

&lt;p&gt;Fair warning: FLUX 3 Max takes four of the six slots. It is one model with three modes, text-to-video, image-to-video, and keyframes-to-video, plus a launch price, and those happen to cover four different jobs. The other two picks are places where I would genuinely send you elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Six Picks at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Pick&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;th&gt;Watch out&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Best overall&lt;/td&gt;
&lt;td&gt;FLUX 3 Max&lt;/td&gt;
&lt;td&gt;#1-tier quality, about 22x faster than its tie-partner&lt;/td&gt;
&lt;td&gt;720p output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Agencies iterating on concepts&lt;/td&gt;
&lt;td&gt;FLUX 3 Max&lt;/td&gt;
&lt;td&gt;Around 22 drafts in the time of one&lt;/td&gt;
&lt;td&gt;Run your own eval for the hero cut&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Storyboarded shots&lt;/td&gt;
&lt;td&gt;FLUX 3 Max keyframes-to-video&lt;/td&gt;
&lt;td&gt;You set the start and end frames&lt;/td&gt;
&lt;td&gt;Both frames have to exist first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Raw speed above all&lt;/td&gt;
&lt;td&gt;H3 Max on fal&lt;/td&gt;
&lt;td&gt;3.49 seconds in fal's benchmark&lt;/td&gt;
&lt;td&gt;1,125 Elo, a step below the 1,139 tie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Committed to &lt;a href="https://www.bytedance.com" rel="noopener noreferrer"&gt;ByteDance&lt;/a&gt;'s stack&lt;/td&gt;
&lt;td&gt;Seedance 2.5 on fal&lt;/td&gt;
&lt;td&gt;Same 1,139 Elo, same fal key&lt;/td&gt;
&lt;td&gt;About 4.5 minutes and about $4.73 per 10-second clip at 720p&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Budget at scale&lt;/td&gt;
&lt;td&gt;FLUX 3 Max at launch pricing&lt;/td&gt;
&lt;td&gt;About $0.43 per 10-second clip&lt;/td&gt;
&lt;td&gt;75% off ends November 7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How the Picks Were Scored
&lt;/h2&gt;

&lt;p&gt;To compare fairly, I leaned on two public sources. First, &lt;a href="https://artificialanalysis.ai" rel="noopener noreferrer"&gt;Artificial Analysis&lt;/a&gt; runs a blind, pairwise human preference arena for image-to-video models. Humans see two clips and choose which one looks better, and those votes produce an Elo score. Second, &lt;a href="https://fal.ai" rel="noopener noreferrer"&gt;fal&lt;/a&gt; publishes benchmark latencies for generating a 10-second video at 720p or 768p, 16:9. The two sources answer different questions. Elo tells you about perceived quality. Latency tells you whether the model fits into a real-time workflow.&lt;/p&gt;

&lt;p&gt;The scores and speeds behind the picks are summarized in the table below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;AA image-to-video Elo&lt;/th&gt;
&lt;th&gt;fal benchmark: one 10-second clip&lt;/th&gt;
&lt;th&gt;Appears in picks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FLUX 3 Max&lt;/td&gt;
&lt;td&gt;1,139&lt;/td&gt;
&lt;td&gt;12.45 seconds&lt;/td&gt;
&lt;td&gt;1, 2, 3, 6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedance 2.5&lt;/td&gt;
&lt;td&gt;1,139&lt;/td&gt;
&lt;td&gt;277.8 seconds&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H3 Max&lt;/td&gt;
&lt;td&gt;1,125 plus or minus 17&lt;/td&gt;
&lt;td&gt;3.49 seconds&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FLUX 3 original&lt;/td&gt;
&lt;td&gt;1,089&lt;/td&gt;
&lt;td&gt;126.44 seconds&lt;/td&gt;
&lt;td&gt;not in picks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 1,139 scores come from Artificial Analysis' launch results for FLUX 3 Max, based on 4,868 votes. FLUX 3's 1,089 is its launch figure, with 1,086 plus or minus 16 on the public board. Latency values come from fal's benchmark for a 10-second video at 720p or 768p, 16:9. Scores are as of October 2026, and the leaderboard updates as new votes arrive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4jatwheardp4gsm2nns6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4jatwheardp4gsm2nns6.webp" alt="Blog Image" width="799" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The live Artificial Analysis image-to-video board with audio on October 24, 2026. The API pricing column is Artificial Analysis' own per-minute figure. The prices in this post are fal's 720p rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Elo Alone Is Not Enough
&lt;/h2&gt;

&lt;p&gt;Developers often ask why a leaderboard number does not settle the argument. Elo captures human preference, but it does not capture the shape of your workload. A model with a high score but a five-minute latency forces you to design a queue, a progress bar, and a retry policy. A model with a slightly lower score but a 3.5-second latency can feel like a native video effect. For product work, speed is not a nice-to-have. It is part of the user experience.&lt;/p&gt;

&lt;p&gt;The other missing dimension is cost. Two models can share an Elo score and still price very differently. Seedance 2.5 shares 1,139 Elo with FLUX 3 Max, but at 720p on fal it costs about $4.73 per 10-second clip compared with FLUX 3 Max's $1.70 list price. During the launch discount, the gap grows even wider. A team generating hundreds of clips must care about that gap.&lt;/p&gt;

&lt;p&gt;There is also the question of what the leaderboard does not measure. It does not tell you whether the model handles a specific brand style, a particular face, or a shot list that your client already approved. That is why the recommendations in this post come with a warning to run your own evaluation for any clip that will ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Dive: FLUX 3 Max
&lt;/h2&gt;

&lt;p&gt;FLUX 3 Max is fal's frontier video model, built by fal Research. It is a post-trained variant of FLUX 3 Video, tuned for stronger prompt adherence, better visual quality, and lower latency. It launched on fal on October 19, 2026.&lt;/p&gt;

&lt;p&gt;The jump over its parent is large. The original FLUX 3 scored 1,089 Elo and took 126.44 seconds to render one 10-second clip in fal's benchmark. FLUX 3 Max scores 1,139 and takes 12.45 seconds. That is a 50-point quality gain and roughly a 10x speedup. The post-training stack is responsible for part of that, and fal's inference infrastructure handles the rest.&lt;/p&gt;

&lt;p&gt;The model covers the basics that developers expect from a modern video model. You get 720p video with native audio. Generated clips can run from 5 to 20 seconds. Aspect ratios span 21:9 to 9:16, which covers cinematic stills, desktop video, and vertical short-form content. Built-in prompt expansion turns short prompts into richer ones, so you do not need to write a paragraph every time. fal Research continues to post-train the model, which means the behavior can improve without you changing your integration.&lt;/p&gt;

&lt;p&gt;Why does post-training matter? A base video model learns how to generate plausible frames, but it may not follow instructions well. Post-training is a second phase that optimizes for specific outcomes: following a text prompt, keeping a subject consistent across frames, or making the camera motion feel intentional. The same base weights can become a fast preview model or a high-quality hero model depending on how the post-training is tuned. FLUX 3 Max is positioned somewhere in the middle: high enough quality to ship, fast enough to iterate.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Can Do with FLUX 3 Max
&lt;/h3&gt;

&lt;p&gt;FLUX 3 Max is not a single endpoint. It is a model family that exposes three modes through fal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Text-to-video. You provide a prompt and get a clip with no starting image. This is useful for brainstorming and for scenes that do not need a locked composition.&lt;/li&gt;
&lt;li&gt;Image-to-video. You provide a starting frame and a prompt. The model animates from that frame. This is the natural fit for product shots, character stills, or any asset that already exists.&lt;/li&gt;
&lt;li&gt;Keyframes-to-video. You provide a start frame and an end frame, plus a prompt. The model generates the motion between them. This is the storyboard tool.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because all three modes are available under one integration, a team can start with text prompts for exploration, move to image-to-video for a controlled first frame, and switch to keyframes-to-video when the director wants a locked ending. The API stays the same, which reduces the number of moving parts in a production pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Best Overall: FLUX 3 Max
&lt;/h2&gt;

&lt;p&gt;If I had to pick one model for a general-purpose image-to-video workflow, it would be FLUX 3 Max. It is the only model on this list with both a share of the top score and a 12.45-second turnaround. Seedance 2.5 has the same score but not the speed. H3 Max has the speed but sits 14 points lower on the public board.&lt;/p&gt;

&lt;p&gt;The why is simple. A 12-second wait is acceptable in an interactive tool. A developer can call the endpoint from a web request, show a spinner for a few seconds, and return a video without building a complex job scheduler. A 277-second wait turns every generation into a background task that requires a progress bar, a callback, or a polling loop.&lt;/p&gt;

&lt;p&gt;The watch out is also simple. Output is 720p. If a delivery specification asks for 1080p or 4K, you need a finishing step, either an upscaler or a separate render pipeline. The model is ideal for social clips, internal drafts, and concept boards. It might not be the final renderer for a broadcast commercial.&lt;/p&gt;

&lt;h3&gt;
  
  
  When 720p Is Enough
&lt;/h3&gt;

&lt;p&gt;For many developer workloads, 720p is enough. Online ads, social posts, presentation backgrounds, and video previews rarely need more. The smaller resolution also makes video files easier to store, cache, and stream. If your product serves short video clips inside a webpage, 720p keeps bandwidth costs low. If you need a hero piece for a client presentation, you may want to upscale later. The key is to plan that step early rather than discovering it after your first batch of clips.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Best for Agencies Iterating on Concepts: FLUX 3 Max
&lt;/h2&gt;

&lt;p&gt;Concept work is a volume game. Clients choose from options, and more directions per review means more chances to land on an idea that excites them. FLUX 3 Max is the strongest option for this because it produces roughly 22 drafts in the time it takes Seedance 2.5 to produce one.&lt;/p&gt;

&lt;p&gt;The arithmetic assumes sequential, single-request generation. It is not a throughput test, but it is useful for estimating wall-clock time in a linear workflow.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sequential, single-request math&lt;/th&gt;
&lt;th&gt;FLUX 3 Max&lt;/th&gt;
&lt;th&gt;Seedance 2.5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One 10-second clip&lt;/td&gt;
&lt;td&gt;12.45 seconds&lt;/td&gt;
&lt;td&gt;277.8 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clips in one hour, back to back&lt;/td&gt;
&lt;td&gt;about 289&lt;/td&gt;
&lt;td&gt;about 13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50 ten-second variants&lt;/td&gt;
&lt;td&gt;about 10.4 minutes&lt;/td&gt;
&lt;td&gt;about 3.9 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clips in the time of one Seedance 2.5 render&lt;/td&gt;
&lt;td&gt;about 22&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Parallel requests shorten wall-clock time for both models, but each individual draft still takes as long as it takes. If you fire off 10 simultaneous requests to FLUX 3 Max, you can get a set of options in under 15 seconds. With Seedance 2.5, the same 10 requests might finish in roughly five minutes under ideal parallel conditions. Neither number is bad, but the feel is completely different.&lt;/p&gt;

&lt;p&gt;The cost picture also favors FLUX 3 Max: $1.70 per 10-second clip at list price versus about $4.73 for Seedance 2.5 at 720p. On the quality side, the two are tied at 1,139 Elo. That means there is no clear quality reason to choose the slower model for early exploration. If your team eventually prefers Seedance's look for the hero cut, run the final round there. Both models are available on fal, so you can A/B test with the same API key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building an Iteration Loop
&lt;/h3&gt;

&lt;p&gt;Agencies can build a simple internal tool around the model. A designer uploads a reference image, types a few prompt variants, and clicks generate. The backend sends the requests to fal, collects the returned video URLs, and presents them in a grid. Because FLUX 3 Max is fast, the designer can refine the prompt in the same meeting where the client is waiting. That turns a review session into a live working session.&lt;/p&gt;

&lt;p&gt;The important part is to separate exploration from final delivery. Use the fast model to explore directions, then switch to the best-looking model for the chosen direction. You do not need to standardize on one model for every stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Best for Storyboarded Shots: FLUX 3 Max Keyframes-to-Video
&lt;/h2&gt;

&lt;p&gt;When a board says "start here, end there," you do not want the model guessing the ending. FLUX 3 Max keyframes-to-video, available from FLUX 3 Max on fal, takes controlled start and end frames for storyboarded work.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you need to lock&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Inputs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The opening frame only&lt;/td&gt;
&lt;td&gt;Image-to-video&lt;/td&gt;
&lt;td&gt;image_url plus prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A pinned ending on a quick one-off&lt;/td&gt;
&lt;td&gt;Image-to-video&lt;/td&gt;
&lt;td&gt;image_url plus optional end_image_url plus prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Board-planned shots, start and end&lt;/td&gt;
&lt;td&gt;Keyframes-to-video&lt;/td&gt;
&lt;td&gt;Controlled start and end frames&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The start and end frame inputs, image_url and end_image_url, are available in fal's FLUX 3 Max image-to-video playground. This is useful for directors who have a clear vision for the first and last frame of a shot but want the model to fill in the motion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhnucik6hh0emqloyt9o1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhnucik6hh0emqloyt9o1.webp" alt="Blog Image" width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The start and end frame inputs in fal's FLUX 3 Max image-to-video playground. Screenshot: fal.ai, October 2026.&lt;/p&gt;

&lt;p&gt;Why choose this option? You control composition at both ends of every shot. Storyboard shots also run through the same API as everything else, so there is no separate tool to wire up. A shot list can map directly to a set of API calls, each with a start frame, an end frame, and a prompt.&lt;/p&gt;

&lt;p&gt;The catch is that both frames have to exist before you generate. That front-loads design work. You cannot ask the model to invent the ending after you see the beginning. You also need to keep shots under 20 seconds. Longer sequences become several shots cut together. For best results, keep the two frames close enough that the move between them reads as one believable action. If the end frame is too far from the start frame, the model has to invent too much intermediate context and the motion can feel unnatural.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Keyframes Matter in Production
&lt;/h3&gt;

&lt;p&gt;Storyboard artists work in discrete panels. Each panel represents a moment in time. An image-to-video model that only takes a start frame forces the artist to describe the ending in text, which the model may or may not honor. Keyframes-to-video removes that ambiguity. The artist draws the last panel, uploads it, and the model knows exactly where the shot has to land. This is especially valuable for product visualization, where the final logo position or the final camera angle must be exact.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Best When Raw Speed Beats Everything: H3 Max on fal
&lt;/h2&gt;

&lt;p&gt;H3 Max is fal Research's earlier post-trained frontier video model. It is the work that FLUX 3 Max builds on. In fal's benchmark, it produced video in 3.49 seconds, which makes it the even-faster option in fal's lineup. It scores 1,125 on Artificial Analysis' current board, where it is listed as MiniMax H3 Max.&lt;/p&gt;

&lt;p&gt;Think of H3 Max as the same research team's other tool, not a rival. It is the model I would reach for when the result has to feel instant. Three and a half seconds is close to the boundary where a user stops perceiving the generation as a separate stage and starts treating it as a live effect. That opens the door to interactive sliders, preview-on-change, and rapid shot exploration.&lt;/p&gt;

&lt;p&gt;Why use it? A 3.49-second turn suits live previews and any flow where even a short wait feels too long. If you are building a product where the user adjusts a prompt and expects to see a new clip within seconds, H3 Max is the safer bet. The quality is only 14 points below the top tie, which is a meaningful but small gap. For rough previews, the difference is easy to miss. For the clip that ships, I would use FLUX 3 Max.&lt;/p&gt;

&lt;p&gt;Watch out for the caveats. The score of 1,125 is 14 points below the 1,139 tie. It may also have different input or output constraints. Check the fal model page for current inputs and pricing before you build an integration around it. Raw speed is valuable, but it is not a substitute for checking the actual output quality on your own content.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Use Case for Ultra-Fast Previews
&lt;/h3&gt;

&lt;p&gt;Imagine a video editor that lets you scrub through different motion styles. A user clicks smooth dolly, the backend sends a request, and in 3.5 seconds the clip updates. That is a very different product from one that shows generation in progress, check back in five minutes. If your product depends on interactivity, you may want to use H3 Max for previews and then use FLUX 3 Max or Seedance 2.5 for the final export. The extra integration work is small because all three are on fal.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Best If You Are Committed to ByteDance's Stack: Seedance 2.5
&lt;/h2&gt;

&lt;p&gt;Seedance 2.5 shares the 1,139 score. If your team has prompt libraries, templates, or client approvals built around it, or an internal evaluation that prefers its look, there is no quality case for ripping it out. The model is strong enough to sit at the top of the leaderboard, and a tie means the average human preference is statistically indistinguishable from FLUX 3 Max.&lt;/p&gt;

&lt;p&gt;The table below shows the cost and speed difference on fal at 720p as of October 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;On fal, 720p, as of October 2026&lt;/th&gt;
&lt;th&gt;Seedance 2.5&lt;/th&gt;
&lt;th&gt;FLUX 3 Max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Price per second&lt;/td&gt;
&lt;td&gt;about $0.473 with audio&lt;/td&gt;
&lt;td&gt;$0.17 list, $0.0425 at launch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 10-second clip&lt;/td&gt;
&lt;td&gt;about $4.73&lt;/td&gt;
&lt;td&gt;$1.70 list, about $0.43 at launch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to a 10-second clip on fal benchmark&lt;/td&gt;
&lt;td&gt;277.8 seconds&lt;/td&gt;
&lt;td&gt;12.45 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The main reason to keep Seedance 2.5 is ecosystem lock-in. If your prompt engineering team has written hundreds of prompts and refined them against Seedance's visual style, switching costs more than a model comparison. You also know how the model behaves in your existing pipeline. There is no quality reason to rip it out.&lt;/p&gt;

&lt;p&gt;The catch is operational. At about 4.5 minutes per clip, every draft becomes a background job. If you need a dozen options for a client review, you need to parallelize aggressively and manage queueing. At 720p on fal, it costs about 2.8 times FLUX 3 Max's list price per second and about 11 times the launch price. Seedance 2.5 is billed by tokens, at $0.0214 per 1,000 tokens at 480p or 720p, so the exact cost shifts with aspect ratio. Check the current rates before committing to a large batch.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Stay with Seedance 2.5
&lt;/h3&gt;

&lt;p&gt;You should stay with Seedance 2.5 if you have already built an evaluation set that prefers its output. You should also stay if your client or internal stakeholder specifically asks for a style that Seedance produces. In that scenario, you can A/B test it against FLUX 3 Max with the same fal key. Because fal hosts both models, the experiment takes minutes, not days. If FLUX 3 Max wins your eval, you can switch without changing billing relationships.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Best Budget at Scale: FLUX 3 Max During Launch Pricing
&lt;/h2&gt;

&lt;p&gt;List price for FLUX 3 Max on fal is $0.17 per second at 720p, or $1.70 per 10-second clip. From October 19 to November 7, it is 75% off. After November 7, check fal's pricing page for current rates.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Window&lt;/th&gt;
&lt;th&gt;Discount&lt;/th&gt;
&lt;th&gt;Per second&lt;/th&gt;
&lt;th&gt;Per 10-second clip&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;October 19 to November 7&lt;/td&gt;
&lt;td&gt;75% off&lt;/td&gt;
&lt;td&gt;$0.0425&lt;/td&gt;
&lt;td&gt;about $0.43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.17&lt;/td&gt;
&lt;td&gt;$1.70&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To put that in campaign terms: a library of 500 ten-second clips costs about $212.50 at 75% off, or 500 times $0.425. At list, it costs $850. On Seedance 2.5 at 720p, the same library costs about $2,365, or 500 times about $4.73. For volume beyond that, enterprise teams can contact fal sales for further discounts.&lt;/p&gt;

&lt;p&gt;Why choose this? During the launch discount, FLUX 3 Max is about 11 times cheaper per second than Seedance 2.5 at 720p on fal, with the same top-tier score. You also pay only for successful outputs. Queue time and failed generations are not billed. That makes it unusually friendly for batch production where some percentage of outputs will end up being discarded anyway.&lt;/p&gt;

&lt;p&gt;The watch out is the expiration date. The discount is temporary. Budget anything after November 7 using fal's current pricing page. List price is $0.17 per second. Billing is per second, so a 20-second clip costs twice as much as a 10-second clip. If your workload uses long clips, plan accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Volume Planning With Failed Generations
&lt;/h3&gt;

&lt;p&gt;Every generative model produces some percentage of unusable clips. Financial planning should include a retry budget. Suppose your success rate is 80%. To get 500 usable clips, you may need around 625 generations. At launch pricing, that is about $265.63, not $212.50. At Seedance 2.5 pricing, the same 625 generations would be about $2,956.25. The difference matters, and the pricing model that only charges on success reduces the cost of retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honorable Mentions
&lt;/h2&gt;

&lt;p&gt;Other models near the top of the public board deserve attention. &lt;a href="https://www.minimax.io" rel="noopener noreferrer"&gt;MiniMax&lt;/a&gt; H3 sits at 1,140 plus or minus 16, which is actually the highest point on the board by a hair. &lt;a href="https://www.google.com" rel="noopener noreferrer"&gt;Google&lt;/a&gt;'s Gemini Omni Flash 1.1 sits at 1,132 plus or minus 17. &lt;a href="https://www.alibaba.com" rel="noopener noreferrer"&gt;Alibaba&lt;/a&gt;'s Wan 3.0 sits at 1,127 plus or minus 16. They are credible models, and the leaderboard margins are small enough that a particular content type might make any of them the winner.&lt;/p&gt;

&lt;p&gt;I left them out of the main picks because I cannot compare their speed or 720p cost on the same footing as the fal benchmark and fal prices used here. H3 is listed under MiniMax H3 on a different platform. Gemini and Wan have their own hosting routes. A proper comparison would need consistent latency and price measurements on identical hardware or API infrastructure, which is not what the public leaderboard provides. If one of those models is already in your stack, you should run your own evaluation rather than take my picks as absolute.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Choose
&lt;/h2&gt;

&lt;p&gt;Use the decision guide below if you want a fast answer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Someone is waiting on the result, such as a client on a call or a user in your app: choose FLUX 3 Max. About 12 seconds is a wait people sit through. Nearly five minutes is not.&lt;/li&gt;
&lt;li&gt;You need many drafts per review: choose FLUX 3 Max.&lt;/li&gt;
&lt;li&gt;The board fixes the first and last frame: choose FLUX 3 Max keyframes-to-video.&lt;/li&gt;
&lt;li&gt;Instant previews matter more than the top score: choose H3 Max.&lt;/li&gt;
&lt;li&gt;You are locked into Seedance 2.5: keep it, and A/B FLUX 3 Max on the same fal key.&lt;/li&gt;
&lt;li&gt;You are generating volume before November 7: choose FLUX 3 Max at launch pricing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My recommendation is to start on FLUX 3 Max on fal, then A/B Seedance 2.5 on the same key if your team cares about its look. One API covers text, image, and keyframes, so you can try all three modes without a second integration. The evaluation should measure visual preference, latency, cost, and failure rate on your own content. Do not rely on a leaderboard alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Decision Matrix in Practice
&lt;/h3&gt;

&lt;p&gt;Consider a team with four common tasks. For a client kickoff, they need a quick range of styles. FLUX 3 Max gives them 20 options in a few minutes. For a storyboard reveal, they need the ending to match the panel. Keyframes-to-video is the only tool that guarantees that. For a live preview in an editor, they need feedback in seconds. H3 Max is the right choice. For a high-volume batch before a campaign, the launch pricing on FLUX 3 Max wins. The same platform serves all four tasks, which simplifies procurement and integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt Design for Image-to-Video
&lt;/h2&gt;

&lt;p&gt;Prompting for video is different from prompting for images. In image generation, you describe a single frame. In video generation, you describe a state change over time. Use action verbs, camera moves, and temporal cues. Avoid static descriptions that do not imply motion.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Weak prompt&lt;/th&gt;
&lt;th&gt;Better prompt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Camera move&lt;/td&gt;
&lt;td&gt;zoom in&lt;/td&gt;
&lt;td&gt;Slow push-in from a wide establishing shot to a close-up on the character's eyes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Action&lt;/td&gt;
&lt;td&gt;the car moves&lt;/td&gt;
&lt;td&gt;The car accelerates from a stop and turns left, kicking up dust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ending&lt;/td&gt;
&lt;td&gt;she smiles&lt;/td&gt;
&lt;td&gt;She starts with a neutral expression and ends with a confident smile&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;FLUX 3 Max has built-in prompt expansion, but specificity still helps. The expansion feature is useful when you want to explore quickly. If you need a precise result, write the prompt as if you are directing a camera operator. Mention the subject, the motion, the framing, and the desired mood.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reasoning Levels and Latency
&lt;/h3&gt;

&lt;p&gt;The model exposes a reasoning_level parameter. At high reasoning, the model may expand short prompts and spend more time planning the output. At low reasoning, it skips extra processing and returns faster. This matters for two reasons. First, high reasoning can improve adherence on complex prompts. Second, low reasoning reduces per-request latency, which is important when you are serving interactive previews.&lt;/p&gt;

&lt;p&gt;Test both levels on your own prompts. If the quality difference is small, choose low reasoning for speed. If the model misses important instructions at low reasoning, go back to high. The optimum depends on your content, not on the model's default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Considerations for Image-to-Video APIs
&lt;/h2&gt;

&lt;p&gt;Moving from a playground to production requires more than picking a model. You need to think about queueing, retries, storage, callback handling, and cost controls. The following sections cover the operational details that make or break a video generation pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Synchronous Requests vs Asynchronous Jobs
&lt;/h3&gt;

&lt;p&gt;A model with a 12-second response time can be used synchronously from a backend. A user clicks a button, the server calls the video API, waits for the result, and returns the URL. This is simple to build and easy to reason about. A model with a 277-second response time cannot be used synchronously in a normal web request. You need a job queue, a database row for each generation, and a way to notify the client when the result is ready.&lt;/p&gt;

&lt;p&gt;Even with FLUX 3 Max, synchronous requests have limits. A single web request that lasts 12 seconds can time out if your load balancer has a short timeout. You should either configure the timeout to something generous, such as 30 or 60 seconds, or move the generation to a background worker and poll for the result. The worker approach is more robust and also allows parallel generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Concurrent Generation
&lt;/h3&gt;

&lt;p&gt;If you want to generate 50 concept variants, you can fire 50 requests in parallel. The platform will handle the queueing, but you should still think about concurrency from the client side. A simple thread pool in Python or a worker queue in Node.js can keep your code organized. Here is a small Python example that uses a thread pool for parallel image-to-video generation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;concurrent.futures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_completed&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;image_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;first_frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;end_image_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;last_frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;video_length&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;generate_audio&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reasoning_level&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;video_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;video_url&lt;/span&gt;

&lt;span class="n"&gt;prompts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;slow zoom into the subject&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;handheld reveal from the left&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;top-down shot with a soft shadow&lt;/span&gt;&lt;span class="sh"&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;with&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generate_clip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prompts&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;as_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an illustrative example, not a copy-paste integration for a specific SDK. Use the actual client library and endpoint names for the model you choose. The point is that parallel calls are straightforward and should be part of your design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Audio Output
&lt;/h3&gt;

&lt;p&gt;FLUX 3 Max generates native audio on by default. If your product already has a music bed or a voiceover, you may want to disable audio or replace it later. Native audio is useful for prototypes and for social-ready clips. It also increases the output size, so plan your storage and streaming choices accordingly. If you do not want audio, look for a parameter such as generate_audio and set it to false in your request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storing and Serving Generated Clips
&lt;/h3&gt;

&lt;p&gt;The video API returns a URL. You can treat that URL as the output, but production systems usually copy the video into their own storage. That way you can add retention policies, rewrite URLs, and avoid depending on a temporary host. Cloud object storage with a CDN is a common choice. Keep the video format simple, such as MP4 with H.264, for broad browser support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost Controls and Budgeting
&lt;/h3&gt;

&lt;p&gt;Generate only what you need. Before sending a batch, estimate the number of seconds and the success rate. If a 10-second clip costs $0.43 during the launch discount, a mistaken loop that generates 1,000 variants costs $430. Add a concurrency limit, a daily spending cap, and an alerting mechanism to your integration. The pricing model on fal only charges for successful outputs, but a failed generation still consumes time and may occupy a slot in your queue. Use a small batch for testing before you scale up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retry Logic
&lt;/h3&gt;

&lt;p&gt;Video generation can fail for many reasons: a malformed image URL, an unsupported aspect ratio, a prompt that triggers a safety filter, or a temporary service issue. Build retry logic with exponential backoff for transient errors. Do not retry immediately for errors caused by bad input. Log the failure reason and expose it in a dashboard so you can spot recurring problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting Common Failure Modes
&lt;/h2&gt;

&lt;p&gt;Even with a strong model, things go wrong. Here are the most common failure modes and how to handle them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subject drift. If the main subject changes identity across frames, tighten the start and end frames. Add more descriptive language about the subject's appearance and clothing in the prompt.&lt;/li&gt;
&lt;li&gt;Artificial camera motion. If the camera moves in a way that feels too smooth or too robotic, describe the intended camera operator behavior. Use terms like handheld, tripod, or drone to guide the motion.&lt;/li&gt;
&lt;li&gt;Ignored end frame. If the model does not land on the end frame, check that both frames use the same aspect ratio and resolution. The model needs a clear visual connection between the two frames.&lt;/li&gt;
&lt;li&gt;Unwanted lighting changes. When a scene's lighting shifts suddenly, specify consistent lighting in the prompt. Phrases like "same lighting throughout" can help.&lt;/li&gt;
&lt;li&gt;Audio mismatch. If generated audio does not match the action, disable audio and add a sound design step in post-production.&lt;/li&gt;
&lt;li&gt;Prompt overloading. If a single prompt asks for too many actions, split it into multiple shots. A 10-second clip has room for one primary action, not five.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Practical Evaluation Plan
&lt;/h2&gt;

&lt;p&gt;Before committing to any model, build a small evaluation set that represents your real workload. Include at least ten prompts and ten pairs of start and end frames. Generate the same set with FLUX 3 Max and Seedance 2.5, ideally on the same day to reduce the risk of model updates. Ask at least three people to rank the results without knowing which model produced each clip. Measure the time per clip, the failure rate, and the cost. Then make a decision.&lt;/p&gt;

&lt;p&gt;Automated metrics can supplement human review. Use a simple face consistency check, a frame interpolation sanity check, or a prompt adherence metric if you have one. But human preference is still the closest proxy for whether a clip works. The 1,139 Elo tie means you should let your own eval decide.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to Look For in Human Evaluation
&lt;/h3&gt;

&lt;p&gt;When comparing clips, look for artifacts like morphing, extra limbs, or sudden lighting changes. Check whether the motion follows the prompt. Check whether the end frame matches the requested end frame. Note whether the sound is synchronized with the action. A model can score well on an average clip and still fail on the shots most important to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the best AI image-to-video generator model right now?
&lt;/h3&gt;

&lt;p&gt;FLUX 3 Max on fal is my pick. It is a top image-to-video model on the Artificial Analysis leaderboard, in the leading tier alongside Seedance 2.5 at 1,139 Elo, and about 22 times faster than Seedance 2.5 in fal's benchmark.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is FLUX 3 Max better than Seedance 2.5?
&lt;/h3&gt;

&lt;p&gt;On quality, they are tied at 1,139 Elo. FLUX 3 Max wins on speed, 12.45 seconds versus 277.8 seconds, and on 720p price on fal. If your team prefers Seedance's look, there is no quality argument against keeping it. The operational cost is the main reason to switch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does FLUX 3 Max generate audio?
&lt;/h3&gt;

&lt;p&gt;Yes. Native audio is on by default, with generate_audio set to true. Clips run from 5 to 20 seconds. If you do not need audio, look for a parameter to disable it in your API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to be good at prompting?
&lt;/h3&gt;

&lt;p&gt;No. By default, the reasoning level is set to high, and the model expands short prompts for you. This is useful for exploration. If you need the fastest possible turnaround, set reasoning_level to low, because prompt expansion adds a processing step. For production, test both levels and measure whether the expanded prompts improve adherence enough to justify the extra latency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where can I run it?
&lt;/h3&gt;

&lt;p&gt;On fal, through three endpoints: text-to-video, image-to-video, and keyframes-to-video. All are available from the FLUX 3 Max model page on fal. The same API key works for all three modes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use FLUX 3 Max for long-form video?
&lt;/h3&gt;

&lt;p&gt;Not directly. Individual clips are capped at 20 seconds. For longer content, generate multiple shots and cut them together. The keyframes mode is useful here because you can set the last frame of one shot as the first frame of the next, creating a continuous edit.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about mini-batch jobs for thumbnails and previews?
&lt;/h3&gt;

&lt;p&gt;Use H3 Max for instant previews and FLUX 3 Max for final clips. The camera and prompt inputs stay similar, so you can reuse the same request structure. A 3.49-second preview lets you show a direction to a client, and the 12.45-second final render is still fast enough for a review meeting.&lt;/p&gt;

&lt;h3&gt;
  
  
  How should I handle safety and moderation?
&lt;/h3&gt;

&lt;p&gt;Apply your own content policy on top of the model. Check generated frames for inappropriate content before serving them to users. The provider likely has safety filters, but you should not depend on them as your only layer. Store a moderation decision for each clip if you need an audit trail.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if the price changes after November 7?
&lt;/h3&gt;

&lt;p&gt;The launch discount expires, and the list price becomes $0.17 per second at 720p. That is still competitive. Recalculate your unit economics with the new price. If you have enterprise volume, contact fal sales for custom rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The image-to-video space is moving fast enough that leaderboard positions are more like weather reports than permanent rankings. As of this October 2026 comparison, FLUX 3 Max offers the best balance of quality, speed, and price for most teams. H3 Max is the specialist for instant previews, and Seedance 2.5 remains a solid choice for those who value its ecosystem. The best move is to run a small evaluation, measure cost and failure rate on your own data, and choose a model that fits the way your team actually works.&lt;/p&gt;

&lt;p&gt;If you are starting today, begin with FLUX 3 Max on fal. Try the image-to-video mode with a still frame, then explore keyframes-to-video for storyboarded work. Keep an eye on the Artificial Analysis board and fal's pricing page, because the next model release could change the recommendation again. In AI video generation, the only constant is that the next update arrives before you finish optimizing the current one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pinggy.io/blog/best_ai_image_to_video_generator_by_use_case/" rel="noopener noreferrer"&gt;What Is the Best AI Image-to-Video Generator Model? Picks for 6 Use Cases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fal.ai" rel="noopener noreferrer"&gt;fal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://artificialanalysis.ai" rel="noopener noreferrer"&gt;Artificial Analysis image-to-video leaderboard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article was written in partnership with fal and originally published on the &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; blog.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>video</category>
      <category>generativeai</category>
      <category>fal</category>
    </item>
    <item>
      <title>FLUX 3 Max vs. Seedance 2.5: A Practical Field Guide to AI Image-to-Video Models</title>
      <dc:creator>Lightning Developer</dc:creator>
      <pubDate>Wed, 28 Oct 2026 07:29:12 +0000</pubDate>
      <link>https://dev.to/lightningdev123/flux-3-max-vs-seedance-25-a-practical-field-guide-to-ai-image-to-video-models-2n4e</link>
      <guid>https://dev.to/lightningdev123/flux-3-max-vs-seedance-25-a-practical-field-guide-to-ai-image-to-video-models-2n4e</guid>
      <description>&lt;h2&gt;
  
  
  Introduction: The Image-to-Video Model Shortlist
&lt;/h2&gt;

&lt;p&gt;Choosing an AI image-to-video generator used to mean settling for one obvious weakness. The model produced stunning clips, but each render took so long that nobody could iterate. Or the model was fast, but the output looked like a morph between unrelated frames. The latest release cycle changed that calculus. On the &lt;a href="https://artificialanalysis.ai" rel="noopener noreferrer"&gt;Artificial Analysis&lt;/a&gt; image-to-video leaderboard, two models now sit in the leading tier with 1,139 Elo: &lt;a href="https://fal.ai" rel="noopener noreferrer"&gt;FLUX 3 Max&lt;/a&gt; and Seedance 2.5. One of them renders a 10-second clip in 12.45 seconds on &lt;a href="https://fal.ai" rel="noopener noreferrer"&gt;fal&lt;/a&gt;'s benchmark, while the other takes 277.8 seconds. That is not a minor difference. It changes where you can put the model in a product.&lt;/p&gt;

&lt;p&gt;Before going further, keep the data scope in mind. This guide uses October 2026 launch-era scores, pricing, and fal latency measurements. It is a desk-researched comparison, not a hands-on benchmark. The 1,139 Elo figures refer to the launch snapshot. The live Artificial Analysis board, checked on October 24, lists FLUX 3 Max and Seedance 2.5 at 1,138 each, with MiniMax H3 at 1,140. Calculated workload times assume sequential generation and exclude queueing, retries, and review time. Always check current model pricing before budgeting.&lt;/p&gt;

&lt;p&gt;"Best" still depends on the job. A creative agency, a storyboard artist, and a startup watching its bill want different things. So instead of one ranked list, here are six jobs and the model I would hand each one, with the reason and the catch.&lt;/p&gt;

&lt;p&gt;Fair warning: FLUX 3 Max takes four of the six slots. It is one model with three modes, text-to-video, image-to-video, and keyframes-to-video, plus a launch price, and those happen to cover four different jobs. The other two picks are places where I would genuinely send you elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Six Picks at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Pick&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;th&gt;Watch out&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Best overall&lt;/td&gt;
&lt;td&gt;FLUX 3 Max&lt;/td&gt;
&lt;td&gt;#1-tier quality, about 22x faster than its tie-partner&lt;/td&gt;
&lt;td&gt;720p output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Agencies iterating on concepts&lt;/td&gt;
&lt;td&gt;FLUX 3 Max&lt;/td&gt;
&lt;td&gt;Around 22 drafts in the time of one&lt;/td&gt;
&lt;td&gt;Run your own eval for the hero cut&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Storyboarded shots&lt;/td&gt;
&lt;td&gt;FLUX 3 Max keyframes-to-video&lt;/td&gt;
&lt;td&gt;You set the start and end frames&lt;/td&gt;
&lt;td&gt;Both frames have to exist first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Raw speed above all&lt;/td&gt;
&lt;td&gt;H3 Max on fal&lt;/td&gt;
&lt;td&gt;3.49 seconds in fal's benchmark&lt;/td&gt;
&lt;td&gt;1,125 Elo, a step below the 1,139 tie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Committed to &lt;a href="https://www.bytedance.com" rel="noopener noreferrer"&gt;ByteDance&lt;/a&gt;'s stack&lt;/td&gt;
&lt;td&gt;Seedance 2.5 on fal&lt;/td&gt;
&lt;td&gt;Same 1,139 Elo, same fal key&lt;/td&gt;
&lt;td&gt;About 4.5 minutes and about $4.73 per 10-second clip at 720p&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Budget at scale&lt;/td&gt;
&lt;td&gt;FLUX 3 Max at launch pricing&lt;/td&gt;
&lt;td&gt;About $0.43 per 10-second clip&lt;/td&gt;
&lt;td&gt;75% off ends November 7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How the Picks Were Scored
&lt;/h2&gt;

&lt;p&gt;To compare fairly, I leaned on two public sources. First, &lt;a href="https://artificialanalysis.ai" rel="noopener noreferrer"&gt;Artificial Analysis&lt;/a&gt; runs a blind, pairwise human preference arena for image-to-video models. Humans see two clips and choose which one looks better, and those votes produce an Elo score. Second, &lt;a href="https://fal.ai" rel="noopener noreferrer"&gt;fal&lt;/a&gt; publishes benchmark latencies for generating a 10-second video at 720p or 768p, 16:9. The two sources answer different questions. Elo tells you about perceived quality. Latency tells you whether the model fits into a real-time workflow.&lt;/p&gt;

&lt;p&gt;The scores and speeds behind the picks are summarized in the table below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;AA image-to-video Elo&lt;/th&gt;
&lt;th&gt;fal benchmark: one 10-second clip&lt;/th&gt;
&lt;th&gt;Appears in picks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FLUX 3 Max&lt;/td&gt;
&lt;td&gt;1,139&lt;/td&gt;
&lt;td&gt;12.45 seconds&lt;/td&gt;
&lt;td&gt;1, 2, 3, 6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedance 2.5&lt;/td&gt;
&lt;td&gt;1,139&lt;/td&gt;
&lt;td&gt;277.8 seconds&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H3 Max&lt;/td&gt;
&lt;td&gt;1,125 plus or minus 17&lt;/td&gt;
&lt;td&gt;3.49 seconds&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FLUX 3 original&lt;/td&gt;
&lt;td&gt;1,089&lt;/td&gt;
&lt;td&gt;126.44 seconds&lt;/td&gt;
&lt;td&gt;not in picks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 1,139 scores come from Artificial Analysis' launch results for FLUX 3 Max, based on 4,868 votes. FLUX 3's 1,089 is its launch figure, with 1,086 plus or minus 16 on the public board. Latency values come from fal's benchmark for a 10-second video at 720p or 768p, 16:9. Scores are as of October 2026, and the leaderboard updates as new votes arrive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4jatwheardp4gsm2nns6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4jatwheardp4gsm2nns6.webp" alt="Blog Image" width="799" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The live Artificial Analysis image-to-video board with audio on October 24, 2026. The API pricing column is Artificial Analysis' own per-minute figure. The prices in this post are fal's 720p rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Elo Alone Is Not Enough
&lt;/h2&gt;

&lt;p&gt;Developers often ask why a leaderboard number does not settle the argument. Elo captures human preference, but it does not capture the shape of your workload. A model with a high score but a five-minute latency forces you to design a queue, a progress bar, and a retry policy. A model with a slightly lower score but a 3.5-second latency can feel like a native video effect. For product work, speed is not a nice-to-have. It is part of the user experience.&lt;/p&gt;

&lt;p&gt;The other missing dimension is cost. Two models can share an Elo score and still price very differently. Seedance 2.5 shares 1,139 Elo with FLUX 3 Max, but at 720p on fal it costs about $4.73 per 10-second clip compared with FLUX 3 Max's $1.70 list price. During the launch discount, the gap grows even wider. A team generating hundreds of clips must care about that gap.&lt;/p&gt;

&lt;p&gt;There is also the question of what the leaderboard does not measure. It does not tell you whether the model handles a specific brand style, a particular face, or a shot list that your client already approved. That is why the recommendations in this post come with a warning to run your own evaluation for any clip that will ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Dive: FLUX 3 Max
&lt;/h2&gt;

&lt;p&gt;FLUX 3 Max is fal's frontier video model, built by fal Research. It is a post-trained variant of FLUX 3 Video, tuned for stronger prompt adherence, better visual quality, and lower latency. It launched on fal on October 19, 2026.&lt;/p&gt;

&lt;p&gt;The jump over its parent is large. The original FLUX 3 scored 1,089 Elo and took 126.44 seconds to render one 10-second clip in fal's benchmark. FLUX 3 Max scores 1,139 and takes 12.45 seconds. That is a 50-point quality gain and roughly a 10x speedup. The post-training stack is responsible for part of that, and fal's inference infrastructure handles the rest.&lt;/p&gt;

&lt;p&gt;The model covers the basics that developers expect from a modern video model. You get 720p video with native audio. Generated clips can run from 5 to 20 seconds. Aspect ratios span 21:9 to 9:16, which covers cinematic stills, desktop video, and vertical short-form content. Built-in prompt expansion turns short prompts into richer ones, so you do not need to write a paragraph every time. fal Research continues to post-train the model, which means the behavior can improve without you changing your integration.&lt;/p&gt;

&lt;p&gt;Why does post-training matter? A base video model learns how to generate plausible frames, but it may not follow instructions well. Post-training is a second phase that optimizes for specific outcomes: following a text prompt, keeping a subject consistent across frames, or making the camera motion feel intentional. The same base weights can become a fast preview model or a high-quality hero model depending on how the post-training is tuned. FLUX 3 Max is positioned somewhere in the middle: high enough quality to ship, fast enough to iterate.&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Can Do with FLUX 3 Max
&lt;/h3&gt;

&lt;p&gt;FLUX 3 Max is not a single endpoint. It is a model family that exposes three modes through fal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Text-to-video. You provide a prompt and get a clip with no starting image. This is useful for brainstorming and for scenes that do not need a locked composition.&lt;/li&gt;
&lt;li&gt;Image-to-video. You provide a starting frame and a prompt. The model animates from that frame. This is the natural fit for product shots, character stills, or any asset that already exists.&lt;/li&gt;
&lt;li&gt;Keyframes-to-video. You provide a start frame and an end frame, plus a prompt. The model generates the motion between them. This is the storyboard tool.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because all three modes are available under one integration, a team can start with text prompts for exploration, move to image-to-video for a controlled first frame, and switch to keyframes-to-video when the director wants a locked ending. The API stays the same, which reduces the number of moving parts in a production pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Best Overall: FLUX 3 Max
&lt;/h2&gt;

&lt;p&gt;If I had to pick one model for a general-purpose image-to-video workflow, it would be FLUX 3 Max. It is the only model on this list with both a share of the top score and a 12.45-second turnaround. Seedance 2.5 has the same score but not the speed. H3 Max has the speed but sits 14 points lower on the public board.&lt;/p&gt;

&lt;p&gt;The why is simple. A 12-second wait is acceptable in an interactive tool. A developer can call the endpoint from a web request, show a spinner for a few seconds, and return a video without building a complex job scheduler. A 277-second wait turns every generation into a background task that requires a progress bar, a callback, or a polling loop.&lt;/p&gt;

&lt;p&gt;The watch out is also simple. Output is 720p. If a delivery specification asks for 1080p or 4K, you need a finishing step, either an upscaler or a separate render pipeline. The model is ideal for social clips, internal drafts, and concept boards. It might not be the final renderer for a broadcast commercial.&lt;/p&gt;

&lt;h3&gt;
  
  
  When 720p Is Enough
&lt;/h3&gt;

&lt;p&gt;For many developer workloads, 720p is enough. Online ads, social posts, presentation backgrounds, and video previews rarely need more. The smaller resolution also makes video files easier to store, cache, and stream. If your product serves short video clips inside a webpage, 720p keeps bandwidth costs low. If you need a hero piece for a client presentation, you may want to upscale later. The key is to plan that step early rather than discovering it after your first batch of clips.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Best for Agencies Iterating on Concepts: FLUX 3 Max
&lt;/h2&gt;

&lt;p&gt;Concept work is a volume game. Clients choose from options, and more directions per review means more chances to land on an idea that excites them. FLUX 3 Max is the strongest option for this because it produces roughly 22 drafts in the time it takes Seedance 2.5 to produce one.&lt;/p&gt;

&lt;p&gt;The arithmetic assumes sequential, single-request generation. It is not a throughput test, but it is useful for estimating wall-clock time in a linear workflow.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sequential, single-request math&lt;/th&gt;
&lt;th&gt;FLUX 3 Max&lt;/th&gt;
&lt;th&gt;Seedance 2.5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One 10-second clip&lt;/td&gt;
&lt;td&gt;12.45 seconds&lt;/td&gt;
&lt;td&gt;277.8 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clips in one hour, back to back&lt;/td&gt;
&lt;td&gt;about 289&lt;/td&gt;
&lt;td&gt;about 13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50 ten-second variants&lt;/td&gt;
&lt;td&gt;about 10.4 minutes&lt;/td&gt;
&lt;td&gt;about 3.9 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clips in the time of one Seedance 2.5 render&lt;/td&gt;
&lt;td&gt;about 22&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Parallel requests shorten wall-clock time for both models, but each individual draft still takes as long as it takes. If you fire off 10 simultaneous requests to FLUX 3 Max, you can get a set of options in under 15 seconds. With Seedance 2.5, the same 10 requests might finish in roughly five minutes under ideal parallel conditions. Neither number is bad, but the feel is completely different.&lt;/p&gt;

&lt;p&gt;The cost picture also favors FLUX 3 Max: $1.70 per 10-second clip at list price versus about $4.73 for Seedance 2.5 at 720p. On the quality side, the two are tied at 1,139 Elo. That means there is no clear quality reason to choose the slower model for early exploration. If your team eventually prefers Seedance's look for the hero cut, run the final round there. Both models are available on fal, so you can A/B test with the same API key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building an Iteration Loop
&lt;/h3&gt;

&lt;p&gt;Agencies can build a simple internal tool around the model. A designer uploads a reference image, types a few prompt variants, and clicks generate. The backend sends the requests to fal, collects the returned video URLs, and presents them in a grid. Because FLUX 3 Max is fast, the designer can refine the prompt in the same meeting where the client is waiting. That turns a review session into a live working session.&lt;/p&gt;

&lt;p&gt;The important part is to separate exploration from final delivery. Use the fast model to explore directions, then switch to the best-looking model for the chosen direction. You do not need to standardize on one model for every stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Best for Storyboarded Shots: FLUX 3 Max Keyframes-to-Video
&lt;/h2&gt;

&lt;p&gt;When a board says "start here, end there," you do not want the model guessing the ending. FLUX 3 Max keyframes-to-video, available from FLUX 3 Max on fal, takes controlled start and end frames for storyboarded work.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you need to lock&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Inputs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The opening frame only&lt;/td&gt;
&lt;td&gt;Image-to-video&lt;/td&gt;
&lt;td&gt;image_url plus prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A pinned ending on a quick one-off&lt;/td&gt;
&lt;td&gt;Image-to-video&lt;/td&gt;
&lt;td&gt;image_url plus optional end_image_url plus prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Board-planned shots, start and end&lt;/td&gt;
&lt;td&gt;Keyframes-to-video&lt;/td&gt;
&lt;td&gt;Controlled start and end frames&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The start and end frame inputs, image_url and end_image_url, are available in fal's FLUX 3 Max image-to-video playground. This is useful for directors who have a clear vision for the first and last frame of a shot but want the model to fill in the motion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhnucik6hh0emqloyt9o1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhnucik6hh0emqloyt9o1.webp" alt="Blog Image" width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The start and end frame inputs in fal's FLUX 3 Max image-to-video playground. Screenshot: fal.ai, October 2026.&lt;/p&gt;

&lt;p&gt;Why choose this option? You control composition at both ends of every shot. Storyboard shots also run through the same API as everything else, so there is no separate tool to wire up. A shot list can map directly to a set of API calls, each with a start frame, an end frame, and a prompt.&lt;/p&gt;

&lt;p&gt;The catch is that both frames have to exist before you generate. That front-loads design work. You cannot ask the model to invent the ending after you see the beginning. You also need to keep shots under 20 seconds. Longer sequences become several shots cut together. For best results, keep the two frames close enough that the move between them reads as one believable action. If the end frame is too far from the start frame, the model has to invent too much intermediate context and the motion can feel unnatural.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Keyframes Matter in Production
&lt;/h3&gt;

&lt;p&gt;Storyboard artists work in discrete panels. Each panel represents a moment in time. An image-to-video model that only takes a start frame forces the artist to describe the ending in text, which the model may or may not honor. Keyframes-to-video removes that ambiguity. The artist draws the last panel, uploads it, and the model knows exactly where the shot has to land. This is especially valuable for product visualization, where the final logo position or the final camera angle must be exact.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Best When Raw Speed Beats Everything: H3 Max on fal
&lt;/h2&gt;

&lt;p&gt;H3 Max is fal Research's earlier post-trained frontier video model. It is the work that FLUX 3 Max builds on. In fal's benchmark, it produced video in 3.49 seconds, which makes it the even-faster option in fal's lineup. It scores 1,125 on Artificial Analysis' current board, where it is listed as MiniMax H3 Max.&lt;/p&gt;

&lt;p&gt;Think of H3 Max as the same research team's other tool, not a rival. It is the model I would reach for when the result has to feel instant. Three and a half seconds is close to the boundary where a user stops perceiving the generation as a separate stage and starts treating it as a live effect. That opens the door to interactive sliders, preview-on-change, and rapid shot exploration.&lt;/p&gt;

&lt;p&gt;Why use it? A 3.49-second turn suits live previews and any flow where even a short wait feels too long. If you are building a product where the user adjusts a prompt and expects to see a new clip within seconds, H3 Max is the safer bet. The quality is only 14 points below the top tie, which is a meaningful but small gap. For rough previews, the difference is easy to miss. For the clip that ships, I would use FLUX 3 Max.&lt;/p&gt;

&lt;p&gt;Watch out for the caveats. The score of 1,125 is 14 points below the 1,139 tie. It may also have different input or output constraints. Check the fal model page for current inputs and pricing before you build an integration around it. Raw speed is valuable, but it is not a substitute for checking the actual output quality on your own content.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Use Case for Ultra-Fast Previews
&lt;/h3&gt;

&lt;p&gt;Imagine a video editor that lets you scrub through different motion styles. A user clicks smooth dolly, the backend sends a request, and in 3.5 seconds the clip updates. That is a very different product from one that shows generation in progress, check back in five minutes. If your product depends on interactivity, you may want to use H3 Max for previews and then use FLUX 3 Max or Seedance 2.5 for the final export. The extra integration work is small because all three are on fal.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Best If You Are Committed to ByteDance's Stack: Seedance 2.5
&lt;/h2&gt;

&lt;p&gt;Seedance 2.5 shares the 1,139 score. If your team has prompt libraries, templates, or client approvals built around it, or an internal evaluation that prefers its look, there is no quality case for ripping it out. The model is strong enough to sit at the top of the leaderboard, and a tie means the average human preference is statistically indistinguishable from FLUX 3 Max.&lt;/p&gt;

&lt;p&gt;The table below shows the cost and speed difference on fal at 720p as of October 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;On fal, 720p, as of October 2026&lt;/th&gt;
&lt;th&gt;Seedance 2.5&lt;/th&gt;
&lt;th&gt;FLUX 3 Max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Price per second&lt;/td&gt;
&lt;td&gt;about $0.473 with audio&lt;/td&gt;
&lt;td&gt;$0.17 list, $0.0425 at launch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price per 10-second clip&lt;/td&gt;
&lt;td&gt;about $4.73&lt;/td&gt;
&lt;td&gt;$1.70 list, about $0.43 at launch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to a 10-second clip on fal benchmark&lt;/td&gt;
&lt;td&gt;277.8 seconds&lt;/td&gt;
&lt;td&gt;12.45 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The main reason to keep Seedance 2.5 is ecosystem lock-in. If your prompt engineering team has written hundreds of prompts and refined them against Seedance's visual style, switching costs more than a model comparison. You also know how the model behaves in your existing pipeline. There is no quality reason to rip it out.&lt;/p&gt;

&lt;p&gt;The catch is operational. At about 4.5 minutes per clip, every draft becomes a background job. If you need a dozen options for a client review, you need to parallelize aggressively and manage queueing. At 720p on fal, it costs about 2.8 times FLUX 3 Max's list price per second and about 11 times the launch price. Seedance 2.5 is billed by tokens, at $0.0214 per 1,000 tokens at 480p or 720p, so the exact cost shifts with aspect ratio. Check the current rates before committing to a large batch.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Stay with Seedance 2.5
&lt;/h3&gt;

&lt;p&gt;You should stay with Seedance 2.5 if you have already built an evaluation set that prefers its output. You should also stay if your client or internal stakeholder specifically asks for a style that Seedance produces. In that scenario, you can A/B test it against FLUX 3 Max with the same fal key. Because fal hosts both models, the experiment takes minutes, not days. If FLUX 3 Max wins your eval, you can switch without changing billing relationships.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Best Budget at Scale: FLUX 3 Max During Launch Pricing
&lt;/h2&gt;

&lt;p&gt;List price for FLUX 3 Max on fal is $0.17 per second at 720p, or $1.70 per 10-second clip. From October 19 to November 7, it is 75% off. After November 7, check fal's pricing page for current rates.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Window&lt;/th&gt;
&lt;th&gt;Discount&lt;/th&gt;
&lt;th&gt;Per second&lt;/th&gt;
&lt;th&gt;Per 10-second clip&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;October 19 to November 7&lt;/td&gt;
&lt;td&gt;75% off&lt;/td&gt;
&lt;td&gt;$0.0425&lt;/td&gt;
&lt;td&gt;about $0.43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.17&lt;/td&gt;
&lt;td&gt;$1.70&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To put that in campaign terms: a library of 500 ten-second clips costs about $212.50 at 75% off, or 500 times $0.425. At list, it costs $850. On Seedance 2.5 at 720p, the same library costs about $2,365, or 500 times about $4.73. For volume beyond that, enterprise teams can contact fal sales for further discounts.&lt;/p&gt;

&lt;p&gt;Why choose this? During the launch discount, FLUX 3 Max is about 11 times cheaper per second than Seedance 2.5 at 720p on fal, with the same top-tier score. You also pay only for successful outputs. Queue time and failed generations are not billed. That makes it unusually friendly for batch production where some percentage of outputs will end up being discarded anyway.&lt;/p&gt;

&lt;p&gt;The watch out is the expiration date. The discount is temporary. Budget anything after November 7 using fal's current pricing page. List price is $0.17 per second. Billing is per second, so a 20-second clip costs twice as much as a 10-second clip. If your workload uses long clips, plan accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Volume Planning With Failed Generations
&lt;/h3&gt;

&lt;p&gt;Every generative model produces some percentage of unusable clips. Financial planning should include a retry budget. Suppose your success rate is 80%. To get 500 usable clips, you may need around 625 generations. At launch pricing, that is about $265.63, not $212.50. At Seedance 2.5 pricing, the same 625 generations would be about $2,956.25. The difference matters, and the pricing model that only charges on success reduces the cost of retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honorable Mentions
&lt;/h2&gt;

&lt;p&gt;Other models near the top of the public board deserve attention. &lt;a href="https://www.minimax.io" rel="noopener noreferrer"&gt;MiniMax&lt;/a&gt; H3 sits at 1,140 plus or minus 16, which is actually the highest point on the board by a hair. &lt;a href="https://www.google.com" rel="noopener noreferrer"&gt;Google&lt;/a&gt;'s Gemini Omni Flash 1.1 sits at 1,132 plus or minus 17. &lt;a href="https://www.alibaba.com" rel="noopener noreferrer"&gt;Alibaba&lt;/a&gt;'s Wan 3.0 sits at 1,127 plus or minus 16. They are credible models, and the leaderboard margins are small enough that a particular content type might make any of them the winner.&lt;/p&gt;

&lt;p&gt;I left them out of the main picks because I cannot compare their speed or 720p cost on the same footing as the fal benchmark and fal prices used here. H3 is listed under MiniMax H3 on a different platform. Gemini and Wan have their own hosting routes. A proper comparison would need consistent latency and price measurements on identical hardware or API infrastructure, which is not what the public leaderboard provides. If one of those models is already in your stack, you should run your own evaluation rather than take my picks as absolute.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Choose
&lt;/h2&gt;

&lt;p&gt;Use the decision guide below if you want a fast answer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Someone is waiting on the result, such as a client on a call or a user in your app: choose FLUX 3 Max. About 12 seconds is a wait people sit through. Nearly five minutes is not.&lt;/li&gt;
&lt;li&gt;You need many drafts per review: choose FLUX 3 Max.&lt;/li&gt;
&lt;li&gt;The board fixes the first and last frame: choose FLUX 3 Max keyframes-to-video.&lt;/li&gt;
&lt;li&gt;Instant previews matter more than the top score: choose H3 Max.&lt;/li&gt;
&lt;li&gt;You are locked into Seedance 2.5: keep it, and A/B FLUX 3 Max on the same fal key.&lt;/li&gt;
&lt;li&gt;You are generating volume before November 7: choose FLUX 3 Max at launch pricing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My recommendation is to start on FLUX 3 Max on fal, then A/B Seedance 2.5 on the same key if your team cares about its look. One API covers text, image, and keyframes, so you can try all three modes without a second integration. The evaluation should measure visual preference, latency, cost, and failure rate on your own content. Do not rely on a leaderboard alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Decision Matrix in Practice
&lt;/h3&gt;

&lt;p&gt;Consider a team with four common tasks. For a client kickoff, they need a quick range of styles. FLUX 3 Max gives them 20 options in a few minutes. For a storyboard reveal, they need the ending to match the panel. Keyframes-to-video is the only tool that guarantees that. For a live preview in an editor, they need feedback in seconds. H3 Max is the right choice. For a high-volume batch before a campaign, the launch pricing on FLUX 3 Max wins. The same platform serves all four tasks, which simplifies procurement and integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt Design for Image-to-Video
&lt;/h2&gt;

&lt;p&gt;Prompting for video is different from prompting for images. In image generation, you describe a single frame. In video generation, you describe a state change over time. Use action verbs, camera moves, and temporal cues. Avoid static descriptions that do not imply motion.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Weak prompt&lt;/th&gt;
&lt;th&gt;Better prompt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Camera move&lt;/td&gt;
&lt;td&gt;zoom in&lt;/td&gt;
&lt;td&gt;Slow push-in from a wide establishing shot to a close-up on the character's eyes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Action&lt;/td&gt;
&lt;td&gt;the car moves&lt;/td&gt;
&lt;td&gt;The car accelerates from a stop and turns left, kicking up dust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ending&lt;/td&gt;
&lt;td&gt;she smiles&lt;/td&gt;
&lt;td&gt;She starts with a neutral expression and ends with a confident smile&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;FLUX 3 Max has built-in prompt expansion, but specificity still helps. The expansion feature is useful when you want to explore quickly. If you need a precise result, write the prompt as if you are directing a camera operator. Mention the subject, the motion, the framing, and the desired mood.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reasoning Levels and Latency
&lt;/h3&gt;

&lt;p&gt;The model exposes a reasoning_level parameter. At high reasoning, the model may expand short prompts and spend more time planning the output. At low reasoning, it skips extra processing and returns faster. This matters for two reasons. First, high reasoning can improve adherence on complex prompts. Second, low reasoning reduces per-request latency, which is important when you are serving interactive previews.&lt;/p&gt;

&lt;p&gt;Test both levels on your own prompts. If the quality difference is small, choose low reasoning for speed. If the model misses important instructions at low reasoning, go back to high. The optimum depends on your content, not on the model's default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Considerations for Image-to-Video APIs
&lt;/h2&gt;

&lt;p&gt;Moving from a playground to production requires more than picking a model. You need to think about queueing, retries, storage, callback handling, and cost controls. The following sections cover the operational details that make or break a video generation pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Synchronous Requests vs Asynchronous Jobs
&lt;/h3&gt;

&lt;p&gt;A model with a 12-second response time can be used synchronously from a backend. A user clicks a button, the server calls the video API, waits for the result, and returns the URL. This is simple to build and easy to reason about. A model with a 277-second response time cannot be used synchronously in a normal web request. You need a job queue, a database row for each generation, and a way to notify the client when the result is ready.&lt;/p&gt;

&lt;p&gt;Even with FLUX 3 Max, synchronous requests have limits. A single web request that lasts 12 seconds can time out if your load balancer has a short timeout. You should either configure the timeout to something generous, such as 30 or 60 seconds, or move the generation to a background worker and poll for the result. The worker approach is more robust and also allows parallel generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Concurrent Generation
&lt;/h3&gt;

&lt;p&gt;If you want to generate 50 concept variants, you can fire 50 requests in parallel. The platform will handle the queueing, but you should still think about concurrency from the client side. A simple thread pool in Python or a worker queue in Node.js can keep your code organized. Here is a small Python example that uses a thread pool for parallel image-to-video generation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;concurrent.futures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_completed&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;image_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;first_frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;end_image_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;last_frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;video_length&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;generate_audio&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reasoning_level&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;video_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;video_url&lt;/span&gt;

&lt;span class="n"&gt;prompts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;slow zoom into the subject&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;handheld reveal from the left&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;top-down shot with a soft shadow&lt;/span&gt;&lt;span class="sh"&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;with&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generate_clip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prompts&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;as_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an illustrative example, not a copy-paste integration for a specific SDK. Use the actual client library and endpoint names for the model you choose. The point is that parallel calls are straightforward and should be part of your design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Audio Output
&lt;/h3&gt;

&lt;p&gt;FLUX 3 Max generates native audio on by default. If your product already has a music bed or a voiceover, you may want to disable audio or replace it later. Native audio is useful for prototypes and for social-ready clips. It also increases the output size, so plan your storage and streaming choices accordingly. If you do not want audio, look for a parameter such as generate_audio and set it to false in your request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storing and Serving Generated Clips
&lt;/h3&gt;

&lt;p&gt;The video API returns a URL. You can treat that URL as the output, but production systems usually copy the video into their own storage. That way you can add retention policies, rewrite URLs, and avoid depending on a temporary host. Cloud object storage with a CDN is a common choice. Keep the video format simple, such as MP4 with H.264, for broad browser support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost Controls and Budgeting
&lt;/h3&gt;

&lt;p&gt;Generate only what you need. Before sending a batch, estimate the number of seconds and the success rate. If a 10-second clip costs $0.43 during the launch discount, a mistaken loop that generates 1,000 variants costs $430. Add a concurrency limit, a daily spending cap, and an alerting mechanism to your integration. The pricing model on fal only charges for successful outputs, but a failed generation still consumes time and may occupy a slot in your queue. Use a small batch for testing before you scale up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retry Logic
&lt;/h3&gt;

&lt;p&gt;Video generation can fail for many reasons: a malformed image URL, an unsupported aspect ratio, a prompt that triggers a safety filter, or a temporary service issue. Build retry logic with exponential backoff for transient errors. Do not retry immediately for errors caused by bad input. Log the failure reason and expose it in a dashboard so you can spot recurring problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting Common Failure Modes
&lt;/h2&gt;

&lt;p&gt;Even with a strong model, things go wrong. Here are the most common failure modes and how to handle them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subject drift. If the main subject changes identity across frames, tighten the start and end frames. Add more descriptive language about the subject's appearance and clothing in the prompt.&lt;/li&gt;
&lt;li&gt;Artificial camera motion. If the camera moves in a way that feels too smooth or too robotic, describe the intended camera operator behavior. Use terms like handheld, tripod, or drone to guide the motion.&lt;/li&gt;
&lt;li&gt;Ignored end frame. If the model does not land on the end frame, check that both frames use the same aspect ratio and resolution. The model needs a clear visual connection between the two frames.&lt;/li&gt;
&lt;li&gt;Unwanted lighting changes. When a scene's lighting shifts suddenly, specify consistent lighting in the prompt. Phrases like "same lighting throughout" can help.&lt;/li&gt;
&lt;li&gt;Audio mismatch. If generated audio does not match the action, disable audio and add a sound design step in post-production.&lt;/li&gt;
&lt;li&gt;Prompt overloading. If a single prompt asks for too many actions, split it into multiple shots. A 10-second clip has room for one primary action, not five.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Practical Evaluation Plan
&lt;/h2&gt;

&lt;p&gt;Before committing to any model, build a small evaluation set that represents your real workload. Include at least ten prompts and ten pairs of start and end frames. Generate the same set with FLUX 3 Max and Seedance 2.5, ideally on the same day to reduce the risk of model updates. Ask at least three people to rank the results without knowing which model produced each clip. Measure the time per clip, the failure rate, and the cost. Then make a decision.&lt;/p&gt;

&lt;p&gt;Automated metrics can supplement human review. Use a simple face consistency check, a frame interpolation sanity check, or a prompt adherence metric if you have one. But human preference is still the closest proxy for whether a clip works. The 1,139 Elo tie means you should let your own eval decide.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to Look For in Human Evaluation
&lt;/h3&gt;

&lt;p&gt;When comparing clips, look for artifacts like morphing, extra limbs, or sudden lighting changes. Check whether the motion follows the prompt. Check whether the end frame matches the requested end frame. Note whether the sound is synchronized with the action. A model can score well on an average clip and still fail on the shots most important to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the best AI image-to-video generator model right now?
&lt;/h3&gt;

&lt;p&gt;FLUX 3 Max on fal is my pick. It is a top image-to-video model on the Artificial Analysis leaderboard, in the leading tier alongside Seedance 2.5 at 1,139 Elo, and about 22 times faster than Seedance 2.5 in fal's benchmark.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is FLUX 3 Max better than Seedance 2.5?
&lt;/h3&gt;

&lt;p&gt;On quality, they are tied at 1,139 Elo. FLUX 3 Max wins on speed, 12.45 seconds versus 277.8 seconds, and on 720p price on fal. If your team prefers Seedance's look, there is no quality argument against keeping it. The operational cost is the main reason to switch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does FLUX 3 Max generate audio?
&lt;/h3&gt;

&lt;p&gt;Yes. Native audio is on by default, with generate_audio set to true. Clips run from 5 to 20 seconds. If you do not need audio, look for a parameter to disable it in your API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to be good at prompting?
&lt;/h3&gt;

&lt;p&gt;No. By default, the reasoning level is set to high, and the model expands short prompts for you. This is useful for exploration. If you need the fastest possible turnaround, set reasoning_level to low, because prompt expansion adds a processing step. For production, test both levels and measure whether the expanded prompts improve adherence enough to justify the extra latency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where can I run it?
&lt;/h3&gt;

&lt;p&gt;On fal, through three endpoints: text-to-video, image-to-video, and keyframes-to-video. All are available from the FLUX 3 Max model page on fal. The same API key works for all three modes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use FLUX 3 Max for long-form video?
&lt;/h3&gt;

&lt;p&gt;Not directly. Individual clips are capped at 20 seconds. For longer content, generate multiple shots and cut them together. The keyframes mode is useful here because you can set the last frame of one shot as the first frame of the next, creating a continuous edit.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about mini-batch jobs for thumbnails and previews?
&lt;/h3&gt;

&lt;p&gt;Use H3 Max for instant previews and FLUX 3 Max for final clips. The camera and prompt inputs stay similar, so you can reuse the same request structure. A 3.49-second preview lets you show a direction to a client, and the 12.45-second final render is still fast enough for a review meeting.&lt;/p&gt;

&lt;h3&gt;
  
  
  How should I handle safety and moderation?
&lt;/h3&gt;

&lt;p&gt;Apply your own content policy on top of the model. Check generated frames for inappropriate content before serving them to users. The provider likely has safety filters, but you should not depend on them as your only layer. Store a moderation decision for each clip if you need an audit trail.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if the price changes after November 7?
&lt;/h3&gt;

&lt;p&gt;The launch discount expires, and the list price becomes $0.17 per second at 720p. That is still competitive. Recalculate your unit economics with the new price. If you have enterprise volume, contact fal sales for custom rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The image-to-video space is moving fast enough that leaderboard positions are more like weather reports than permanent rankings. As of this October 2026 comparison, FLUX 3 Max offers the best balance of quality, speed, and price for most teams. H3 Max is the specialist for instant previews, and Seedance 2.5 remains a solid choice for those who value its ecosystem. The best move is to run a small evaluation, measure cost and failure rate on your own data, and choose a model that fits the way your team actually works.&lt;/p&gt;

&lt;p&gt;If you are starting today, begin with FLUX 3 Max on fal. Try the image-to-video mode with a still frame, then explore keyframes-to-video for storyboarded work. Keep an eye on the Artificial Analysis board and fal's pricing page, because the next model release could change the recommendation again. In AI video generation, the only constant is that the next update arrives before you finish optimizing the current one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pinggy.io/blog/best_ai_image_to_video_generator_by_use_case/" rel="noopener noreferrer"&gt;What Is the Best AI Image-to-Video Generator Model? Picks for 6 Use Cases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fal.ai" rel="noopener noreferrer"&gt;fal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://artificialanalysis.ai" rel="noopener noreferrer"&gt;Artificial Analysis image-to-video leaderboard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article was written in partnership with fal and originally published on the &lt;a href="https://pinggy.io" rel="noopener noreferrer"&gt;Pinggy&lt;/a&gt; blog.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>video</category>
      <category>generativeai</category>
      <category>fal</category>
    </item>
    <item>
      <title>Navigating the 2026 AI Decision Model Landscape: A Deep Technical Dive</title>
      <dc:creator>Lightning Developer</dc:creator>
      <pubDate>Tue, 27 Oct 2026 11:38:00 +0000</pubDate>
      <link>https://dev.to/lightningdev123/navigating-the-2026-ai-decision-model-landscape-a-deep-technical-dive-15ll</link>
      <guid>https://dev.to/lightningdev123/navigating-the-2026-ai-decision-model-landscape-a-deep-technical-dive-15ll</guid>
      <description>&lt;p&gt;In the modern software ecosystem, the vast majority of AI interactions are not creative writing tasks. Whether it is routing a support ticket, validating a tool call, or scoring an incident, these operations represent discrete, bounded decision problems. Relying on generative chat models to produce prose that must then be parsed into a programmatic condition is inherently suboptimal. It introduces unnecessary latency, ballooning costs, and the risk of malformed output. Instead, we have entered the era of the specialized decision model.&lt;/p&gt;

&lt;p&gt;Since the introduction of &lt;a href="https://typesafe.ai" rel="noopener noreferrer"&gt;TypeSafe&lt;/a&gt; Jev, the market has exploded with high-performance alternatives from OpenAI, Microsoft, Perplexity, Cloudflare, Liquid AI, and AWS. The &lt;a href="https://github.com/multimodalart/jev-decision-index" rel="noopener noreferrer"&gt;Decision Index&lt;/a&gt; now tracks 174 distinct entries. These models output typed probabilities across a set of defined answers in a single forward pass, typically billed at a fraction of the cost of their conversational counterparts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feao96jixwe8oe6v7du9m.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feao96jixwe8oe6v7du9m.webp" alt="Blog Image" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Architectural Divergence: Decision Models vs. Chat Models
&lt;/h3&gt;

&lt;p&gt;When working with a standard chat model, you essentially treat it as a black box that accepts a prompt and produces a token stream. Decision models, however, operate on a fixed contract. You provide a state and a set of questions with predefined types. These types generally fall into three categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;noul&lt;/code&gt;: A binary yes/no probability.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;choice&lt;/code&gt;: Selecting from a specific list of options.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;score&lt;/code&gt;: Evaluating an input on a continuous or ordinal scale.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The core advantage here is that the model processes all possible answers in a single pass. There is no risk of the model hallucinating an answer outside of your allowed set. The wire format is increasingly standardized around the &lt;a href="https://typesafe.ai" rel="noopener noreferrer"&gt;TypeSafe&lt;/a&gt; POST &lt;code&gt;/v1/systemone&lt;/code&gt; specification, which allows developers to switch providers with minimal configuration changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Benchmarking Conundrum
&lt;/h3&gt;

&lt;p&gt;Determining which model is best is non-trivial because the available benchmarks are optimized for different metrics. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://jevbench.com" rel="noopener noreferrer"&gt;JevBench&lt;/a&gt; focuses on a harmonic mean of intelligence, speed, and cost, heavily favoring lightweight, performant hosted APIs.&lt;/li&gt;
&lt;li&gt;  The &lt;a href="https://github.com/multimodalart/jev-decision-index" rel="noopener noreferrer"&gt;Decision Index&lt;/a&gt; balances public benchmarks with private domain tasks and filters out slower models, making it a better indicator of real-world precision.&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://artificialanalysis.ai" rel="noopener noreferrer"&gt;Artificial Analysis&lt;/a&gt; stands out by comparing decision models against traditional LLMs, finding that while decision models win on cost and speed, they can occasionally be overconfident on complex knowledge queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0qm7qnwkw70sq2rpdjpc.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0qm7qnwkw70sq2rpdjpc.webp" alt="Blog Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A Comparative Analysis of Top Contenders
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. TypeSafe Jev
&lt;/h4&gt;

&lt;p&gt;Jev remains the industry benchmark. With a cost of $0.042 per million input tokens and output tokens provided for free, it is highly economical. The recent 1.14 update introduced experimental features like bounding boxes and expanded context windows. While some minor discrepancies exist between benchmark rankings for different versions, Jev remains the safest "default" choice for high-reliability integrations.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Liquid AI d1-ultra and d1-3B
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://liquid.ai" rel="noopener noreferrer"&gt;Liquid AI&lt;/a&gt; is currently setting the pace with its hosted d1-ultra model. It consistently outperforms Jev on JevBench while maintaining competitive pricing. For local execution, the d1-3B model is exceptionally fast, though developers should be mindful of the &lt;a href="https://liquid.ai/license" rel="noopener noreferrer"&gt;LFM Open License&lt;/a&gt; commercial revenue caps.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Perplexity pplx-decider v1.2
&lt;/h4&gt;

&lt;p&gt;For those needing an open-weight solution from a reputable vendor, &lt;a href="https://www.perplexity.ai" rel="noopener noreferrer"&gt;Perplexity&lt;/a&gt; offers the most efficient models. The 27B variant is a fine-tune of Qwen and boasts the highest accuracy scores of any model on this list according to the Decision Index. It is an excellent choice if you have the VRAM to support it.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Microsoft-Decision-1
&lt;/h4&gt;

&lt;p&gt;Hosted on &lt;a href="https://ai.azure.com" rel="noopener noreferrer"&gt;Microsoft Azure AI Foundry&lt;/a&gt;, this model excels in enterprise environments where data residency and existing infrastructure are key. It implements the standard TypeSafe protocol, making it a drop-in replacement for Jev-based codebases.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. OpenAI Decisions API
&lt;/h4&gt;

&lt;p&gt;If you already utilize &lt;a href="https://openai.com" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt; services, the new &lt;code&gt;/v1/decisions&lt;/code&gt; endpoint for gpt-6-luna is the most seamless integration path. It offers the same compliance and security guarantees as the rest of the OpenAI ecosystem, though at a slightly higher price point.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Cloudflare Clef
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.cloudflare.com" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt; has integrated decision models directly into the Workers ecosystem via &lt;a href="https://developers.cloudflare.com/workers-ai/" rel="noopener noreferrer"&gt;Workers AI&lt;/a&gt;. Clef and Clef-flash are optimized for low-latency edge computing. The availability of Clef-omni, which handles audio and video, provides a unique capability for multi-modal decision pipelines.&lt;/p&gt;

&lt;h4&gt;
  
  
  7. AWS Strands Decider 2B
&lt;/h4&gt;

&lt;p&gt;Built by the &lt;a href="https://aws.amazon.com" rel="noopener noreferrer"&gt;AWS&lt;/a&gt; Strands Labs team, this is the premier model for local, self-hosted deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"strands-decider @ git+https://github.com/strands-labs/strands-decider"&lt;/span&gt;
strands-decider serve StrandsAgents/strands-decider-2B-qwen3.5-v1-2610 &lt;span class="nt"&gt;--port&lt;/span&gt; 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Production Considerations and Implementation
&lt;/h3&gt;

&lt;p&gt;Running these models locally or in production requires careful attention to input construction. As demonstrated by our testing, the phrasing of your questions and the descriptive labels provided for choices significantly affect the resulting probabilities. &lt;/p&gt;

&lt;p&gt;When building out your infrastructure, we recommend implementing a "shadow mode" testing phase. Capture a sample of your traffic, run it through multiple candidate models, and track your metrics over a week. Do not rely solely on third-party leaderboard scores; your specific domain data will behave differently than generalized benchmarks.&lt;/p&gt;

&lt;p&gt;To ensure low latency during inference, ensure your environment is configured for the appropriate hardware backend. For Apple Silicon users, utilizing the MPS backend via PyTorch is essential. If you are deploying on server-grade hardware, prioritize models that support continuous batching and quantization to keep costs down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Complexity and Edge Cases
&lt;/h3&gt;

&lt;p&gt;Decision models are essentially probability engines. When a model returns a confidence score below a certain threshold (e.g., 0.85), you must have a fallback mechanism. This could involve triggering a human-in-the-loop review process or escalating the request to a more capable generative model.&lt;/p&gt;

&lt;p&gt;Furthermore, keep track of model versioning. As shown with Jev 1.13 vs 1.14, performance can fluctuate between minor releases. Always pin your model versions in production and run automated regression tests against your core decision-making logic whenever you update to a new checkpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting and Optimization
&lt;/h3&gt;

&lt;p&gt;If you find your model is failing on specific categories of input:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Context Clutter:&lt;/strong&gt; Ensure you are not sending unnecessary context in the state object. Keep it focused on the decision task.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Calibration:&lt;/strong&gt; Check your Expected Calibration Error (ECE). If the model's confidence is consistently misaligned with its accuracy, you may need to apply a scaling factor or reconsider your question framing.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Ambiguity:&lt;/strong&gt; Ensure your option definitions are mutually exclusive. If there is overlap in the semantic definition of two choices, the model will struggle to assign a distinct probability.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In conclusion, the right decision model is the one that best balances cost, speed, and accuracy for your unique workload. With the current rate of innovation, we expect these models to become even more specialized by the end of 2026. Start with Jev for prototyping, test Perplexity or Liquid for performance, and lean on enterprise-grade endpoints from Microsoft or OpenAI for production compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pinggy.io/blog/best_ai_decision_models/" rel="noopener noreferrer"&gt;7 Best AI Decision Models in 2026, Compared on Independent Benchmarks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jevbench.com" rel="noopener noreferrer"&gt;JevBench Independent Leaderboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/multimodalart/jev-decision-index" rel="noopener noreferrer"&gt;Decision Index&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
