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.
npx otterkit tunnel <port>$ 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
| Option | What 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) |
| --daemon | Detach into the background; manage with status / stop |
| --log | Capture every request to a local JSONL log for inspect / replay |
| --auth user:pass | HTTP Basic auth, enforced on your machine before forwarding |
| --json | Machine-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.
# First run reserves the name; later runs reuse it
npx otterkit tunnel 3000 --subdomain my-api
# -> https://my-api.otterkit.app, every timeAuto-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.
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 maximumBackground tunnels with --daemon
# 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-apiCapture traffic with --log
Add --log to also capture every request (headers, body, status, duration) to a local JSONL file:
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 5xxProtect 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.
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:
npx otterkit tunnel 5173 --ttl 4h # Vite, auto-stops after the demoDevelop against real webhooks. Give Stripe or GitHub a stable URL that forwards straight into your local handler:
npx otterkit tunnel 3000 --subdomain stripe-dev --log
# paste https://stripe-dev.otterkit.app/webhook into the Stripe dashboardTest 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:
npx otterkit tunnel 3000 --daemon --ttl 2h --json
# {"subdomain":"tunnel-…","publicUrl":"https://tunnel-….otterkit.app","pid":51423}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.