Skip to main content

Python SDK

Submit jobs and chain them together programmatically from Python.

Prerequisites

Before using the SDK, make sure you have:

  1. Python >= 3.10
  2. anycloud API running — start it with anycloud api start
  3. Cloud credentials configured — create them with anycloud credentials new

See Getting Started for full setup instructions.

Install

Install the current public SDK release:

pip install anycloud-sdk

Bucket operations stream through the local anycloud api server using a credential you've already registered with anycloud credentials new — no extra installs needed.

Quick Start

If you have credentials configured via anycloud credentials new, the SDK picks them up automatically:

import anycloud

ac = anycloud.Client()

job = ac.submit("my-training:latest", gpu="h100:8")
job.wait()

print(job.logs())

Multiple credential sets

When you have more than one credential set, select one by name:

ac = anycloud.Client(credentials="aws-prod")

Explicit cloud config

You can still pass credentials directly — this takes precedence over the credentials file:

from anycloud.types import CloudConfig, AWSCredentials

cc = CloudConfig(
cloudProvider="AWS",
credentials=AWSCredentials(
accessKeyId="AKIA...",
secretAccessKey="...",
),
)

ac = anycloud.Client(cloud_config=cc)

The client connects to your local anycloud API at http://localhost:8080 by default.

Workload Images

The SDK runs published container images. Bake application code and dependencies into the image, use an immutable tag or digest when reproducibility matters, and pass command when you need to override the image's default command:

job = ac.submit(
"ghcr.io/acme/training:git-a1b2c3d",
gpu="h100:8",
command=["python", "-m", "training", "--shard", "0"],
)

service = ac.serve(
"ghcr.io/acme/inference:git-a1b2c3d",
gpu="L40S:1",
command=["python", "-m", "myapp"],
deployment_id="model-a-001",
)

Use Client.submit_many for parallel jobs and Client.get_or_submit for restart-safe workflow steps. See Container Images for build and publish workflows.

CloudConfig Parameters

CloudConfig accepts credentials as either a string name (e.g. "azure2", resolved lazily from saved credentials) or a credential object. See Configuration for GPU vs VM type, credential resolution, mappings, and constraints.

ParameterTypeDescription
credentialsstr | CloudCredentialsCredential name or object.
cloud_providerCloudTypeCloud provider (inferred from credentials if omitted).
vm_typestrVM type (e.g. "Standard_NC16as_T4_v3").
spotboolUse spot/preemptible instances.
regionstrCloud region.
availability_zonestrAvailability zone.
disk_size_gbintRoot disk capacity in GB.
disk_tierDiskTierRoot disk performance tier: medium, high, or ultra (AWS, GCP, Azure).
input_bucketstrInput bucket name (mounted at /mnt/input).
output_bucketstrOutput bucket name (mounted at /mnt/output).
checkpoint_bucketstrExisting checkpoint bucket (mounted at /mnt/checkpoint).
input_storage_credentialsCloudCredentialsCredentials for input bucket (cross-cloud).
input_storage_regionstrRegion for input bucket storage.
output_storage_credentialsCloudCredentialsCredentials for output bucket (cross-cloud).
output_storage_regionstrRegion for output bucket storage.
checkpoint_storage_credentialsCloudCredentialsCredentials for a Job checkpoint bucket (cross-cloud); not valid for Services.
checkpoint_storage_regionstrRegion for checkpoint bucket storage.

Runtime Environment

Inside the remote container, the following environment variable is available:

VariableDescription
DEPLOYMENT_IDUnique deployment ID.
PORTService listen port for serve deployments. Defaults to 8088; override with env={"PORT": "<port>"}.

For mount paths (/mnt/input, /mnt/output, /mnt/checkpoint), see Buckets.

Buckets

Use Bucket objects to manage cloud buckets and wire data between jobs. See Buckets for roles and sync behavior, and Jobs for when to chain deployments.

Bucket Handles

ac.bucket() returns a lazy handle — no cloud calls are made until you use it:

data = ac.bucket("training-data")
results = ac.bucket("results")

Discover Buckets

Client.list_buckets() returns sorted Bucket handles for every bucket visible to the client's selected saved AWS, GCP, or Azure credential:

ac = anycloud.Client(credentials="aws-prod")

for bucket in ac.list_buckets():
print(bucket.name, bucket.region, bucket.created_at)

Each returned handle exposes name, nullable region, and nullable created_at in epoch milliseconds. region is the provider's own location string for the bucket — Google Cloud reports bucket locations such as US or US-CENTRAL1, which are not deployment regions. It remains fully usable with upload(), download(), and Job bucket arguments. The default limit is 1,000; pass limit=None to exhaust every page or a positive integer to cap the complete result. Bucket discovery requires a named saved credential and rejects inline cloud_config-only clients.

Browse Objects

Bucket.list_objects() returns sorted BucketEntry models. The default view contains the requested hierarchy level: virtual folders have kind="prefix" and objects have kind="object".

data = ac.bucket("training-data")

for entry in data.list_objects():
print(entry.kind, entry.key)

# Prefixes are literal; use a trailing slash to browse a folder.
folder = data.list_objects(prefix="datasets/")

# A flat full-tree view contains objects only.
everything = data.list_objects(recursive=True, limit=None)

Object entries also expose nullable size_bytes and last_modified_at (epoch milliseconds). limit defaults to 1,000, may be any positive integer, and may span multiple API pages; None exhausts pagination. A failed page raises an APIError without returning a partial list.

Upload and Download

data = ac.bucket("training-data")

# Upload a local file to a key in the bucket (the bucket must already exist —
# create it first with `anycloud bucket create`)
data.upload("~/datasets/shard-0.bin", remote_path="shard-0.bin")

# Upload to a specific key
data.upload("~/labels.csv", remote_path="metadata/labels.csv")

# Download a single object to a local file
data.download("~/shard-0.bin", remote_path="shard-0.bin")

Downloads use a sibling temporary file and validate the received byte count before atomically replacing the destination. Incomplete responses are fetched again up to two times; after three incomplete attempts, DownloadIntegrityError is raised and any existing destination is preserved. An existing destination keeps its permissions.

Recursive Directory Operations

Directory methods return a BucketOperationResult containing operation, total_objects, completed_objects, failed_objects, nullable total_bytes, and nullable completed_bytes:

data = ac.bucket("training-data")

uploaded = data.upload_directory(
"~/datasets/shards",
prefix="datasets/shards/",
concurrency=8,
)
downloaded = data.download_directory(
"~/restored-shards",
prefix="datasets/shards/",
)

data.delete("datasets/obsolete.bin")
removed = data.delete_prefix("datasets/old-runs/", concurrency=4)

Prefixes are literal. Use the default empty prefix to transfer the bucket root; non-empty prefixes must end in /. delete_prefix() rejects an empty prefix. Concurrency defaults to 4 and must be an integer from 1 through 32.

Uploads include hidden regular files, ignore empty directories, and reject symlinks or special files before starting. Downloads validate every key and destination before writing, reject path traversal and mapping collisions, and atomically replace existing regular files only after the reported object size has been received.

If individual requests fail, Anycloud attempts the rest and raises BucketOperationError. Its result contains the partial summary and its ordered errors list contains (object_key, exception) pairs. Successful work is not rolled back, so rerunning naturally resumes through overwrite and idempotent deletion. Listing or preflight failures happen before any object is changed.

Handles

Client.submit() returns a Job, and Client.serve() returns a Service. Both share state(), status(), logs(), exec(), and terminate().

Service is the canonical public class name. The former Server name remains as a deprecated alias so existing imports continue to work.

Lifecycle responses keep outcome and teardown timing separate. A deployment's completed_at is the terminal outcome time; cleaned_at remains None until all provider teardown is confirmed. Per-VM endedAt values in StatusResponse.vms are the compute-spend cutoffs.

Job Methods

Client.submit() returns a Job — a future-like handle to a deployment.

MethodDescription
job.wait(timeout=None, poll_interval=2.0)Block until Completed. Returns self on success, raises DeploymentFailedError on failure.
job.state()Fetch current DeploymentState from the API.
job.status()Full StatusResponse (events, VM health, SSH key).
job.logs()Container stdout/stderr.
job.exec(command)Run a command in the deployment execution environment. Returns stdout.
job.terminate()Terminate the deployment.
job.resubmit()Resubmit a terminal deployment. Returns a new Job.

Properties:

PropertyDescription
job.idDeployment ID.

Service Methods

Client.serve() returns a Service — a handle to one long-running serve deployment.

MethodDescription
service.wait_running(timeout=None, poll_interval=2.0)Block until Running. Raises DeploymentFailedError on terminal failure.
service.state()Fetch current DeploymentState from the API.
service.status()Full StatusResponse (events, VM health, SSH key).
service.logs()Container stdout/stderr.
service.exec(command)Run a command in the deployment execution environment. Returns stdout.
service.terminate()Terminate the deployment.

Properties:

PropertyDescription
service.idDeployment ID.
service.urlStable public URL: https://<id>.anycloud.sh.

JobGroup

JobGroup is returned by client.submit_many(...). It cannot be constructed directly.

Method / PropertyDescription
group.wait(timeout=None, poll_interval=2.0)Block until all jobs finish. Raises JobGroupError if any failed.
group.terminate()Terminate all jobs in parallel.
group.idsList of deployment IDs (successful submissions only).
group.errors[(input_index, exception), ...] — only populated with return_exceptions=True.
len(group), group[i], for job in groupSequence protocol. With return_exceptions=True, entries may be Job or Exception.

Client Methods

MethodDescription
ac.submit(image, ...)Submit a job. See parameters below.
ac.get_or_submit(step_id, image, ...)Submit a restart-safe workflow step or reattach to the matching deployment.
ac.submit_many(submissions, ...)Submit N jobs in parallel from a list of Submissions. Returns a JobGroup.
ac.serve(image, ...)Start a long-running service deployment. Returns a Service.
ac.bucket(name)Get a lazy Bucket handle. No cloud calls until use.
ac.list_buckets(limit=1000)Return sorted discoverable Bucket handles; None exhausts pagination.
ac.list(limit=20)List recent deployments.
ac.get(deployment_id)Get a Job or Service handle for an existing deployment.
ac.close()Close the HTTP connection.

Client() Parameters

ParameterTypeDescription
api_urlstrAPI server URL. Falls back to API_URL, then ~/.anycloud/api-url, then http://localhost:8080.
cloud_configCloudConfigDefault cloud config for all submits. Takes precedence over credentials file.
credentialsstrName of a credential set saved in the local anycloud API database. Auto-selects if only one exists.

submit() Parameters

ParameterTypeDescription
imagestrDocker image reference (required).
cloud_configCloudConfigCloud config (provider, credentials, region, etc.). Falls back to client default.
gpustrGPU type (e.g. "h100:8", "a100:4").
envdictEnvironment variables for the container.
secretslist[str]Names of saved secrets to inject as env vars. See Secrets & Environment.
docker_optionsdictDocker runtime options (shmSize, gpus, ipc, etc.).
commandlist[str]Override container CMD.
deployment_idstrCustom deployment ID.
inputBucketBucket to mount at /mnt/input (read-only).
outputBucketBucket to mount at /mnt/output (write, synced periodically).
checkpointBucketExisting bucket to restore and sync at /mnt/checkpoint.

submit() also accepts a Submission object directly when you've built one programmatically:

from anycloud import Submission

s = Submission(image="train:latest", gpu="h100:8")
job = ac.submit(s)

Client.serve() accepts the same checkpoint=Bucket(...) argument for VM-backed Services. The Bucket must use the same provider and credentials as the selected compute identity; separate checkpoint storage credentials, input Buckets, and output Buckets remain Job-only.

get_or_submit() Parameters

get_or_submit() accepts the same job options as submit(), except you pass a required step_id first and do not pass deployment_id:

job = ac.get_or_submit(
"train",
"train:latest",
workflow_id="daily-retrain-2026-06-22",
gpu="h100:8",
env={"LR": "0.001"},
)
job.wait()

The SDK generates a deterministic deployment ID that satisfies the deployment ID rules (lowercase letters, digits, hyphens, max 28 chars). The ID is based on workflow_id, step_id, and a normalized submission spec including image, env, secrets, command, Docker options, and cloud config. If the API returns ConflictError for that generated ID, the SDK returns ac.get(generated_id).

Use submit() when every call should create a new deployment. Use get_or_submit() when restarting the same workflow should reattach to completed, failed, running, or queued matching steps.

submit_many() Parameters

ParameterTypeDescription
submissionslist[Submission]List of submissions to submit in parallel.
max_workersintThread pool size (defaults to len(list)).
return_exceptionsboolIf True, return a mixed Job | Exception group instead of raising. Default False.

Submission accepts the same fields as submit() (image, cloud_config, gpu, env, command, input, output, checkpoint, etc.).

from anycloud import Submission

jobs = ac.submit_many([
Submission(image="features:latest", output=shared),
Submission(image="labels:latest", output=shared),
])
jobs.wait()

Use Submission.replace(**kwargs) to vary one field off a shared base — useful when most of the config is constant across a fan-out:

base = Submission(image="worker:latest", gpu="h100:8", cloud_config=cc)
jobs = ac.submit_many([
base.replace(env={"SHARD": str(i)}) for i in range(8)
])

Workflow patterns

Chain dependent Jobs by waiting before starting the next step. A Bucket carries durable output between them:

prep_out = ac.bucket("pipeline-prep")
train_out = ac.bucket("pipeline-train")

prep = ac.submit("prep:latest", output=prep_out)
prep.wait()

train = ac.submit(
"train:latest",
gpu="h100:8",
input=prep_out,
output=train_out,
)
train.wait()

evaluation = ac.submit("evaluate:latest", input=train_out)
evaluation.wait()

Fan out heterogeneous submissions and fan them into one shared output Bucket:

from anycloud import Submission

shared = ac.bucket("sweep-results")
base = Submission(image="worker:latest", gpu="h100:8", output=shared)

jobs = ac.submit_many([
base.replace(env={"SHARD": str(index)})
for index in range(8)
])
jobs.wait()

Output sync uses upload-only copies, so parallel Jobs do not delete one another's objects. Give each worker its own object prefix to avoid overwriting the same key.

Partial failures

By default, if any submission fails, submit_many raises BatchSubmitError. Successful submissions are attached to the error so you can still wait on or terminate them:

from anycloud import BatchSubmitError

try:
jobs = ac.submit_many(subs)
except BatchSubmitError as e:
print(f"{len(e.errors)} failed, {len(e.submitted)} running")
for i, exc in e.errors:
print(f" submission {i}: {exc}")
# Optionally keep the successful ones running, or clean up:
for job in e.submitted:
job.terminate()

Pass return_exceptions=True to never raise. You get a JobGroup whose entries are Job or Exception in input order; group.wait(), group.terminate(), and group.ids silently skip the exception entries, and group.errors lists them:

jobs = ac.submit_many(subs, return_exceptions=True)
jobs.wait() # only awaits the successful entries
for idx, exc in jobs.errors:
print(f"submission {idx} failed: {exc}")

Catalog browsing (regions, VM types, pricing, GPUs) is available via the anycloud CLI: anycloud regions, anycloud vm-types, anycloud pricing, anycloud gpus.

Credentials Methods

Manage cloud provider credentials stored on the API server.

MethodDescription
ac.list_credentials()List saved credentials (secrets redacted).
ac.get_credential(name)Get a single credential by name (secrets redacted).
ac.save_credential(name, credential)Save a cloud credential set. credential must include cloudProvider and provider-specific fields.
ac.delete_credential(name)Delete a saved credential set.
creds = ac.list_credentials()
ac.save_credential("aws-prod", {
"cloudProvider": "AWS",
"accessKeyId": "AKIA...",
"secretAccessKey": "...",
})
ac.delete_credential("aws-prod")

Secrets Methods

Manage named secrets. See Secrets & Environment.

MethodDescription
ac.list_secrets()List saved secrets (names and timestamps only — values are never returned).
ac.create_secret(name, values)Create or update a secret. values is a dict[str, str] of env-var names to values.
ac.delete_secret(name, *, force=False)Delete a secret. Raises ConflictError when non-terminal deployments reference it; pass force=True to override.
ac.create_secret("hf", {"HF_TOKEN": "hf_xxxx"})
job = ac.submit("train:latest", gpu="h100:8", secrets=["hf"])
ac.delete_secret("hf", force=True)

Configuration

Environment Variables

VariableDescriptionDefault
GITHUB_TOKENGitHub token for authentication (CI fallback).
API_URLAPI server URL override. If unset, ~/.anycloud/api-url is used when present.http://localhost:8080

Context Manager

The client can be used as a context manager to automatically close the HTTP connection:

with anycloud.Client() as ac:
job = ac.submit("task:latest")
job.wait()

Error Handling

ExceptionDescription
AnyCloudErrorBase exception for all SDK errors.
APIErrorHTTP error from the conductor API.
ConflictErrorDeployment ID already exists (409).
NotFoundErrorDeployment not found (404).
DeploymentFailedErrorDeployment reached a terminal failure state. .logs has last output.
JobGroupErrorOne or more jobs in a group failed during wait(). .errors has all failures.
BatchSubmitErrorOne or more submissions in submit_many failed. .submitted and .errors have the split.
TimeoutErrorOperation timed out.
from anycloud import AnyCloudError, DeploymentFailedError

try:
job.wait(timeout=3600)
except DeploymentFailedError as e:
print(f"Deployment {e.deployment_id} failed with state: {e.state}")
if e.logs:
print(e.logs)
except AnyCloudError as e:
print(f"SDK error: {e}")