Skip to main content

sillydev

The Python client for the Silly Development panel API: start, stop and restart your bots, send console commands, manage files and backups, and deploy from Git, all from your own code. It also comes with a sillydev command line tool, an MCP server for AI assistants and opt-in application monitoring.

Full API docs: https://sillydev.co.uk/docs/panel

Install

pip install sillydev

Python 3.9 or later. The only dependency is httpx.

Quick start

Create an API key in the panel under Account, API keys and put it in the SILLYDEV_API_KEY environment variable. Keys start with sdevc_ (older ptlc_ keys keep working).

from sillydev import SillyDev

client = SillyDev()  # or SillyDev(api_key="sdevc_...")

for server in client.panel.servers.list():
    print(server["identifier"], server["name"])

client.panel.servers.power("1a7ce997", "restart")
client.panel.servers.command("1a7ce997", "say Back in a minute")

Each server has an 8-character identifier, shown in the panel and in the identifier field of every server. Every method takes it (or the full UUID) as its first argument.

Examples

from sillydev import SillyDev

client = SillyDev()
panel = client.panel
server = "1a7ce997"

# Live state and resource use
stats = panel.servers.resources(server)
print(stats["current_state"], stats["resources"]["memory_bytes"])

# Restart and wait until it's running again (raises WaitTimeoutError after 120 seconds)
panel.servers.restart(server)
panel.servers.wait_for_state(server, "running", timeout=120)

# Files
for f in panel.files.list(server, directory="/"):
    print(f["name"], f["size"])
config = panel.files.read(server, "/config.json")
panel.files.write(server, "/config.json", config.replace("debug", "info"))

# Backups
backup = panel.backups.create(server, name="before-update")
print(backup["uuid"])

# Deploy the linked Git repository
deployment = panel.git.deploy(server)
print(deployment["status"])  # "queued"

# Startup variables
panel.startup.set_variable(server, "MAIN_FILE", "bot.py")

Responses

Methods return plain dicts and lists. The API's {"object": ..., "attributes": {...}} envelopes are removed, so panel.servers.get(server)["name"] just works.

Pagination

panel.servers.list(), panel.backups.list(), panel.servers.activity() and panel.account.activity() return iterators that fetch the next page as you go. For one page at a time, with the list's meta, use the *_page methods:

page = panel.servers.list_page(page=1, per_page=25)
print(page.total, page.has_next, page.meta.get("pending_orders"))

Async

import asyncio
from sillydev import AsyncSillyDev

async def main():
    async with AsyncSillyDev() as client:
        async for server in client.panel.servers.list():
            print(server["name"])
        await client.panel.servers.restart("1a7ce997")

asyncio.run(main())

Errors

Every error is a sillydev.SillyDevError. Errors the API answers with have their own class:

Status Exception
400 BadRequestError
401 AuthenticationError
403 PermissionDeniedError
404 NotFoundError
409 ConflictError
422 ValidationError (.errors has every problem, .fields groups them by field)
429 RateLimitError (.retry_after in seconds)
5xx ServerError

If the API cannot be reached at all you get APIConnectionError.

from sillydev import ConflictError, NotFoundError

try:
    panel.servers.restart(server)
except ConflictError:
    print("The server is installing or being transferred; try again later")
except NotFoundError:
    print("No such server, or the key cannot reach it")

Retries and limits

Each account may make 768 requests a minute. Requests answered 429 are retried twice by default, and reads answered 502, 503 or 504 too (a command or power action is not repeated, since the server may already have run it), waiting as long as the Retry-After header asks. Change it with SillyDev(max_retries=...); 0 turns retries off.

Ordering a paid server (panel.billing.checkout(...)) always sends an Idempotency-Key, so a retried request never buys twice. Pass idempotency_key= yourself to make retries safe across runs.

Everything else

client.panel has one attribute for each area of the API:

Attribute What it covers
servers list, get, delete, resources, activity, permissions, power, command, share logs, websocket
settings rename, reinstall, Docker image, status badge
startup startup command and variables
files list, read, write, rename, copy, compress, decompress, delete, folders, chmod, pull, signed URLs
backups list, create, get, delete, download, lock, restore
schedules schedules and their tasks
network allocations (ports)
subusers other accounts with access to a server
git link a repository, deploy, deployments, sillydev.conf
account the key's account and its activity
server_creation create free servers
databases free MariaDB and Postgres databases
billing paid servers and subscriptions

Every method's docstring names the API operation it calls, so you can look it up in the API reference.

Command line

export SILLYDEV_API_KEY="sdevc_..."

sillydev servers list
sillydev status 1a7ce997
sillydev power 1a7ce997 restart
sillydev command 1a7ce997 "say hi"
sillydev deploy 1a7ce997
sillydev files ls 1a7ce997 /src
sillydev files cat 1a7ce997 /config.json
sillydev backups list 1a7ce997
sillydev backups create 1a7ce997 --name before-update

Add --json to any command for JSON output. python -m sillydev works too.

MCP server

The MCP server lets an AI assistant (Claude Desktop, Claude Code, Cursor and others) check on and control your servers. It needs Python 3.10 or later:

pip install 'sillydev[mcp]'

Then add it to your assistant's MCP settings, for example:

{
  "mcpServers": {
    "sillydev": {
      "command": "sillydev-mcp",
      "env": { "SILLYDEV_API_KEY": "sdevc_..." }
    }
  }
}

Its tools are: list servers, server status, power action, send command, list files, read file, Git deploy and list backups. There is nothing for billing or for deleting anything. Give it a key limited to the servers and permissions you want the assistant to have.

Monitoring

sillydev.monitoring sends your application's metrics, errors, events and slow operations to Silly Development monitoring. It is opt-in: using the SDK never sends monitoring data by itself. import sillydev does not load it, and importing sillydev.monitoring starts no thread, installs no hook and reads no environment variable. Only monitoring.init() does, and only when it is given a key.

  1. In the panel, create a monitoring project and a server key (sdevm_<n>_…, shown once).
  2. Put it in SILLYDEV_MONITORING_KEY (servers linked to a project get it automatically) and call init() once at start-up:
from sillydev import monitoring

monitoring.init()  # reads SILLYDEV_MONITORING_KEY; without a key it does nothing

monitoring.increment("orders.created", tags={"plan": "free"})
monitoring.gauge("queue.size", 12)
monitoring.timing("payments.charge", 182.5)          # milliseconds
with monitoring.timer("reports.render"):
    render()

@monitoring.timed                                     # sync or async functions
async def sync_inventory(): ...

monitoring.event("deploy", {"version": "1.4.2"})
monitoring.record_slow("nightly-export", 42_000, kind="task")
try:
    risky()
except Exception:
    monitoring.capture_exception(tags={"job": "import"})   # the exception being handled
monitoring.capture_message("Disk almost full", level="warning")

Every function is a no-op that never raises until init() has a key, so libraries can call them freely. Unhandled exceptions (sys.excepthook, threading.excepthook and the asyncio exception handler of instrumented loops) are reported and then passed to the previous hook, so tracebacks are still printed and the process still exits as before.

Metrics are added up in 10-second windows in your process and sent by a background thread every flush_interval seconds (gzipped, at most 500 items a request), along with process metrics: process.cpu.percent, process.memory.rss, process.threads, process.uptime, runtime.gc.count, runtime.gc.pause and, for asyncio apps, runtime.event_loop.lag (the loop running when init() is called, or any loop passed to monitoring.instrument_loop(loop)). Buffered data is sent on exit (for at most 2 seconds). If the ingest is down or rate limits, data is kept (up to max_buffer_bytes, oldest dropped first) and retried with backoff; if the key is revoked (401/403) the SDK logs one warning and stops.

Option Environment variable Default
key SILLYDEV_MONITORING_KEY none: monitoring stays off
endpoint SILLYDEV_MONITORING_URL https://ingest<n>.sillydev.co.uk, from the key
service SILLYDEV_MONITORING_SERVICE default
environment SILLYDEV_MONITORING_ENVIRONMENT production
release SILLYDEV_MONITORING_RELEASE none
enabled SILLYDEV_MONITORING_DISABLED=1 turns it off True
runtime_metrics True
capture_unhandled True
sample_rate 1.0 (share of errors and events kept; metrics are never sampled)
before_send none: before_send(item_type, item) returns the item, changed or not, or None to drop it ("error", "event", "slow")
flush_interval 10 seconds
max_buffer_bytes 1,000,000
slow_threshold_ms 1000
debug False (logs every payload on the sillydev.monitoring logger)

Privacy. No local variables are ever sent. Tag, context and attribute keys that look like password, secret, token, authorization, cookie or api_key have their values replaced with [redacted]. Use before_send to remove anything else.

Integrations

Each one is imported on its own and needs nothing installed beyond the framework you already use:

# FastAPI / Starlette: http.server.duration by route template, method and status class
from sillydev.monitoring.asgi import MonitoringMiddleware
app.add_middleware(MonitoringMiddleware)

# Flask (or Django: wrap get_wsgi_application(), and add
# "sillydev.monitoring.wsgi.DjangoRouteMiddleware" to MIDDLEWARE for route templates)
from sillydev.monitoring.wsgi import MonitoringMiddleware
app.wsgi_app = MonitoringMiddleware(app.wsgi_app)

# aiohttp
from sillydev.monitoring.aiohttp import middleware
app = web.Application(middlewares=[middleware])

# discord.py: discord.command.duration (command, outcome), command and event errors,
# discord.gateway.latency and discord.guilds
from sillydev.monitoring.discord import monitor_bot
bot = monitor_bot(commands.Bot(command_prefix="!", intents=intents))

Requests and commands slower than slow_threshold_ms are also reported as slow operations. Keep routes few: each distinct route (and tag value) is a separate series, and projects have a series limit. Paths without a route template have their ids replaced with :id.

Configuration

Option Environment variable Default
api_key SILLYDEV_API_KEY (required)
base_url SILLYDEV_BASE_URL https://sillydev.co.uk/panel/api/client
timeout 60 seconds
max_retries 2

License

MIT

Metadata

Release files for sillydev 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sillydev 1.1.0
File Size Uploaded Provenance
sillydev-1.1.0.tar.gz 54.3 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for sillydev 1.1.0
File Interpreter ABI Platform
sillydev-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 120.0 kB

Release files / sillydev-1.1.0.tar.gz

Download URL sillydev-1.1.0.tar.gz
Size 54.3 kB
Tags Source
SHA-256 checksum
How to use checksums
0c8c67581ce0b321fc8ff6886c126cdca5ce92bae069fc4952f6c3d9cc793b4c
BLAKE2b-256 checksum
How to use checksums
d8d5ac7a7505e6320328552bd794020140b335624f26c8aa4d31650cb0fa11c4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / sillydev-1.1.0-py3-none-any.whl

Download URL sillydev-1.1.0-py3-none-any.whl
Size 65.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2a067687e5448bff04c476b1c6e40bb0516c71bbcaf7a02892aa14506b7a6650
BLAKE2b-256 checksum
How to use checksums
585ec6fb0c999c6eba83061f96d0dbe92540a3ac5fa4770cde6f885e3e8da301
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Security for sillydev 1.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for sillydev-1.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page