Python client, CLI, and provisioning toolkit for Sovereign Hybrid Compute (SHC) — the quickest way to spin up a VM and manage it from the command line or Python.
Disclosure: The SHC link above is an affiliate link. If you sign up through it, we receive a 5% recurring commission (grandfathered rate) on your spending, at no extra cost to you. We use SHC as the CI backend for our open-source projects and genuinely recommend the service.
- terraform-provider-shc — Terraform provider for SHC
- shc-pulumi — Pulumi provider for SHC
shcCLI — order, manage, and snapshot VMs from the command line- Python client —
SHCClientwraps the full SHC User API - NoDNS integration (experimental) — publish DNS records via Nostr events (kind 11111). May be unreliable; use
shc nodns --ip <ip>separately from IaC tools. - Certbot DNS-01 — get Let's Encrypt certs without opening port 80
- Provisioning helpers — one-call setup for Caddy + HTTPS on a fresh VM
pip install -e .Requires Python 3.11+.
AI agents: read "Operating VMs with the shc CLI" in AGENTS.md before ordering. It covers the size-name trap, the order/pay two-step, the payment confirmation gate, and per-VM SSH key commands.
export SHC_API_KEY="shc_live_..."
shc catalog
shc order --hostname my-vm --package-id 23 --pricing-id 55 \
-o 108=50 -o 126=debian13-cloud -o 167=none \
--ssh-key ~/.ssh/id_ed25519.pub --pay --verify-reachability
shc list
shc info <service_id>
shc cancel <service_id>--verify-reachability waits for the VM to come up and polls TCP/22; if it
never opens, the VM is cancelled immediately (prorated refund) and the
command exits 1 — no paying for an unroutable box. shc pay <invoice_id>
is spend-gated: it explains by default and pays with --confirm. The full
ephemeral-VM loop (order → run → exfiltrate → close, with receipts):
docs/campaign-lifecycle.md.
Catalog lines live in different physical facilities, and reachability differs per route (earned 2026-09-01: two fresh ssd orders were dead on arrival from an EU route — port 22 closed, cloud-init bootstrap never fired — while a Katy hdd order provisioned SSH-reachable in under a minute):
| Line | Facility (module group) | Notes |
|---|---|---|
nvme-* |
Katy, Texas (g4) | default recommendation |
hdd-* |
Katy, Texas — HDD (g8) | same facility, spinning disk |
ssd-* |
Cherryvale, Kansas (g7) | unreachable from EU routes 2026-09-01 |
dev-* |
Cherryvale, Kansas (g7) | same facility as ssd |
shc sizes # every size with its facility + reachability flag
shc sizes --available # + live stock probe (~20 API calls)
shc stock # live stock across the catalog, in-stock first
shc order --size nvme-4c-16gb --reap 8h ... # Katy line — no guardNotes:
shc orderREFUSES flagged facilities by default (fail-early, 2026-09-04): an order intossd-*/dev-*exits 1 before submitting — those VMs go billing-active and then never attach to the network (issue #39, still failing 2026-09-04), so waiting out the provisioning timeout cannot help. Debugging the zone deliberately?--allow-unstable-zoneopts in (or--dry-run, which only previews), andscripts/dev-zone-probe.pycarries the long-wait knobs (--timeout,--net-timeout). The same guard coversshc contextvmorders.shc order --hostname x-reap8h --reap 8hno longer double-tags; the existing tag wins and the CLI says so.provisioning_statemay stay"provisioning"forever on a healthy VM — the real health signal is SSH reachability (port 22).shc reinstallrequires a STOPPED VM (shc stop→reinstall→start); the CLI prints the exact sequence on conflict.
from shc_toolkit import SHCClient
c = SHCClient()
vms = c.list_vms()
c.start_vm(123)
c.create_snapshot(123, name="pre-deploy")
⚠️ Experimental: NoDNS integration may be unreliable. It is a Python-only provisioning feature, not part of the Terraform or Pulumi providers. Useshc nodns --ip <ip>separately from IaC workflows.
NoDNS maps Nostr keypairs to .nodns.shop subdomains. You publish DNS records as kind 11111 Nostr events, and the nodns-bot pushes them to Knot DNS. No domain registration, no DNS provider account.
Your machine
└── nostr-sdk → publishes kind 11111 events to Nostr relays
↓
nodns-bot → Knot DNS (ns1.nodns.shop)
↓
Public DNS: <npub>.nodns.shop A <your-vm-ip>
from shc_toolkit.nodns import NoDNSKeyPair, publish_dns_records
kp = NoDNSKeyPair.generate()
print(f"Domain: {kp.fqdn}")
print(f"Save this nsec to update records later: {kp.nsec}")
publish_dns_records(kp, [
{"type": "A", "name": "@", "value": "<your-vm-ip>", "ttl": 300},
])shc nodns --ip <your-vm-ip>The provisioning module automates the complete flow on a fresh Debian VM:
from shc_toolkit.client import SHCClient
from shc_toolkit.nodns import NoDNSKeyPair, publish_dns_records
from shc_toolkit.provision import install_caddy, get_cert_dns01
# 1. Order a VM (or use an existing one)
c = SHCClient()
vm = c.get_vm_summary(123)
ip = vm["ips"][0]["ip"]
# 2. Publish A record via NoDNS
kp = NoDNSKeyPair.generate()
publish_dns_records(kp, [{"type": "A", "name": "@", "value": ip, "ttl": 300}])
# 3. Install Caddy + get Let's Encrypt cert via DNS-01
install_caddy(ip)
get_cert_dns01(ip, kp.fqdn, keypair_path="/tmp/nodns-keypair.json")For the certbot auth hook to work, save the keypair and deploy nodns_vm.py to the VM first. See shc_toolkit/provision.py for details.
$ curl https://npub1mv7l45exqsu5nr5tnefkr33ruhzjj4r8prg6qtcedv4lyf3rzguqptuwm4.nodns.shop
Hello from NoDNS + Let's Encrypt DNS-01!Certificate issued by Let's Encrypt via DNS-01 through nodns. No port 80, no traditional DNS provider, no domain registrar.
The SHC User API has full documentation available at:
- Interactive docs: https://blesta.sovereignhybridcompute.com/user-api/docs/
- OpenAPI 3.1 spec: https://blesta.sovereignhybridcompute.com/user-api/openapi.json
- LLM-friendly docs: https://blesta.sovereignhybridcompute.com/user-api/llms.txt
This toolkit covers the most common endpoints (VM lifecycle, ordering, snapshots, billing). For operations not yet wrapped (reinstall, backups, etc.), use SHCClient._request() directly or open a PR.
API-native console access — see a VM's screen without SSH (works even when the guest is broken, keyless, or mid-bootstrap):
shc console 1077 # availability check
shc console-session 1077 # single-use noVNC URL (default 30s TTL)
shc console-session 1077 --ttl 300 # 5-minute sessionThe response carries a console_url (JWT-authenticated Proxmox bridge) —
open it in a browser immediately; tokens are single-use. Confirmation is
auto-handled (_confirmed_request). For scripted typing/screenshots inside
the console, see shc_toolkit/console.py (Playwright-based portal
automation: ConsoleSession().open_console() → type_text() →
screenshot()).
The toolkit supports dual transport: REST v2 (default) or MCP Streamable HTTP. Both transports are fully functional for all operations including reads, writes, spend/destructive actions with confirmation flow, and upgrades.
The flagship SHC MCP server at https://mcp.sovereignhybridcompute.com/ exposes
157 tools over Streamable HTTP (the curated everyday subset is 35 ops marked
x-shc-core in the spec). The toolkit's TOOL_MAP wraps 157 of the 157
(100% of all MCP-exposed ops; 35/35 = 100% of the x-shc-core curated subset).
Every spend and destructive op is confirm-gated with automatic
Idempotency-Key generation and
X-User-Api-Confirm handling.
CI tests randomly select REST or MCP per run, ensuring both transports receive equal coverage over time without doubling test cost.
from shc_toolkit import create_client
# Auto (defaults to REST — change with SHC_TRANSPORT=mcp)
c = create_client(transport="auto")
# Force MCP
c = create_client(transport="mcp")
# Force REST (default)
c = create_client(transport="rest")Or via environment variable:
export SHC_TRANSPORT=mcp # or 'rest' or 'auto'Install the MCP optional dependency:
pip install shc-toolkit[mcp]Both transports implement the same SHCTransport interface — your code works
unchanged regardless of which backend is selected.
Option IDs differ by VPS line. Always read them from shc catalog or GET /ordering/catalog.
For NVMe Starter (package_id 23):
| Option | ID | Example values |
|---|---|---|
| RAM | 106 | 4096 (base), 8192, 16384 |
| CPU | 107 | 1 (base), 2, 4 |
| Disk | 108 | 8 (base), 32, 50, 100 |
| IPv4 | 109 | 1 (base), 2, 4 |
| Template (NVMe/HDD) | 126 | see the live list below |
| Template (Dev VPS) | 174 | same list |
| GUI | 167 | none, gnome, kde, xfce, cinnamon, mate |
Live template list (32, verified 2026-09-11 via get_config_options — the
old 11-entry list here was badly stale): almalinux10-cloud, almalinux9-cloud,
alpine323-cloud, arch-cloud, cs10-cloud, debian12-cloud, debian13-cloud,
devuan5-cloud, fedora42-cloud, fedora43-cloud, freebsd14-cloud,
gentoo-cloud, kali-cloud, netbsd10-cloud, nixos-cloud, ol10-cloud,
ol9-cloud, openbsd79-cloud, opensuse-leap156-cloud, rocky10-cloud,
rocky9-cloud, ubuntu2204-cloud, ubuntu2404-cloud, ubuntu2604-cloud,
win11-pro-byol, win2022-byol, win2022-core-byol, win2025-byol,
win2025-core-byol, firecracker-cloud, openwrt-cloud, pve-ve-cloud
(Proxmox VE). Not every template accepts the same addons — read live values
from shc catalog / GET /ordering/catalog before ordering.
Values are the value field from the catalog, not value_id.
Windows is available bring-your-own-license on all VPS lines: Windows Server 2022/2025 (Core or Desktop) and Windows 11 Pro. Windows Server requires >=32GB disk; Windows 11 requires >=64GB disk. Apply your own license after first boot.
GUI requires >=16GB disk AND >=4GB RAM.
Disposable per-job CI runners on cheap SHC VPSs. The toolkit orders a VPS,
bootstraps the GitHub Actions runner over SSH with --ephemeral, registers
it with a unique per-run label, and the workflow destroys the VM in an
if: always() teardown job when the benchmark finishes.
Live-measured cold-start: 135.8 s end-to-end on dev-4c-16gb
($0.90/day, ~$0.01 per run prorated). Workload execution matches
ubuntu-latest per-shard. Full perf comparison and the Firecracker
cold-start reduction target live in
docs/github-ephemeral-runners.md.
Firecracker PoC validated: nested KVM works on SHC Dev VPS, μVMs boot
in ~2 s vs the 100 s VPS scheduling floor — 50× faster per job,
150× throughput at parallelism 4. Pool-mode architecture and live
benchmark numbers in
docs/firecracker-pool-mode.md.
export SHC_API_KEY="shc_live_..."
export SHC_GITHUB_ADMIN_TOKEN="ghp_..." # PAT with repo admin / runners:write
shc github-runner provision \
--repo Amperstrand/tollgate-module-basic-go \
--labels shc-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}
shc github-runner destroy --service-id "$SERVICE_ID"
# optional controller-dead failsafe: the VM cancels ITSELF N minutes after
# boot via an on-VM systemd timer + bounded full-scope key
# (SHC_SUICIDE_KEY secret, or SHC_ACCOUNT_EMAIL/PASSWORD for a per-run
# 1-day mint — only full scope can cancel). See shc_toolkit/selfdestruct.py
shc github-runner provision --self-destruct-minutes 120 …Intended audience: OSS maintainers with expensive / slow / custom CI who want pay-per-minute runners on machines they control.
Start from nothing — no SHC account, no browser, one Lightning payment. The first top-up invoice opens as a QR in your browser by default; pay it with any Lightning wallet and the account is live.
pip install -e ".[register]"
shc register # THE getting-started path: generates
# everything, opens the browser QR page,
# waits for your payment — done
shc register --interactive # prompts for email/amount/context name
shc register --amount 5.00 --no-browser # terminal QR + URL instead
shc topup --context default # re-fund an existing account later
shc mail # read the account's nomail inbox
shc mail --send-to a@b.c --subject hi --text "..." --cashu-token cashuB...What happens (unattended): nsec generated locally → anonymous
POST /register with generated password/names (hard API minimums) →
full-scope API key minted over HTTP Basic → Nostr key linked
(NIP-98 kind-27235) → BTCPay top-up invoice served on a stable local
URL (http://127.0.0.1:8923/ by default; QR + lightning: link,
auto-refreshes across invoice reissues) → polled until paid → context
saved to ~/.config/shc/contexts/<name>.json (0600:
email, password, client_id, api_key, nsec).
Any shc command with no key configured will offer the same wizard
(TTY only; disable with SHC_NO_REGISTER=1 or --no-register).
Multiple accounts (profiles): aws/gcloud-style named profiles —
run shc register once per account. Identities live at
~/.config/shc/profiles/<name>.json (0600) and are identified by
npub (the nsec is the account identity: portable to any Nostr tool,
and the recovery/mail key for that account).
shc profile list # accounts by npub, * marks the active one
shc profile use eddy-e2e # persist the active account
shc profile show # full credential set of the active accountResolution precedence (aws-style): --api-key flag > SHC_PROFILE
env > SHC_API_KEY env > active profile. --context <name> still
works as an alias; legacy contexts.json and register contexts
migrate lazily and non-destructively.
Referral note: /register takes no referral field (attribution is
web-session based). Our affiliate link stays in this README; an
npub…@nomail.name address strongly implies tool registration.
- Nested KVM: Available ONLY on Dev VPS plans (pkg 80–84, Cherryvale, KS). NVMe/SSD/HDD VPS plans do NOT expose VMX/SVM to guests — QEMU runs in TCG (software emulation) only. Empirically verified 2026-07-20: NVMe Starter (pkg 23, Katy-TX) probed via SSH —
grep -c 'vmx|svm' /proc/cpuinfo= 0,/dev/kvmabsent. Verify after ordering withgrep -E 'vmx|svm' /proc/cpuinfoorshc kvm-check <service_id>. - Dev zone scheduler hang (issue #28) — RESOLVED: Dev VPS plans (pkg 80–84, Cherryvale, KS) previously failed to provision (scheduler never assigned an IP). Recovered and verified 2026-08-25 via
scripts/dev-zone-probe.py: pkg 80 provisioned in 85s (debian12) and 102s (debian13) — confirming the old "debian13 deadlock" (#24) was purely the zone scheduler, not the template. Nested-KVM workloads (Dev plans only) are available again. - Hourly proration: You're charged the full daily rate at order time, but get refunded for unused hours when you cancel (minimum 1 hour charge). A 2-hour session on a $0.49/day plan costs ~$0.04. Industry terms: cancel = terminate/delete (billing ends); stop = pause — which on SHC still bills the full daily price. See docs/iac-lifecycle.md.
- Locations: Katy, Texas (NVMe/SSD/HDD VPS) and Cherryvale, Kansas (Dev VPS plans only).
- API key lifecycle: API keys expire after 90 days (max 730). A 401 on a working key means it expired — mint a new one at
/account/api-keys. Maximum 25 active keys per account. - Snapshot/backup limit: All VPS plans (including Dev VPS) support 1 snapshot and 1 backup concurrently (
snapshot_limit: 1,backup_limit: 1per package). Verified working on Dev VPS via front-door E2E (2026-07-01).
A pay-per-minute SSH server that accepts Cashu ecash tokens as payment. Users paste a token as their SSH username and get an interactive bash shell for as many minutes as the token is worth. Includes a static faucet page for minting free test tokens.
MIT
The toolkit includes an auto-generated Python client (shc_toolkit.generated)
with 100% API endpoint coverage (148 endpoint modules, 729 attrs models)
generated from the OpenAPI spec via openapi-python-client.
pip install shc-toolkit[generated] # installs httpx + attrsfrom shc_toolkit.generated import Client
from shc_toolkit.generated.api.ordering import get_ordering_catalog
client = Client(base_url="https://blesta.sovereignhybridcompute.com/user-api/v2",
headers={"Authorization": "Bearer shc_live_..."})
catalog = get_ordering_catalog.sync(client=client)The generated client provides type-safe raw API calls. For convenience features
(retry with jitter, cost tracking, confirmation flow, MCP transport), use
SHCClient instead — it wraps the generated layer and adds production niceties.
The hand-written SHCClient returns raw dict / list[dict]; users who want
typed responses should reach for shc_toolkit.generated directly.
Regenerate from the latest spec: bash scripts/generate_client.sh
The recommended way to use SHC from Pulumi is via the Terraform Bridge — no separate Python provider needed:
# Build the TF provider
go build -o terraform-provider-shc .
# Generate a Pulumi SDK from the TF provider
pulumi package add terraform-provider ./terraform-provider-shc --language pythonThe generated Pulumi SDK includes all resources (Vm, Snapshot, Backup,
FirewallRule, Rdns) + the term attribute (v2.4.3 VM term management).
Lifecycle semantics through the bridge (industry-aligned terminology):
pulumi destroy / terraform destroy → terminate (SHC: immediate cancel +
prorated refund, billing ends) — never a mere stop; power_state = "stopped"
maps to GCP's desired_status pattern and pauses without destroying, but
unlike AWS/GPC a stopped SHC VM keeps billing its full daily price.
Deletion protection = SHC's server-side confirm-gate + Terraform-native
prevent_destroy. Full mapping and reasoning:
docs/iac-lifecycle.md and
terraform-provider-shc/docs/lifecycle-alignment.md.
→ Migration guide from shc-pulumi
→ CHANGELOG.md — full version history in Keep a Changelog format.
- Unit suite (network-isolated by a conftest fixture that blocks all real HTTP; grows with the API surface —
pytest --collect-only -qprints the current count) — incl. nostr operate-lane grant exchange, BIP21 stitch table, invoice-polling path regression - Corpus audit (2026-08-26, llms-full.txt v2.4.15): confirm-gate, gated/routine op split, register, MCP 157/157 — conformant in both repos; nostr operate-lane + BIP21 stitching added; jit_pay polling f-string bug fixed
- API: v2.4.24 (148 paths, 177 operations, no spec drift); live smoke verified
- Unit suite (network-isolated, zero flakes across 5 consecutive runs at release time)
- mypy type checking: 0 errors (17 source files; generated/ excluded)
- Cross-repo parity: 5/5 checks pass (size map, feature matrix, resolve_addons contract, billing claims, Dev VPS claims)
- API: v2.4.24 (148 paths, 197 schemas, 177 operations); live MCP server exposes 157 tools; curated x-shc-core subset is 35;
TOOL_MAPwraps 157 entries (100% of all MCP-exposed ops) - API resilience: 408 retry, exponential backoff with ±20% jitter, auto-generated Idempotency-Key on all confirmed requests
- Generated typed client: 932 files, 148 endpoint modules, 729 attrs models
- CI: dispatch/tag/monthly workflows (unit+smoke+integration, OpenAPI+MCP drift, cross-repo parity, typecheck, coverage, security, ansible, reaper, publish) + auto-issue-creation on drift
- Ansible: ansible-lint CI + molecule caddy scenario + monthly live E2E
- READS: All working (getAccount, getBillingBalance, listVirtualMachines, getOrderingCatalog)
- WRITES: All working (createVirtualMachineOrder with confirmation flow, cancelVirtualMachine)
- CI: Randomly selects REST or MCP per run for equal coverage
- Code is complete (keypair generation, event publishing, DNS verification)
- Not actively tested: Published A record may or may not resolve correctly
shc nodns --ip <ip> --zone nodns.shoppublishes,shc order --nodnsauto-publishes after VM creation
- Bootstrap code complete and tested on NVMe VPS (Debian 13, Katy TX)
- VM becomes discoverable MCP server on Nostr via
wss://relay.contextvm.org