# Python

The canonical SDK — module-level primitives, resource namespaces, and results that carry n and ci95 by construction.

The Python SDK is the canonical surface: these docs are written in it first,
and the other SDKs mirror it. It reads `ROBORAMA_API_KEY` (keys carry the
`rbr_live_` prefix) from the environment, so authentication is an export, not
an argument.

```bash
pip install roborama-sdk
```

*Example: first call*

**Python**

```python
import roborama  # reads ROBORAMA_API_KEY from the environment

run = roborama.run(
    robot="g1-edu-pro@fw2.3",
    environment="kitchen-std@v1.2",
    task="pick_place@v1",
    episodes="auto(ci=0.95, moe=0.03)",
)
print(run.result())  # n=612  rate=0.874  ci95=(0.846, 0.898)
```

**TypeScript**

```typescript
import Roborama from "@roborama/sdk"; // reads ROBORAMA_API_KEY

const roborama = new Roborama();

const run = await roborama.runs.create({
  robot: "g1-edu-pro@fw2.3",
  environment: "kitchen-std@v1.2",
  task: "pick_place@v1",
  episodes: "auto(ci=0.95, moe=0.03)",
});

console.log(await run.result()); // n=612  rate=0.874  ci95=(0.846, 0.898)
```

**cURL**

```bash
curl https://api.roborama.com/v1/runs \
  -H "Authorization: Bearer $ROBORAMA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "run",
    "robot": "g1-edu-pro@fw2.3",
    "environment": "kitchen-std@v1.2",
    "task": "pick_place@v1",
    "episodes": "auto(ci=0.95, moe=0.03)"
  }'
```

**Agent (tool-use payload)**

```json
{
  "type": "tool_use",
  "name": "roborama_run",
  "input": {
    "robot": "g1-edu-pro@fw2.3",
    "environment": "kitchen-std@v1.2",
    "task": "pick_place@v1",
    "episodes": "auto(ci=0.95, moe=0.03)"
  }
}
```

## Primitives

The seven primitives are module-level functions — `roborama.run()`,
`roborama.eval()`, `roborama.verify()`, `roborama.matrix()`,
`roborama.threshold()`, `roborama.compare()`, and `roborama.transfer()` — one
call per physical question. Each is documented field by field on its own
page, starting with [`run()`](/docs/primitives/run/).

## Resources

Nouns live in resource namespaces: `roborama.runs`, `roborama.robots`,
`roborama.environments`, `roborama.suites`, `roborama.streams`,
`roborama.keys`, `roborama.budgets`, and `roborama.data`, with the
`list()`/`get()` shapes you would expect — `roborama.runs.get("run_8842")`,
`roborama.robots.list()`, `roborama.environments.list(cls="kitchen")`.
`roborama.quote()` and `roborama.usage()` sit at module level, like the
primitives.

## Policies

Three constructors, each with a declared contract:
`roborama.Policy.container(image, action_space, observation_contract)` for
images we run GPU-adjacent in the facility;
`roborama.Policy.checkpoint(hf="acme/skill-v4", runtime="openpi")` for known
runtimes (`openpi`, `lerobot`); and `roborama.Policy.endpoint(url,
latency_budget_ms=80, fallback="halt")` for inference you host, with measured
round-trip time logged per step. Contracts are validated before any motor
moves — see [policies](/docs/concepts/policies/). Everything the run
primitive offers, in one call:

*Example: full-fat run*

**Python**

```python
import roborama  # reads ROBORAMA_API_KEY from the environment

run = roborama.run(
    robot="g1-edu-pro@fw2.3",            # embodiment @ pinned firmware
    environment="kitchen-std@v1.2",      # versioned catalogue scene
    policy=roborama.Policy.container(    # see policy packaging
        image="ghcr.io/acme/skill:v4",
        action_space="joint_delta_50hz",
        observation_contract="droid-3cam",
    ),
    task="load_dishwasher@v2",
    episodes="auto(ci=0.95, moe=0.03)",  # size n for ±3% at 95% — or an int
    perturbation={                       # the schedule IS the product spec
        "layout_jitter_mm": 25,
        "lighting": ["3000K", "5600K"],
        "distractors": "set-B",
        "seed": 42,
    },
    data={"retention": "30d", "train_on_failures": False},  # IP posture
    max_budget_usd=4_000,                # hard stop, metered live
)

print(run.result())
#  n=612  success_rate=0.874  ci95=(0.846, 0.898)
#  failure_clusters: [grasp_slip: 41, perception_miss: 22, collision: 9]
#  robot_hours=20.4  environment_hours=20.4  cost_usd=3_812
#  artifacts: mcap[], video[], ground_truth[], report_pdf
```

**TypeScript**

```typescript
import Roborama from "@roborama/sdk"; // reads ROBORAMA_API_KEY

const roborama = new Roborama();

const run = await roborama.runs.create({
  robot: "g1-edu-pro@fw2.3", // embodiment @ pinned firmware
  environment: "kitchen-std@v1.2", // versioned catalogue scene
  policy: {
    type: "container",
    image: "ghcr.io/acme/skill:v4",
    action_space: "joint_delta_50hz",
    observation_contract: "droid-3cam",
  },
  task: "load_dishwasher@v2",
  episodes: "auto(ci=0.95, moe=0.03)", // size n for ±3% at 95%
  perturbation: {
    layout_jitter_mm: 25,
    lighting: ["3000K", "5600K"],
    distractors: "set-B",
    seed: 42,
  },
  data: { retention: "30d", train_on_failures: false },
  max_budget_usd: 4000, // hard stop, metered live
});

const result = await run.result();
console.log(result);
// { n: 612, success_rate: 0.874, ci95: [0.846, 0.898], cost_usd: 3812 }
```

**cURL**

```bash
curl https://api.roborama.com/v1/runs \
  -H "Authorization: Bearer $ROBORAMA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "run",
    "robot": "g1-edu-pro@fw2.3",
    "environment": "kitchen-std@v1.2",
    "policy": {
      "type": "container",
      "image": "ghcr.io/acme/skill:v4",
      "action_space": "joint_delta_50hz",
      "observation_contract": "droid-3cam"
    },
    "task": "load_dishwasher@v2",
    "episodes": "auto(ci=0.95, moe=0.03)",
    "perturbation": {
      "layout_jitter_mm": 25,
      "lighting": ["3000K", "5600K"],
      "distractors": "set-B",
      "seed": 42
    },
    "data": { "retention": "30d", "train_on_failures": false },
    "max_budget_usd": 4000
  }'
```

**Agent (tool-use payload)**

```json
{
  "type": "tool_use",
  "name": "roborama_run",
  "input": {
    "robot": "g1-edu-pro@fw2.3",
    "environment": "kitchen-std@v1.2",
    "policy": {
      "type": "container",
      "image": "ghcr.io/acme/skill:v4",
      "action_space": "joint_delta_50hz",
      "observation_contract": "droid-3cam"
    },
    "task": "load_dishwasher@v2",
    "episodes": "auto(ci=0.95, moe=0.03)",
    "perturbation": {
      "layout_jitter_mm": 25,
      "lighting": ["3000K", "5600K"],
      "distractors": "set-B",
      "seed": 42
    },
    "data": { "retention": "30d", "train_on_failures": false },
    "max_budget_usd": 4000
  }
}
```

## Results

| Accessor | What it returns |
| --- | --- |
| `run.result()` | `n`, `success_rate`, `ci95`, `failure_clusters`, `robot_hours`, `environment_hours`, `cost_usd` |
| `run.episodes[i]` | one episode: `video_url`, `mcap_url`, `replay_spec` |
| `run.watch()` | live iterator — episode ticker plus WebRTC stream URLs |
| `run.export(format="lerobot")` | dataset export; also `"rlds"` and `"mcap-bundle"` |
| `run.report(format="pdf")` | the verification report, citable |

`result()` blocks until the run completes; `watch()` is how you avoid
blocking. There is no accessor that returns a success rate without `n` and
`ci95` attached, and that is a feature.

## Errors

`roborama.RoboramaError` is the base class. Every code in
[the error reference](/docs/errors/) maps to a subclass — `budget_exceeded`
raises `roborama.BudgetExceeded` — and every instance carries the code as
`err.code`, so you can catch narrowly and log exactly:

```python
import roborama

try:
    run = roborama.run(
        robot="g1-edu-pro@fw2.3",
        environment="kitchen-std@v1.2",
        task="pick_place@v1",
        episodes=600,
        max_budget_usd=500,
    )
    print(run.result())
except roborama.BudgetExceeded as err:
    print(err.code, err.run_id)   # budget_exceeded run_8907
    partial = err.partial_result  # kept: n, success_rate, ci95 for what ran
    print(partial.n, partial.success_rate, partial.ci95)
```

## Versioning

SDK releases ship with the control plane, so the client and the API never
drift. The surface documented on this page is the contract; anything the SDK
does beyond it is convenience, not behavior to build against.
