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.1a2, 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.1a2
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.1a2'
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.1a2. With the viewer extra, it is └── bsln[viz] v0.2.1a2.
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.1a2stops withNo 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 runsource .venv/bin/activate.
Command line only. If you use only the bsln commands and no Python scripts, you can install bsln as a uv tool instead.
The bsln command then works in every terminal, without a project folder or activation. Give the version number here too:
uv tool install --python 3.12 bsln==0.2.1a2
For the viewer extra, install the tool with the extra:
uv tool install --python 3.12 'bsln[viz]==0.2.1a2'
uv tool list shows the installed version, bsln v0.2.1a2. To change to a newer release, run uv tool install again with its version number.
If your terminal says bsln: command not found after the install, run uv tool update-shell and open a new terminal.
The tool cannot run the Python script of step 4. For scripts, use the project.
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. CheckBSLN_TOKEN, or runbsln loginagain.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. Thefaultcolumn 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:
- It reads the station description: the joint names, the units, the safe range of each joint and the control rate.
- It starts a session. Only you can control the station during your session.
- It sends one target with
set_joints. The station keeps this target until you send a new one. - It streams targets with
send_targetsfor 3 s. A streamed target expires after 0.25 s. - 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 withuv add 'bsln[viz]==0.2.1a2', or withuv tool install --python 3.12 'bsln[viz]==0.2.1a2'for the uv tool.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 getsSessionError: session ended: safety_abort after ... steps. - The station gets the status
fault. A new session getsHTTP 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.mdexplains every command. It also showsbsln session control, a local page with sliders that moves the arms.SDK.mdexplains 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.1a2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bsln-0.2.1a2-py3-none-any.whl | Python 3 | none | any | Details |
Release files / bsln-0.2.1a2-py3-none-any.whl
| Download URL | bsln-0.2.1a2-py3-none-any.whl |
|---|---|
| Size | 98.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
51865478df3352cc06a108b7f10b5ea03759988b77b069b88dd3e472a33614e1
|
|
BLAKE2b-256 checksum How to use checksums |
15fe53dfc9a6d7fc1cb3d1cc8c4df7ef35df233c05802ffc10e5a4fa7c9a0ef9
|
| 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.1a2
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.