Python SDK for the Convox API.
pip install convoxfrom convox import ConvoxClient
client = ConvoxClient(
host="console.convox.com",
api_key="your-deploy-key",
rack="org/rack-name", # required for console connections
)
# List apps
for app in client.apps.list():
print(f"{app.name}: {app.status}")
# Get app details
app = client.apps.get("myapp")
print(f"Release: {app.release}, Locked: {app.locked}")For console-managed racks the api_key is a deploy key generated in the
Convox console (Organization > Deploy Keys). Deploy keys are the recommended
auth method for programmatic and SDK usage. The examples below all use deploy
keys.
Three options, in order of precedence:
client = ConvoxClient(
host="console.convox.com",
api_key="your-deploy-key",
rack="org/rack-name", # required for console-managed racks
)Set CONVOX_HOST, CONVOX_PASSWORD (deploy key), and CONVOX_RACK:
client = ConvoxClient.from_env()Reads the auth file written by convox login (~/.config/convox/auth for
v3, ~/.convox/auth for v2). This is a convenience for local development
when you have already authenticated via the CLI -- it reads whatever
credential is stored for the given host, whether that is a CLI session token
or a deploy key you placed there manually.
# Console-managed rack — specify both host and rack
client = ConvoxClient.from_cli_config(
host="console.convox.com", # auth file lookup key
rack="org/rack-name", # rack to target
)
# Direct rack connection (host and rack are the same)
client = ConvoxClient.from_cli_config(rack="rack.example.com")# List, get, create, delete
apps = client.apps.list()
app = client.apps.get("myapp")
app = client.apps.create("myapp")
client.apps.delete("myapp")
# Update (lock/unlock, set parameters)
client.apps.update("myapp", lock=True)
client.apps.update("myapp", parameters={"BuildCpu": "512"})
# Cancel in-progress operation
client.apps.cancel("myapp")builds = client.builds.list("myapp")
build = client.builds.get("myapp", "BABCDEF")
build = client.builds.create("myapp", "https://example.com/source.tgz",
description="v2.0 release")releases = client.releases.list("myapp")
release = client.releases.get("myapp", "RABCDEF")
# Promote a release to production
client.releases.promote("myapp", "RABCDEF")
# Rollback to a previous release
client.releases.rollback("myapp", "RPREVIOUS")# Get current env
env = client.environment.get("myapp")
print(env) # {"DATABASE_URL": "postgres://...", "SECRET_KEY": "..."}
# Set env vars (creates a new release)
release = client.environment.set("myapp", {"KEY": "value", "OTHER": "val"})
# Unset env vars (creates a new release)
release = client.environment.unset("myapp", "KEY")services = client.services.list("myapp")
client.services.update("myapp", "web", count=3, memory=1024)
client.services.restart("myapp", "web")processes = client.processes.list("myapp")
process = client.processes.get("myapp", "proc-abc123")
client.processes.stop("myapp", "proc-abc123")# App-scoped resources
resources = client.resources.list("myapp")
resource = client.resources.get("myapp", "database")
# System-scoped resources
all_resources = client.resources.system_list()
client.resources.system_create("postgres", name="mydb")
client.resources.system_link("mydb", "myapp")system = client.system.get()
print(f"{system.name} v{system.version} ({system.provider})")
capacity = client.system.capacity()Escape hatch for endpoints not covered by resource methods:
# Equivalent to `convox api get /apps`
data = client.api.get("/apps")
# POST with form data
data = client.api.post("/apps", data={"name": "newapp"})All API errors raise typed exceptions:
from convox import (
ConvoxAPIError, # Base for all API errors
ConvoxAuthError, # 401 - bad credentials
ConvoxForbiddenError, # 401 - insufficient permissions
ConvoxNotFoundError, # 404 - resource not found
ConvoxValidationError, # 400 - invalid input
ConvoxConflictError, # 409 - already exists / locked
ConvoxServerError, # 500 - server error
ConvoxConnectionError, # Network failure
ConvoxTimeoutError, # Request timeout
)
try:
app = client.apps.get("myapp")
except ConvoxNotFoundError:
print("App does not exist")
except ConvoxConflictError:
print("App is locked")
except ConvoxAPIError as e:
print(f"API error {e.status_code}: {e.message}")
print(f"Request ID: {e.request_id}") # for support ticketsThe SDK retries on 429 (rate limit) and 502/503/504 (gateway errors) with exponential backoff:
from convox import ConvoxClient, RetryConfig
client = ConvoxClient(
host="rack.example.com",
api_key="key",
retry=RetryConfig(
max_retries=5, # default: 3
backoff_base=2.0, # default: 1.0
backoff_max=60.0, # default: 30.0
retry_on_500=True, # default: False (500s may be permanent)
),
)Disable retries:
client = ConvoxClient(host="...", api_key="...", retry=RetryConfig(max_retries=0))The default timeout is 30 seconds. Most API operations complete well within this
limit, since build creation (builds.create) returns immediately after
dispatching the build to a Kubernetes pod.
The one exception is builds.import_build(), which is synchronous — the
server reads the entire gzip archive and pushes every service image to the
container registry before responding. This method defaults to a 1800-second
(30-minute) per-request timeout, independent of the client-wide default:
# Uses the 30-minute default automatically
build = client.builds.import_build("myapp", tarball_bytes)
# Override with a custom timeout if needed
build = client.builds.import_build("myapp", tarball_bytes, timeout=3600)You can also pass a per-request timeout to the low-level API:
data = client.api.post("/apps/myapp/builds/import", content=tarball, timeout=600)The stream() method returns the raw httpx.Response without checking for
error status codes. This is intentional: streaming endpoints (logs, build
output) may send partial data before an error occurs.
response = client.stream("GET", "/apps/myapp/logs", params={"follow": "true"})
try:
if response.status_code >= 400:
print(f"Error: {response.status_code}")
else:
for line in response.iter_lines():
print(line)
finally:
response.close()| CLI Command | SDK Equivalent |
|---|---|
convox apps |
client.apps.list() |
convox apps info -a myapp |
client.apps.get("myapp") |
convox apps create myapp |
client.apps.create("myapp") |
convox apps delete myapp |
client.apps.delete("myapp") |
convox builds -a myapp |
client.builds.list("myapp") |
convox releases -a myapp |
client.releases.list("myapp") |
convox releases promote RABCDEF -a myapp |
client.releases.promote("myapp", "RABCDEF") |
convox env -a myapp |
client.environment.get("myapp") |
convox env set KEY=val -a myapp |
client.environment.set("myapp", {"KEY": "val"}) |
convox env unset KEY -a myapp |
client.environment.unset("myapp", "KEY") |
convox api get /apps |
client.api.get("/apps") |
# Install dev dependencies
pip install -e ".[dev]"
# Run unit tests (mocked, no rack needed)
pytest tests/
# Run integration tests (requires a live rack)
CONVOX_HOST=console.convox.com \
CONVOX_PASSWORD=your-deploy-key \
CONVOX_RACK=org/rack-name \
pytest integration_tests/ -v
# Lint and format
ruff check .
ruff format .
# Type check
mypy src/Apache 2.0