> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ascii.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Host with box

> Run a service inside a Box and expose it on a stable public HTTPS URL.

## Quick path

1. Enter or target a Box. In integrations, use the command endpoint to run the same setup inside the Box:

<CodeGroup>
  ```bash CLI theme={null}
  box ssh bx_f7k2q9hd
  npm run dev -- --host 0.0.0.0 --port 3000
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/bx_f7k2q9hd/commands" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command":"npm run dev -- --host 0.0.0.0 --port 3000","cwd":"my-repo"}'
  ```

  ```ts TypeScript theme={null}
  await box.command({
    boxId: "bx_f7k2q9hd",
    commandRequest: { command: "npm run dev -- --host 0.0.0.0 --port 3000", cwd: "my-repo" },
  });
  ```

  ```python Python theme={null}
  box.command("bx_f7k2q9hd", CommandRequest(
      command="npm run dev -- --host 0.0.0.0 --port 3000",
      cwd="my-repo",
  ))
  ```
</CodeGroup>

2. Start your app on an explicit port.

<Warning>
  Your app must bind to `0.0.0.0`. If it only listens on `localhost` or `127.0.0.1`,
  the hosted HTTPS URL will not be able to reach it.
</Warning>

3. Inside the Box, run `host` to start hosting your app. Programmatic integrations can execute the same command in the Box:

<CodeGroup>
  ```bash CLI theme={null}
  host 3000 --title "App preview"
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/bx_f7k2q9hd/commands" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command":"host 3000 --title \"App preview\""}'
  ```

  ```ts TypeScript theme={null}
  await box.command({
    boxId: "bx_f7k2q9hd",
    commandRequest: { command: 'host 3000 --title "App preview"' },
  });
  ```

  ```python Python theme={null}
  box.command("bx_f7k2q9hd", CommandRequest(command='host 3000 --title "App preview"'))
  ```
</CodeGroup>

4. Open the public HTTPS URL printed by `host`:

```text theme={null}
https://<box-subdomain>-3000.on.ascii.dev
```

If the port is protected, the usable URL includes a `_token` query parameter:

```text theme={null}
https://<box-subdomain>-3000.on.ascii.dev?_token=<access-token>
```

<Info>
  A fresh `host <port>` URL is protected by default and includes `_token`. Pass
  `--public` only when you want to clear the access token and return an ungated URL.
</Info>

<Info>
  If you want the process to survive after your SSH command exits, start it as a
  detached process before hosting the port.
</Info>

## How HTTPS hosting works

When you run `host <port>`, the Box asks the Ascii backend to create a stable
public route for that Box and port. The backend registers a subdomain using the
Box's current machine address and the port you asked to expose.

The generated hostname is based on the Box subdomain plus the port:

```text theme={null}
https://<box-subdomain>-<port>.on.ascii.dev
```

Ascii terminates TLS for `on.ascii.dev`, then proxies each request to the target
Box over the exposed port. Your application still runs inside the Box; the public
HTTPS route sits in front of it. This is why your server must listen on `0.0.0.0`:
the route connects to the Box from outside the application process, not through
your app's local loopback interface.

The Box authenticates to the Ascii backend with its machine token, and the backend
performs the route registration. This keeps routing credentials out of the Box
environment while still letting the `host` CLI create, list, and remove routes.

## Host CLI reference

The `host` CLI runs inside a Box and exposes services from that Box on public HTTPS URLs.

<Warning>
  The service you expose must listen on `0.0.0.0`, not only on `localhost` or `127.0.0.1`.
</Warning>

### `host <port>`

Expose a running service on a stable HTTPS URL:

<CodeGroup>
  ```bash CLI theme={null}
  host 3000
  host 3000 --title "Login preview"
  host 3000 --private
  host 3000 --public
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/bx_f7k2q9hd/commands" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command":"host 3000 --title \"Login preview\""}'
  ```

  ```ts TypeScript theme={null}
  await box.command({ boxId: "bx_f7k2q9hd", commandRequest: { command: "host 3000" } });
  await box.command({ boxId: "bx_f7k2q9hd", commandRequest: { command: 'host 3000 --title "Login preview"' } });
  await box.command({ boxId: "bx_f7k2q9hd", commandRequest: { command: "host 3000 --private" } });
  await box.command({ boxId: "bx_f7k2q9hd", commandRequest: { command: "host 3000 --public" } });
  ```

  ```python Python theme={null}
  box.command("bx_f7k2q9hd", CommandRequest(command="host 3000"))
  box.command("bx_f7k2q9hd", CommandRequest(command='host 3000 --title "Login preview"'))
  box.command("bx_f7k2q9hd", CommandRequest(command="host 3000 --private"))
  box.command("bx_f7k2q9hd", CommandRequest(command="host 3000 --public"))
  ```
</CodeGroup>

The command opens the firewall for that port, registers an HTTPS subdomain, and prints
the URL. Calling it again for the same port returns the same URL. A Box can host up to
50 ports.

If you only need raw access without HTTPS or a subdomain, you can instead open the port
yourself with `ufw` and use `http://<box-ip>:<port>` directly.

| Option            | Description                                                                   |
| ----------------- | ----------------------------------------------------------------------------- |
| `--title <title>` | Set the display title for the hosted port.                                    |
| `--private`       | Require the generated `_token` query parameter to access the URL.             |
| `--public`        | Clear any saved access token and return a URL that does not require `_token`. |

By default, `host <port>` creates a protected URL with an `_token` query parameter.
That protection is sticky: hosting the same port again returns a URL with the same
`_token` query parameter unless you pass `--public`.

### `host list`

Show hosted ports for the current Box:

<CodeGroup>
  ```bash CLI theme={null}
  host list
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/bx_f7k2q9hd/commands" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command":"host list"}'
  ```

  ```ts TypeScript theme={null}
  await box.command({ boxId: "bx_f7k2q9hd", commandRequest: { command: "host list" } });
  ```

  ```python Python theme={null}
  box.command("bx_f7k2q9hd", CommandRequest(command="host list"))
  ```
</CodeGroup>

Protected ports are shown as `(gated)`. `host list` does not print the access token.
Use `host url <port>` to print the full URL with `_token=...`.

### `host url <port>`

Wait until the HTTPS URL is ready, then print it:

<CodeGroup>
  ```bash CLI theme={null}
  host url 3001
  host url 3001 --public
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/bx_f7k2q9hd/commands" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command":"host url 3001"}'
  ```

  ```ts TypeScript theme={null}
  await box.command({ boxId: "bx_f7k2q9hd", commandRequest: { command: "host url 3001" } });
  await box.command({ boxId: "bx_f7k2q9hd", commandRequest: { command: "host url 3001 --public" } });
  ```

  ```python Python theme={null}
  box.command("bx_f7k2q9hd", CommandRequest(command="host url 3001"))
  box.command("bx_f7k2q9hd", CommandRequest(command="host url 3001 --public"))
  ```
</CodeGroup>

For a protected port, this prints the full token-gated URL:

```text theme={null}
https://<box-subdomain>-3001.on.ascii.dev?_token=<access-token>
```

This is useful when one service needs another service's public URL:

<CodeGroup>
  ```bash CLI theme={null}
  BACKEND_URL=$(host url 3001)
  ```

  ```bash curl theme={null}
  BACKEND_URL=$(curl -sS -X POST "$BOX_API_BASE/boxes/bx_f7k2q9hd/commands" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command":"host url 3001"}' | jq -r '.stdout')
  ```

  ```ts TypeScript theme={null}
  const result = await box.command({ boxId: "bx_f7k2q9hd", commandRequest: { command: "host url 3001" } });
  const backendUrl = result.stdout.trim();
  ```

  ```python Python theme={null}
  result = box.command("bx_f7k2q9hd", CommandRequest(command="host url 3001"))
  backend_url = result.stdout.strip()
  ```
</CodeGroup>

### `host hide <port>`

Take down the public URL:

<CodeGroup>
  ```bash CLI theme={null}
  host hide 3000
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/bx_f7k2q9hd/commands" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command":"host hide 3000"}'
  ```

  ```ts TypeScript theme={null}
  await box.command({ boxId: "bx_f7k2q9hd", commandRequest: { command: "host hide 3000" } });
  ```

  ```python Python theme={null}
  box.command("bx_f7k2q9hd", CommandRequest(command="host hide 3000"))
  ```
</CodeGroup>

This closes public access and unregisters the HTTPS route. It does not stop the local
server process. Stop the server separately when you are done. Access tokens are preserved,
so hosting the same port again keeps existing protected links valid.

To get an ungated URL after a port has become protected, run `host <port> --public`.

## Related

* [SSH Access](/box/ssh-access)
* [Secrets & Setup](/box/secrets)
* [Long-Running Tasks](/box/long-running-tasks)
* [Desktop Streaming](/box/desktop-streaming)
