Python SDK for the Convox API.
pip install convoxfromconvoximportConvoxClientclient=ConvoxClient(
host="console.convox.com",
api_key="your-deploy-key",
rack="org/rack-name", # required for console connections
)
# List appsforappinclient.apps.list():
print(f"{app.name}: {app.status}")
# Get app detailsapp=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 rackclient=ConvoxClient.from_cli_config(
host="console.convox.com", # auth file lookup keyrack="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, deleteapps=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 operationclient.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 productionclient.releases.promote("myapp", "RABCDEF")
# Rollback to a previous releaseclient.releases.rollback("myapp", "RPREVIOUS")# Get current envenv=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 resourcesresources=client.resources.list("myapp")
resource=client.resources.get("myapp", "database")
# System-scoped resourcesall_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 datadata=client.api.post("/apps", data={"name": "newapp"})All API errors raise typed exceptions:
fromconvoximport (
ConvoxAPIError, # Base for all API errorsConvoxAuthError, # 401 - bad credentialsConvoxForbiddenError, # 401 - insufficient permissionsConvoxNotFoundError, # 404 - resource not foundConvoxValidationError, # 400 - invalid inputConvoxConflictError, # 409 - already exists / lockedConvoxServerError, # 500 - server errorConvoxConnectionError, # Network failureConvoxTimeoutError, # Request timeout
)
try:
app=client.apps.get("myapp")
exceptConvoxNotFoundError:
print("App does not exist")
exceptConvoxConflictError:
print("App is locked")
exceptConvoxAPIErrorase:
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:
fromconvoximportConvoxClient, RetryConfigclient=ConvoxClient(
host="rack.example.com",
api_key="key",
retry=RetryConfig(
max_retries=5, # default: 3backoff_base=2.0, # default: 1.0backoff_max=60.0, # default: 30.0retry_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 automaticallybuild=client.builds.import_build("myapp", tarball_bytes)
# Override with a custom timeout if neededbuild=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:
ifresponse.status_code>=400:
print(f"Error: {response.status_code}")
else:
forlineinresponse.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