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 raiseUnprocessableEntityError(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.
Secrets
Secrets are environment variables every instance and job launched in an environment gets, such as HF_TOKEN. The SDK lists their names, never their values; a workspace owner or manager sets them in the dashboard. A value passed for one launch (env_vars) wins over the secret of the same name.
secrets = client.secrets.list() # [Secret(key="HF_TOKEN", sensitive=False, updated_at=...)]
has_token = any(s.key == "HF_TOKEN" for s in secrets)
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.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| runcrate_sdk-0.3.0.tar.gz | 31.5 kB | Without provenance | Details for runcrate_sdk-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| runcrate_sdk-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for runcrate_sdk-0.3.0-py3-none-any.whl |
Total release size: 63.3 kB
Release files / runcrate_sdk-0.3.0.tar.gz
| Download URL | runcrate_sdk-0.3.0.tar.gz |
|---|---|
| Size | 31.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b0e5626a59bad1468357031f83d7317efab694ebd50f03e9d0e377cdc76529fc
|
|
BLAKE2b-256 checksum How to use checksums |
fd01198b4319b88ceeefb1ef4b38a9d7d0184923af35411571d4e59cabaacb85
|
| 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.3.0-py3-none-any.whl
| Download URL | runcrate_sdk-0.3.0-py3-none-any.whl |
|---|---|
| Size | 31.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f911c69af221bc5b6a9f93893223ab7d30cb5bf93e13dd06280a3537d8fedac4
|
|
BLAKE2b-256 checksum How to use checksums |
f567c298d388abd1e710d0aea087508422fff99c2baa682d7f32231dba820d22
|
| 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.3.0
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.