Python SDK
import matmul gives you the whole GPU workflow from Python: offers, instances, SSH keys, billing and jobs. It's the same package as the CLI, needs Python 3.9+ and uses only the standard library.
Install
pip install matmul-cloud
A GPU box in ten lines
import matmul
client = matmul.Client() # MATMUL_API_KEY, or the key saved by `matmul login`
offer = client.offers(gpu="H100", max_price_cents=300)[0] # cheapest first
key = client.ssh_keys()[0]
inst = client.create_instance(offer.id, "dev", image=None, ssh_key_id=key.id,
max_hourly_price_cents=300)
inst = client.wait_instance(inst.id) # blocks until running (up to 15 min)
print(inst.ssh_command) # ssh -p <port> <user>@<ip>
# ... work ...
client.delete_instance(inst.id) # stops billingClient
matmul.Client(api_key=None, base_url=None, timeout=30.0)
Each argument falls back to an environment variable, then to ~/.config/matmul/config.json (written by matmul login):
| Argument | Fallbacks |
|---|---|
api_key | MATMUL_API_KEY, then the config file. With none, the constructor raises MatMulError(401, ...). |
base_url | MATMUL_API_URL, the config file, then https://api.matmul.cloud. |
timeout | Seconds per HTTP request. |
MATMUL_CONFIG points at a different config file. Prices are always integer US cents per hour for the whole machine; Offer.hourly_price gives dollars.
Offers
| Method | Returns |
|---|---|
offers(gpu=None, count=None, region=None, max_price_cents=None, kind=None) | list[Offer], cheapest first. kind="gpu" or "cpu" narrows it to GPU or CPU-only machines. |
Offer has id, gpu_type, num_gpus, vram_per_gpu_gb, vcpus, memory_gb, storage_gb, interconnect, region, hourly_price_cents, boot_min_sec and boot_max_sec (the typical boot time range; 0 when unknown), and the properties hourly_price (dollars) and is_cpu.
The kind argument and the storage_gb, interconnect, boot_min_sec, boot_max_sec and is_cpu fields needs a matmul-cloud release after 0.1.0. In 0.1.0, filter on num_gpus instead of kind.
SSH keys
| Method | Returns |
|---|---|
add_ssh_key(name, public_key) | SSHKey (id, name, fingerprint). Pass the public key line, e.g. the contents of ~/.ssh/id_ed25519.pub. |
ssh_keys() | list[SSHKey] |
delete_ssh_key(key_id) | None. Like add_ssh_key, not for viewers. |
Instances
create_instance(offer_id, name, image, ssh_key_id, *, args=None, envs=None, ports=None,
shared_memory_gb=None, max_hourly_price_cents=None, keep_alive=False,
expose=None) -> Instanceimage=None (or "") launches the provider's plain VM. With an image it runs as a container on that VM. The keyword arguments match the CLI flags: keep_alive keeps an interactive image running, max_hourly_price_cents refuses the launch if the live price is higher, and expose asks for public app links, which aren't switched on yet.
| Method | Returns |
|---|---|
instances() | list[Instance], including deleted ones. |
instance(ref) | Instance by id (ins_...) or by name. |
wait_instance(ref, timeout=900.0, poll=3.0, on_update=None) | The Instance once it's running (with an image, once the container is up). Raises MatMulError if it errors or is deleted (409) or times out (408). on_update(instance, elapsed_seconds) is called on every poll. |
delete_instance(ref) | The Instance, now deleting. Billing stops and its slot frees up right away. |
Instance fields: id, name, status, image, gpu_type, num_gpus, region, hourly_price_cents, ip, ssh_user, ssh_port, ssh_command, container_shell_command (a shell inside the container, for instances with an image), error, running_seconds, accrued_cost_cents, ports and exposable. The running time and cost are estimates for display; the ledger is what you're billed.
Billing
| Method | Returns |
|---|---|
billing_balance() | BillingAccount: balance_cents, frozen, frozen_reason, frozen_at, grace_hours_remaining, verified, limits. |
billing_ledger(limit=None, before=None) | list[LedgerEntry], newest first: id, kind, amount_cents, created_at, instance_id. Pass the last entry's id as before for the next page. |
billing_checkout(amount_cents) | A Stripe checkout URL (str) to open and pay. |
These need the owner role.
Managed jobs
Not available on MatMul yet; see Managed jobs. capabilities() returns the features the server has switched on, e.g. {"instances": True, "app_links": False, "jobs": False}.
job = client.launch("train", "python train.py", accelerators="H100:1",
setup="pip install -r requirements.txt",
max_hourly_price_cents=350) # whole job, all nodes
job = client.wait("train") # until succeeded/failed/cancelled/finished
print(client.logs("train"))
print(job.ok, job.billed_cents)| Method | Returns |
|---|---|
launch(name, run, *, setup, envs, num_nodes=1, cpus, memory, accelerators, disk_size, max_hourly_price_cents) | Job |
jobs() | list[Job] |
job(name) | Job by job_... id, or by name (the most recent). |
logs(name) | The job's logs (str). |
down(name) | Stops the job; returns the Job. |
wait(name, timeout=86400.0, poll=5.0, on_update=None) | The finished Job. |
Job fields: id, name, state, detail, error, created_at, gpu_type, num_gpus, num_nodes, hourly_price_cents, running_seconds, billed_cents, plus the properties done and ok.
Errors
Every failure raises matmul.MatMulError with .status (the HTTP status, or 0 when the API can't be reached) and .message (the API's error text). The statuses are listed in the REST API reference.
try:
client.create_instance(offer.id, "dev", None, key.id, max_hourly_price_cents=300)
except matmul.MatMulError as e:
if e.status == 409: # offer gone or price rose: search again
...
elif e.status == 429: # instance limit, frozen account, low balance,
... # or "GPU capacity is temporarily limited" (retry later)← All docs