Python SDK
Submit jobs and chain them together programmatically from Python.
Prerequisites
Before using the SDK, make sure you have:
- Python >= 3.10
- anycloud API running — start it with
anycloud api start - 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.
| Parameter | Type | Description |
|---|---|---|
credentials | str | CloudCredentials | Credential name or object. |
cloud_provider | CloudType | Cloud provider (inferred from credentials if omitted). |
vm_type | str | VM type (e.g. "Standard_NC16as_T4_v3"). |
spot | bool | Use spot/preemptible instances. |
region | str | Cloud region. |
availability_zone | str | Availability zone. |
disk_size_gb | int | Root disk capacity in GB. |
disk_tier | DiskTier | Root disk performance tier: medium, high, or ultra (AWS, GCP, Azure). |
input_bucket | str | Input bucket name (mounted at /mnt/input). |
output_bucket | str | Output bucket name (mounted at /mnt/output). |
checkpoint_bucket | str | Existing checkpoint bucket (mounted at /mnt/checkpoint). |
input_storage_credentials | CloudCredentials | Credentials for input bucket (cross-cloud). |
input_storage_region | str | Region for input bucket storage. |
output_storage_credentials | CloudCredentials | Credentials for output bucket (cross-cloud). |
output_storage_region | str | Region for output bucket storage. |
checkpoint_storage_credentials | CloudCredentials | Credentials for a Job checkpoint bucket (cross-cloud); not valid for Services. |
checkpoint_storage_region | str | Region for checkpoint bucket storage. |
Runtime Environment
Inside the remote container, the following environment variable is available:
| Variable | Description |
|---|---|
DEPLOYMENT_ID | Unique deployment ID. |
PORT | Service 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.
| Method | Description |
|---|---|
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:
| Property | Description |
|---|---|
job.id | Deployment ID. |
Service Methods
Client.serve() returns a Service — a handle to one long-running serve deployment.
| Method | Description |
|---|---|
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:
| Property | Description |
|---|---|
service.id | Deployment ID. |
service.url | Stable public URL: https://<id>.anycloud.sh. |
JobGroup
JobGroup is returned by client.submit_many(...). It cannot be constructed directly.
| Method / Property | Description |
|---|---|
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.ids | List of deployment IDs (successful submissions only). |
group.errors | [(input_index, exception), ...] — only populated with return_exceptions=True. |
len(group), group[i], for job in group | Sequence protocol. With return_exceptions=True, entries may be Job or Exception. |
Client Methods
| Method | Description |
|---|---|
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
| Parameter | Type | Description |
|---|---|---|
api_url | str | API server URL. Falls back to API_URL, then ~/.anycloud/api-url, then http://localhost:8080. |
cloud_config | CloudConfig | Default cloud config for all submits. Takes precedence over credentials file. |
credentials | str | Name of a credential set saved in the local anycloud API database. Auto-selects if only one exists. |
submit() Parameters
| Parameter | Type | Description |
|---|---|---|
image | str | Docker image reference (required). |
cloud_config | CloudConfig | Cloud config (provider, credentials, region, etc.). Falls back to client default. |
gpu | str | GPU type (e.g. "h100:8", "a100:4"). |
env | dict | Environment variables for the container. |
secrets | list[str] | Names of saved secrets to inject as env vars. See Secrets & Environment. |
docker_options | dict | Docker runtime options (shmSize, gpus, ipc, etc.). |
command | list[str] | Override container CMD. |
deployment_id | str | Custom deployment ID. |
input | Bucket | Bucket to mount at /mnt/input (read-only). |
output | Bucket | Bucket to mount at /mnt/output (write, synced periodically). |
checkpoint | Bucket | Existing 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
| Parameter | Type | Description |
|---|---|---|
submissions | list[Submission] | List of submissions to submit in parallel. |
max_workers | int | Thread pool size (defaults to len(list)). |
return_exceptions | bool | If 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.
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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
| Variable | Description | Default |
|---|---|---|
GITHUB_TOKEN | GitHub token for authentication (CI fallback). | — |
API_URL | API 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
| Exception | Description |
|---|---|
AnyCloudError | Base exception for all SDK errors. |
APIError | HTTP error from the conductor API. |
ConflictError | Deployment ID already exists (409). |
NotFoundError | Deployment not found (404). |
DeploymentFailedError | Deployment reached a terminal failure state. .logs has last output. |
JobGroupError | One or more jobs in a group failed during wait(). .errors has all failures. |
BatchSubmitError | One or more submissions in submit_many failed. .submitted and .errors have the split. |
TimeoutError | Operation 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}")