# OtterKit - Full Documentation > OtterKit provides two primitives for developers and AI agents: always-on webhook endpoints (permanent URLs served on OtterKit's edge that answer senders, verify signatures, store history, and forward events 24/7 - no server of yours involved) and instant HTTPS tunnels to any local port. Drive both from the web console (console.otterkit.com), the agent-first CLI with built-in MCP server, or OtterKit for Mac - a free native companion app. One login covers all surfaces. Billing is prepaid credits - 1 credit ($0.01) per connected hour; no endpoint bills more than 300 credits ($3) per rolling 30 days - webhooks and tunnels alike. ## Key Links - [Home](https://www.otterkit.com) - [Features](https://www.otterkit.com/#features) - [Pricing](https://www.otterkit.com/#pricing) - [FAQ](https://www.otterkit.com/#faq) - [Mac App](https://www.otterkit.com/#mac) - [Download (Apple Silicon)](https://releases.otterkit.com/latest/OtterKit_aarch64.dmg) - [Download (Intel)](https://releases.otterkit.com/latest/OtterKit_Intel.dmg) - [Summary for LLMs](https://www.otterkit.com/llms.txt) --- ## OtterKit for Mac (free desktop companion) A free native macOS app that complements the CLI and console. It is never a dependency - webhook endpoints are answered on OtterKit's edge whether the app is running, closed, or never installed. Why it exists: ### One live window for everything Every tunnel and webhook on the account - created from the app, any terminal, or by an AI agent - appears in one window with traffic streaming in live. The app shares the CLI's credentials and session records, so there is nothing to connect or sync. Log in from the app and the terminal and agents are logged in too, and vice versa. ### One-click tunnels for local ports The dashboard lists the ports actually listening on the Mac (with process names) and puts an Expose button next to each - same result as `npx otterkit tunnel `, no terminal needed. Stat cards show live sessions, current spend, and the projected monthly cost if everything keeps running. ### Full configuration, edited live Creating an endpoint in the app has full parity with CLI flags: custom responses (status/body/headers/delay), signature verification (16 provider presets + generic HMAC), forwards, email notifications, TTLs with cost labels. Configuration is editable while a session runs - including sessions started by the CLI or an agent - and applies without a restart. ### Spend guardrails (run locally, so they live in the app) - Stop tunnels on sleep (default on): a sleeping Mac can't serve a tunnel, so the app stops them before they keep billing. - Idle nudges (default 3h): a native notification when a tunnel has had no traffic but is still running. - Cost-labeled TTL picker (default 8h): every TTL option shows worst-case cost; "never" on a tunnel needs explicit confirmation. - Menu bar: always shows how many sessions are live and the projected monthly spend. Webhook endpoints are never auto-stopped - they're meant to be always-on and are hard-capped at $3/month each. ### Ended sessions Stopped and expired sessions keep their captured traffic for 7 days (up to 20 sessions) and can be restarted on their original URL. Closing the window hides the app to the menu bar so guardrails keep running; quit entirely and sessions simply continue as the CLI left them. Signed, notarized, auto-updating. Requires macOS 12+. --- ## OtterKit CLI - Tunnels for AI Agents AI agents can provision and use tunnels programmatically via the OtterKit CLI. No desktop app installation required. Payment uses prepaid OtterKit credits (1 credit = $0.01), metered by connected time: 1 credit per connected hour (first hour charged at provision). No endpoint bills more than 300 credits ($3) per rolling 30 days - webhooks and tunnels alike. Billing pauses while disconnected. Sessions run until stopped, with an optional auto-stop TTL (default 24h, or "never"). ### Setup (one-time) ```bash npx otterkit login # opens the browser; sign in and approve the device ``` A token is saved to `~/.otterkit/credentials.json`. After that, the CLI - and any agent on the same machine - provisions automatically, debiting the account's credit balance. Buy credits at https://console.otterkit.com (new accounts get a free-credit grant). Check with `npx otterkit whoami` or `npx otterkit balance`. For headless/CI agents, set the `OTTERKIT_TOKEN` environment variable (create a token at console.otterkit.com → API Tokens) instead of running `otterkit login`. ### Foreground Tunnel Expose a local port to the internet. Stays alive while the terminal is open. Metered at 1 credit/hour (first hour charged at provision). Auto-reconnects on network interruption or laptop wake. ```bash npx otterkit tunnel npx otterkit tunnel 3000 --host 0.0.0.0 ``` ### Daemon Tunnel (background) Run a tunnel in the background as a detached process. Survives terminal close. Same metered pricing (1 credit/hour); the TTL is an auto-stop, not a price. ```bash npx otterkit tunnel --daemon [--ttl ] npx otterkit tunnel 3000 --daemon # auto-stops after 24h (default) npx otterkit tunnel 3000 --daemon --ttl 4h # auto-stops after 4 hours npx otterkit tunnel 3000 --daemon --ttl 3d # auto-stops after 3 days (or --ttl never) ``` ### Webhook Command Create a webhook endpoint that captures incoming HTTP requests without needing a local server. Same pricing as tunnels. ```bash npx otterkit webhook # Foreground webhook (1 credit/hr) npx otterkit webhook --daemon --ttl 4h # Background webhook, auto-stops after 4h ``` #### How Webhooks Work 1. The CLI charges 1 credit (first hour) and provisions an ALWAYS-ON webhook endpoint (e.g., `https://hook-e5f6g7h8.otterkit.app`) - OtterKit's servers answer every request (default response + dynamic rules); the terminal is a live viewer 2. Point any third-party service (Stripe, GitHub, Slack, etc.) to the webhook URL 3. All incoming HTTP requests are captured and displayed in the terminal with full headers, body, and metadata; requests arriving while the terminal is away (up to 200) replay into the log on reconnect 4. Every request is also saved to `~/.otterkit/requests/.jsonl` - view it any time with `npx otterkit inspect ` 5. Lifetime: a foreground run stops the endpoint on Ctrl+C; `--daemon` (or console) endpoints run until their TTL or `otterkit stop` #### Payload Drift Watch (Drift tab - cloud endpoints) CLOUD ENDPOINTS ONLY (CLI endpoints keep captures local; drift routes answer cli_session). Enable Drift Watch from the endpoint page's Drift tab (or MCP schema_drift) and OtterKit learns the JSON shape of what the endpoint receives - one baseline per event group, keyed by the request's first path segment plus the payload's own discriminator field (`type`/`event`/`event_type`/`action`/`kind`), so providers routed to different paths of one endpoint never share a baseline. Groups arm after ~20 samples; every arriving body is then diffed inline (bounded walk: depth 6, 80 props/object, 256KB bodies) and changes become findings: new_field, missing_field (a >=95%-present field absent 5 consecutive requests), type_change, format_change (ISO date-time became a plain string), new_event_type. Findings dedupe by (group, path, kind) and alert exactly once via a throttled email digest (max 1 per 15 minutes, 10/day) - the baseline then follows reality, so repeats bump a counter instead of re-alerting. Accept closes a finding; Ignore closes it and mutes that dot-path from all future detection (reversible). With signature verification armed, only requests whose signature verified 'valid' feed the baseline. Agents: MCP `schema_drift` tool (status/enable/disable/accept/ignore/unignore) or `GET/PATCH /api/agent/tunnels//drift` + `POST /drift//resolve` with the connect token. #### Webhook Examples ```bash # Create a foreground webhook endpoint npx otterkit webhook # Create a background webhook, auto-stops after 4 hours npx otterkit webhook --daemon --ttl 4h ``` #### Custom Auto-Response (--respond / --respond-body / --respond-content-type) By default a webhook endpoint answers every request with `200 {"received":true}`. Some providers require a specific status, body, or content type before they deliver events (challenge echoes, strict 2xx checks): ```bash npx otterkit webhook --respond 204 npx otterkit webhook --respond 200 --respond-body '{"challenge":"accepted"}' npx otterkit webhook --respond-body '' --respond-content-type 'application/xml' npx otterkit webhook --respond-header 'X-Handled: yes' --respond-delay 2000 ``` Extra headers (--respond-header, repeatable, max 16) and an artificial delay (--respond-delay, up to 10s - simulate a slow consumer); the server applies the whole response config to every answer. Cloud (console-created) endpoints configure the same response from the endpoint page, editable after creation - changes apply to the live endpoint immediately (PATCH /api/console/webhooks/:id/respond; no restart, no new charge). #### Dynamic Responses (--respond-match / response rules) An endpoint can answer differently per request via response rules: ordered, first-match-wins, each matching on method, path prefix, or a JSON body dot-path, and answering with its own status/body/content-type/headers/delay; no match falls through to the default response. Rule bodies may be templates rendered per request with the transform placeholders ({{dot.path}}, {{$method}}, {{$path}}, {{$body}}, {{$header.}}) - a Slack URL-verification echo is one rule: match type=url_verification, body {"challenge": "{{challenge}}"}. Failure simulation is another: match path=/flaky, status 500. CLI: `--respond-match k=v` (repeatable clauses) binds the --respond* flags into one conditional rule; bodies containing {{ render as templates. Console: the Response card's Rules list manages the full ordered set (add/edit/reorder/delete, presets for Slack challenge / echo body / simulate failure), applied live via PATCH /api/console/webhooks/:id/respond-rules {rules: [...]|null}; agent API equivalent PATCH /api/agent/tunnels//respond-rules?token=. Up to 20 rules; bodies up to 4KB. Response SCRIPTS (phase 2): a rule's body can be a JS/TS module run per request in the same no-egress isolate as forward transforms (globalOutbound:null, 50ms CPU, sucrase TS strip, content-hash isolate cache). Contract: export default {respond(req)} (or export function respond) where req={method,path,headers,text,json}; return {status?, body?, contentType?, headers?, delayMs?} (fields override the rule's own), a string (body with the rule's status/content-type), any other object (JSON body), or null (fall through to the next rule / default). Validated in a throwaway isolate at arm time (syntax error / missing respond export = 400). Runtime failures fail OPEN - the request falls through and the sender is always answered. Script bodies capped at 16KB. CLI: --respond-script (optionally scoped with --respond-match); console: the rule modal's "JS script" mode with a Random-chaos preset. Use cases: random failure injection for retry testing, rate-limit simulation, computed/faker-style mock responses. Server-side answering: when rules are armed - and always, for console (cloud) endpoints - the session's Durable Object answers every request itself, even with a live-feed page or CLI attached (they receive display-only mirrors). Responses are identical whether or not anything is connected. #### Inbound Signature Verification (--verify) Arm an endpoint with the provider's signing secret and the server checks every arriving request's signature AT ARRIVAL - before you look at it, even while your machine is off. The verdict (valid | invalid | missing) badges every capture in the console feed, is stored with the request (filter history with ?verify=valid|invalid|missing), and reaches a connected local server as an `X-OtterKit-Verified` header. Verification never blocks a request; invalid requests are answered and captured normally, just flagged. Failure reasons are specific: signature_mismatch, timestamp_out_of_tolerance, missing_signature_header, etc. ```bash npx otterkit webhook --verify stripe:whsec_abc123 npx otterkit webhook --daemon --verify github:my-webhook-secret ``` Provider presets: stripe, github, shopify, slack, svix (covers Resend, Clerk, Polar, and every Svix-powered sender), paddle, zoom, linear, lemonsqueezy, dropbox, gitlab, twilio, square, hubspot, discord, sendgrid. Twilio/Square/HubSpot sign the full endpoint URL - reconstructed automatically. Discord (Ed25519) and SendGrid (ECDSA P-256) verify with a PUBLIC key: paste the hex (discord) or base64 (sendgrid) key as the secret. Cloud endpoints configure verification from the Verification card on the endpoint page (PATCH /api/console/webhooks/:id/verify; applies to the live endpoint immediately), which adds a generic HMAC mode: any signature header, HMAC-SHA1/256/512, hex or base64 encoding, optional prefix (e.g. sha256=), and optional timestamp.body signed-payload with a timestamp header. Timestamped schemes (Stripe, Slack, Svix, Paddle, Zoom) also check signature age (default tolerance 300s, configurable). All comparisons are constant-time (WebCrypto verify), and inbound attempts to spoof X-OtterKit-Verified are stripped. Also available on the MCP webhook_create tool via the verify parameter. Related but different: `otterkit verify --secret ...` checks signatures on already-captured requests locally (forensics); `--verify` is the live, server-side version. #### Notifications Endpoints can notify you when requests arrive, four ways (on the endpoint's console page, or CLI flags at creation): (1) Browser notifications - the bell on the live-traffic header fires a desktop notification when a request lands while the tab is in the background; bursts coalesce, clicking focuses the tab. (2) Email on arrival - the mail toggle or `--notify-email` emails the account owner on arriving requests, working with the page closed and machine off; edge-triggered and throttled (max 1 email/15min per endpoint, 20/day; suppressed arrivals fold into a count in the next email; CLI/agents can also toggle it via `PATCH api.otterkit.com/api/me/webhooks//notify {"email":true}`). (3) Conditional email rules - the Notifications tab takes the same match clauses and transform templates/scripts as forwarding rules but delivers to the owner's inbox instead of a URL ("email me when event.type=payment.failed", body shaped by the template/script), throttled per rule; from the CLI: `--notify-match event.type=payment.failed` (repeatable). (4) Push notifications via Web Push (desktop & phone) - "Push notifications" in the Notifications tab or `--notify-push [k=v]` delivers matching requests to every device enabled on the account, over standard Web Push (RFC 8291/8292): no app, no third-party service, payloads end-to-end encrypted to each browser. One-time setup per device: Notifications tab -> "Enable on this device" (registers the console's service worker and stores the subscription; up to 10 devices per account, manageable in the same panel). Works on Android and desktop browsers directly; on iPhone add the console to the Home Screen first (Safari ships Web Push for installed web apps only), then enable from the installed app. Every push carries a title (subdomain · METHOD /path), a tap-through link back to the endpoint's traffic, and a "+N more" fold for suppressed matches. Throttled max 1 push/min per rule, 300/day per endpoint; dead subscriptions are pruned automatically. Agents: MCP `webhook_forwarding` with `destination: 'push'`, or the waits API with `{destination: 'push'}`. #### Forwarding & Transforms Forwarding is webhook config: every request matching a rule is also delivered to the rule's callback URL - HMAC-signed (X-OtterKit-Signature: sha256= over the delivered body), retried with backoff (1/2/4/8 min, five attempts), even while your machine is off - until the rule is removed. Match clauses (all must hold): method= (exact), path= (prefix), any other key is a dot-path equality into the JSON body. Up to 20 rules per endpoint. A callback that keeps failing pauses the rule. Rules take an optional requireVerified flag (checkbox in the console rule modal, --verified-only on the CLI, requireVerified on the APIs/MCP): the rule then fires ONLY on requests whose inbound signature verified valid - tampered and unsigned requests are skipped, and with verification unarmed a gated rule fires for nothing (fail-closed). By default a rule delivers a JSON envelope `{ruleId, deliveryId, subdomain, matchedAt, request: {method, path, headers, body(base64)}}`. Add a transform template and matching requests deliver the rendered template instead - `{{data.object.id}}` pulls a dot-path from the JSON body; built-ins are `{{$method}}`, `{{$path}}`, `{{$body}}` (raw body), `{{$header.}}`. String values are JSON-escaped so a quote in the payload can't break a JSON template; missing paths render empty; templates are validated at rule creation (bad template = 400). The rendered body is what gets signed. ```bash # Every matching request delivered to your CI npx otterkit webhook --forward https://ci.example.com/hooks --match event.type=payment.succeeded # Stripe payment → Slack message (no relay server; any incoming-webhook service works) npx otterkit webhook --match type=payment_intent.succeeded \ --forward https://hooks.slack.com/services/T00/B00/xxxx \ --forward-transform '{"text": "💰 Paid: {{data.object.amount}} {{data.object.currency}}"}' # Template from a file, delivered as plain text (ntfy.sh push) npx otterkit webhook --forward https://ntfy.sh/my-topic \ --forward-transform @notify.tpl --forward-content-type text/plain ``` Manage rules on the endpoint's console page (template editor with presets for Slack/Discord/ntfy) or via the MCP webhook_forwarding tool. For logic a template can't express (conditionals, computed fields, reshaping arrays), attach a script instead of a template - real JavaScript run per matching request in a dedicated V8 isolate (Cloudflare Dynamic Workers) with NO network access (globalOutbound: null) and a 50ms CPU cap. Export default {transform(req){...}} where req is {method, path, headers, text, json}; return string | {body, contentType?} | object (JSON-encoded) | null (skip this delivery). The script never sees the signing secret (delivery stays with OtterKit), is validated in a throwaway isolate at rule creation (syntax error / missing export = 400), and caches by content hash (~$0.06/month per unique script). A script that throws/times out fails that delivery like any other failure; keep failing and the rule pauses. Template or script, not both on one rule. ```bash npx otterkit webhook --forward https://hooks.slack.com/services/... --forward-script @transform.js ``` ```js // transform.js export default { transform(req) { // req: { method, path, headers, text, json } const amount = req.json?.data?.amount ?? 0; if (amount < 100) return null; // skip this delivery return { body: JSON.stringify({ text: `Paid $${(amount / 100).toFixed(2)}` }) }; }, }; ``` #### Server-Side History (requests) Cloud endpoints store every capture server-side (up to 10,000 per endpoint); CLI endpoints keep captures in their local log only. Read cloud history from any machine on the account: ```bash npx otterkit requests # stored history, newest first npx otterkit requests --json # full fidelity: headers + base64 body npx otterkit requests --method POST --path /stripe --limit 100 --body ``` Token-authed API equivalents: `GET https://api.otterkit.com/api/me/webhooks` (list), `/api/me/webhooks//requests` (paged; ?limit/?before/?q/?method/?verify), `/api/me/webhooks//requests/export?format=json|csv`, `POST /api/me/webhooks//requests//replay` ({targetUrl, method?, headers?, bodyText?} - re-deliver a stored capture, transport headers stripped, X-OtterKit-Replay marker; also the MCP history_replay tool). Custom domains ride the same surface: `GET/POST/DELETE /api/me/webhooks//domain` (POST {hostname} returns the CNAME target; also `otterkit domain` and the MCP webhook_domain tool). Auth: `Authorization: Bearer `. History is also the MCP webhook_history tool. CLOUD ENDPOINTS ONLY: for CLI-created endpoints these history routes (list/export/replay) answer 409 {error:'cli_session'} with hints - captures live in the local log on the owning machine (~/.otterkit/requests/.jsonl; use requests_list/request_await MCP tools or `otterkit inspect` there). CLI-endpoint captures never leave the owning machine; create endpoints from the console when you need server-stored, cross-machine history. On the endpoint's console page you can export JSON/CSV and resend any stored capture to a target URL, editing method/headers/body first. ### Capture Tunnel Traffic (--log) Add `--log` to a tunnel to capture every request to a local JSONL file while forwarding to your local server. Useful for debugging webhook integrations against a real server. ```bash npx otterkit tunnel --log # Forward + capture (1 credit/hr) npx otterkit tunnel --log --daemon --ttl 4h # Background capture tunnel ``` Every incoming HTTP request is captured to `~/.otterkit/requests/.jsonl` with response status and duration. (Webhook endpoints log to the same place automatically - for capture without a local server, use `npx otterkit webhook`.) #### Inspect Command View requests captured by `webhook` or `tunnel --log`: ```bash npx otterkit inspect # Pretty-print last 20 requests npx otterkit inspect --json # Raw JSONL output (pipe-friendly) npx otterkit inspect --last 50 # Show last 50 requests npx otterkit inspect --follow # Live-tail new requests (Ctrl+C to stop) npx otterkit inspect --method POST --status 5xx --path /hook # Filters npx otterkit inspect --har # Export HAR 1.2 (browser devtools / HAR viewers) ``` Filters combine and work with `--json`, `--follow`, and `--har`. `--status` accepts an exact code (`500`) or a class (`5xx`). Each captured request includes: timestamp, method, path, headers, body (base64), response status, and duration. The JSONL format (one JSON object per line) is streamable and works with standard tools like `jq`, `tail -f`, etc. #### Replay Command Re-send a captured request to the local target - no tunnel round-trip, no credits spent. The replayed exchange is appended to the capture log so it shows up in `inspect`. ```bash npx otterkit replay # Re-send the latest captured request npx otterkit replay --index 3 # Re-send request #3 (1 = oldest, -1 = latest) npx otterkit replay --target 127.0.0.1:3000 # Explicit target (webhook captures / stopped tunnels) npx otterkit replay --json # Machine-readable response: {status, headers, body (base64), durationMs} # Modify the request before re-sending npx otterkit replay --method PUT --path /v2/hook -H "X-Debug: 1" --body '{"event":"payment.failed"}' ``` The target defaults to the running daemon's host:port for that subdomain; pass `--target` otherwise. Exit code is 0 whenever the local server responded (even 4xx/5xx), 1 if it was unreachable. ### Basic Auth (--auth) Protect a tunnel's public URL with HTTP Basic auth. Enforced by the CLI on the local machine before anything reaches the local server - unauthenticated requests get a `401` with a `WWW-Authenticate` challenge and are never forwarded. Credentials are not sent to or stored by OtterKit's servers. Free - auth checks don't consume credits. ```bash npx otterkit tunnel 3000 --auth admin:s3cret # Only valid Basic auth reaches port 3000 curl -u admin:s3cret https://tunnel-xxxx.otterkit.app/ ``` Works in foreground and `--daemon` mode. Tunnel command only (webhook endpoints are capture-only). ### Local Delivery (--deliver / --deliver-exec) Server-answered endpoints never need your machine - `--deliver` adds a local mirror for development: every capture is ALSO sent to a local server (`--deliver 3000`, host:port, or a URL) while the public URL keeps answering senders on its own. `--deliver-exec 'cmd'` runs a shell command per capture (body on stdin; OTTERKIT_* env vars carry method, path, headers, and the verification verdict); both combine. Local-only plumbing: nothing extra is billed and no server-side rule is created - use `--forward` when delivery must survive your machine sleeping. ### Email Inboxes `npx otterkit webhook --email` (or the console's inbox switch, or `webhook_create` with `email: true` over MCP) gives the endpoint an inbox at `@otterkit.app`. Incoming mail is parsed and lands in the endpoint's feed as a capture with method `EMAIL` and a JSON body envelope ({from, to, subject, text, html, attachments: [{filename, contentType, size}]}) - so stored history, `request_await` (filter method=EMAIL - the way an agent catches an OTP or confirmation link), forwarding rules (email -> HTTP bridge, transforms included), and notifications all work on email exactly like HTTP. The console's Email tab is a two-pane reader with rendered HTML and subject/sender search. Semantics: mail to a disabled inbox, expired/stopped endpoint, or unknown address bounces at SMTP time with the reason (never silently dropped); emails count toward the endpoint's fair-use request cap; envelopes are capped at 64 KB (truncated HTML-first) and attachment contents are not stored. ### Pulses (scheduled HTTP - the third primitive) `npx otterkit pulse 10m https://api.me.com/cron` provisions a server-side scheduler: it calls the URL on the interval (30s-1d floor 30s; --method/--data/-H for the request; --ttl, default never) and stores every response as run history readable with `otterkit requests `, the console Pulses page, or /api/me/webhooks/:name/requests. Failures alert once on the healthy->failing transition and once on recovery. `--expect 15m` flips direction: you get a URL that must be pinged within the window (a backup job, a nightly script); a missed window emails the owner, the next check-in sends the all-clear, and check-ins are captured with their bodies. Pricing: 1 credit per 100 runs (first credit at creation), same $3/30-day cap as every endpoint - a nightly cron is ~1 cent/month. Agents: the `pulse_create` MCP tool provisions either mode; POST /api/agent/pulses is the raw API (returns a stopToken; DELETE /api/agent/tunnels/:name?token= stops it). Edit a live pulse in place - history, URL, and billing window survive; the next run counts from the update - with `otterkit pulse --edit [interval] [url]`, the `pulse_update` MCP tool, or `PATCH /api/me/pulses/:name` ({mode, interval, url?, method?, body?}; `GET /api/me/pulses/:name/config` returns the live config). ### Custom Domains Serve any endpoint on a hostname you own: open the endpoint in the console, enter the hostname in the Custom domain card, and add one CNAME record (`hooks.your-company.com -> customers.otterkit.app`) wherever the domain is hosted. The certificate is issued and renewed automatically (usually live within minutes); the .otterkit.app URL keeps working alongside it. One domain per endpoint; subdomains are the supported shape (apex needs CNAME flattening). Included free. ### Project Config - otterkit up / down Define named tunnel profiles in `otterkit.toml` and bring them all up with one command. Each profile runs as a background daemon (`status`/`inspect`/`replay`/`stop` work on them like any other daemon). `up` is idempotent - already-running profiles are skipped and reported as `already_running`. ```toml [tunnels.web] port = 3000 subdomain = "myapp" # optional stable URL auth = "admin:s3cret" # optional HTTP Basic auth log = true # optional request capture ttl = "8h" # optional auto-stop (default 24h) [tunnels.hooks] webhook = true # capture-only endpoint respond = 200 # optional custom auto-response respond_body = '{"ok":true}' respond_content_type = "application/json" ``` ```bash npx otterkit up # Start everything npx otterkit up --json # Machine-readable results npx otterkit down # Stop the daemons started from the config npx otterkit up --config # Non-default config location ``` Pricing is unchanged: each profile is a normal daemon (1 credit/hour; every endpoint capped at 300 credits per rolling 30 days). ### JSON Output (for agents) `--json` works on `tunnel --daemon`, `webhook --daemon`, `up`, `down`, `status`, `inspect`, `replay`, `subdomains list`, `whoami`, and `balance`. Provision results include `{subdomain, publicUrl, target, pid, ttl, expiresAt, logPath}`. Errors in `--json` mode are JSON as well (e.g. `{"error":"insufficient_credits","balance":0,"topUpUrl":"..."}`) with exit code 1. ### Manage Daemons ```bash npx otterkit status # List running daemon tunnels and webhooks npx otterkit status --json # Machine-readable daemon list npx otterkit stop # Stop a daemon tunnel or webhook ``` ### CLI Options | Flag | Description | Default | |------|-------------|---------| | `--host ` | Local host to forward to | `127.0.0.1` | | `--log` | Capture requests to a JSONL log for `inspect` (tunnel only) | off | | `--daemon` | Run tunnel in background | off | | `--ttl ` | Auto-stop after duration, e.g. 45m, 4h, 3d, or "never" | `24h` | | `--subdomain ` | Use a stable reserved URL (claimed on first use) | auto | | `--auth ` | Require HTTP Basic auth (tunnel only, enforced locally) | off | | `--respond ` | Webhook auto-response status (webhook only) | `200` | | `--respond-body ` | Webhook auto-response body (webhook only) | `{"received":true}` | | `--respond-content-type ` | Webhook auto-response Content-Type (webhook only) | `application/json` | | `--respond-header ` | Extra response header (webhook only, repeatable, max 16) | none | | `--respond-delay ` | Delay before answering, max 10000 (webhook only) | 0 | | `--respond-match ` | Make the --respond* flags a conditional rule (webhook only, repeatable) | off | | `--verify ` | Check each request's provider signature at arrival (webhook only) | off | | `--verified-only` | Rules created by this command only fire on valid signatures (webhook only) | off | | `--notify-email` | Email on arrivals, max 1 / 15 min (webhook only) | off | | `--notify-match ` | Email when a matching request arrives (webhook only, repeatable) | off | | `--notify-push [k=v]` | Web Push to your enabled devices, max 1 / min (webhook only, repeatable) | off | | `--forward ` | Deliver matching requests to this URL, signed, with retries | off | | `--match ` | Filter for --forward: method=, path=, or JSON dot-path (repeatable) | all | | `--forward-transform ` | Deliver a rendered template instead of the raw capture (@file supported) | envelope | | `--forward-script ` | Run JS per match in a no-egress isolate (50ms CPU); return is the delivery body, null skips (@file supported; excludes --forward-transform) | off | | `--forward-content-type ` | Content-Type of the transformed delivery | `application/json` | | `--json` | Machine-readable output (daemon/read commands) | off | ### CLI Pricing (metered) | What | Cost | |------|------| | Per connected hour (any mode) | 1 credit ($0.01), first hour at provision | | Monthly cap (every endpoint) | 300 credits ($3) per endpoint per rolling 30 days - hours beyond are free | | While disconnected | Free - billing pauses | | Auto-stop TTL | Default 24h, up to 7d (safety, not a price) | ### Agent Skill Install the OtterKit skill for AI coding agents (Claude Code, Cursor, Copilot, etc.): ```bash npx skills add useotterkit/skill -g ``` ### API Endpoints | Method | Endpoint | Description | |--------|----------|-------------| | POST | `/api/agent/tunnels` | Provision a foreground tunnel (1 credit; then metered hourly) | | POST | `/api/agent/tunnels/daemon?ttl=4h` | Provision a daemon tunnel (same pricing; ttl = auto-stop) | | GET | `/api/agent/tunnels/:subdomain` | Check tunnel connection status | | DELETE | `/api/agent/tunnels/:subdomain` | Deprovision a tunnel | | GET | `/api/agent/pricing` | Get current pricing | ### How It Works 1. Agent calls `POST /api/agent/tunnels` with `Authorization: Bearer otk_…` (saved by `otterkit login`, or `OTTERKIT_TOKEN`) 2. The server authenticates the token and debits 1 credit (the first connected hour); insufficient balance returns HTTP 402 with a top-up URL 3. Server provisions the tunnel and returns `{ subdomain, publicUrl, connectUrl, pricing, autoStopAt, balance }` 4. CLI connects via WebSocket to `wss://otterkit.app/ws/connect?subdomain=tunnel-xxxx`; each further connected hour debits 1 credit, and the session auto-stops at its TTL (if one is set) 5. All HTTP traffic to `https://tunnel-xxxx.otterkit.app` is forwarded to the local port 6. Two-level keepalive (30s protocol pings + 120s app pings) keeps tunnel alive and cost-efficient --- ## Benefits - Webhook endpoints with no server behind them - answered, verified, stored, and forwarded 24/7 on OtterKit's edge - Tunnels work with any local HTTP server (Node.js, Python, Ruby, Go, PHP, anything) - Agent-first: log in once, every agent on the machine provisions on its own; --json everywhere, MCP server built in - One account, one credit pool, three surfaces: console, CLI, free Mac app - No subscription - prepaid credits, every endpoint hard-capped at $3/month - CLI works on any OS with Node.js 18+; Mac app adds native guardrails (sleep-stop, idle nudges, spend projection) --- ## How to Get Started ### Mac App #### Step 1: Download & Install Download OtterKit for Mac (free): - Apple Silicon (M1-M4): https://releases.otterkit.com/latest/OtterKit_aarch64.dmg - Intel-based Mac: https://releases.otterkit.com/latest/OtterKit_Intel.dmg Drag OtterKit to your Applications folder and open it. #### Step 2: Log in The app walks you through the same browser login as `npx otterkit login` and stores the same credentials - the terminal, agents, and app share one identity from then on. #### Step 3: Everything appears Existing sessions show up immediately with live traffic. Click Expose next to a listening port for an instant tunnel, or create a webhook endpoint from the Webhooks screen. Total setup time: approximately 2 minutes. ### CLI (for AI agents) #### Step 1: Log in (one-time) ```bash npx otterkit login ``` The browser opens; sign in and approve the device. Buy credits at https://console.otterkit.com - new accounts get a free-credit grant. #### Step 2: Tunnel a Port ```bash npx otterkit tunnel 3000 ``` After the one-time login, any agent on the machine provisions automatically using the account's credits. For headless/CI, set `OTTERKIT_TOKEN` instead. Total setup time: under 1 minute. --- ## Pricing ### Mac App Free. No license, no trial, no separate subscription - the app spends the same prepaid credits as the CLI and console, and only when it provisions endpoints. ### CLI (prepaid credits, metered) - 1 credit = $0.01; buy credits at https://console.otterkit.com (new accounts get a free grant) - 1 credit per connected hour, first hour charged at provision - No endpoint bills more than 300 credits ($3) per rolling 30 days - webhooks and tunnels alike; billing pauses while disconnected - Sessions auto-stop after their TTL (default 24h, or "never" to run until stopped) - Webhook / tunnel --log: same pricing as tunnels --- ## Frequently Asked Questions ### What is OtterKit? OtterKit provides always-on webhook endpoints (server-answered permanent URLs that verify, store, and forward events with nothing running on your machine) and instant HTTPS tunnels to any local port. You drive it from the web console, the agent-first CLI with MCP server, or the free OtterKit for Mac app. ### Is it free to try? Yes. New accounts get a free credit grant with no card required, and the Mac app is free outright. After that you buy prepaid credits - there is no subscription. ### Why is there a Mac app if webhooks are server-answered? Because endpoints accumulate across terminals, agents, and the console, and some protections must run locally. The app is the one live window for every session on the account, exposes listening local ports in one click, edits endpoint configuration live, and enforces guardrails a browser can't: stopping tunnels when the Mac sleeps, nudging about idle tunnels, and projecting monthly spend in the menu bar. It is optional - quitting it stops nothing except the guardrails and the live view. ### What operating systems does OtterKit support? The Mac app supports macOS 12.0 (Monterey) and later, Apple Silicon (M1-M4) and Intel. The CLI and MCP server work on any OS with Node.js 18+. The console works in any browser. ### How do tunnels work? Tunnels create a secure connection between a public `*.otterkit.app` URL and your local server. When someone accesses the public URL, the request is securely forwarded to your machine. Tunnels are great for client demos, webhook testing, and mobile device testing, and they bill only while connected. ### Can AI agents use OtterKit? Yes! The OtterKit CLI allows AI agents to provision tunnels programmatically. You log in once with `npx otterkit login` (or set `OTTERKIT_TOKEN`), then agents provision automatically using your prepaid credits - 1 credit ($0.01) per connected hour; no endpoint bills more than 300 credits ($3) per rolling 30 days - webhooks and tunnels alike. No desktop app installation required - just `npx otterkit tunnel `. ### How do daemon tunnels work? Daemon tunnels run as detached background processes that survive terminal close. Billing is the same as any tunnel: 1 credit per connected hour. Daemons auto-stop after their TTL (default 24h, or "never" to run until stopped). Use `npx otterkit status` to check running daemons and `npx otterkit stop ` to stop one. ### How do CLI webhooks work? The `npx otterkit webhook` command creates a webhook endpoint that captures incoming HTTP requests without needing a local server. You get a public URL like `https://hook-xxxx.otterkit.app` that you can point any third-party service to. All incoming requests are captured and displayed with full headers, body, and metadata. Webhooks bill $0.01 per live hour but never more than $3 per rolling 30 days - an always-on endpoint costs at most $3/month. ### How do I capture HTTP requests for inspection? Use `npx otterkit webhook` to capture requests without a local server, or `npx otterkit tunnel 3000 --log` to capture while forwarding to your local server. Both save every request to `~/.otterkit/requests/.jsonl`; view them with `npx otterkit inspect ` and re-send any of them to your local server with `npx otterkit replay `. Same hourly pricing as regular tunnels (1 credit per connected hour; every endpoint capped at $3 per rolling 30 days). ### Can I put an endpoint on my own domain? Yes - custom domains are built in. Attach a hostname on the endpoint's console page, add the one CNAME record it shows you, and the certificate issues automatically. Senders then use https://hooks.your-company.com while OtterKit answers, verifies, and stores as usual. Included free. ### Can I use OtterKit with any local server? Absolutely! Tunnels work with any HTTP server running on your machine - Node.js, Python, Ruby, Go, PHP, or any other framework. Just point OtterKit to the port your server runs on. ### Does OtterKit require an internet connection? Tunnels require an internet connection on your machine since they forward public traffic to it. Webhook endpoints are the opposite: they live on OtterKit's edge and keep answering, verifying, storing, and forwarding even when every machine you own is offline. --- ## System Requirements - **Mac App**: macOS 12.0 (Monterey) or later, Apple Silicon or Intel (free) - **CLI**: Node.js 18+ (macOS, Linux, Windows) ## Contact - Website: https://www.otterkit.com - GitHub: https://github.com/otterkit