The Impreza Host REST API has been powering programmatic access to domains, hosting, VPS, email, billing, and webhook deliveries for a while. We’ve been using it internally and quietly with a handful of customers; what was missing was first-class tooling that any developer could pip install or curl and have working in a minute.
Today we’re shipping that — open-source, MIT-licensed, on GitHub at imprezahost/impreza-devkit. Three packages built against the same OpenAPI 3.1 contract, plus the contract itself (and the AsyncAPI 3.0 webhook contract that goes with it).
This post is for the developer who’s going to actually integrate against it. We’ll cover what’s in the box, the design decisions we made, and the places where we deliberately stopped short of competitor parity.
What ships
| Artifact | Version | Where | What it gives you |
|---|---|---|---|
impreza-sdk |
0.3.2 |
PyPI | Python SDK — sync + async, fully typed |
impreza-cli |
0.3.2 |
PyPI | Reference CLI built on the SDK |
impreza-cli-go |
0.1.1 |
GitHub Releases | Static Go binary, no runtime |
openapi/openapi.yaml |
— | repo | OpenAPI 3.1 REST contract |
openapi/asyncapi.yaml |
— | repo | AsyncAPI 3.0 webhook contract |
Both Python packages are co-released in lock-step — pip show impreza-sdk and pip show impreza-cli always carry matching versions. The Go CLI ships independently under the cli-go-v* tag track because Go binaries don’t need the Python lock-step.
Verb surface: 86 commands across 10 resource groups — same on both CLIs:
account 5 info / balance / services / topup / topup-status
catalog 4 products / product / product-groups / tlds
context 5 create / use / list / current / delete
doctor 1 5-check reachability + auth + IP + config probe
domain 17 show / check / pricing / register / transfer /
set-nameservers / lock / unlock / id-protection /
raa-verify / gdpr-auth / transfer-approval
+ dns: list / add / update / delete / activate
invoice 2 list / show
key 1 whoami
order 4 list / show / create / upgrade
service 1 cancel
vps 32 base 12 + proxmox 11 + cloud 9 (rdns hidden — see below)
webhook 8 list / show / create / update / delete /
rotate-secret / deliveries / event-types
The Go binary and the Python CLI parse the same TOML config file, expose the same flags, accept the same --output json|yaml|table switches, and produce byte-identical JSON output for the same call. You can install both side-by-side and pick whichever wins $PATH per shell.
Architecture
github.com/imprezahost/impreza-devkit
├── openapi/openapi.yaml OpenAPI 3.1, source of truth for REST
├── openapi/asyncapi.yaml AsyncAPI 3.0, source of truth for webhooks
├── sdk-python/ impreza-sdk: typed HTTP client + resources
├── cli-python/ impreza-cli: typer/rich CLI on top of the SDK
├── cli-go/ impreza-cli-go: hand-written client + Cobra CLI
└── examples/ curl + Python recipes
Three things to call out about this layout:
The OpenAPI spec is in the same repo, not a separate one. That means SDK regenerations stay reviewable against the spec change that motivated them, and the spec ships in every Go-CLI release archive (bundled alongside the binary) so users have the contract on disk without a second download.
The Python CLI depends on the Python SDK as a path dep. No code is duplicated. The CLI wraps the SDK’s typed resources with roughly 3,000 lines of Typer/Rich glue, while the SDK handles authentication, retries, and validation.
The Go CLI does NOT depend on the OpenAPI spec at build time. We tried — oapi-codegen v2 doesn’t yet handle OpenAPI 3.1’s type: [X, null] nullable form, and pivoting to OpenAPI 3.0 would have lost expressive power we use elsewhere. So the Go client is hand-written. It re-implements the typed-request, typed-response, retry, and error-mapping layers in idiomatic Go (about 1,200 lines under cli-go/internal/client/).
Python SDK design
impreza-sdk is the reference implementation. Everything in the public surface is type-checked, every response is a Pydantic v2 model, both sync and async clients share extractor helpers, and there is no string-typed Any floating around customer code.
from impreza import Client
with Client.from_env() as c:
me = c.account.get() # AccountInfo
print(me.balance, me.currency) # float, str
for vps in c.vps.list(): # list[VPS]
print(vps.id, vps.hostname, vps.status)
new = c.domains.register(
domain="example.com",
years=2,
nameservers=["ns1.foo.example.com", "ns2.foo.example.com"],
) # DomainRegistration
print(new.order_id, new.invoice_id, new.amount)
Async surface is identical except for await and AsyncClient:
import asyncio
from impreza import AsyncClient
async def main():
async with AsyncClient.from_env() as c:
me = await c.account.get()
vps_list = await c.vps.list()
print(me.balance, len(vps_list))
asyncio.run(main())
A few specific design calls we made:
mypy --strictclean. The SDK shipspy.typed. We dogfood this with our own integration code; the CLI test suite usesmypy --strict impreza_clias a gate.- VPS dispatch.
c.vps.get(id)returns aVPSobject whose backend-specific operations are exposed via sub-resources (.proxmox.snapshots,.cloud.images,.rdns). Operations that don’t apply on the current backend raiseBackendNotSupportedwith a clear message, not a silent error. No more “is this a Proxmox or a Cloud VPS?” branching in customer code. - Operation polling. Long-running calls (
vps.reinstall(),vps.migrate(),vps.cloud.images.create()) return anOperationfuture.Operation.wait()pollsGET /v1/operations/{id}until a terminal state with a sensible default timeout per op type — 600 s for fast ops (snapshot, image), 1800 s for slow ops (reinstall, migrate, restore). - Tor / SOCKS5 first-class. Pass
proxy="socks5://127.0.0.1:9050",use_tor=True, or setIMPREZA_USE_TOR=1. Useshttpx[socks]under the hood.
# Three equivalent ways to route through Tor:
Client(api_key=..., api_secret=..., use_tor=True)
Client(api_key=..., api_secret=..., proxy="socks5://127.0.0.1:9050")
# IMPREZA_USE_TOR=1 in env + Client.from_env()
- Crypto top-up with ETA. This is the one we haven’t seen any other cloud provider expose via API. Top up your account balance in BTC / XMR / TRX / USDT, return a typed
TopupInvoicefuture, optionallywait_until_paid()to block until the gateway confirms:
invoice = c.account.topup(amount=50, method="xmr")
print(invoice.invoice_url) # BTCPay invoice URL
print(invoice.btc_address) # or xmr_address / trx_address etc.
invoice.wait_until_paid(timeout=7200) # default 2h
Python CLI design
impreza-cli is built on typer + rich. It mirrors the SDK surface 1:1; if c.foo.bar(...) exists, impreza foo bar ... does too.
Three things that aren’t obvious from the verb list:
Multi-context config. Multiple API keys, multiple environments, switch between them with one command:
impreza context create personal --key imp_... --secret ...
impreza context create company --key imp_... --secret ...
impreza context use company # set default
impreza --context personal vps list # one-off override
impreza context list # show all
Config lives in TOML at the platform-native path:
| OS | Path |
|---|---|
| Linux | $XDG_CONFIG_HOME/impreza/config.toml (default ~/.config/impreza/config.toml) |
| macOS | ~/Library/Application Support/impreza/config.toml |
| Windows | %APPDATA%\impreza\config.toml |
Override with IMPREZA_CONFIG=/path/to/config.toml. On POSIX the file is chmod 0600 after every write — the SDK enforces this on save.
Three output formats with stable contracts. --output table (default, human), --output json (scriptable, stable field names), --output yaml (round-trip of the JSON shape). The same fields show up regardless of format. The YAML output uses the JSON tags, not Python attribute names — service_id, not serviceId, not service-id. Whatever you pipe into jq is what you’ll see in yq.
Confirmation prompts on destructive operations + cost-charging operations. --yes / -y skips them in scripts; without it, the CLI prints what’s about to happen and waits for y/N. Cost-charging verbs (register, transfer, top-up, id-protection, order create, order upgrade) also do a best-effort price lookup so the prompt shows the actual charge upfront:
$ impreza domain register example.com --years 2
Register 'example.com' for 2 year(s) — 24.00 USD from your balance
(balance: 100.00 USD). Continue? [y/N]
InsufficientCredit (HTTP 402) gets a tailored hint pointing at impreza account topup — same pattern in both CLIs.
Go CLI design
Why a Go CLI when the Python one already exists? Three reasons.
- Drop-in deployment. Single static binary, around 8 MB stripped, no Python runtime, no
pip, no virtualenv. The Hetzner / DigitalOcean / Vultr CLIs all follow this shape and ops people expect it. - Faster cold start.
impreza --versionreturns in under 10 ms in Go vs about 150 ms in Python (interpreter spin-up). Matters in CI pipelines that fan out 50 invocations. - No dependency footprint on production hosts. You don’t need to taint your server’s Python with
pip installs just to interact with the API.
The Go binary is built with cobra (commands), BurntSushi/toml + yaml.v3 (config + output), fatih/color + jedib0t/go-pretty (palette + tables), x/term (no-echo password prompts), and x/net/proxy (SOCKS5 / Tor).
Five platforms ship per release:
impreza-cli-go_0.1.1_Linux_x86_64.tar.gz 3.21 MB
impreza-cli-go_0.1.1_Linux_arm64.tar.gz 2.91 MB
impreza-cli-go_0.1.1_Darwin_x86_64.tar.gz 3.24 MB
impreza-cli-go_0.1.1_Darwin_arm64.tar.gz 3.00 MB
impreza-cli-go_0.1.1_Windows_x86_64.zip 3.32 MB
+ impreza-cli-go_0.1.1_checksums.txt (SHA-256, one line per archive)
Each archive bundles the binary, README.md, LICENSE, and copies of both openapi.yaml and asyncapi.yaml. You get the contract on disk without a second download — and you can pipe it to oapi-codegen for your own language if you want.
Builds are reproducible: CGO_ENABLED=0, -trimpath, -ldflags="-s -w -X main.version=...". Verify a download against the published checksum:
sha256sum -c impreza-cli-go_0.1.1_checksums.txt
The Go CLI reads and writes the same TOML config file as the Python CLI. Same field names (default, contexts, key, secret, use_tor, proxy, url), same types, same ordering. You can install both side-by-side:
$ impreza context create personal --key imp_... --secret ... # via Python CLI
$ impreza --version # whichever wins $PATH
impreza-cli 0.3.2
$ impreza-go context list # if you renamed the Go binary
NAME KEY PREFIX USE_TOR CURRENT
personal imp_... no *
The contracts
OpenAPI 3.1 (REST) lives at openapi/openapi.yaml. Covers every endpoint the SDK exposes — auth scheme, response envelope ({success, data, error, meta}), typed errors per HTTP status, the full account / catalog / domain / vps / order / invoice / webhook surface. Use it with any OpenAPI-aware tool: client codegen, request validation in your reverse proxy, contract testing in your CI.
AsyncAPI 3.0 (webhooks) lives at openapi/asyncapi.yaml. Covers:
- 16 event types organized in dot-namespaces:
webhook.test— delivery probe on subscription createtopup.paidinvoice.created/invoice.paidorder.createdservice.activated/service.suspended/service.cancelleddomain.registered/domain.transferred/domain.expiring_soon/domain.expiredvps.power_state_changed/vps.backup_completed/vps.snapshot_created/vps.reinstall_completed
- 3 wildcard patterns:
*(all events),vps.*(all VPS events),domain.*(all domain events) EventEnvelopeshape, identical across every event:{id, type, timestamp, data}- Per-event payload schemas under
components.messages— autocompletes correctly in your AsyncAPI-aware editor X-Impreza-SignatureHMAC-SHA256 security scheme + standard delivery headers (X-Impreza-Event,X-Impreza-Delivery,X-Impreza-Timestamp)
Honesty: as of this release only webhook.test is actively fired by the server. We document the other 15 event types as part of the public contract, and we’re progressively wiring the server-side hooks that trigger them. The wire shape is stable — you can register a subscription for any event today and you’ll start receiving them as the hooks land. We decided that documenting the contract first was better than waiting until integrations had already calcified around inconsistent shapes.
Webhook signature verification
Every outbound delivery carries X-Impreza-Signature: sha256=<hex> computed as HMAC-SHA256(secret, body). Both SDKs ship a constant-time verifier so you don’t have to roll your own:
Python:
from impreza.webhooks import verify_signature
@app.post("/webhooks/impreza")
async def handle(request):
body = await request.body()
sig = request.headers["X-Impreza-Signature"]
if not verify_signature(secret=SECRET, body=body, signature=sig):
return Response(status_code=401)
event = parse_event(body)
...
Go:
import "github.com/imprezahost/impreza-devkit/cli-go/internal/client"
func handler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
sig := r.Header.Get("X-Impreza-Signature")
if !client.VerifySignature(secret, body, sig) {
http.Error(w, "bad signature", 401)
return
}
// ... handle event
}
Both use hmac.compare_digest / hmac.Equal — constant-time comparison to avoid timing-attack signature recovery.
Auth model
API keys are issued in your Client Area under API Management. Each key is a pair: a 32-character imp_... public id and a 64-character hex secret. The secret is HMAC-signed at request time, not sent in the clear.
Two security guardrails worth knowing about:
- IP allowlist per key. Each key has an allowlist of source IPs (or CIDR ranges); requests from other IPs return 403 with code
IP_NOT_WHITELISTED. Manage via Client Area → API Management → <key> → Allowed IPs. Empty allowlist = key is disabled.impreza key whoamishows the current allowlist. - Per-key scopes. Read-only keys exist if you want to feed monitoring without exposing write paths. The SDK surfaces the scope via
c.key.whoami()so your code can sanity-check before attempting a write.
impreza doctor runs a 5-check probe end-to-end:
$ impreza doctor
[OK] Config loaded — context: company (api_key imp_f4b8a7...)
[OK] API reachable — 200 OK on GET /v1 (62ms)
[OK] Key authenticated — 200 OK on GET /v1/key/whoami
[OK] IP whitelisted — caller 198.51.100.42 in allowed list
[OK] Account profile readable — balance 100.00 USD
All checks passed.
[FAIL] / [WARN] / [SKIP] for the failure modes. Exit code 0 if all green, non-zero otherwise. Wire it into a Nagios / Prometheus / your-monitor-of-choice check and you’ll know when your CI key rotates out of an IP allowlist before your deploy tries.
Production concerns
Retry with exponential backoff is built into both clients. Transient failures (HTTP 502 / 503 / 504, plus DNS / connect errors) get retried up to 3 times with 100ms * 2^n backoff + jitter. Idempotent verbs (GET, PUT, DELETE of the same record) are always retried; POST only on connect failures (not on 5xx) to avoid double-charging if the server received but failed to acknowledge the response.
HTTP timeout is 180 seconds. Cloud upstream operations (boot-order changes, instance resize, image create) sometimes take 60-120 s to return; Proxmox reads are sub-second. 180 s is the upper bound that still catches a stuck CI / network failure quickly without aborting valid slow operations.
Typed errors map 1:1 with HTTP status codes:
from impreza.exceptions import (
AuthError, # 401
IPNotWhitelisted, # 403 + code=IP_NOT_WHITELISTED
Forbidden, # 403 (other)
NotFound, # 404
InvalidRequest, # 400
Conflict, # 409
InsufficientCredit, # 402
RateLimited, # 429
UpstreamError, # 502/503/504 (after retry budget)
ServerError, # 5xx (other)
)
Go has the same hierarchy in client.APIError and friends; check via errors.As(err, &client.InsufficientCredit{}).
Rate limits. Per-key bucket, surfaces as 429 with a Retry-After header. The retry transport honors Retry-After (overriding its own backoff) before re-attempting.
What’s deliberately not here
A short honesty section:
- No Terraform provider yet. Reserved as Phase 8 in our roadmap. The OpenAPI contract is stable enough to generate one — open an issue if you want to collaborate.
vps cloud rdnsverbs are absent from both CLIs as of 0.3.2 / 0.1.1. The public-edge WAF onapi.imprezahost.comrejects/vps/cloud/rdns/{ip}paths with dotted-IPv4 segments and returns its HTML maintenance page instead of JSON. Shipping CLI verbs that always fail in user-visible HTML felt worse than hiding them. The underlying SDK methods (vps.rdns.get / set / deletein Python,CloudRdnsGet/Set/Deletein Go) stay available for library users who proxy around the WAF. Verbs return to the CLI surface once the server-side rule is fixed.- 15 of 16 webhook events not yet fired server-side (see the AsyncAPI section above). Contract is stable; firing them is incremental server work.
vps-cloud-sizes/vps-cloud-locationscatalog endpoints aren’t wrapped yet. The endpoints exist on the server; the SDK just doesn’t have helpers for them. Deferred three times across earlier phases — we’ll pick it up when we add cloud-specific resize workflows.- Load balancers, firewalls-as-a-service, VPC private networking, object storage, K8s, App Platform. Not in the Impreza product catalog. If we add them, the SDK gets them automatically; until then, they’re out of scope.
Start Here
If you already have an API key:
# Python
pip install impreza-cli
impreza context create personal --key imp_... --secret ...
impreza doctor
impreza vps list
# Or Go (Linux x86_64 shown; adjust for your platform):
curl -L https://github.com/imprezahost/impreza-devkit/releases/latest/download/impreza-cli-go_Linux_x86_64.tar.gz -o impreza.tar.gz
tar -xzf impreza.tar.gz
sudo mv impreza /usr/local/bin/
impreza --version
If you don’t have one yet: Client Area → API Management → Create key. Allowlist your IP (or CIDR), copy the secret immediately (we only show it once), and start using it.
Tab completion works on both CLIs:
impreza completion bash > /etc/bash_completion.d/impreza
impreza completion zsh > "${fpath[1]}/_impreza"
impreza completion fish > ~/.config/fish/completions/impreza.fish
impreza completion powershell | Out-String | Invoke-Expression
Reference docs:
- Repo + issues: github.com/imprezahost/impreza-devkit
- REST + webhook specs:
openapi.yaml/asyncapi.yaml - Per-package READMEs:
sdk-python//cli-python//cli-go/ - Full version history:
CHANGELOG.md - Hosted docs: docs.imprezahost.com
We welcome issues, PRs, and feature requests — the repo is MIT-licensed, and we read everything that comes in.
Versions at publish time: impreza-sdk 0.3.2 · impreza-cli 0.3.2 · impreza-cli-go 0.1.1 · OpenAPI 3.1 + AsyncAPI 3.0 contracts shipped in-tree.
Get the API Key too!




















