Skip to content

Python SDK ​

The official Python SDK for the WAYSCloud API. One client for VPS, Kubernetes, DNS, databases, storage, apps, IoT and more — with typed exceptions, automatic retries and connection pooling.

bash
pip install wayscloud

Requires Python 3.10+. PyPI page.

Authentication ​

python
from wayscloud import WaysCloudClient

# Personal Access Token
client = WaysCloudClient(token="wayscloud_pat_xxx...")

# API key
client = WaysCloudClient(api_key="wayscloud_api_xxx...")

# Environment variables (no args needed)
# Set WAYSCLOUD_TOKEN or WAYSCLOUD_API_KEY
client = WaysCloudClient()

Priority: explicit arguments > environment variables.

ParameterEnvironment variableDefault
tokenWAYSCLOUD_TOKEN—
api_keyWAYSCLOUD_API_KEY—
base_urlWAYSCLOUD_API_URLhttps://api.wayscloud.services
timeout—30.0

Services ​

ServicePropertyAuthDescription
VPSclient.vpsAPI keyVirtual private servers
DNSclient.dnsAPI keyDNS zones and records
Storageclient.storageAPI keyS3-compatible object storage
Databaseclient.databasePATManaged PostgreSQL & MariaDB
Redisclient.redisAPI keyManaged Redis
Kubernetesclient.kubernetesPATManaged Kubernetes clusters
Appsclient.appsAPI keyApp Platform (containers)
IoTclient.iotAPI keyIoT device management
SMSclient.smsAPI keySMS messaging
Accountclient.accountPATProfile and SSH keys

Method reference ​

Kubernetes ​

python
# Catalogue
client.kubernetes.plans(region="no", kind=None)
client.kubernetes.regions()
client.kubernetes.versions()
client.kubernetes.estimate(plan_code, node_pools, currency="NOK", region="no")

# Clusters (create/delete are async — use wait())
client.kubernetes.list()
client.kubernetes.get(cluster_id)
client.kubernetes.create(name, node_pools, plan_code="k8s-cluster-dev", region="no",
                         version="1.35", api_ip_filter=None, ssh_key_ids=None)
client.kubernetes.wait(cluster_id, timeout=1800, interval=15)
client.kubernetes.delete(cluster_id, confirm_name)
client.kubernetes.history(cluster_id)

# Node pools
client.kubernetes.add_node_pool(cluster_id, name, plan_code, count, labels=None, taints=None,
                                ssh_key_ids=None)
client.kubernetes.scale_node_pool(cluster_id, pool_name, count)
client.kubernetes.delete_node_pool(cluster_id, pool_name)

# Access
client.kubernetes.kubeconfig(cluster_id)          # YAML text — a secret
client.kubernetes.set_api_access(cluster_id, cidrs)

# Public IPs and reverse DNS
client.kubernetes.allocate_ip(cluster_id)
client.kubernetes.release_ip(cluster_id, address)
client.kubernetes.set_ptr(cluster_id, address, ptr_record)

# Backups
client.kubernetes.backup_policy(cluster_id)
client.kubernetes.set_backup_policy(cluster_id, enabled=True, schedule_cron="0 3 * * *",
                                    retention_days=14, include_volumes=True, namespaces=None)
client.kubernetes.backups(cluster_id)
client.kubernetes.backup_now(cluster_id, namespaces=None)
client.kubernetes.restore(cluster_id, backup_id, namespaces=None)

# Upgrades
client.kubernetes.available_upgrades(cluster_id)

# Pre-upgrade check: errors block the upgrade, warnings are advisory
check = client.kubernetes.upgrade_preflight(
    cluster_id, "1.35",
    mode="next_maintenance_window",   # "immediate" | "next_maintenance_window" | "scheduled"
    scheduled_at=None,                # required with mode="scheduled" (ISO-8601, inside the window)
    strategy="rolling-update",        # or "manual"
    backup_before_upgrade=True,       # check as if a fresh backup is taken first
)
check["errors"], check["warnings"], check["backup"]["age_hours"]

client.kubernetes.upgrade(cluster_id, "1.35", mode="next_maintenance_window",
                          strategy="rolling-update", backup_before_upgrade=True)

# Maintenance window (DST-safe occurrence math on the server)
client.kubernetes.get_maintenance_window(cluster_id)
client.kubernetes.set_maintenance_window(cluster_id, timezone="Europe/Oslo",
                                         days=["SUN"], start="02:00", duration_minutes=120)
client.kubernetes.delete_maintenance_window(cluster_id)

# Upgrade jobs (queued/scheduled: list, inspect, cancel before start)
client.kubernetes.upgrade_jobs(cluster_id)
client.kubernetes.upgrade_job(cluster_id, job_id)
client.kubernetes.cancel_upgrade_job(cluster_id, job_id)

# Worker convergence (one pool at a time; rolling upgrades do this automatically)
client.kubernetes.upgrade_node_pool(cluster_id, pool_name)

VPS ​

python
# Instances
client.vps.list(status=None, region=None)
client.vps.get(vps_id)
client.vps.create(hostname, plan, region, os_template, ssh_keys=None)
client.vps.delete(vps_id)
client.vps.start(vps_id)
client.vps.stop(vps_id)
client.vps.reboot(vps_id)
client.vps.status(vps_id)

# Plans & discovery
client.vps.plans(region=None)
client.vps.os_templates()
client.vps.regions()

# Snapshots (requires qcow2 disk format)
client.vps.snapshots(vps_id)
client.vps.create_snapshot(vps_id, name, description=None)
client.vps.delete_snapshot(vps_id, snapshot_name)
client.vps.rollback_snapshot(vps_id, snapshot_name)  # VPS must be stopped

# Backups (off-site)
client.vps.backups(vps_id)
client.vps.create_backup(vps_id)
client.vps.delete_backup(vps_id, backup_id)
client.vps.backup_usage(vps_id)
client.vps.backup_policy(vps_id)
client.vps.set_backup_policy(vps_id, enabled=True, frequency="daily",
                             time_of_day="03:00", retention_days=7)
client.vps.delete_backup_policy(vps_id)

# Firewall
client.vps.firewall_rules(vps_id)
client.vps.add_firewall_rule(vps_id, port, protocol="tcp", source="0.0.0.0/0")
client.vps.remove_firewall_rule(vps_id, rule_id)

DNS ​

python
# Zones
client.dns.zones()
client.dns.get(zone_name)
client.dns.create_zone(name)
client.dns.delete_zone(zone_name)

# Records
client.dns.records(zone_name)
client.dns.create_record(zone_name, record_type, value, name="", ttl=3600)
client.dns.update_record(zone_name, record_id, value=None, ttl=None)
client.dns.delete_record(zone_name, record_id)

# DNSSEC
client.dns.dnssec_status(zone_name)
client.dns.dnssec_activate(zone_name)
client.dns.dnssec_deactivate(zone_name)

Database ​

Requires PAT authentication with database:read and database:write scopes.

python
client.database.list()
client.database.get(db_type, name)
client.database.create(name, db_type="postgresql", tier="standard")
client.database.delete(db_type, name)

# Firewall
client.database.firewall_rules(db_type, name)
client.database.add_firewall_rule(db_type, name, source)
client.database.remove_firewall_rule(db_type, name, rule_id)

Connection credentials (host, port, username, password) are returned in the create() response.

Storage ​

python
# Buckets
client.storage.list()
client.storage.get(bucket_name)
client.storage.create_bucket(name, tier="standard")
client.storage.delete_bucket(bucket_name)

# Bucket access keys
client.storage.bucket_keys(bucket_name)
client.storage.create_bucket_key(bucket_name, name)   # returns access_key + secret_key
client.storage.delete_bucket_key(bucket_name, key_id)

Redis ​

python
client.redis.list()
client.redis.get(instance_id)          # includes connection info (host, password)
client.redis.create(name, plan="redis-starter", region="no")
client.redis.delete(instance_id)
client.redis.restart(instance_id)
client.redis.rotate_credentials(instance_id)

# Discovery
client.redis.plans()
client.redis.regions()

# Firewall
client.redis.firewall_rules(instance_id)
client.redis.add_firewall_rule(instance_id, source, description="")
client.redis.remove_firewall_rule(instance_id, rule_id)

Apps ​

python
client.apps.list()
client.apps.get(app_id)
client.apps.create(name, plan="app-basic", region="no", port=8080)
client.apps.update(app_id, **kwargs)
client.apps.delete(app_id)
client.apps.deploy(app_id, image)
client.apps.start(app_id)
client.apps.stop(app_id)
client.apps.restart(app_id)
client.apps.logs(app_id, lines=100)

# Discovery
client.apps.plans()
client.apps.regions()

# Environment variables
client.apps.env_vars(app_id)
client.apps.set_env(app_id, key, value)
client.apps.unset_env(app_id, key)

IoT ​

python
# Devices
client.iot.devices()
client.iot.get_device(device_id)
client.iot.create_device(device_id, name, device_type=None)  # returns MQTT credentials
client.iot.update_device(device_id, name=None, is_active=None)
client.iot.delete_device(device_id)
client.iot.device_telemetry(device_id)

# Groups
client.iot.groups()
client.iot.create_group(name, description=None)
client.iot.delete_group(group_id)

# Rules
client.iot.rules()
client.iot.create_rule(name, rule_type, severity="warning")
client.iot.delete_rule(rule_id)

SMS ​

python
client.sms.send(to, message, sender_id=None)
client.sms.list()
client.sms.get(message_id)

Account ​

python
client.account.profile()
client.account.ssh_keys()
client.account.add_ssh_key(name, public_key)
client.account.delete_ssh_key(key_id)

Examples ​

Create a VPS with snapshot ​

python
vps = client.vps.create(
    hostname="web01.example.com",
    plan="vps-medium",
    region="no",
    os_template="ubuntu-24.04",
)

# After setup, create a snapshot
client.vps.create_snapshot(vps["id"], "initial-setup")

Database with firewall ​

python
db = client.database.create(name="prod", db_type="postgresql")
print(db["host"], db["port"], db["password"])

# Whitelist your IP
client.database.add_firewall_rule("postgresql", "prod", "203.0.113.0/32")

App deployment with environment variables ​

python
app = client.apps.create(name="api", region="eu", port=8080)
client.apps.set_env(app["id"], "DATABASE_URL", "postgres://...")
client.apps.deploy(app["id"], image="ghcr.io/org/api:latest")

Error handling ​

python
from wayscloud import NotFoundError, AuthenticationError, ValidationError, ServerError

try:
    client.vps.get("nonexistent")
except NotFoundError:
    print("Resource not found")
except AuthenticationError:
    print("Invalid or expired credentials")
except ValidationError:
    print("Invalid request parameters")
except ServerError:
    print("Server error — retry later")

All exceptions inherit from WaysCloudError with status_code, message, and detail.

ExceptionHTTP Status
AuthenticationError401, 403
NotFoundError404
ValidationError422
ServerError5xx

Connection management ​

The SDK uses a persistent HTTP client for connection pooling:

python
with WaysCloudClient(token="wayscloud_pat_xxx...") as client:
    zones = client.dns.zones()
    vps = client.vps.list()
# Connections released here

Or close manually:

python
client = WaysCloudClient()
try:
    data = client.vps.list()
finally:
    client.close()

Retries ​

Automatic retries on 429, 502, 503, 504 — up to 3 attempts with exponential backoff. Respects Retry-After headers.


Next steps ​