---
title: "Stream Bot Console Output Live"
description: "Watch your bot's console live from a script with the Silly Development WebSocket: authenticate, read output, send commands and renew the token."
url: "https://sillydev.co.uk/docs/panel/recipes/stream-console-output"
updated: "2026-10-10"
---

Recipe

# Stream console output

The live console in the panel is a WebSocket you can open from your own code too: every line your bot prints, its status and its resource use, as they happen.

[← API docs](https://sillydev.co.uk/docs/panel) · Updated 10 October 2026

**Browse the docs**

## How it works

Ask the API for a token with `GET /servers/{server}/websocket`, which needs the `websocket.connect` permission. It returns a short-lived token and the address of the node's socket. Open the socket with an `Origin: https://sillydev.co.uk` header, send `{"event": "auth", "args": ["<token>"]}`, and wait for `auth success`.

From then on every message is JSON with an `event` and a list of `args`. `console output` carries one line; `status` says the server is starting, running, stopping or offline; `stats` carries resource use. Send `send logs` to get recent history first, and `send command` to type into the console.

The token lasts a few minutes. Shortly before it runs out you get `token expiring`: fetch a new token from the API and send `auth` again on the same socket.

## Python

With the `websockets` and `requests` packages.

console.py

```
import asyncio
import json
import os

import requests
import websockets

API = "https://sillydev.co.uk/panel/api/client"
SERVER = "1a7ce997"
HEADERS = {"Authorization": f"Bearer {os.environ['SILLYDEV_API_KEY']}", "Accept": "application/json"}


def credentials() -> dict:
    response = requests.get(f"{API}/servers/{SERVER}/websocket", headers=HEADERS, timeout=10)
    response.raise_for_status()
    return response.json()["data"]


async def main() -> None:
    first = credentials()
    async with websockets.connect(first["socket"], origin="https://sillydev.co.uk") as socket:
        await socket.send(json.dumps({"event": "auth", "args": [first["token"]]}))
        async for raw in socket:
            message = json.loads(raw)
            event, args = message["event"], message.get("args") or []
            if event == "auth success":
                await socket.send(json.dumps({"event": "send logs"}))
            elif event == "console output":
                print(args[0])
            elif event == "status":
                print(f"[status: {args[0]}]")
            elif event == "token expiring":
                fresh = credentials()
                await socket.send(json.dumps({"event": "auth", "args": [fresh["token"]]}))


asyncio.run(main())
```

## JavaScript

With the `ws` package in Node.js, which can set the Origin header. Bun and Deno have WebSocket built in.

console.mjs

```
import WebSocket from 'ws';

const API = 'https://sillydev.co.uk/panel/api/client';
const SERVER = '1a7ce997';
const headers = { Authorization: `Bearer ${process.env.SILLYDEV_API_KEY}`, Accept: 'application/json' };

const credentials = async () => {
    const response = await fetch(`${API}/servers/${SERVER}/websocket`, { headers });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return (await response.json()).data;
};

const { socket: url, token } = await credentials();
const socket = new WebSocket(url, { origin: 'https://sillydev.co.uk' });
const send = (event, args = []) => socket.send(JSON.stringify({ event, args }));

socket.on('open', () => send('auth', [token]));
socket.on('message', async (raw) => {
    const { event, args = [] } = JSON.parse(raw.toString());
    if (event === 'auth success') send('send logs');
    if (event === 'console output') console.log(args[0]);
    if (event === 'status') console.log(`[status: ${args[0]}]`);
    if (event === 'token expiring') send('auth', [(await credentials()).token]);
});
```

FAQ

## Questions

**Can I send console commands this way?**

Yes, with the control.console permission: send the event "send command" with the command as its only argument. For a single command, POST /servers/{server}/command is simpler.

Read next

- [Servers List your servers, start and stop them, send console commands, watch the console live, and change how they start.](https://sillydev.co.uk/docs/panel/servers)
- [Restart a bot from code Send a restart, then wait until the bot is running again.](https://sillydev.co.uk/docs/panel/recipes/restart-a-bot-from-code)
- [Recipes Complete, copyable examples for the things people build most.](https://sillydev.co.uk/docs/panel/recipes)
