Skip to main content
Yanked

This release has been yanked by its maintainers, and will be ignored by installers, except when explicitly specified.
Consider using release 0.0.1a7 instead.

bsln

Python SDK and command line for Baseline Robotics robot stations. Drive a real robot station from your own machine: stream joint targets from your script, get a report of each session and download the recordings.

This package contains only the client. The server and the station software are not part of it.

This guide shows you how to control a Baseline Robotics robot station from your computer with Python. You install the bsln client, connect with your token and run a short script on a bimanual YAM station. The script turns one wrist joint by 0.1 rad and then turns it back. 0.1 rad is about 6 degrees.

This guide is for bsln 0.2.1a1, an alpha version. Baseline Robotics also gives you two more guides: CLI.md explains every command, and SDK.md explains the Python SDK and how to drive a station from your own policy.

1. Install

You install bsln with uv, the Python package manager from Astral. uv also installs the correct Python version for you. If you do not have uv yet, install it:

curl -LsSf https://astral.sh/uv/install.sh | sh

For Windows and for the other ways to install uv, see https://docs.astral.sh/uv/getting-started/installation/.

Make a project folder for your scripts, with Python 3.12:

uv init --python 3.12 bsln-quickstart
cd bsln-quickstart

Add bsln to the project. bsln is an alpha version, so give its version number:

uv add bsln==0.2.1a1

To watch your sessions live in 3D, also add the viewer extra. It adds the Rerun viewer, which is a large download:

uv add 'bsln[viz]==0.2.1a1'

Activate the project environment. Then the bsln command and python use this project. Do this in each new terminal, in the project folder:

source .venv/bin/activate

Make sure that you have the correct version:

uv tree --depth 1

If it works, the last line is └── bsln v0.2.1a1. With the viewer extra, it is └── bsln[viz] v0.2.1a1.

If it fails:

  • uv: command not found: open a new terminal, so that it finds uv. If it still fails, install uv again.
  • uv add bsln==0.2.1a1 stops with No solution found when resolving dependencies: the project allows a Python older than 3.10. Make the project again with --python 3.12.
  • bsln: command not found: the project environment is not active. Go to the project folder and run source .venv/bin/activate.

2. Get access

Baseline Robotics gives you a bsln token. The token starts with bsln_. Keep the token secret. Do not put it in your code or in a Git repository.

Connect in one of these two ways.

Way A: log in once. The command asks for your token. It does not show the token while you type. It saves the URL and the token in ~/.config/bsln/config.json. Only your user can read this file.

bsln login --url https://api.baseline-robotics.com

Always give --url. Without it, bsln login uses http://localhost:8000.

If it works, you see this line:

logged in as <your name> (control, read, write) - saved to /Users/you/.config/bsln/config.json

Way B: environment variables. Set them in each new terminal. They win over the values that bsln login saved.

export BSLN_URL=https://api.baseline-robotics.com
export BSLN_TOKEN=<your token>

Make sure that the connection works:

bsln whoami

If it works, you see your user name and your scopes:

{
  "user_id": "usr_7ca6246ae264090c",
  "name": "customer",
  "scopes": [
    "control",
    "read",
    "write"
  ],
  "is_admin": false
}

The scopes decide what you can do. read lets you list, watch and download. write lets you upload. control lets you move a robot.

If it fails:

  • error 401: missing or invalid token: the token is wrong, has expired or is not set. Check BSLN_TOKEN, or run bsln login again.
  • cannot reach the bsln server at ...: check the URL and your network. A company VPN or proxy can block the connection.

Extra tokens. You can make more tokens for other computers or scripts. This command makes a token that is valid for 30 days. It shows the token one time only, so keep it safe.

python -c "from bsln.sdk import Client; print(Client().create_token(['read', 'write', 'control'], name='second-computer', ttl_days=30)['token'])"

An extra token cannot have more scopes than your token. It expires before your token or at the same time. To stop a token before it expires, contact Baseline Robotics.

3. Find a station

bsln stations

If it works, you see a table with one row for each station:

┏━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ id     ┃ robot_type   ┃ status ┃ fault ┃ cameras          ┃ inventory                      ┃ agent_version ┃
┡━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ yam-01 │ yam_bimanual │ idle   │       │ top, left, right │ ball_tennis, bowl_white,       │ 0.2.0         │
│        │              │        │       │                  │ cube_blue_3cm, cube_red_3cm,   │               │
│        │              │        │       │                  │ tray_black                     │               │
└────────┴──────────────┴────────┴───────┴──────────────────┴────────────────────────────────┴───────────────┘

Use the id in all other commands. inventory lists the objects at the station.

You can start a session only on a station with the status idle. The other status values are:

  • busy: another session or run uses the station.
  • fault: the station stopped after an e-stop or a safety fault. The fault column shows why.
  • offline: the station is not connected.

4. Run your first session

Save this script as quickstart.py.

"""bsln quickstart: start a session, turn the left wrist a little and back, then end the session.

    export BSLN_URL=https://api.baseline-robotics.com
    export BSLN_TOKEN=<your bsln token>
    python quickstart.py <station id>
"""

import sys
import time

from bsln.sdk import Client

STATION = sys.argv[1]
WRIST = "left_joint6"  # the left wrist. Unit: rad.
TURN = 0.1  # rad. This is about 6 degrees.

client = Client()  # reads BSLN_URL and BSLN_TOKEN, or the values that `bsln login` saved
station = client.station_description(STATION)
print("joints:", station["joint_order"])
print("control rate:", station["control_hz"], "Hz")
high = next(joint["safe_range"][1] for joint in station["joints"] if joint["name"] == WRIST)

with client.start_session(STATION) as session:
    print("session:", session.id)
    start = session.get_state()["target"][WRIST]  # the station holds the wrist here now
    turn = TURN if start + TURN <= high else -TURN  # stay inside the safe range

    # 1. Send one target. The station keeps it. The safety supervisor limits the speed.
    session.set_joints({WRIST: start + turn})
    time.sleep(2.0)

    # 2. Stream targets at the control rate for 3 s. The wrist turns back to its start position in 2 s.
    #    A streamed target expires after 0.25 s. Then the arm holds its position.
    period = 1.0 / station["control_hz"]
    begin = time.monotonic()
    while (elapsed := time.monotonic() - begin) < 3.0:
        done = min(1.0, elapsed / 2.0)  # 0 at the start, 1 after 2 s
        session.send_targets({WRIST: start + turn * (1.0 - done)})
        time.sleep(period)

    print("wrist now:", round(session.get_joints()[WRIST], 3), "rad")
    usage = session.end()  # end the session. The arms hold their position.

if usage:  # what the session did, as the server counted it
    print("messages:", usage["messages"], "dropped:", usage["dropped"], "deadman holds:", usage["deadman_holds"])
print("report: bsln session report", session.id)

Run the script. Replace <station id> with an id from bsln stations.

python quickstart.py <station id>

If it works, the left wrist turns by about 6 degrees and back, and the script prints this:

joints: ['left_joint1', 'left_joint2', 'left_joint3', 'left_joint4', 'left_joint5', 'left_joint6', 'left_gripper', 'right_joint1', 'right_joint2', 'right_joint3', 'right_joint4', 'right_joint5', 'right_joint6', 'right_gripper']
control rate: 30.0 Hz
session: ses_21f06bf1d78bb309
wrist now: 0.0 rad
messages: 83 dropped: 0 deadman holds: 0
report: bsln session report ses_21f06bf1d78bb309

The script does these steps:

  1. It reads the station description: the joint names, the units, the safe range of each joint and the control rate.
  2. It starts a session. Only you can control the station during your session.
  3. It sends one target with set_joints. The station keeps this target until you send a new one.
  4. It streams targets with send_targets for 3 s. A streamed target expires after 0.25 s.
  5. It ends the session. The arms hold their position.

Write down the session ID. You need it for the report and for the data.

If it fails, the script stops with a Python traceback. The last line tells you why:

Last line of the traceback What it means What to do
BslnError: HTTP 404: station not found The station ID is wrong. Copy the id from bsln stations.
BslnError: HTTP 409: station is busy Another session uses the station. Wait, or use another station with the status idle.
BslnError: HTTP 409: station is locked by a safety fault The station stopped after an e-stop or a fault. Contact Baseline Robotics.
BslnError: HTTP 403: token lacks the 'control' scope Your token can read, but it cannot move a robot. Use a token with the control scope.
BslnError: HTTP 401: missing or invalid token The token is wrong or has expired. Check BSLN_TOKEN, or run bsln login again.
httpx.ConnectError: ... Your computer cannot reach the server. Check BSLN_URL and your network.

When the script stops with an error inside the with block, the session ends and the arms hold their position.

5. Watch a session live

You can watch a session from a second terminal while your script drives the station. The watcher only reads. It cannot move the robot, and it does not keep a session alive.

The live view needs the viewer extra from step 1. Find the session ID with bsln session ls. Then run:

bsln session watch <session id> --view

If it works, a Rerun window opens. It shows the station in 3D, a plot for each joint and the camera images. The terminal shows the state of the session and its events. The command ends when the session ends:

session ses_67ca333e573f89d7 active
  episode.end
session ended: stopped after 336 steps
the recording: bsln session download ses_67ca333e573f89d7

The quickstart script runs for only about 6 s. Your own scripts usually run longer. A computer without a display can save the stream to a file. Open the file later with rerun session.rrd.

bsln session watch <session id> --save session.rrd

If it fails:

  • this needs the optional dependency 'rerun': add the viewer extra with uv add 'bsln[viz]==0.2.1a1'.
  • session ... has ended ..., so there is nothing to watch.: the session is over. Download its recording, as step 8 shows.

6. Stop a session

End the session in your script with session.end(). The with block also ends the session when your script leaves it, for example after an error. From a terminal, use this command:

bsln session stop <session id>

To find the ID of a session, list your sessions:

bsln session ls

If it works, you see each session with its state and the reason why it ended:

┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ id                   ┃ station_id ┃ state ┃ created_at                 ┃ end_reason                        ┃
┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ ses_9c73890697daf597 │ yam-01     │ ended │ 2026-10-15T09:11:50.401412 │ operator disconnected for more    │
│                      │            │       │                            │ than 30 s (after 1041 steps)      │
│ ses_21f06bf1d78bb309 │ yam-01     │ ended │ 2026-10-15T09:06:41.110187 │ stopped after 152 steps           │
└──────────────────────┴────────────┴───────┴────────────────────────────┴───────────────────────────────────┘

If your script stops or loses the connection, the station holds the arms where they are. The session stays open for 30 s. In this time, your script can connect again with session.reconnect(). After 30 s without a connection, bsln ends the session with the reason operator disconnected for more than 30 s. Until then the station stays busy. To free it sooner, stop the session yourself with bsln session stop.

7. Emergency stop

Use the emergency stop, or e-stop, when the robot does something that you do not expect. You can send it at any time. In your script, call session.estop(). From a terminal, use one of these commands:

bsln estop <station id>
bsln session estop <session id>

If it works, bsln estop shows a line that starts with E-STOP sent, and bsln session estop shows {"sent": true}.

After an e-stop:

  • The arms stop and hold their position. The motors stay on.
  • Your session ends with the reason safety_abort. Your script gets SessionError: session ended: safety_abort after ... steps.
  • The station gets the status fault. A new session gets HTTP 409: station is locked by a safety fault.
  • Only Baseline Robotics can clear the fault. Contact us as step 10 explains.

If it fails: E-STOP could not be delivered means that the station is offline, so the e-stop cannot reach it. Contact Baseline Robotics at once.

8. Get the report and your data

The report is ready after the session ends. It shows the commands that you sent, the holds, the safety adjustments, the faults, the e-stops and the connection quality.

bsln session report <session id>

If it works, you see a JSON document. These fields are the most important:

Field What it tells you
end_reason Why the session ended: a code, who ended it in by, and a detail text.
commands How many messages you sent, and how many bsln dropped or refused.
holds How often the deadman held the arms, and for how long.
safety How often the safety supervisor changed your targets, for each joint, and why.
faults and estops The faults and e-stops during the session.
connection and rtt_ms Disconnects, and the round-trip time between your script and the server.
recording The number of episodes, the size and the steps of the recording.

While the session still runs, the command fails with error 409: the session is active: its report is ready when it has ended.

This command downloads the recorded data to the folder ./data:

bsln session download <session id> --out ./data

If it works, you see saved to data/<session id>. The folder contains:

data/ses_21f06bf1d78bb309/
├── report.json                          the session report
└── 000_ses_21f06bf1d78bb309_ep000/      one folder for each episode
    ├── ses_21f06bf1d78bb309_ep000.rrd   the Rerun recording. Open it with: rerun <file>
    ├── steps.parquet                    one row for each control step
    ├── cameras/top/000000.jpg ...       the camera images, 10 for each second
    ├── meta.json                        the station, the robot and the joint limits
    └── result.json                      the outcome, the number of steps and the safety counts

In steps.parquet, obs.pos.<joint>, obs.vel.<joint> and obs.eff.<joint> are the measured position, velocity and effort. action.raw.<joint> is the target that you sent. action.safe.<joint> is the target after the safety supervisor.

9. Limits

Item Value
Joints 14: left_joint1 to left_joint6, left_gripper, right_joint1 to right_joint6, right_gripper
Units Arm joints: rad. Do not send degrees. Grippers: 0 is closed, 1 is open.
Control rate 30 Hz. The station runs one control step every 1/30 s.
Messages 60 messages per second at most. bsln drops more messages and sends you a notice.
Deadman 0.25 s. If no new streamed target arrives in this time, the arm holds its position.
Safety supervisor It moves each target into the safe range and limits its speed. It never makes a target larger or faster.
Session start Joints 2 and 3 and the grippers can move a little into the safe range: 0.02 rad or 3 % at most.
Session length 1 hour at most. Then the station ends the session with the reason time_limit.
Lost connection After 30 s without a connection, bsln ends the session.
Faults A fault stops the station. It stays stopped until Baseline Robotics clears the fault.
E-stop Always permitted.

The station description gives the exact values for each station: client.station_description("<station id>").

10. Get help

Contact Baseline Robotics. Tell us:

  • the station ID and the session ID,
  • the time of the problem and your time zone,
  • the full error message.

Each error message from bsln includes a request ID, for example error 404: session not found (request ID 1023e596c15f446680304ccff00d8afc). Include this request ID. With it, we can find your request in our logs.

11. Next steps

  • CLI.md explains every command. It also shows bsln session control, a local page with sliders that moves the arms.
  • SDK.md explains the Python SDK. It shows how to drive a station from your own policy with action chunks.

Copyright 2026 Baseline Robotics AG. License: Apache-2.0 (see LICENSE and NOTICE).

Metadata

Release files for bsln 0.2.1a1

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

Built distribution (wheel)

Table of built distributions (wheels) for bsln 0.2.1a1
File Interpreter ABI Platform
bsln-0.2.1a1-py3-none-any.whl Python 3 none any Details

Release files / bsln-0.2.1a1-py3-none-any.whl

Download URL bsln-0.2.1a1-py3-none-any.whl
Size 97.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
92d8824071e822e6e1a2843bd2c62ea0a3492e15108902ed3a3d624a6f36dd1d
BLAKE2b-256 checksum
How to use checksums
70394aecc435ad4682cc18bc033488cb16934e881e85e5e1d557d419def803eb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for bsln 0.2.1a1

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.

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