Core Primitives

Tunnels

A tunnel exposes a local port on a public HTTPS URL. The CLI provisions it, debits your credits, and connects via WebSocket - all automatically (run otterkit login once first). Billed at 1 credit ($0.01) per connected hour, first hour charged up front, capped at 10 credits/day per tunnel.

bash
npx otterkit tunnel <port>
bash
$ npx otterkit tunnel 3000
Checking login…
Provisioning tunnel...
Tunnel provisioned: tunnel-a1b2c3d4

  Tunnel ready: https://tunnel-a1b2c3d4.otterkit.app -> 127.0.0.1:3000

  Press Ctrl+C to disconnect

  200 GET / (12ms)
  200 GET /api/users (8ms)

Options

OptionWhat it does
<port>The local port to forward to (1-65535)
--subdomain <name>Reserve a stable name (up to 10 per workspace); the URL survives restarts
--ttl <duration>Auto-stop server-side, e.g. 45m, 4h, 7d (default 24h, max 7d)
--daemonDetach into the background; manage with status / stop
--logCapture every request to a local JSONL log for inspect / replay
--auth user:passHTTP Basic auth, enforced on your machine before forwarding
--jsonMachine-readable output (errors too, exit code 1)

Stable URLs with --subdomain

By default every tunnel gets a random tunnel-… name. Pass --subdomain to reserve a name to your workspace: restart the tunnel any time and the URL stays the same, so webhooks, OAuth redirect URIs, and bookmarks keep working.

bash
# First run reserves the name; later runs reuse it
npx otterkit tunnel 3000 --subdomain my-api
# -> https://my-api.otterkit.app, every time

Auto-stop with --ttl

A TTL is a safety net for forgotten tunnels: the tunnel stops itself server-side when the timer runs out, even if the process is still up.

bash
npx otterkit tunnel 3000 --ttl 45m    # quick review call
npx otterkit tunnel 3000 --ttl 4h     # an afternoon demo
npx otterkit tunnel 3000 --ttl 7d     # the maximum

Background tunnels with --daemon

bash
# Stable background tunnel for the workday
npx otterkit tunnel 3000 --subdomain my-api --ttl 8h --daemon

# See what's running, then stop it by name
npx otterkit status
npx otterkit stop my-api

Capture traffic with --log

Add --log to also capture every request (headers, body, status, duration) to a local JSONL file:

bash
npx otterkit tunnel 3000 --log

# Later: browse, follow, filter, or export what came in
npx otterkit inspect tunnel-a1b2c3d4
npx otterkit inspect tunnel-a1b2c3d4 --follow --status 5xx

Protect the URL with --auth

The check runs in the CLI on your machine, before anything reaches your local server: requests without valid credentials get a 401 and are never forwarded. Credentials are not sent to or stored by OtterKit's servers.

bash
npx otterkit tunnel 3000 --auth admin:s3cret
curl -u admin:s3cret https://tunnel-a1b2c3d4.otterkit.app/

Use cases

Demo work-in-progress. Share the dev server you already have running - a client sees your machine over HTTPS, no deploy:

bash
npx otterkit tunnel 5173 --ttl 4h        # Vite, auto-stops after the demo

Develop against real webhooks. Give Stripe or GitHub a stable URL that forwards straight into your local handler:

bash
npx otterkit tunnel 3000 --subdomain stripe-dev --log
# paste https://stripe-dev.otterkit.app/webhook into the Stripe dashboard

Test from a phone or another machine. Point any device at the public URL and hit your local build.

Agents exposing their own work. An agent that just built a web app can provision its own preview URL and hand it back - no human in the loop:

bash
npx otterkit tunnel 3000 --daemon --ttl 2h --json
# {"subdomain":"tunnel-…","publicUrl":"https://tunnel-….otterkit.app","pid":51423}
Running several tunnels for one project? Define profiles in otterkit.toml and bring the whole environment up with one command - see Project Config (up). Live tunnels appear on the portal's Tunnels page with real-time status.