Python SDK for the Convox API.
Installation
pip install convox
Quick Start
from 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}")
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 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")
API Reference
Apps
# 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
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 production
client.releases.promote("myapp", "RABCDEF")
# Rollback to a previous release
client.releases.rollback("myapp", "RPREVIOUS")
Environment Variables
# 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
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 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
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 data
data = client.api.post("/apps", data={"name": "newapp"})
Error Handling
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 tickets
Retry Configuration
The 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))
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 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)
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:
if response.status_code >= 400:
print(f"Error: {response.status_code}")
else:
for line in response.iter_lines():
print(line)
finally:
response.close()
CLI Comparison
| 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") |
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
Metadata
Release files for convox 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| convox-0.1.0.tar.gz | 36.6 kB | Without provenance | Details for convox-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| convox-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for convox-0.1.0-py3-none-any.whl |
Total release size: 66.4 kB
Release files / convox-0.1.0.tar.gz
| Download URL | convox-0.1.0.tar.gz |
|---|---|
| Size | 36.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f0c053a30eaadfe56adc20b847b5e8880b4f70ec334f9ce2dbafcd52012f49d8
|
|
BLAKE2b-256 checksum How to use checksums |
a48a9a1b42cb739ee10a6867b3d1a4593505f989261d74374959fce9319a0384
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / convox-0.1.0-py3-none-any.whl
| Download URL | convox-0.1.0-py3-none-any.whl |
|---|---|
| Size | 29.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d31418affcb7d650318a16d6e4714cfd6e063b8cccfd447f70b6d9be1831ffcd
|
|
BLAKE2b-256 checksum How to use checksums |
15400cfdb338905bfe7975a4f5ea3f8ed10b754afe3dab91df294395f8f462c2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Security for convox 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.