# TabTabTab Docs

TabTabTab runs coding agents in the background, triggered by webhooks, schedules, Slack, or direct requests. Code changes arrive as pull requests you can verify before you merge; ops and data-analysis jobs skip the PR and report back when done. You create an environment in the dashboard and set everything up inside it; use it in the browser or via the CLI.

Need help, or want a feature that isn't here yet? Email support@tabtabtab.ai.

# TabTabTab Overview

TabTabTab runs coding agents in the background. You create an environment from the dashboard, set up your triggers inside it, and the work comes back to you. Code changes arrive as pull requests you can review and merge. Ops and data-analysis jobs don't need one; the agent just does the work and reports back, for example in Slack, where you can follow up. The agents run on a persistent cloud machine, on your own Codex or Claude Code accounts, around the clock.

## How work starts

Create an environment from the [dashboard](https://dash.tabtabtab.ai), open it, and set up your triggers:

- **[Webhooks](/docs/webhooks):** Put a webhook anywhere in your own app and its events become agent work: intelligence at an API call. External systems like CI, GitHub, or alerting tools can call one too.
- **[Automations](/docs/automations):** Recurring runs on a schedule ("every weekday at 9am, review open PRs and post a digest").
- **[Slack](/docs/slack):** Connect the Slack agent, then mention it in a channel. It picks up the work, does it in the background, and replies in the thread.

## A few live examples

- A user files a bug report, and a pull request with the fix lands in your repo.
- A customer finishes a session with your support agent, and a full analysis gets posted to your team's Slack for follow-up.
- A nightly schedule bumps dependencies and opens a PR only if the test suite still passes.

## Every PR comes with receipts

You should never have to trust an agent blindly. Every pull request TabTabTab opens comes with receipts, so you can verify before you merge:

- A video of the agent testing the change in a real browser.
- A preview URL so you can click through the running app yourself.
- A trace of what triggered the run and what the agent did to produce the change.

See [Receipts](/docs/receipts).

## Where everything lives

Everything starts at the [dashboard](https://dash.tabtabtab.ai). Create an [environment](/docs/environment) there, then use it in the browser or interact with it via the [CLI](/docs/cli-reference).

## What it isn't

- Not a hosted Git provider. Bring your own GitHub repos.
- Not an editor you have to babysit. You can close the tab and the work keeps going.
- Not a managed production runtime. Environments are for development, not for hosting customer traffic.

## Get started

Create an environment from the [dashboard](https://dash.tabtabtab.ai), connect GitHub, add a repo, set your secrets, and wire your first trigger. The [Quickstart](/docs/quickstart) walks you through it.

If something doesn't work or you need a feature that isn't documented, email support@tabtabtab.ai.


# Quickstart

This takes you from nothing to your first background pull request.

## 1. Create an environment

Open the [dashboard](https://dash.tabtabtab.ai), sign in, and click `Create`. Pick a name, confirm your Git author name and email, and submit. The dashboard shows each provisioning step while the cloud machine comes up.

An environment is a long-lived cloud VM that holds your repos, tools, and secrets. Create one per project or per role and keep it around.

## 2. Enable GitHub

From the environment detail page, connect GitHub so the machine can clone, pull, and push with your account.

## 3. Enable Codex

Sign in with Codex so the agents run on your own account. If you skip this, you get $50 in OpenRouter credits out of the box.

## 4. Open the environment, connect a repo, wire a trigger

Open the environment (click `Open` in the dashboard). Add a repository: click the plus button in the left sidebar, choose `Add Repository`, and paste an HTTPS Git URL.

Then pick whichever trigger fits how you want work to arrive:

- **[Webhook](/docs/webhooks):** Click the webhook icon in the bottom-left rail and create one. You get a secret URL; call it from your app or hand it to the system that should start work. Each `POST` to it becomes an isolated agent run.
- **[Automation](/docs/automations):** Open the automation panel (right below the webhook icon) and schedule it, or ask the meta agent: "Every weekday at 9am, review open PRs and post a digest."
- **[Slack](/docs/slack):** Go to `Settings > Slack`, connect your workspace, then `@mention` the bot in a channel.
- **On demand.** Just tell the meta agent what you want done ("fix the flaky tests in api/ and open a PR") right in its chat.

**With the TabTabTab CLI**
The optional [CLI](/docs/cli-reference) can wire the same triggers when you want to automate setup or let a local coding agent drive TabTabTab:

```bash
tabtabtab webhook create bug-reports --project my-app
tabtabtab agent kick "Fix the flaky tests in api/ and open a PR" --project my-app
```

## 5. Get a PR, verify, merge

The agent does the work in the background and opens a pull request with receipts: a video of it testing the change, a preview URL, and a trace of what it did. Review the PR, click through the preview, and merge.

**Optional: install the CLI**
Everything above happens in the dashboard, the environment, and Slack, so most people never need the CLI. Install it if you want to script TabTabTab or drive it from your terminal:

```bash
curl -fsSL https://tabtabtab.ai/install.sh | sh
tabtabtab auth login
```

If you use a local coding agent like Claude Code, the [tabtabtab skill](https://github.com/tabtabtabai/tabtabtab-skill) teaches it the CLI so you can describe the goal instead of memorizing commands. See [CLI Reference](/docs/cli-reference).

Stuck on any step? Email support@tabtabtab.ai with the environment name and what you saw.


# How it works

TabTabTab is built to run agents you don't watch. A few concepts make the difference between "a remote laptop I SSH into" and "work that just shows up as PRs."

## Triggers do the starting.

Most work should start without you. Wire a trigger once and events turn into agent runs on their own:

- A [webhook](/docs/webhooks) call from your app or your tools kicks off a run: intelligence at an API call.
- An [automation](/docs/automations) runs on a schedule, or keeps re-checking a job until it's done.
- A [Slack](/docs/slack) mention picks up the work and replies in the thread.

You can always open the environment and start work by hand, but the product is at its best in the background: events happen, agents work, results come back.

## Environments are your machine.

An **environment** is a long-lived cloud VM. It holds your shell config, tools, secrets, and repos. Create one per project or per role and keep it around. Destroying it is like wiping your laptop.

## Sessions are the unit of work.

A **session** is one agent run on that VM, isolated from everything else and created fresh from `origin/main`. Each piece of background work gets its own, so parallel runs never step on each other. They're cheap and disposable.

## Steer when you want, walk away when you don't.

Because everything runs on the cloud machine, you can close the tab and the work keeps going. Jump into a session to redirect or correct it if the work matters. Otherwise, let it come back to you as a PR. The point of the product is that you don't have to babysit it.

## The meta agent keeps work moving.

The **meta agent** is an always-on agent for your environment: a way to launch jobs anywhere on the machine and check what's going on. You talk to it in plain English, and it routes work to the right project and dispatches **workers**, the isolated sessions that actually edit files and open PRs. See [Meta Agent](/docs/meta-agent).

It also tracks work that isn't one shot. A **durable job** has an explicit end state, and the meta agent keeps re-checking it on its own until it gets there: "land PR #42, keep rebasing and re-running CI until it merges." See [Automations](/docs/automations).

## When in doubt, email us.

If a workflow feels awkward, it usually means the product should do more. Email support@tabtabtab.ai with what you were trying to do.


# Webhooks

A webhook is a secret URL that starts a background agent run every time something POSTs to it. Call it from your own app, so events in your product turn into agent work: intelligence at an API call. External tools like CI, GitHub Actions, alerting, or support products can point at it too.

## Create one

In the environment, click the webhook icon in the bottom-left rail (right below the TabTabTab icon) and create it there, or just ask the [meta agent](/docs/meta-agent) in chat: "create a webhook that files incoming bug reports against my-app." Either way, you pick a target:

- **A project.** Each call runs in a **fresh session** in that repo, isolated from everything else running.
- **The meta agent.** Each call becomes a turn with the meta agent, which decides where the work belongs across your projects.

The full URL is shown once, at creation. **The URL is the credential.** Treat it like a password, hand it to the external system, and revoke it from the same panel when it's no longer needed.

**With the TabTabTab CLI**
```bash
tabtabtab webhook create bug-reports --project my-app   # full URL shown once
tabtabtab webhook list --reveal
tabtabtab webhook revoke bug-reports --yes
```

## Call it

Every call is a single `POST` with a JSON body. There is no separate upload step:

```text
POST https://<env>.tabtabtab.app/webhooks/<token>
Content-Type: application/json
```

The `message` field becomes the agent's prompt, verbatim:

```bash
curl -X POST -H 'Content-Type: application/json' \
  -d '{"message": "A user reported checkout fails on Safari. Reproduce it and open a PR with the fix."}' \
  https://<env>.tabtabtab.app/webhooks/<token>
```

Each call starts a new run and returns immediately with a `sessionUrl` you can open to watch it. It's fire-and-forget: the agent works in the background and opens a PR when it's done.

## Send files with the payload

Most webhook calls only need a `message`. Add attachments when the agent needs extra context, like a bug report, screenshot, log file, CSV, or zip.

What happens:

- Your app sends the file in the same `POST` as the prompt.
- TabTabTab saves the file into the run's workspace.
- The agent's prompt includes the file paths, so it knows what to inspect.

**Payload shape**
Use an `attachments` array when you need files. Each attachment has a `type`, `name`, `contentType`, and `data` field:

```json
{
  "message": "Act on the attached report.",
  "attachments": [
    {
      "type": "file",
      "name": "report.md",
      "contentType": "text/markdown",
      "data": "data:text/markdown;base64,..."
    }
  ]
}
```

Limits: up to 5 attachments, 50MB total. Supported types: PNG, JPEG, WebP, GIF, plain text, Markdown, JSON, CSV, and zip. Zip anything else.

## Get the result back

Two ways to close the loop instead of watching the session:

- **Outgoing webhook.** Set an outgoing URL on the webhook and TabTabTab POSTs the agent's reply back to you when the run produces a response. The callback carries an idempotency key so you can safely dedupe.
- **Slack.** Bridge the webhook to a [Slack](/docs/slack) channel so the run and its result post into a thread.

A webhook can use the outgoing URL or the Slack bridge, not both.

## Follow up on the same run

Every response includes a `sessionUrl`. Open it to watch the run in the environment, or to continue the conversation in that same session.

**With the TabTabTab CLI**
```bash
tabtabtab agent send <sessionID> "Also add a regression test for that case."
```


# Automations

Automations run agents on a schedule, without anyone kicking them off. Set them up in the automation panel in the environment (the icon below webhooks in the bottom-left rail), or ask the [meta agent](/docs/meta-agent) in plain English and it sets the schedule up for you.

## Schedule a recurring run

Open the automation panel and create the schedule there, or describe the job and the cadence to the meta agent (in its chat, or by mentioning the bot in [Slack](/docs/slack)):

- "Every weekday at 9am Europe/London, review open PRs across my projects and post a digest."
- "Every day at 7am, pull main in my-app, run the test suite, and open an issue if anything fails."

Schedules support:

- **Once** at a specific time.
- **Daily** at a time of day.
- **Weekdays** (Monday to Friday) at a time of day.
- **Weekly** on chosen days.
- **Custom** recurrence rules for anything more specific.

A timezone is always part of the schedule, so "9am" means 9am where you are. An automation can target a specific project, or the meta agent itself for cross-project work.

**With the TabTabTab CLI**
```bash
tabtabtab agent kick "Every weekday at 9am Europe/London, review open PRs across my projects and post a digest"
```

Work doesn't have to be a single run either. Ask for an end state ("land PR #42 in my-app; keep rebasing and re-running CI until it merges") and the job keeps re-checking itself in the background until it gets there.

## Check on everything

Ask the meta agent "what's running, anything stuck, anything need me?" in its chat or in Slack, and it answers from live context: running sessions, anything waiting on you, open PRs, and your automations across every project.

**With the TabTabTab CLI**
```bash
tabtabtab agent status
```

## Good to know

- An automation that fails 3 times in a row auto-pauses, so a broken job doesn't run forever. Fix the cause and resume it.
- The automation panel and the meta agent edit the same schedules. Create, change, pause, or delete in whichever is closer at hand.


# Slack

Connect Slack and the [meta agent](/docs/meta-agent) becomes a member of your team. Mention it in a channel and it picks up the work in the background, then replies in the thread. It acts through its own bot identity, never as you.

## What it's good for

- **Kick off work from a conversation.** "@TabTabTab the login page 404s on mobile, can you fix it?" It routes the work to the right repo, does it, and posts the PR back in the thread.
- **Close the loop from your own agents.** A customer finishes a session with your support agent, your app posts the transcript to Slack (or a [webhook](/docs/webhooks)), and a full analysis lands in your team's channel for follow-up.
- **Answer the agent when it needs you.** When a run has a question, needs a permission, or is blocked awaiting a human, it posts a thread. You reply in that thread and the run continues.

## Connect Slack

Set it up from **Settings > Slack** in the environment. You will:

1. Create a Slack app and add the bot to your workspace.
2. Add the bot scopes it needs: posting messages, reading channel history, reacting, reading files, and app mentions.
3. Point the Slack app's Events request URL at the value shown in Settings > Slack, and subscribe to app mention and message events.
4. Paste the bot token and signing secret into Settings > Slack, set which channels it may act in, and use `Test connection` to confirm.

Credentials are stored on your environment, never shown back in the UI. The meta agent can use Slack without ever seeing the raw token.

## How it behaves

- **@mention the bot** in an allowed channel and your message goes to the meta agent as a prompt. It reacts with :eyes: while it's working, posts its reply in the thread, then clears the reaction.
- **Replies in its threads** keep routing to the same work. That's how you answer a question, grant a permission (reply `once`, `always`, or `reject <reason>`), or unblock an "Awaiting human" job.
- **It stays quiet unless addressed.** The bot doesn't jump into ambient channel chatter. It responds when you mention it or reply in one of its threads.
- **Attachments come along.** Images and PDFs on a message it picks up are pulled in and attached to the prompt.

## Webhooks close the loop in Slack

If you create a [webhook](/docs/webhooks) with Slack enabled, every time the webhook finishes processing, the bot configured here posts a thread to the channel. You can follow up right in that thread, and the whole team sees what the agents are doing, so everyone stays in the know.

## Channels are for outbound posts

The channels you configure decide where this environment's bot posts. If you run several environments with the same bot, give them distinct channels so replies route to the right machine. With a different bot per environment, this doesn't matter.


# Meta Agent

The meta agent is an always-on agent for your environment: a way to launch jobs anywhere on the machine and to check what's going on. You talk to it in plain English, and it turns "fix this bug" into a reviewed pull request.

## What it does

The meta agent doesn't write your product code itself. It coordinates. It sees every project, running session, open PR, and schedule at once, and from that vantage point it:

- **Routes work to the right project.** A bug report names the repo it was filed against, which isn't always where the fix belongs. The meta agent figures out the actual target.
- **Dispatches workers.** For real work it starts a Lead session to coordinate, and the Lead spins up isolated worker sessions. Workers are the sessions that edit files, run tests, and open PRs.
- **Runs on your accounts.** Workers use your own Codex or Claude Code accounts, so if you're already paying for tokens, this puts them to work around the clock.
- **Tracks work to completion.** Durable jobs stay open with an end state and background checks until the work is actually done. See [Automations](/docs/automations).
- **Remembers.** It keeps durable memory of your goals, constraints, and routing decisions across conversations, so you don't re-explain your setup every time.

## Supervised or yolo

When a worker hits a question or needs a permission, the meta agent handles it one of two ways:

- **Supervised.** It asks you, or marks the job "Awaiting human," and waits.
- **Yolo.** It answers on your behalf from the intent you already gave it, so the run finishes without interrupting you.

Yolo isn't a security bypass. It just decides how often the work stops to check in.

## How you talk to it

- **The environment.** The meta agent chat is front and center. Ask it to create webhooks, set up automations, check on work, or kick off anything.
- **Slack.** @mention the bot. See [Slack](/docs/slack).
- **Webhooks and schedules.** Both route into the same meta agent. See [Webhooks](/docs/webhooks) and [Automations](/docs/automations).

Ask it anything about the state of your machine ("what's running, anything stuck, anything need me?") and it answers from live cross-project context, not a stale dashboard.

**With the TabTabTab CLI**
```bash
tabtabtab agent kick "<what you want>"              # no --project: goes to the meta agent
tabtabtab agent kick "<prompt>" --project my-app    # target one repo directly
tabtabtab agent status                              # the structured snapshot
```


# Secrets

Your background agents need real credentials to do real work: API keys, database URLs, tokens for the services your app talks to. You set those once, in the dashboard, and the agent uses them without ever seeing the raw values.

## Set them in the dashboard

Open your environment in the [dashboard](https://dash.tabtabtab.ai/app/environments) and add your secrets as key/value pairs. They're stored encrypted, scoped to that environment, and made available to the running machine at runtime.

Set them once. Every new session and every scheduled run in that environment gets them, so you never paste `OPENAI_API_KEY` into a session again.

## The agent uses them, but never sees them

This is the important part. A secret is injected at the moment it's actually needed, at runtime, and substituted in outside the agent's view. The agent can call an API that requires the key, but the raw value is never exposed to the model, never printed into the transcript, and never something the agent can read back and leak into a PR, a log, or a Slack message.

You get to hand agents the credentials they need without handing them something they can exfiltrate.


# Receipts

An agent telling you "done" isn't worth much on its own. So every pull request TabTabTab opens comes with receipts: the evidence you need to trust the change without re-doing the work yourself.

## What's in the receipts

- **A video of the agent testing the change.** TabTabTab includes a browser agent that starts your app on its own, opens it in a real browser on the cloud machine, and clicks through the thing that changed. The recording is attached to the PR, so you can watch the new behavior actually work instead of guessing from the diff.
- **A preview URL.** The branch runs on a public, password-protected URL so you can open the running app yourself, on desktop or mobile, and click around before you merge. See [Preview URL](/docs/preview-url).
- **A trace of what created it.** The PR links back to what triggered the run (the webhook payload, the Slack thread, the schedule) and what the agent did to get there, so there's a clear line from "why" to "what changed."

## Why it matters for background work

The whole point of background agents is that you weren't watching. Receipts are what make that safe. You don't have to trust the agent's word or reconstruct its reasoning. You watch the video, click the preview, skim the trace, and merge. Verification is a two-minute review, not a re-investigation.

For UI work especially, the browser recording closes the "did this actually render right?" loop that a code diff can never answer on its own.


# CLI Reference

Setup happens in the environment or here, via the CLI. Reach for the CLI when you want to script TabTabTab or drive it from your terminal. And you don't have to learn it yourself: the [tabtabtab skill](https://github.com/tabtabtabai/tabtabtab-skill) teaches the full CLI to coding agents like Claude Code, so you can describe the goal and let your agent run the commands. The command is `tabtabtab`, with `ttt` as a short alias.

If you use a local coding agent such as Claude Code, don't memorize any of this: install the [tabtabtab skill](https://github.com/tabtabtabai/tabtabtab-skill) and the agent learns the whole CLI, so you can just say what you want.

Pass `--json` on any command you parse. Destructive commands (`env destroy`, `webhook revoke`, `repo env rm`) refuse without `--yes`.

Install and log in:

```bash
curl -fsSL https://tabtabtab.ai/install.sh | sh
tabtabtab auth login
```

Select an environment once so you don't pass `--env` on every call:

```bash
tabtabtab env use $(whoami)
```

## Control agents

The core of the CLI. Kick off work, watch it, and steer it.

```bash
tabtabtab agent kick "<prompt>"                     # prompt the meta agent
tabtabtab agent kick "<prompt>" --project my-app    # fresh session in one project
tabtabtab agent kick "<prompt>" --watch             # stream output until idle
tabtabtab agent list --json                         # recent sessions
tabtabtab agent tail <session-id> --follow          # read or stream a session
tabtabtab agent last <session-id>                   # just the last reply
tabtabtab agent send <session-id> "<message>" --queue   # follow up on a session
tabtabtab agent abort <session-id>                  # cancel a running session
tabtabtab agent status                              # what's running across all projects
```

Session IDs accept unique prefixes (`ses_195d9f`). `agent send` on a busy session needs `--queue` to deliver when the current run finishes.

## Webhooks

```bash
tabtabtab webhook create <name> --project my-app    # full URL shown once
tabtabtab webhook list --reveal
tabtabtab webhook revoke <name> --yes
```

See [Webhooks](/docs/webhooks) for the request format.

## Environments

```bash
tabtabtab env create --name $(whoami)
tabtabtab env list --json
tabtabtab env use $(whoami)
tabtabtab env info --reveal
tabtabtab env destroy $(whoami) --yes
```

`env destroy` permanently removes the cloud VM.

## Repos and env vars

```bash
tabtabtab repo add <git-url-or-dir> --yes
tabtabtab repo list --json
tabtabtab repo sync --repo my-app
tabtabtab repo env list --repo my-app --reveal
tabtabtab repo env add KEY=VALUE --repo my-app --secret
```

## Move files

```bash
tabtabtab upload <local paths...> --to <remote path>
tabtabtab download <remote paths...> --to <local dir>
```

Use `upload` to push context (notes, datasets, specs) into the machine; use `download` to pull back a file an agent produced, such as a PDF, a build artifact, or a report.

## Runtime

Runtime commands need an onboarded repo with a supported `.devcontainer/devcontainer.json`.

```bash
tabtabtab runtime up
tabtabtab runtime status
tabtabtab runtime ports
tabtabtab runtime preview 3000
tabtabtab runtime logs
tabtabtab runtime down
```

## Local editors and SSH

Attach a local editor or shell to the same cloud machine:

```bash
tabtabtab open vscode
tabtabtab open cursor
tabtabtab ssh
tabtabtab ssh -- uptime
```

For "what's going on in the environment?" ask the meta agent (`tabtabtab agent status`) rather than SSHing in. It has live cross-project context that shell commands don't.


# Environment

The environment is home base. It's where you onboard repos, set up your triggers (webhooks from the bottom-left rail, Slack under `Settings > Slack`), and talk to the [meta agent](/docs/meta-agent). Once triggers are wired, you don't have to keep it open: work runs in the background and comes back as PRs. Open it when you want to watch a run, steer it, or review a diff in place.

It's a real cloud workspace. The files, terminals, agents, and dev servers you see run on the cloud machine and keep running after you close the tab.

## Open it

From the [dashboard](https://dash.tabtabtab.ai), select an environment and click `Open`. The dashboard mints a short-lived login link, so there's nothing to copy around. On a phone or tablet, sign into the dashboard and tap `Open`, or scan the environment's QR code.

## What you do here

- **Set up triggers.** Create webhooks from the webhook icon in the bottom-left rail, create automations from the automation panel right below it, and connect Slack under `Settings > Slack`.
- **Talk to the meta agent.** Kick off work, ask what's running, answer its questions.
- **Watch and steer.** Follow a running session live, jump in to redirect or correct it, then step back out.
- **See the work laid out.** Sessions are laid out in the sidebar, so you can see what the meta agent dispatched and where.
- **Open previews.** Start a dev server and get a public preview URL to click through the running app. See [Preview URL](/docs/preview-url).
- **Add repos.** Click the plus button in the left sidebar to add a repository. Set secrets in the dashboard; see [Secrets](/docs/secrets).

After setup, keeping the environment open is optional. The loop, from trigger to PR to merge, runs whether or not the tab is open.


# Preview URL

Runtime previews turn a dev server in your cloud workspace into a public URL you can open from your phone, send to a teammate, or point a webhook at. Useful when you want eyes on a feature before it goes anywhere near staging.

Your app runs on the TabTabTab VM, so `localhost` from your laptop or phone can't reach it the way it would for a server on your own machine. A preview URL gives that VM-local server a reachable HTTPS address.

## Security

Preview URLs are password-protected by default. The CLI prints a username and password with the URL, and the Agent Browser inside the environment authenticates automatically. Humans can also find the same credentials in the Environment panel. Use this default for anything that isn't meant for the open internet, including in-progress features, internal tools, and apps that read user data.

If you explicitly want a public preview that anyone with the link can open, ask the agent for a no-auth preview. Under the hood that runs `tabtabtab runtime preview ... --no-auth`. Use it for shareable demos and webhook receivers, not for anything sensitive.

## Asking an agent for a preview

You usually don't run preview commands yourself. The intended workflow is to ask the agent in your environment something like "give me a preview URL for the app." The agent figures out the right ports, verifies the app locally, runs the preview command, and hands back the public URL and credentials. The `tabtabtab runtime preview` CLI is built for agents to drive; everything below describes what the agent does on your behalf so you know what to expect.

## How preview URLs work

When the agent creates a preview, TabTabTab registers one or more VM-local ports with the preview router. Public HTTPS traffic reaches the preview URL, passes through preview authentication by default, and is forwarded back to the selected local port inside the environment.

For a single self-contained app, the agent exposes the frontend port:

```bash
tabtabtab runtime preview 5173
```

The returned URL serves port `5173` from `/`.

## Multi-port apps and CORS

Many apps are not one port. A Vite or Next.js frontend may run on `5173` or `3000`, while an API, websocket server, auth service, Storybook, or docs server runs on another port.

We do not create separate preview URLs for each browser-called service. Separate preview URLs are separate origins, which can break cookies, redirects, browser auth, websockets, and CORS. We also do not leave browser code pointing at `localhost` or `127.0.0.1`; from the public browser, those names mean the user's device, not the VM.

Instead, the agent creates one preview URL that includes every port the browser needs. It uses named routes for secondary ports so the frontend and backend share the same preview origin:

```bash
tabtabtab runtime preview 5173 --port api:3001
```

This serves the frontend from `/` and the API from `/ttt-api/` on the same preview host. Browser requests should use the printed API route, for example:

```bash
VITE_API_BASE_URL=https://<preview-host>/ttt-api/ npm run dev -- --host 127.0.0.1 --port 5173
```

If the API has its own base path, keep it after the named route, such as `https://<preview-host>/ttt-api/api`.

## Ports, service names, and named routes

The agent uses `tabtabtab runtime ports` to see the services and host ports the runtime has published:

```bash
tabtabtab runtime ports
tabtabtab runtime ports --all
```

The default target can be a host port or a discovered service name:

```bash
tabtabtab runtime preview web
tabtabtab runtime preview 5173
```

Secondary ports use repeated `--port` flags. We prefer named routes for anything the browser calls directly:

```bash
tabtabtab runtime preview 5173 --port api:3001 --port storybook:6006
```

Named routes use the format `key:host-port`. The key becomes a stable path prefix like `/ttt-api/` or `/ttt-storybook/`. Route keys should be short lowercase names such as `api`, `backend`, `storybook`, `docs`, `auth`, or `ws`.

Bare secondary ports are still supported as fallback routes under `/__ports/<port>`, but named routes are easier to configure in frontend environment variables and easier to debug:

```bash
tabtabtab runtime preview 5173 --port 3001
```

Start the repo runtime:

```bash
tabtabtab runtime up
```

Check status and ports:

```bash
tabtabtab runtime status
tabtabtab runtime ports
tabtabtab runtime ports --all
```

Expose a running app through a public preview URL:

```bash
tabtabtab runtime preview 3000
```

Inspect logs, rebuild, or stop the runtime:

```bash
tabtabtab runtime logs
tabtabtab runtime rebuild
tabtabtab runtime down
```

## Troubleshooting

**Preview loads but API or websocket requests fail.** The browser is calling the wrong origin. Check that frontend code uses the printed named route, such as `https://<preview-host>/ttt-api/`, not `localhost`, `127.0.0.1`, or an internal container name. From the public browser those addresses resolve to the user's device, not the VM.

**Derived URLs drop the named route.** If the frontend stores an API base and then strips the path to build websocket or fetch URLs from `window.location.origin`, derived requests collapse back to the frontend origin and lose the `/ttt-api/` prefix. Configure the app with the full absolute URL (`https://<preview-host>/ttt-api/`) and preserve the path when constructing websocket or sub-resource URLs (e.g. `wss://<preview-host>/ttt-api/socket`).

**Bare relative paths don't work.** Setting an API base to just `/ttt-api/` works for simple `fetch` calls but breaks anything that needs an absolute URL, including new `URL()` construction, websocket clients, and OAuth redirects. Prefer the full preview URL.

**Cookies, auth, or CORS break across services.** That usually means the frontend and API are on different preview URLs (two separate origins). Re-create one grouped preview that includes both ports so they share the same origin.

**401 prompt when opening the preview.** Previews are password-protected by default. Use the username and password printed by the CLI, the Environment panel credentials, or ask the agent for a no-auth preview if the user explicitly wants a public link.

Preview URLs work from desktop or mobile. They're meant for development and team review, not production traffic.


# Support

Email support@tabtabtab.ai for anything: bug reports, missing features, onboarding help, billing, or a request for an agent or editor we don't support yet. Include the environment name and what you were doing so we can look at logs without going back and forth.

