bios-sdk
Official Python SDK for the BIOS fine-tuning platform API.
Installation
The distribution is bios-sdk; the import name is bios. (bios on PyPI is
an unrelated project — installing it will not give you this SDK.)
pip install bios-sdk
Quick Start
from bios import BiOS
client = BiOS(api_key="bios-...")
# Search the hosted catalog. Rows come straight from the model registry, so
# they are snake_case, and the HANDLE you pass everywhere else is repo_id
# (`id` is the registry UUID).
result = client.models.search(query="llama", type="llm", limit=5)
for model in result["models"]:
print(f"{model['repo_id']} -- {model['params_total_b']}B params")
# Create a training job
job = client.training.create(
idempotency_key="training-create-20260711-0001",
model="meta-llama/Llama-3.1-8B-Instruct",
dataset_ids=["ds_abc123", "ds_def456"],
method="sft",
adapter="lora",
epochs=3,
learning_rate=2e-4,
lora_rank=16,
)
print(f"Job {job['id']} created -- status: {job['status']}")
Authentication
The SDK supports two authentication methods:
API Key (recommended). Keys start with bios- (legacy usf- keys stay
valid):
client = BiOS(api_key="bios-...")
Environment variables. When api_key, base_url, or inference_key is
omitted, the SDK reads BIOS_API_KEY, BIOS_BASE_URL, and
BIOS_INFERENCE_KEY from the environment:
export BIOS_API_KEY=bios-...
export BIOS_BASE_URL=https://api.usbios.ai # optional; this is the default
export BIOS_INFERENCE_KEY=sk-bios-... # optional; defaults to BIOS_API_KEY
client = BiOS() # uses BIOS_API_KEY / BIOS_BASE_URL / BIOS_INFERENCE_KEY
JWT Access Token:
client = BiOS(
access_token="eyJhbG...",
org_id="org_abc123",
)
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
str |
BIOS_API_KEY env var |
API key for authentication (prefix: bios-; legacy usf- keys stay valid) |
access_token |
str |
None |
JWT access token (alternative to API key) |
org_id |
str |
None |
Organization ID (required for JWT auth) |
workspace_id |
str |
None |
Workspace ID (optional, overrides key default) |
base_url |
str |
BIOS_BASE_URL env var, then https://api.usbios.ai |
Canonical production hostname (release-gated; this documentation does not assert current availability). During prelaunch/dev, pass https://api-dev.usbios.ai explicitly. |
timeout |
float |
30.0 |
Request timeout in seconds |
inference_key |
str |
BIOS_INFERENCE_KEY env var, then api_key |
Key used by client.inference for /v1 calls. A per-deployment sk-bios-... key, or the platform api_key itself when it carries the serverless scope — you never pass the same key twice |
inference_base_url |
str |
base_url |
Explicit dev or production inference hostname |
inference_timeout |
float |
900.0 |
End-to-end inference/stream timeout in seconds |
Resources
Inference
Inference keys are separate from control-plane API keys, but you never pass one
twice: inference_key falls back to BIOS_INFERENCE_KEY and then to the
control-plane api_key, so a platform key carrying the serverless scope calls
/v1 directly. Pass an explicit inference_key for a per-deployment
sk-bios-... key. Streaming yields each OpenAI SSE chunk as a dictionary and
closes the upstream response when the iterator is closed. Calls are never retried automatically; if your application
chooses to retry, reuse the same idempotency_key. The header is propagated,
but the SDK does not claim server-side replay/deduplication unless the endpoint
returns an explicit replay acknowledgement.
client = BiOS(
api_key="bios-control-plane-key",
inference_key="sk-bios-deployment-key", # omit to reuse api_key
# Use https://api-dev.usbios.ai explicitly during dev.
)
tools = [{
"type": "function",
"function": {
"name": "lookup",
"parameters": {"type": "object", "properties": {"id": {"type": "integer"}}},
},
}]
stream = client.inference.stream_chat_completions(
messages=[{"role": "user", "content": "Look up record 42"}],
tools=tools,
idempotency_key="chat-42-attempt-1",
)
try:
for chunk in stream:
print(chunk)
finally:
stream.close() # cancels/disconnects an unfinished generation
Serverless catalog models
Call any catalog model by id on the unified /v1 endpoint with a workspace
platform key that carries the serverless scope — no per-deployment key. The
gateway routes by model; dedicated deployments and serverless models share the
same endpoint. reasoning_effort is forwarded, and streaming surfaces
content and reasoning_content deltas incrementally.
# A platform key with the serverless scope is enough — `api_key` doubles as the
# inference key, so BiOS(api_key=K).inference.chat_completions(...) just works.
client = BiOS(api_key="bios-platform-key-with-serverless-scope")
stream = client.inference.stream_chat_completions(
model="meta-llama/Llama-3.1-8B-Instruct", # serverless catalog id
messages=[{"role": "user", "content": "Explain tensor parallelism briefly."}],
reasoning_effort="low",
)
try:
for chunk in stream:
delta = (chunk.get("choices") or [{}])[0].get("delta", {})
if delta.get("reasoning_content"):
print(delta["reasoning_content"], end="", flush=True)
if delta.get("content"):
print(delta["content"], end="", flush=True)
finally:
stream.close()
# Non-streaming; the final response carries "usage" when the model reports it.
completion = client.inference.chat_completions(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[{"role": "user", "content": "One sentence on GPUs."}],
)
choice = completion["choices"][0]
answer = choice["message"].get("content")
if answer:
print(answer)
else:
# A 200 is NOT proof the model answered. Reasoning models can spend the
# whole budget inside reasoning_content and return content: null with
# finish_reason "length" — an empty answer that looks like success. Always
# read choices[0].message.content, and raise max_tokens (or lower
# reasoning_effort) when finish_reason is "length".
print("no answer:", choice.get("finish_reason"),
"reasoning tokens only:", bool(choice["message"].get("reasoning_content")))
Streaming billing is charged server-side on completed usage; the SDK only needs
to request usage where the endpoint exposes it (no client change).
Models
Search the model catalog, fetch training configs, and check adapter compatibility. The catalog lists only models hosted on BiOS (the platform's own verified registry, mirrored in BiOS storage) — every result can be trained and deployed; it is never a live Hugging Face search.
Search results are the registry's own rows: snake_case fields, repo_id as
the model handle (id is the registry UUID), plus maxContext — the native
context window that caps a deployment's context_length — and weightBytes,
the on-disk weight size. query becomes the registry's q filter, which is
the only search parameter it reads.
# Search models
result = client.models.search(query="llama", type="llm", limit=10)
for m in result["models"]:
print(m["repo_id"], m["params_total_b"], m["surface_type"],
m.get("maxContext"), m.get("weightBytes"))
# One model, by its author/name handle
detail = client.models.get("meta-llama/Llama-3.1-8B-Instruct")
print(detail["model"]["architecture"], detail["model"]["maxContext"])
# The context ceiling on its own — None when the registry does not record it
print(client.models.native_max_context("meta-llama/Llama-3.1-8B-Instruct"))
# Get model config
config = client.models.get_config("meta-llama/Llama-3.1-8B-Instruct")
print(f"{config['totalParams']}B params, MoE: {config['isMoE']}")
# Check adapter compatibility
compat = client.models.get_adapter_compatibility(
model_type="llama",
training_method="rlhf",
rlhf_algorithm="dpo",
)
usable = [a for a in compat["adapters"] if a["compatible"]]
print(f"{len(usable)} compatible adapters")
Datasets
Upload, import, preview, and manage training datasets.
# List datasets
datasets = client.datasets.list()
# Upload a dataset
uploaded = client.datasets.upload(
file_path="./training_data.jsonl",
name="My SFT Dataset",
)
print(f"Uploaded: {uploaded['id']}")
# Import from HuggingFace
imported = client.datasets.import_from_huggingface(
repo_id="databricks/dolly-15k",
integration_id="int_abc123",
name="Dolly 15k",
)
# Preview dataset rows
preview = client.datasets.preview("ds_abc123", page=1, page_size=5)
print(preview["columns"])
# Validate before uploading
result = client.datasets.validate("./data.jsonl")
if result["format_valid"]:
print(f"Valid {result['detected_format']} with {result['num_samples']} rows")
else:
print("Errors:", result["validation_errors"])
# Search HuggingFace Hub
hub_results = client.datasets.search_hub(query="code instruct")
# Preview a Hub dataset
hub_preview = client.datasets.preview_hub(
dataset_id="databricks/dolly-15k",
split="train",
limit=5,
)
# Get format specs
specs = client.datasets.get_format_specs()
# Get storage usage
usage = client.datasets.get_storage_usage()
# Delete a dataset
client.datasets.delete("ds_abc123")
Training
Create, monitor, stop, and resume fine-tuning jobs.
request = {
"idempotency_key": "training-create-20260711-0001",
"model": "meta-llama/Llama-3.1-8B-Instruct",
"dataset_ids": ["ds_abc123", "ds_def456"],
"method": "sft",
"adapter": "lora",
"epochs": 3,
"learning_rate": 2e-4,
"lora_rank": 16,
"lora_alpha": 32,
"gpu_type": "A100_80GB",
"gpu_count": 1,
"gpu_priorities": [
{"gpu_type": "A100_80GB", "gpu_count": 1},
{"gpu_type": "H100_80GB", "gpu_count": 1},
{"gpu_type": "L40S_48GB", "gpu_count": 1},
],
"queue_if_unavailable": True,
"queue_deadline": "2026-07-18T00:00:00Z",
"max_price_hour_cents": 500,
}
# Side-effect-free validation, canonical sizing, live stock and alternatives
check = client.training.preflight(request)
print(check["request_hash"], check.get("recommended"), check["queue_eligible"])
# Create the paid job only after reviewing preflight
job = client.training.create(**request)
# List jobs
jobs = client.training.list(status="running")
page = client.training.list_page(limit=50, offset=0)
# Get job details
job = client.training.get("job_abc123")
print(f"Status: {job['status']}, Progress: {job.get('progress', 0)}%")
# Get metrics
metrics = client.training.get_metrics("job_abc123")
for point in metrics["metrics"]:
print(point["step"], point.get("loss"))
# Get checkpoints
checkpoints = client.training.get_checkpoints("job_abc123")
for cp in checkpoints:
print(f"{cp['name']}: {cp['size_bytes']} bytes")
# Get logs
logs = client.training.get_logs("job_abc123")
for entry in logs["logs"]:
print(entry["level"], entry["message"])
# Stop a job
client.training.stop("job_abc123", keep_data=True)
# Resume a stopped job
client.training.resume("job_abc123", idempotency_key="training-resume-20260711-0001")
# Delete a checkpoint
client.training.delete_checkpoint("job_abc123", "cp_xyz789")
Wallet
View wallet balance and transaction history.
balance_cents is the deposited balance; available_balance_cents is what can
actually be spent right now (balance minus active_holds_cents and
accruing_cents). Spend decisions read the second one. Auto top-up is flat
(auto_topup_enabled / auto_topup_threshold / auto_topup_amount), and the
transaction list is a wrapped page.
# Get balance
balance = client.wallet.get_balance()
print(f"Balance: ${balance['balance_cents'] / 100:.2f}")
print(f"Spendable: ${balance['available_balance_cents'] / 100:.2f}")
print(f"On hold: ${balance['active_holds_cents'] / 100:.2f}")
print(f"Accruing: ${balance['accruing_cents'] / 100:.2f}")
# List transactions -- the rows are WRAPPED, so iterate ["transactions"]
page = client.wallet.get_transactions(limit=20)
print(f"{page['total']} transactions")
for t in page["transactions"]:
print(f"{t['type']}/{t['category']}: ${t['amount_cents'] / 100:.2f} -- {t.get('description')}")
# Get pricing
pricing = client.wallet.get_pricing()
Inference deployment management
The same client.inference resource that makes OpenAI-compatible chat calls also
manages model-serving deployments (create, monitor, stop, and delete).
allow_capacity_queue=True is explicit consent to wait for stock, so it
requires 3 to 5 ranked gpu_priorities — the SDK rejects the combination
locally before any request. Leave the queue off to book exactly one placement.
serving_mode, model_task, and supports_images are all server-derived and
immutable: the platform reads them off the resolved model and rejects a
conflicting assertion, so omit them and read the result back from the
deployment. Which model_task values are accepted depends on the model, so the
SDK does not judge one locally — it forwards whatever you pass and the server
rules on it. preflight() echoes the derived task at
canonical_request["model_task"] if you want to see it before creating.
context_length is optional and follows one policy: omit it and the server
pre-fills min(native_max, 262144); the window is adjustable only when the
model's native max exceeds the 32,768 floor, and it can never exceed the
model's own native max. It is also a sizing input — a bigger window means a
bigger KV cache, which can raise the minimum GPU count. Read the ceiling with
client.models.native_max_context(model_id) first, and read it back from
native_max_context on the deployment detail. Pass it as an int: the local
capacity pre-check only runs on a window it can prove fits, so anything else
(a numeric string included) is forwarded for the server to answer.
request = {
"name": "llama-api",
"source_type": "hf_model",
"hf_model_id": "meta-llama/Llama-3.1-8B-Instruct",
"gpu_type": "H100_80GB",
"gpu_count": 1,
# Queueing needs 3-5 ranked placements; the first is the one booked now.
"allow_capacity_queue": True,
"gpu_priorities": [
{"gpu_type": "H100_80GB", "gpu_count": 1},
{"gpu_type": "A100_80GB", "gpu_count": 1},
{"gpu_type": "L40S_48GB", "gpu_count": 2},
],
"max_price_hour_cents": 500,
}
# Never ask for more context than the model has.
native_max = client.models.native_max_context(request["hf_model_id"])
if native_max:
request["context_length"] = min(65536, native_max)
# No wallet mutation and no GPU allocation.
check = client.inference.preflight(request)
print(check["selected_gpu"], check["alternatives"], check["billing"])
deployment = client.inference.create(request, idempotency_key="deploy-create-20260711-0001")
print(deployment["inference_key"]) # returned once; store securely
status = client.inference.status(deployment["id"])
print(status["status"], status.get("status_reason"), status.get("queue_expires_at"), status.get("wallet_authorization_status"))
# The detail resolves the per-deployment serving settings a list row omits.
print(status["context_length"], status["native_max_context"], status["model_task"])
# status stays "failed" for existing filters when status_reason is "queue_expired".
notification_history = client.inference.notifications(deployment["id"])
print([(row["event_type"], row["state"], row["attempt_count"]) for row in notification_history])
# Bounded newest-first listing; keep filters unchanged while using next_cursor.
# LIST rows are a lean grid projection: the model handle is `model_ref` (there
# is no `model` key) and the serving settings above are absent -- call
# client.inference.get(id) for those.
page = client.inference.list_page(limit=100, status="running", search="llama")
if page["has_more"]:
older = client.inference.list_page(
limit=100, status="running", search="llama", cursor=page["next_cursor"]
)
print(older["deployments"])
# Lazy traversal avoids one unbounded response.
for item in client.inference.iter_all(status="running"):
print(item["name"], item["model_ref"], item["requests_total"])
client.inference.stop(deployment["id"])
client.inference.delete(deployment["id"])
GPU
View GPU pricing and get hardware recommendations.
# Get all GPU pricing
pricing = client.gpu.get_pricing()
for gpu in pricing["gpus"]:
print(f"{gpu['display_name']}: {gpu['price_display']} -- {gpu['vram_gb']}GB VRAM")
# Authoritative model-aware choices, live stock, total pricing, and alternatives
options = client.gpu.get_options(
"meta-llama/Llama-3.1-8B-Instruct",
train_type="qlora",
method="sft",
)
# Get recommended GPU for a model
rec = client.gpu.get_recommended("meta-llama/Llama-3.1-8B-Instruct")
if rec:
print(f"Recommended: {rec['display_name']} x{rec['recommended_count']}")
print(f"Total VRAM: {rec['total_vram_gb']}GB")
print(f"Cost: ${rec['estimated_cost_per_hour_cents'] / 100:.2f}/hr")
print(f"Reason: {rec['reason']}")
Introspect
Discover the permissions and scope of your API key.
info = client.introspect()
print(f"Org: {info['org']['name']}")
print(f"Scopes: {', '.join(info['scopes'])}")
print(f"Allowed tools: {len(info['allowed_mcp_tools'])}")
Error Handling
All API errors raise ApiError with status, code, request_id, and message attributes.
from bios import BiOS, ApiError
client = BiOS(api_key="bios-...")
try:
job = client.training.get("bad_id")
except ApiError as e:
if e.status == 404:
print("Job not found")
elif e.status == 401:
print("Invalid API key")
elif e.status == 403:
print("Insufficient permissions")
else:
print(f"API error {e.status}: {e.message}")
if e.request_id:
print(f"Request ID: {e.request_id}")
Python Version Support
- Python 3.10+
License
MIT
Release files for bios-sdk 0.2.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 | |
|---|---|---|---|
| bios_sdk-0.2.0.tar.gz | 66.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bios_sdk-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 119.7 kB
Release files / bios_sdk-0.2.0.tar.gz
| Download URL | bios_sdk-0.2.0.tar.gz |
|---|---|
| Size | 66.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c496ccb061e9c7a4a57959a1d4faeb6a61ddbe788b7bff5aff9f2f3d38737fe1
|
|
BLAKE2b-256 checksum How to use checksums |
3fe809f44ba4c3f8971b9a9e73ffde13d2902c751940acfba7103e04777ed59c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Release files / bios_sdk-0.2.0-py3-none-any.whl
| Download URL | bios_sdk-0.2.0-py3-none-any.whl |
|---|---|
| Size | 53.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d698025b943ee15608be062c8acf65da2a65e636814835f55b734b0f0da74b43
|
|
BLAKE2b-256 checksum How to use checksums |
0b5b9a93d040211ab88d6e02eba1a413db45c9876717fb73617aad706df4a27d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|