Skip to main content

Runcrate Python SDK

Official Python SDK for the Runcrate API.

Installation

pip install runcrate-sdk

Quick Start

from runcrate import Runcrate

client = Runcrate(api_key="rc_live_...")

# Chat completion
response = client.models.chat_completion(
    model="meta-llama/Meta-Llama-3.1-8B-Instruct",
    messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)

# List GPU instances
instances = client.instances.list()

# Create an instance
instance = client.instances.create(
    name="training-run",
    ssh_key_id="key_abc123",
    gpu_type="A100",
)

# Check status
status = client.instances.get_status(instance.id)
print(status.status, status.ip)

# Check billing
balance = client.billing.get_balance()
print(f"Credits: ${balance.credits_balance}")

client.close()

Async Usage

import asyncio
from runcrate import AsyncRuncrate

async def main():
    async with AsyncRuncrate(api_key="rc_live_...") as client:
        response = await client.models.chat_completion(
            model="meta-llama/Meta-Llama-3.1-8B-Instruct",
            messages=[{"role": "user", "content": "Hello!"}],
        )
        print(response.choices[0].message.content)

asyncio.run(main())

Configuration

client = Runcrate(
    api_key="rc_live_...",                        # or set RUNCRATE_API_KEY env var
    base_url="https://runcrate.ai",               # infra API (default)
    inference_url="https://api.runcrate.ai",      # model inference API (default)
    timeout=30.0,                                 # request timeout in seconds
    max_retries=3,                                # retry on 429/5xx with exponential backoff (Retry-After honoured up to 60s; never a 502 launch_refused)
)

Model Inference

The client.models resource connects to api.runcrate.ai for AI model inference. Same API key works for both infrastructure and inference.

Chat Completions

# Standard request
response = client.models.chat_completion(
    model="meta-llama/Meta-Llama-3.1-8B-Instruct",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Explain quantum computing in 3 sentences."},
    ],
    max_tokens=256,
    temperature=0.7,
)
print(response.choices[0].message.content)
print(f"Tokens used: {response.usage.total_tokens}")

# Streaming
for chunk in client.models.chat_completion(
    model="meta-llama/Meta-Llama-3.1-8B-Instruct",
    messages=[{"role": "user", "content": "Write a poem about GPUs"}],
    stream=True,
):
    delta = chunk.get("choices", [{}])[0].get("delta", {})
    print(delta.get("content", ""), end="", flush=True)

Image Generation

result = client.models.generate_image(
    model="black-forest-labs/FLUX.1-schnell",
    prompt="A futuristic city skyline at sunset",
    width=1024,
    height=1024,
)
# result.data[0].b64_json contains the base64-encoded image

Video Generation

# Submit video job (async processing)
job = client.models.generate_video(
    model="google/veo-3.0",
    prompt="A drone flyover of a mountain landscape",
    duration=8,
)
print(f"Job ID: {job.id}, Status: {job.status}")

# Poll until complete
import time
while True:
    job = client.models.get_video_status(job.id)
    print(f"Status: {job.status}")
    if job.status in ("completed", "failed"):
        break
    time.sleep(5)

# Download the video
if job.status == "completed":
    video_bytes = client.models.download_video(job.id)
    with open("output.mp4", "wb") as f:
        f.write(video_bytes)

Text-to-Speech

audio_bytes = client.models.text_to_speech(
    model="openai/tts-1",
    input="Hello, welcome to Runcrate!",
    voice="alloy",
    response_format="mp3",
)
with open("speech.mp3", "wb") as f:
    f.write(audio_bytes)

Transcription (Speech-to-Text)

with open("audio.wav", "rb") as f:
    result = client.models.transcribe(
        model="openai/whisper-1",
        file=f,
        filename="audio.wav",
    )
print(result.text)
print(f"Duration: {result.duration}s")

Infrastructure Resources

Instances

client.instances.list(search="my-gpu")
client.instances.create(name="run", ssh_key_id="key", gpu_type="A100")
client.instances.get("instance-id")
client.instances.terminate("instance-id")
client.instances.get_status("instance-id")
client.instances.list_types(gpu_type="A100", region="us-east")

Schedules

Start an instance or a job later: once at a time (run_at), or on a recurring 5-field cron read in an IANA timezone. A schedule starts within about 2 minutes of its time, as you, in the environment it was created in. spec is the create body of that kind and must set max_runtime_minutes.

from datetime import datetime
from zoneinfo import ZoneInfo

# Every night at 02:00 London time
nightly = client.schedules.create(
    kind="job",
    cron="0 2 * * *",
    timezone="Europe/London",
    name="nightly-train",
    spec={"command": "python train.py", "gpu_type": "H100", "max_runtime_minutes": 120},
)
print(nightly.next_run_at)

# Once, tomorrow morning. run_at must carry a timezone: a naive datetime raises ValueError.
client.schedules.create(
    kind="instance",
    run_at=datetime(2026, 10, 21, 9, 0, tzinfo=ZoneInfo("Europe/London")),
    spec={"name": "eval-box", "gpu_type": "A100", "ssh_key_id": "key_abc123", "max_runtime_minutes": 60},
)

client.schedules.list()
client.schedules.get(nightly.id)
client.schedules.update(nightly.id, enabled=False)        # pause
client.schedules.update(nightly.id, enabled=True)         # resume from the next occurrence
client.schedules.update(nightly.id, cron="0 3 * * 1-5")   # retime
run = client.schedules.run_now(nightly.id)                # launch now; run.job or run.instance
client.schedules.delete(nightly.id)

Recurring runs must be at least 15 minutes apart, run_at can be up to 365 days ahead, and you can have at most 50 schedules per environment and 200 per workspace. overlap="skip" (the default) skips a run while the previous one is still live; overlap="allow" starts it anyway. run_now raises InsufficientCreditsError with code == "BUDGET_REACHED" when the environment's monthly budget is spent.

Each run launches as schedule.run_as, the person who created the schedule or last changed its spec or timing or resumed it, using the same kind of sign-in (run_as_credential: "user", "api_key" or "agent"). If that API key is revoked or the agent sign-in ends, the schedule pauses with last_status == "credential_revoked". Resume it to run as you.

Auto-stop for unattended work

When an agent or script launches instances, set a max runtime and an idle stop so a forgotten instance ends on its own:

instance = client.instances.create(
    name="agent-run",
    ssh_key_id="key_abc123",
    gpu_type="H100",
    max_runtime_minutes=240,   # terminate after 4 hours running (5 to 10080)
    idle_shutdown_minutes=30,  # terminate after 30 idle minutes (5 to 1440)
)

instance = client.instances.get(instance.id)
print(instance.auto_stop_at)   # when max runtime will stop it, or None
print(instance.stop_reason)    # None while running, else "user", "max_runtime", "idle",
                               # "budget", "low_balance", "failed" or "system"
  • max_runtime_minutes: the clock starts when the instance first becomes running. An alert goes out 10 minutes before the stop.
  • idle_shutdown_minutes: idle means GPU utilisation under 5% with nobody connected (SSH, web terminal or editor); GPU instances only.

Change the limits on a running instance, for example to extend before a long job. Max runtime counts from when it started, so pass the total; an argument left out keeps its value and None turns it off:

client.instances.update(instance.id, max_runtime_minutes=480, idle_shutdown_minutes=None)
  • Both default to None (no limit, idle stop off) and are left out of the request. Values out of range raise UnprocessableEntityError (HTTP 422, validation_error).

Environments

client.environments.list()
client.environments.create(name="staging")
client.environments.get("env-id")
client.environments.update("env-id", name="production")
client.environments.delete("env-id")

env = client.environments.get("env-id")
print(env.monthly_budget_usd)    # None when the environment has no cap
print(env.month_to_date_usd)     # this UTC month's spend: GPU instances plus inference
print(env.budget_remaining_usd)  # None when the environment has no cap

Monthly budgets are read-only here. A workspace owner or admin sets them in the dashboard under Workspace settings. At 100% of the budget, launches in that environment raise InsufficientCreditsError with code == "BUDGET_REACHED", running instances in it stop with stop_reason == "budget", and inference calls with its API keys fail with code == "budget_exceeded" until the month ends or the budget is raised.

SSH Keys

client.ssh_keys.list()
client.ssh_keys.create(name="my-key", public_key="ssh-ed25519 AAAA...")
client.ssh_keys.delete("key-id")

Storage

client.storage.list()
client.storage.get("volume-id")

Billing

client.billing.get_balance()
client.billing.list_transactions(limit=10, offset=0)
client.billing.usage(from_date="2025-01-01", to_date="2025-01-31")

Templates

client.templates.list(search="cuda", category="ml", page_size=10)

Error Handling

from runcrate import Runcrate, NotFoundError, InsufficientCreditsError, RateLimitError

client = Runcrate(api_key="rc_live_...")

try:
    instance = client.instances.get("nonexistent")
except NotFoundError as e:
    print(f"Not found: {e.message}")
except InsufficientCreditsError as e:
    if e.code == "BUDGET_REACHED":
        print(f"Environment budget reached: {e.message}")
    else:
        print(f"Need more credits: {e.message}")
except RateLimitError as e:
    print(f"Rate limited: {e.message}")

Pagination

# Transactions support offset-based pagination
page = client.billing.list_transactions(limit=10, offset=0)
print(page.data)        # list of Transaction objects
print(page.has_more)    # True if more pages exist
print(page.total)       # total count

# Templates support page-based pagination
templates = client.templates.list(page=1, page_size=25)

Requirements

  • Python >= 3.9
  • httpx >= 0.25.0
  • pydantic >= 2.0

Metadata

Release files for runcrate-sdk 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for runcrate-sdk 0.2.1
File Size Uploaded Provenance Actions
runcrate_sdk-0.2.1.tar.gz 30.2 kB Without provenance Details for runcrate_sdk-0.2.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for runcrate-sdk 0.2.1
File Interpreter ABI Platform Actions
runcrate_sdk-0.2.1-py3-none-any.whl Python 3 none any Details for runcrate_sdk-0.2.1-py3-none-any.whl

Total release size: 60.6 kB

Release files / runcrate_sdk-0.2.1.tar.gz

Download URL runcrate_sdk-0.2.1.tar.gz
Size 30.2 kB
Tags Source
SHA-256 checksum
How to use checksums
aef196f6b598b79ddc293ad33bd899833871c701266b798cef0245738bded4b8
BLAKE2b-256 checksum
How to use checksums
8ffa121e3178fa00e1c85dbdfd4486d4d34c2d6847b978f2454ddfa3276affbd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.13

Release files / runcrate_sdk-0.2.1-py3-none-any.whl

Download URL runcrate_sdk-0.2.1-py3-none-any.whl
Size 30.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
891735c75f30c1b0f007b14c086ce1da696ed2393b24400c4fb1702f469d2e36
BLAKE2b-256 checksum
How to use checksums
0728f20cd53644818c74140aa991513b557a782ed6d754833894d1d1ab540c85
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.13

Security for runcrate-sdk 0.2.1

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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page