Skip to content

Python SDK

Read and manage your account, workspaces, pods, storage, assets, GPUs, templates, serverless deployments, and machines from Python with the sync Meshive and async AsyncMeshive clients — typed models, price estimates before you spend, idempotent writes, and a structured error hierarchy.

The SDK exposes two clients with identical method names — Meshive (synchronous) and AsyncMeshive (asyncio). Both read the same credentials and configuration as the CLI (see Authentication).

from meshive import Meshive
with Meshive() as client: # reads MESHIVE_API_KEY
me = client.me()
print(me.email, me.user_role)
for ws in client.list_workspaces():
print(ws.namespace_name, ws.status)
detail = client.get_workspace("my-workspace")
print(detail.price_per_hour, detail.gpus)
pods = client.list_pods("my-workspace")
pod = client.get_pod(pods[0].pod_name, "my-workspace")
print(pod.status, pod.raw) # .raw holds the full payload
usage = client.get_pod_metrics(pod.pod_name, "my-workspace")
print(usage.cpu_usage_rate, [g.vram_usage_rate for g in usage.gpus])
# what can I rent right now?
for gpu in client.list_gpus(min_vram=40):
print(gpu.gpu_model, gpu.vram, gpu.price_per_hour, gpu.available_gpus)
credit = client.get_credit()
print(credit.paid_balance, credit.bonus_balance)
# host view: the machines you contribute to the network
machines = client.list_machines()
for m in machines:
print(m.machine_id, m.status, m.gpu_count, m.gpu_model)
machine = client.get_machine(machines[0].machine_id)
print(machine.earning_hourly, machine.raw)

Using the client as a context manager (with / async with) ensures the underlying HTTP connection is closed. Otherwise, call client.close() (or await client.close()) yourself.

Meshive(api_key=None, *, base_url=None, timeout=30.0, max_retries=2, headers=None)
AsyncMeshive(api_key=None, *, base_url=None, timeout=30.0, max_retries=2, headers=None)

With no arguments, credentials resolve in the usual order (explicit argument › environment variable › meshive login file). You can also pass the key explicitly:

client = Meshive(api_key="meshive_xxxxxxxx")

timeout is the per-request timeout in seconds (default 30.0). max_retries controls automatic retries of rate limits, gateway errors and dropped connections (0 disables them). headers adds custom headers to every request (the Authorization and Accept headers cannot be overridden). If no API key can be resolved, the first request raises ConfigurationError.

Write methods send an Idempotency-Key and reuse it on their automatic retries. Save your own idempotency_key= before a write if you need to recover across process restarts. A retained key belongs to the original request: changing its arguments raises MeshiveAPIError (HTTP 422). See Recovering an uncertain write before retrying or choosing a new key.

Both clients expose the same methods (await them on AsyncMeshive):

MethodReturnsDescription
me()WhoAmIThe current API key’s owner.
list_api_keys()list[ApiKey]Your active API keys (prefixes only).
get_credit()CreditCredit balance and auto-recharge setting.
list_credit_history(*, start_date=None, end_date=None)list[CreditHistoryEntry]Top-ups and refunds (default: last 90 days).
list_workspaces()list[Workspace]Workspaces you can access.
get_workspace(workspace)WorkspaceDetailCost and resource summary of one workspace.
list_members(workspace)list[Member]Members of a workspace.
list_pods(workspace)list[Pod]Pods in a workspace (by ID).
get_pod(pod_name, workspace)PodA single pod.
wait_for_pod(pod_name, workspace, *, until="running", timeout=600, interval=5)PodPoll until the pod reaches a status.
get_pod_metrics(pod_name, workspace)PodMetricsLive CPU/RAM/GPU/disk usage of a pod.
list_storages(workspace)list[Storage]Storages (volumes) in a workspace.
get_storage(storage_name, workspace)StorageA single storage.
list_gpus(*, rental_type="demand", min_vram=None)list[GpuAvailability]GPU tiers you can rent right now, with prices.
list_templates(workspace=None, *, app_type=None)list[Template]Official templates, plus a workspace’s custom ones when workspace is given.
get_template(template_id, workspace=None)TemplateA single template (custom ones need their workspace).
list_servings(workspace)list[Serving]Serverless serving deployments in a workspace.
get_serving(serving_id)ServingA single serving deployment.
list_tasks(workspace, *, status=None, limit=50, offset=0)list[Task]Serverless tasks, newest first. status is a string or list of statuses.
get_task(task_id)TaskA single task.
list_assets(workspace, *, asset_type=None, status=None, page=1, page_size=20)AssetPageOne page of a workspace’s assets. Iterate it, or read .items, .total, .pages.
get_asset(asset_id)AssetA single asset with its current files (plus deprecated version-compatibility fields, see below).
get_asset_storage(workspace)AssetStorageManaged asset storage usage, cost, and credit status.
list_machines()list[Machine]Machines you host.
get_machine(machine_id)MachineA single machine.
get_machine_metrics(machine_id)MachineMetricsLive metrics of a machine.
get_earnings(*, start_date=None, end_date=None)EarningsHost earnings summary and daily history.
get_pod_logs(pod_name, workspace, *, tail=200, container=None, wait=None)LogsLast tail lines of a pod’s logs (see Logs).
get_task_logs(task_id, *, tail=200, wait=None, cursor=None)LogsLast tail lines of a task’s logs. For tasks on an external provider, pass a previous result’s next_cursor as cursor to get only the lines after it.
get_operation(operation_id, *, method, path)dictRead the caller’s durable acceptance record. Use the original method and SDK-relative path, such as POST and /tasks, without /v1/sdk or query parameters.

These need a key issued with the Read & write permission. Creations are accepted asynchronously: the returned object carries a transaction_id and the estimate, and the resource appears in the matching list a few seconds later.

MethodReturnsDescription
estimate_pod(name, template_id, *, workspace, ...)PodEstimateHourly price and resolved hardware for a pod, without creating it. Same keyword arguments as create_pod.
create_pod(name, template_id, *, workspace, gpu_model=None, gpu_count=1, gpu_vram_gb=None, rental_type="demand", vcpu=None, ram_gb=None, volumes=None, env=None, secret_keys=None, ports=None, command=None, internet_premium=False, uptime_premium=False, cpu_premium=False, region=None, max_price_per_hour=None, idempotency_key=None)PodCreatedCreate a pod. Omit gpu_model for a CPU pod. volumes is a list of (pv_name, "/mount") tuples or {"storage": ..., "mount_path": ...} dicts; ports a list of ints or {"port", "name", "external"} dicts. The system disk is sized by the server (see the estimate’s resources["disk_gb"]).
stop_pod(pod_name, workspace)ResourceActionScale the pod to zero (pod billing stops, storage billing continues).
start_pod(pod_name, workspace, *, placement="same_node", allow_data_loss=False)ResourceActionStart a stopped pod. any_node permits moving to another machine; unpreserved workspace files require separate loss consent (see below).
restart_pod(pod_name, workspace)ResourceActionRestart in place.
delete_pod(pod_name, workspace, *, delete_local_storages=None)ResourceActionDelete the pod; local volumes are deleted only when listed.
estimate_storage(name, size_gb, *, workspace, storage_type="nfs", disk_type="NVMe", encrypted=False, region=None, max_price_per_hour=None)StorageEstimateHourly price of a volume.
create_storage(name, size_gb, *, workspace, ...)StorageCreatedCreate a volume (same arguments as the estimate). encrypted=True is available for nfs volumes only.
delete_storage(storage_name, workspace)ResourceActionDelete a volume; refused with ConflictError("Storage In Use") while a pod mounts it.
deploy_serving(model_registration_id, *, workspace, price_cap_per_hour, min_replicas=1, max_replicas=3, autoscale=True, max_context_tokens=None, share_idle_capacity=False)ResourceActionDeploy a registered model as a serving.
scale_serving(serving_id, *, min_replicas=None, max_replicas=None, autoscale=None, price_cap_per_hour=None)ResourceActionChange replica range, autoscaling or the per-replica cap. Serving.scale_raises_cost(...) with the same keywords tells whether the change can raise the hourly cost.
pause_serving(serving_id, *, paused=True)ResourceActionPause (True) or resume (False).
delete_serving(serving_id)ResourceActionDelete a serving.
estimate_task(name, script, *, workspace, image=None, template_id=None, requirements=None, env=None, secret_keys=None, args=None, gpu_model=None, gpu_count=None, gpu_vram_gb=None, cpu_preset=None, max_duration=3600, webhook_url=None, input_assets=None, max_price_per_hour=None)TaskEstimateCompute hourly estimate; the total cost ceiling is unknown. Pass exactly one of gpu_model or cpu_preset.
submit_task(name, script, *, workspace, ...)TaskSubmittedSubmit the task (same arguments as the estimate). Use print(..., flush=True) in the script.
stop_task(task_id)ResourceActionStop a queued or running task.

Money comes back as the server’s unrounded value — pod.price_per_hour is "0.06770833", not a rounded display string — so your own arithmetic stays exact. To print it the way the console and CLI do, use meshive.format_hourly(value) for an hourly rate (three decimals) and meshive.format_usd(value) for any other amount (two).

Every write method also accepts idempotency_key= (see Constructing a client). Invalid arguments — a GPU count outside 1–8, a relative mount path, a secret_keys entry that is not in env, a task duration outside 1–24 hours, a script above 256 KB — raise ValueError before any request is sent.

For pods and tasks, max_price_per_hour caps the final compute hourly rate, including CPU/RAM charges. Attached or automatically created volumes and Asset Hub retention are billed separately. The server checks the cap again before placement; an over-cap pod can fail asynchronously after the API accepted its creation. Read the resource state and final rate rather than treating acceptance as successful placement. A capped CPU request is refused when the server cannot quote its rate.

For storage, the cap applies to that volume’s final initial hourly rate. Task max_duration bounds script runtime; input fetching can add compute time, and retained storage can outlive the task. max_cost is therefore unknown, including for GPU tasks.

Before start_pod(..., placement="any_node"), inspect pod.has_unpreserved_workspace. Unpreserved workspace files can be permanently deleted during a move; attached local hostPath volumes stay on the old node. Set allow_data_loss=True only after separate consent for that pod and move. pod.storage_rate_per_hour reports storage separately from compute.

Keep the key, request arguments and returned resource/transaction IDs. Success results expose raw["idempotencyKey"], raw["operationMethod"] and raw["operationPath"]. Terminal API/network exceptions expose idempotency_key, operation_method and operation_path when a write was sent.

# Read-only lookup: use the key saved before the original submit_task call.
with Meshive() as client:
operation = client.get_operation(saved_key, method="POST", path="/tasks")
print(operation)
if operation.get("taskId"):
print(client.get_task(operation["taskId"]).status)
StateMeaning and next action
doneThe API response was saved. Repeating the identical request with the same key replays it; check the task/pod/serving/storage separately for completion.
pending / unknownThe request is fenced against duplicate execution. Reconcile the recorded task/transaction; do not submit a new key or assume waiting will unlock it.
not_foundNo record was found for this caller, key, method and path. Verify all four and inspect resource state; absence alone does not prove an earlier timed-out request did nothing.

There is no automatic expiry or takeover of retained records. An explicit refusal before work was committed (for example No Capacity or Scheduling Busy) can release the key. Retry the same request with the same key after resolving that condition, respecting Retry-After. Use a new key for changed work only after resolving the original outcome.

import time
import uuid
from meshive import Meshive, ConflictError
with Meshive() as client: # a Read & write key
ws = "my-workspace-id"
name = "train-" + uuid.uuid4().hex[:12]
est = client.estimate_pod(name, 457, workspace=ws, gpu_model="RTX 3060")
print(f"${est.price_per_hour}/h on {est.resources['gpu_model']}")
create_key = str(uuid.uuid4()) # save durably before sending in an application
print("create operation:", create_key)
try:
created = client.create_pod(name, 457, workspace=ws, gpu_model="RTX 3060",
max_price_per_hour=0.10, idempotency_key=create_key)
except ConflictError as err: # No Capacity / Name Taken / Price Exceeds Cap
raise SystemExit(f"{err.title}: {err.message}")
# Use a unique display name for this operation and wait for it to appear.
deadline = time.monotonic() + 120
while True:
pod = next((p for p in client.list_pods(ws) if p.user_alias == created.name), None)
if pod is not None:
break
if time.monotonic() >= deadline:
raise TimeoutError(f"Create accepted: transaction {created.transaction_id}; reconcile before retrying")
time.sleep(5)
pod = client.wait_for_pod(pod.pod_name, ws, until="running")
print(client.get_pod_logs(pod.pod_name, ws, tail=20).text)
client.stop_pod(pod.pod_name, ws)
client.wait_for_pod(pod.pod_name, ws, until="stopped")
client.delete_pod(pod.pod_name, ws)

Responses are parsed into lightweight dataclasses. Frequently used scalar fields are typed; deeply nested structures aren’t enumerated — the full original payload is preserved on .raw, so the SDK keeps working even when the backend adds fields.

FieldTypeNotes
emailstr
usernamestr | None
user_rolestr
rawdictFull payload.
FieldTypeNotes
namespace_namestrID — pass to list_pods / get_pod.
workspace_namestrDisplay label.
descriptionstr
member_countint
statusstr
price_per_hourstr
resourcesWorkspaceResources.pod, .storage, .serverless counts. Includes stopped pods and paused deployments.
created_at / updated_atdatetime | None
rawdictFull payload.

To count only what a workspace is actually running, read ws.raw["activeResources"] — same three keys, but pods you stopped and deployments you paused are left out. A typed field will follow in a later SDK release.

FieldTypeNotes
pod_namestrID — pass to get_pod.
namespace_namestrOwning workspace ID.
user_aliasstrDisplay label.
statusstr
rental_typestrspot / demand.
price_per_hourstr
storage_rate_per_hourstrStorage rate, separate from compute.
has_unpreserved_workspacebool | NoneWhether a node move may lose workspace files. None means unknown; do not assume preservation.
is_maintenancebool
created_atdatetime | None
rawdictFull payload — machine, template, request, linked storages, …
FieldTypeNotes
machine_idstrID — pass to get_machine.
namestrDisplay label.
machine_typestrgpu / cpu / storage.
statusstr
gpu_modelstrEmpty for cpu/storage machines.
gpu_countint
earning_hourlyfloat
uptime_ratefloat0.0–1.0.
host_tierstr
rawdictFull payload — specs, full state, pod uses, …
FieldTypeNotes
namespace_name / workspace_namestrID / display label.
price_per_hourstrWhat the workspace is being billed right now.
weekly_avg_daily_coststrAverage daily cost over the last 7 days.
gpus / vcpusintTotals across the workspace’s pods.
ram / total_storageint / floatIn MiB (divide by 1024 for GB).
resourceslist[ResourceCondition]One per type (pod, storage, serverless) with .active, .paused, .disabled.
costslist[DailyCost]Recent daily costs (.date, .pod, .storage, .serverless, .task, .asset, .total).
rawdictFull payload — maintenance schedule, messages from hosts, …

user (email), role (admin / billing / viewer), joined_at, raw.

FieldTypeNotes
pv_namestrID — pass to get_storage.
namespace_name / user_aliasstrOwning workspace / display label.
storage_typestrnfs, hostPath, …
statusstr
total_size / available_sizefloatIn MiB.
usage_ratefloat0.0–1.0.
price_per_hourstr
linked_podslist[str]Names of the pods it is mounted in.
is_maintenance / encryptedbool
created_atdatetime | None
rawdictFull payload — host machine, warning thresholds, …

Both expose cpu_cores, cpu_usage_rate, ram_size (MiB), ram_usage_rate, and gpus — a list of GpuUsage with gpu_number, core_usage_rate, vram_usage_rate, vram_size (MiB) and temp. Rates are 0.0–1.0. Every GpuUsage field except gpu_number is None when that particular measurement is unavailable — a GPU that has stopped responding often still reports its temperature while its memory readings drop out, so check each field rather than assuming a GPU is either fully readable or absent. PodMetrics adds ephemeral_storage_request / ephemeral_storage_usage (MiB); MachineMetrics adds cpu_allocated, ram_allocated, root_volume_size / root_volume_usage_rate, pv_volume_size / pv_volume_usage_rate, and network_receive / network_transmit (bytes per second).

FieldTypeNotes
gpu_modelstr
vramintGB. One entry per (model, VRAM) tier.
rental_typestrdemand / spot — the type the price is for.
price_per_hourstrPer GPU.
vcpu_recommended / ram_recommendedintSuggested pairing per GPU.
available_gpusintTotal across machines.
max_gpus_per_podintThe most one pod can get on a single machine.
machine_countint
rawdictFull payload — per-machine combinations, CPU/RAM prices, …
  • ApiKey: key_id, name, prefix (meshive_a1b2c3d4 — the secret is never returned), scopes, status, created_at, last_used_at, expires_at.
  • Credit: balance, paid_balance (GPU pods and workspaces), bonus_balance (serverless inference only), auto_recharge, auto_recharge_threshold, auto_recharge_amount, has_default_payment_method.
  • CreditHistoryEntry: entry_id, amount (negative for refunds), is_paid, payment_method, created_at. Stripe receipt and invoice links are not exposed to the SDK.

template_id (ID), name, description, is_official, deploy_type, app_type, app_sub_type, image, hardware_type, cuda_version, framework, framework_version, raw (environment variables, endpoints, volume mounts, semantic paths).

serving_id (ID), namespace_name, model_name, api_model_id, framework, status, paused, min_replicas / max_replicas / current_replicas / healthy_replicas, autoscale, price_cap_per_hour (None = unlimited), endpoint_url, price_per_hour, billing_active, raw (replicas, live metrics, scaling state). scale_raises_cost(min_replicas=, max_replicas=, autoscale=, price_cap_per_hour=) returns whether such a scale_serving call can raise the hourly cost.

task_id (ID, task_…), name, namespace_name, status, pod_name, image, gpu_model, gpu_count, cpu_cores, ram_gb, price_per_hour, cost_so_far, total_cost, created_at, container_running_at, finished_at, failure_reason, exit_code, raw. For get_task the payload also carries the script, requirements, environment (secret values masked) and the compute/disk cost breakdown.

Asset, AssetVersion, AssetPage, AssetStorage

Section titled “Asset, AssetVersion, AssetPage, AssetStorage”
FieldTypeNotes
Asset.asset_idstrID (asset_…) — pass to get_asset.
Asset.name / asset_type / status / status_reasonstrType is one of dataset, model, adapter, checkpoint, output, config, file.
Asset.storage_providerstrmeshive_r2 (managed), user_s3, or external.
Asset.size_bytes / file_countintThe asset’s current total size and file count.
Asset.version_countintDeprecated compatibility field. Assets no longer have real version history: this is 1 while the asset is ready and not deleted, 0 otherwise.
Asset.in_useboolWhether a pod or task currently uses it (raw["activeUsageContexts"] says which).
Asset.latest_version / versionsAssetVersion / list[AssetVersion]Deprecated compatibility fields. Describe the asset’s own current state, not real version history — present (one item for versions) only while the asset is ready and not deleted, absent otherwise. versions is filled by get_asset only.
AssetVersionDeprecated compatibility model. version_number is always 1; the remaining fields (status (uploading / ready / failed), total_size_bytes, file_count, ingest_source, storage_provider, created_at, deleted, import_failure_reason) describe the asset itself, not a version. File entries are in raw["files"].
AssetPageitems, total, page, page_size, pages; iterable.
AssetStoragemanaged_bytes, price_per_gb_month, estimated_monthly_cost, credit_state (normal / grace / blocked), credit_blocked, blocks_at, purge_deadline_at, paid_balance_available.

PodEstimate, PodCreated, ResourceAction (0.1.0+)

Section titled “PodEstimate, PodCreated, ResourceAction (0.1.0+)”

PodEstimate — price_per_hour (str, USD), breakdown (dict: gpu, cpu_extra, ram_extra, premiums), resources (dict: gpu_model, vram_gb, gpu_count, vcpu, ram_gb, disk_gb, rental_type, and for GPU pods the included vcpu_included/ram_gb_included), availability (dict), template (dict), volumes, note.

PodCreated — name, workspace, transaction_id, estimate (PodEstimate), pod_name (None right after creation; find the pod by user_alias).

ResourceAction — returned by every stop/start/restart/delete/scale/pause call: resource (pod/storage/serving/task), id, action, workspace, accepted, result (the server’s transaction id or message).

StorageEstimate, StorageCreated, TaskEstimate, TaskSubmitted

Section titled “StorageEstimate, StorageCreated, TaskEstimate, TaskSubmitted”

StorageEstimate — price_per_hour, price_per_gb_month, size_gb, storage_type, disk_type (the price depends on both), max_size_gb, note. StorageCreated — name, workspace, transaction_id, estimate, pv_name (None until the volume exists).

TaskEstimate — price_per_hour (None when the machine rate cannot be quoted), max_cost (None: no total-bill ceiling), max_duration, resources, note. TaskSubmitted — task (a Task) and estimate.

Logs — pod_name, workspace, source (live from the log buffer, archive for a finished task, external for tasks on an external provider, none when nothing was captured), lines (list of LogLine(line, ts)), count, truncated (older lines dropped to stay under 64 KB), note (also says when buffered lines may be behind because no log watcher could be started), and for tasks task_id, finished, next_cursor (external provider tasks: pass it back as cursor for the lines after it). .text joins the lines.

current_hourly, daily, accumulated_until_payout, and history — a list of DailyEarning (date, cpu, gpu, storage, total), newest first.

All SDK errors subclass MeshiveError:

ExceptionWhen
ConfigurationErrorNo API key could be resolved (raised before any request).
AuthenticationError401 — missing/invalid/expired key, or an inactive account.
PermissionDeniedError403 — the key lacks the required scope.
NotFoundError404 — the resource doesn’t exist (including a pod that was already deleted).
InsufficientCreditError402 — not enough paid credit to create or start the resource.
ConflictError409 — the request clashes with the current state. .title tells which: No Capacity, Name Taken, Price Exceeds Cap, Storage In Use, VRAM Tier Required, Operation Outcome Unknown. .raw["detail"] carries extras such as availability or pricePerHourUsd.
RateLimitError429 — rate limit exceeded. Exposes .retry_after (seconds, may be None).
MeshiveAPIErrorAny other 4xx/5xx.

MeshiveAPIError (and its subclasses) carry .status_code, .title, .message, and .raw (the parsed response body).

from meshive import Meshive, MeshiveError, RateLimitError, NotFoundError
try:
with Meshive() as client:
pod = client.get_pod("does-not-exist", "my-workspace")
except NotFoundError as err:
print("no such pod:", err.message)
except RateLimitError as err:
print("slow down; retry after", err.retry_after, "s")
except MeshiveError as err:
print("request failed:", err)
from meshive import (
Meshive, AsyncMeshive, # clients
WhoAmI, ApiKey, Credit, CreditHistoryEntry, # account
Workspace, WorkspaceResources, WorkspaceDetail, # workspaces
ResourceCondition, DailyCost, Member,
Pod, PodMetrics, Storage, GpuUsage, # pods / storage
Asset, AssetVersion, AssetPage, AssetStorage, # assets
GpuAvailability, Template, # catalog
Serving, Task, # serverless
Machine, MachineMetrics, Earnings, DailyEarning, # host
PodEstimate, PodCreated, ResourceAction, # write (0.1.0+)
StorageEstimate, StorageCreated, TaskEstimate, TaskSubmitted,
Logs, LogLine,
MeshiveError, ConfigurationError, MeshiveAPIError, # errors
AuthenticationError, PermissionDeniedError,
NotFoundError, InsufficientCreditError, ConflictError,
RateLimitError, WaitTimeoutError,
__version__,
)