BOTYARD
/ Docs

Host apps with Botyard

Run local web apps inside a bot and share them through authenticated Bot Page URLs.

Botyard bots can host web apps they build or manage. A bot runs the app on a local port inside its runtime, then shares it with you through an authenticated Bot Page URL.

Use this for:

  • app previews while a bot is building something
  • lightweight internal tools
  • dashboards and reports
  • Streamlit apps
  • demos that combine a frontend, API, or WebSocket server
  • small dynamic websites that a bot can keep running and repair when needed

Bot-hosted apps are practical and secure by default, but they are not a high-availability deployment. The app depends on the bot runtime, its workspace, and the local server process. For many team workflows, that tradeoff is exactly right: you can ask the bot to build, host, inspect, and fix the app in the same conversation.

How hosting works

There are two pieces:

PieceWhat it does
Runtime servicesKeep local processes running inside the bot, such as pnpm dev, next start, or streamlit run app.py.
Bot PagesPublish one local loopback port as an authenticated HTTPS URL.

For a quick preview, the bot can start a local server and expose it for a short time. For something you want to keep using, ask the bot to register the app as a persistent service first, then expose it as a persistent Bot Page.

Quick start

Ask your bot:

Build a small Vite app, run it locally, and share it with me as a Bot Page.
Use a short-lived preview URL.

The bot should:

  1. create or update the app in its workspace
  2. start the web server on 127.0.0.1
  3. expose the server port with a Bot Page
  4. send you the returned URL

An unauthenticated Bot Page URL redirects to Botyard sign-in. After sign-in, Botyard checks that you can access the bot before proxying traffic to the app.

Persistent hosting

Use persistent hosting when the app should survive command completion or bot runtime restarts.

Ask your bot:

Host this app persistently. Register the web server as a Botyard service with autostart,
then expose it as a persistent Bot Page.

The bot should use the runtime service tools to save a service definition. A typical Vite app looks like this:

service_register(
  name="web",
  command="pnpm",
  args=["dev", "--host", "127.0.0.1", "--port", "5173"],
  cwd="my-app",
  autostart=true,
  restart_policy="on_failure",
  ports=[{"name":"http","port":5173,"kind":"web"}]
)
service_start(name="web")
expose_botpage(port=5173, name="my-app", kind="web_app", persistent=true)

The Bot Page stays available until it is revoked. If the app breaks, ask the bot to inspect service_status, service_logs, and the local port, then restart or repair the service.

Temporary previews

Bot Pages default to a short TTL, currently 2 hours. That is best for previews and one-off checks:

expose_botpage(port=5173, name="vite-preview")

Use a temporary page when:

  • you only need to inspect the current build
  • the server was started manually
  • the app is experimental
  • you do not want a long-lived URL

Use a persistent page when:

  • the page is meant to be reused
  • the bot should keep hosting it after restart
  • other people will rely on the same URL

Streamlit apps

Streamlit uses WebSockets and app-specific XSRF cookies. Botyard supports this with the streamlit_app Bot Page kind.

Ask your bot:

Create a Streamlit dashboard and host it persistently as a Bot Page.
Use the Streamlit app kind so WebSockets work.

A reliable setup uses a virtual environment inside the workspace:

python3 -m venv .venv
.venv/bin/python -m pip install streamlit

Then the bot registers and exposes the app:

service_register(
  name="streamlit",
  command="/home/openclaw/.openclaw/workspace/my-streamlit/.venv/bin/streamlit",
  args=[
    "run",
    "app.py",
    "--server.address",
    "127.0.0.1",
    "--server.port",
    "8501",
    "--server.enableCORS",
    "false",
    "--server.enableXsrfProtection",
    "false",
    "--browser.gatherUsageStats",
    "false"
  ],
  cwd="my-streamlit",
  autostart=true,
  restart_policy="on_failure",
  ports=[{"name":"http","port":8501,"kind":"web"}]
)
service_start(name="streamlit")
expose_botpage(port=8501, name="streamlit", kind="streamlit_app", persistent=true)

Avoid virtual environments or generated files under /tmp; they disappear when the bot runtime restarts.

Frontend plus API

For generated apps, the simplest convention is:

PartDefault
Frontend127.0.0.1:5173
API127.0.0.1:3001
API path/api
WebSocket path/ws

When possible, expose one public Bot Page for the frontend and have the frontend call same-origin paths such as /api and /ws. That keeps auth, cookies, and browser origins simple.

If the frontend and API cannot be rewritten to share one origin, the bot can expose more than one port. Each exposed port gets a different Bot Page URL. Use that only when the app really needs separate origins.

WebSockets

Bot Pages proxy WebSocket traffic under the same authenticated URL as HTTP traffic. The browser must already have a valid Botyard session for the WebSocket request. For app frameworks such as Streamlit, Botyard preserves the WebSocket subprotocol and app cookies needed by the framework.

If a WebSocket fails:

  1. ask the bot to confirm the local WebSocket server is running
  2. check whether the app uses the same Bot Page origin
  3. for Streamlit, confirm the page was exposed with kind="streamlit_app"
  4. ask the bot to restart the service and inspect its logs

Managing hosted apps

Useful requests:

List my active Bot Pages.
Show the services you have registered and their status.
Restart the hosted app and check that the public URL still works.
Take down the old preview URL.

The bot can revoke a page when it is obsolete, exposed on the wrong port, or no longer needed:

revoke_botpage(exposure_id="...")

Security

Bot Pages are authenticated. A user who opens the URL must sign in with Botyard, and Botyard checks access to the bot before forwarding traffic.

Important details:

  • The app listens on loopback inside the bot runtime.
  • The bot opens an outbound tunnel to Botyard; the local app is not directly exposed to the internet.
  • Botyard session cookies are not forwarded to the hosted app.
  • The app may receive forwarded identity metadata such as x-forwarded-user.
  • You can revoke a Bot Page when it should no longer be reachable.

Because the app runs in the bot workspace, treat it like any other bot-authored artifact. Ask the bot what it is running, what port it exposed, and whether the page should be temporary or persistent.

Troubleshooting

SymptomWhat to ask the bot
The page redirects to sign-in"This is expected if I am not authenticated. After I sign in, verify the page loads."
The page shows a 502"Check whether your tunnel is connected, the service is running, and the local port returns 200."
It worked before a restart but not after"Confirm the app is registered as a service with autostart and that dependencies are stored in the workspace."
Streamlit loads but interactivity fails"Confirm the Bot Page kind is streamlit_app and inspect the Streamlit service logs."
The wrong app appears"List Bot Pages and revoke stale exposures for old ports."

For persistent apps, a good recovery request is:

Trace the hosted app end to end: list services, inspect logs, curl the local port,
list Bot Pages, and verify the public URL after auth.

On this page