Results show as you type: arrow keys move through them, Enter opens one, Escape closes them. The slash key brings you back here.
The API by part
Every action Dev offers, by the part of the robot it uses: what it does, its arguments, and one call in each language. Then what the robot senses: the events people make at it, and its readings.
Calling an action
Every action is POST $SDEV_URL/v1/robots/$SDEV_ROBOT/<action> with its arguments as a JSON object (all optional unless marked required) and your key in Authorization: Bearer sdev_… (a new key on Your robots). The same action is the MCP tool robot_<action> at $SDEV_URL/mcp, for AI agents. The calls below use these helpers:
curl
# every action: POST $SDEV_URL/v1/robots/$SDEV_ROBOT/<action> with its arguments as JSON
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/show" \
-H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
-d '{"text":"Hello"}'
Python
# Python 3, the standard library only (as in examples/python)
import json, os, urllib.request
URL, ROBOT, KEY = os.environ["SDEV_URL"].rstrip("/"), os.environ["SDEV_ROBOT"], os.environ["SDEV_KEY"]
def act(action, **args):
"""One action on the robot: POST /v1/robots/<robot>/<action> with the arguments as JSON."""
req = urllib.request.Request(f"{URL}/v1/robots/{ROBOT}/{action}", data=json.dumps(args).encode(),
headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=180) as res:
return json.load(res)
JavaScript
// Node 18 or newer, only fetch (as in examples/js)
const { SDEV_URL, SDEV_ROBOT, SDEV_KEY } = process.env;
if (!SDEV_URL || !SDEV_ROBOT || !SDEV_KEY) {
console.error("set SDEV_URL, SDEV_ROBOT and SDEV_KEY (Dev's page shows them with a new key)");
process.exit(2);
}
// act: one action on the robot: POST /v1/robots/<robot>/<action> with the arguments as JSON.
async function act(action, args = {}) {
const res = await fetch(`${SDEV_URL.replace(/\/$/, "")}/v1/robots/${SDEV_ROBOT}/${action}`, {
method: "POST",
headers: { Authorization: `Bearer ${SDEV_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(args),
});
const out = await res.json().catch(() => ({ ok: false, error: res.statusText }));
if (!res.ok || out.ok === false) throw new Error(`${action}: ${out.error || res.status}`);
return out;
}
Go
// go get github.com/i41-eu/q41eu-dev/client
robot, err := client.FromEnv() // reads SDEV_URL, SDEV_ROBOT, SDEV_KEY
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// any action: Do(ctx, action, its arguments, &the answer); or the named methods:
// Show, Clear, Say, Face, Gesture, Look, LEDs, LEDBar, Chime, Notes, Ask, Events, State
MCP
# give an AI agent the robot (Claude Code here; any MCP client with Streamable HTTP)
claude mcp add --transport http stackchan "$SDEV_URL/mcp" --header "Authorization: Bearer $SDEV_KEY"
# every action is the tool robot_<action>, with the same arguments
A request's body is at most 2 MB (a picture as base64 up to about 1.5 MB; Dev fits it to the screen). The answer is {"ok": true, …}, or {"ok": false, "error": "…"} with the status 400 (a wrong argument), 401 (no key, or not a valid one), 404 (not this key's robot), 429 (too many: 10 actions a second per key, bursts of 30), 501 (the robot does not offer it) or 503 (the robot is offline).
Also: GET /v1/robots (the key's robot), GET /v1/robots/<robot> (its state, as state), GET /v1/robots/<robot>/events?after=&names=&seconds= (as events), GET /v1/actions (the actions and their arguments as JSON), and /v1/openapi.json: the whole API as OpenAPI 3.1, for API tools and client generators.
Screen and face
The 320×240 touch screen, with the robot's face. A program puts a card over the face (a title, big text that sizes itself, a note, a progress bar) or a picture; or a speech bubble or an emotion on the face itself. One card at a time: a new one replaces the last, and show with clear brings the face back. Taps on the screen come as events.
show
Show a card on the robot's screen (a title, big text that sizes itself, a note, a progress bar), or a picture; clear takes it away.
The arguments of show
Argument
Type
What it does
text
string
big text in the middle
title
string
small text at the top, in the colour
note
string
small text at the bottom
color
string
the text's colour (and the title's and the bar's): #rrggbb or a name (red, orange, yellow, green, cyan, blue, purple, pink, white); default white text, a blue title
background
string
the card's background: #rrggbb or a name; default dark blue
progress
number
0..1: a progress bar
image
string
a JPEG or PNG, base64 (instead of a card; fitted to 320x240)
var out map[string]any
err := robot.Do(ctx, "show", map[string]any{"title": "Build", "text": "All tests pass", "color": "green", "progress": 1}, &out)
MCP
# ask your agent:
# Show "All tests pass" on the robot, in green.
robot_show {"title": "Build", "text": "All tests pass", "color": "green", "progress": 1}
say
A speech bubble over the robot's face (needs the face: show clear first if a card is up).
var out map[string]any
err := robot.Do(ctx, "face", map[string]any{"emotion": "happy"}, &out)
MCP
# ask your agent:
# Make the robot smile.
robot_face {"emotion": "happy"}
Head
Two servos turn the head: yaw, left and right, and pitch, up and down (5 to 85 degrees; the robot clamps it). A program nods, shakes it or looks at an angle. Touches on the head come as events.
move
Move the head: a gesture (nod, shake, home = face forward), or look at an angle.
var out map[string]any
err := robot.Do(ctx, "move", map[string]any{"gesture": "nod"}, &out)
MCP
# ask your agent:
# Make the robot nod.
robot_move {"gesture": "nod"}
LEDs
Twelve LEDs, a strip of six on each side of the head. One colour, a bar (a level, from the screen end back), single LEDs, or an effect the robot animates by itself. Colours are corrected, so #7d8cff looks on the LEDs as it does on a screen.
leds
The 12 LEDs on the head (two strips of 6): one colour, a bar from the screen end back (a level), single LEDs, an effect the robot animates, or off. Colours are corrected to look on the LEDs as on a screen.
The arguments of leds
Argument
Type
What it does
color
string
#rrggbb or a name; all LEDs, or the bar's / effect's colour
bar
number
0..1: light both strips from the screen end, this far
pixels
array
12 colours: left strip 0-5 from the screen end back, right strip 6-11 from the back to the screen end
console.log(await act("leds", { bar: 0.5, color: "green" }));
Go
var out map[string]any
err := robot.Do(ctx, "leds", map[string]any{"bar": 0.5, "color": "green"}, &out)
MCP
# ask your agent:
# Light half of the robot's LEDs in green.
robot_leds {"bar": 0.5, "color": "green"}
Speaker
A small speaker. Dev makes the sound (a ready-made chime, or a melody from notes) and sends it in parts the robot can buffer; the answer says how long it plays.
sound
A sound from the speaker: a named chime, or notes like "C5:120 E5:120 G5:300" (note:ms, - is a rest).
The arguments of sound
Argument
Type
What it does
chime
string: ask, break_over, chirp, done, error, hello, lullaby, morning, munch, no, start, tada, whee, yawn, yes
var out map[string]any
err := robot.Do(ctx, "sound", map[string]any{"chime": "tada"}, &out)
MCP
# ask your agent:
# Play the tada chime on the robot.
robot_sound {"chime": "tada"}
Questions and menus
Dev's own, on the touch screen: Dev draws the picture and matches the person's tap to it, and the call waits for the tap and returns it, so a script or an agent can ask the person at the robot before it acts. A question has two to four answers as buttons; a menu two to six tiles, with labels or pictures; a clickmap any places you name (rectangles, circles, polygons) over a picture of yours or over what the screen shows. One waits at a time: a newer one ends the older, which answers replaced. Taps elsewhere change nothing (they still come as tap events).
ask
Ask the person at the robot: the question and 2-4 answers as buttons on its screen; returns the answer they tap, or timed_out.
var out map[string]any
err := robot.Do(ctx, "ask", map[string]any{"question": "Deploy now?", "answers": []string{"Yes", "Not yet"}}, &out)
MCP
# ask your agent:
# Ask me on the robot whether to deploy now.
robot_ask {"question": "Deploy now?", "answers": ["Yes", "Not yet"]}
menu
A menu on the robot's screen: 2-6 tiles with labels, or pictures and labels, under a title; returns the tile the person taps, or timed_out.
The arguments of menu
Argument
Type
What it does
title
string
small text at the top
items
array, required
2-6 tiles: each a label ("Apple"), or {"id", "label", "image"} with a picture (JPEG or PNG, base64) above its label; the answer's item is the id, else the label
var out map[string]any
err := robot.Do(ctx, "menu", map[string]any{"title": "Snack?", "items": []string{"Apple", "Cake", "Carrot", "Milk"}}, &out)
MCP
# ask your agent:
# Ask me on the robot which snack I want: apple, cake, carrot or milk.
robot_menu {"title": "Snack?", "items": ["Apple", "Cake", "Carrot", "Milk"]}
clickmap
Places to tap on the screen, over a picture or over what it shows: returns the first tap inside one (its id, x and y), or timed_out. The screen stays as it is.
The arguments of clickmap
Argument
Type
What it does
image
string
a JPEG or PNG, base64, shown first (fitted to 320x240); without it the regions lie over what the screen shows now
regions
array, required
1-32 places in the screen's pixels (320x240): {"id", "rect": [x, y, w, h]}, {"id", "circle": [x, y, r]} or {"id", "polygon": [[x, y], ...]} (3-32 points); where they overlap, the first listed counts
# ask your agent:
# Wait until I tap the left or the right half of the robot's screen, and tell me which.
robot_clickmap {"regions": [{"id": "left", "rect": [0, 0, 160, 240]}, {"id": "right", "rect": [160, 0, 160, 240]}]}
What people do
The robot reports only raw input (presses, releases with their length, swipes, tags); Dev turns it into what a person did, the same for every program. Each event has a seq: ask for the ones after the last you saw, waiting up to a minute for the next one.
events
What people did at the robot (tap, long_press, pat, pet, stroke, shaken, picked_up, near, away, tag, tag_removed, remote, button, plugged, unplugged), after a sequence number; waits up to seconds for the next one.
The arguments of events
Argument
Type
What it does
after
number
only events after this seq (0: the last ones kept)
var out map[string]any
err := robot.Do(ctx, "events", map[string]any{"after": 0, "seconds": 30}, &out)
MCP
# ask your agent:
# Wait up to 30 s for someone to touch the robot, and tell me what they did.
robot_events {"after": 0, "seconds": 30}
The events
Touch screen
tap
a short touch on the screen: data x, y (screen pixels, 320x240), and sprite when it hit a tappable picture
long_press
a long touch on the screen: data x, y
Head touch
pat
a short touch on the head (under 0.7 s): data ms
pet
a longer touch on the head: data seconds
stroke
a finger along the head: data direction forward or back
Motion
shaken
the robot was shaken
picked_up
the robot was picked up
Light and proximity
near
something came close to the front (a hand, a face)
away
it went away again
NFC
tag
an NFC tag or card held near the head: data uid, type, text (an NTAG sticker's link or text)
tag_removed
the tag was taken away: data uid
Infrared
remote
an infrared remote's button: data address, command (NEC) or raw
Power
button
the power button: data press short or long
plugged
a USB cable plugged in
unplugged
the USB cable taken out
The connection
connected
the robot connected to Dev: at its start, after a restart, or back after it was away
State and readings
The robot reports its sensors every two seconds: the battery and charging, the room light and proximity, the head's angles and its servos' load and temperature, motion and the compass, the clock, Wi-Fi and memory. state returns the latest value of each, the units in their names (battery_pct, light_lux, head_yaw_deg, …), with whether the robot is online, its model, firmware, its own commands and its last events. The robot shows them on its own readings screen too (face with viewreadings).
state
Read the robot's state: online or not, model, firmware, its commands, its latest readings (units in their names: battery_pct, light_lux, head_yaw_deg, …) and its last events.
var out map[string]any
err := robot.Do(ctx, "state", map[string]any{}, &out)
MCP
# ask your agent:
# How is the robot: is it online, and how full is its battery?
robot_state {}
Any robot command
For what the actions above do not cover yet: any of the robot's own commands, as the Raw data app sends them (the q41eu-raw readme lists them: stickers, brightness, the screen off, infrared, the red power LED, …). Not those that switch the robot's apps, restart it or turn its streams on; their answers and events are not passed on.
command
Any of the robot's own commands, as the Raw data app sends them (the q41eu-raw readme lists them), for what the other actions do not cover. Its apps and its manager's settings are not reachable here.
var out map[string]any
err := robot.Do(ctx, "command", map[string]any{"command": "sticker", "args": map[string]any{"name": "heart", "seconds": 5}}, &out)
MCP
# ask your agent:
# Put a heart sticker on the robot's face for five seconds.
robot_command {"command": "sticker", "args": {"name": "heart", "seconds": 5}}