Skip to content

Repository files navigation

Python SDK for the Convox API.

Installation

pip install convox

Quick Start

fromconvoximportConvoxClientclient=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}")

Authentication

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:

1. Explicit credentials (recommended)

client=ConvoxClient(
host="console.convox.com",
api_key="your-deploy-key",
rack="org/rack-name", # required for console-managed racks
)

2. Environment variables

Set CONVOX_HOST, CONVOX_PASSWORD (deploy key), and CONVOX_RACK:

client=ConvoxClient.from_env()

3. CLI config file

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")

API Reference

Apps

# 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

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

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")

Environment Variables

# 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

services=client.services.list("myapp")
client.services.update("myapp", "web", count=3, memory=1024)
client.services.restart("myapp", "web")

Processes

processes=client.processes.list("myapp")
process=client.processes.get("myapp", "proc-abc123")
client.processes.stop("myapp", "proc-abc123")

Resources

# 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

system=client.system.get()
print(f"{system.name} v{system.version} ({system.provider})")
capacity=client.system.capacity()

Low-Level API

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"})

Error Handling

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 tickets

Retry Configuration

The 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))

Timeouts

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)

Streaming

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 Comparison

CLI CommandSDK Equivalent
convox appsclient.apps.list()
convox apps info -a myappclient.apps.get("myapp")
convox apps create myappclient.apps.create("myapp")
convox apps delete myappclient.apps.delete("myapp")
convox builds -a myappclient.builds.list("myapp")
convox releases -a myappclient.releases.list("myapp")
convox releases promote RABCDEF -a myappclient.releases.promote("myapp", "RABCDEF")
convox env -a myappclient.environment.get("myapp")
convox env set KEY=val -a myappclient.environment.set("myapp", {"KEY": "val"})
convox env unset KEY -a myappclient.environment.unset("myapp", "KEY")
convox api get /appsclient.api.get("/apps")

Contributing

# 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/

License

Apache 2.0

About

Python SDK for the Convox API

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages