Show the code in these languages

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
ArgumentTypeWhat it does
textstringbig text in the middle
titlestringsmall text at the top, in the colour
notestringsmall text at the bottom
colorstringthe 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
backgroundstringthe card's background: #rrggbb or a name; default dark blue
progressnumber0..1: a progress bar
imagestringa JPEG or PNG, base64 (instead of a card; fitted to 320x240)
clearbooleantake the card or picture away
curl
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/show" \
  -H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
  -d '{"title":"Build","text":"All tests pass","color":"green","progress":1}'
Python
print(act("show", title="Build", text="All tests pass", color="green", progress=1))
JavaScript
console.log(await act("show", { title: "Build", text: "All tests pass", color: "green", progress: 1 }));
Go
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).

The arguments of say
ArgumentTypeWhat it does
textstring, requiredat most 80 characters
secondsnumberhow long (default 6, at most 30)
curl
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/say" \
  -H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
  -d '{"text":"Hi!"}'
Python
print(act("say", text="Hi!"))
JavaScript
console.log(await act("say", { text: "Hi!" }));
Go
var out map[string]any
err := robot.Do(ctx, "say", map[string]any{"text": "Hi!"}, &out)
MCP
# ask your agent:
#   Say hi on the robot.
robot_say {"text": "Hi!"}

face

The robot's face: an emotion; or its own screen under cards: the face or its readings (its sensors live).

The arguments of face
ArgumentTypeWhat it does
emotionstring: neutral, happy, angry, sad, doubt, sleepythe face's emotion
viewstring: face, readingsthe robot's own screen
curl
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/face" \
  -H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
  -d '{"emotion":"happy"}'
Python
print(act("face", emotion="happy"))
JavaScript
console.log(await act("face", { emotion: "happy" }));
Go
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"}

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.

The arguments of move
ArgumentTypeWhat it does
gesturestring: nod, shake, homea gesture
yawnumberdegrees, -128..128, + to the robot's left
pitchnumberdegrees, 5..85, up
curl
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/move" \
  -H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
  -d '{"gesture":"nod"}'
Python
print(act("move", gesture="nod"))
JavaScript
console.log(await act("move", { gesture: "nod" }));
Go
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
ArgumentTypeWhat it does
colorstring#rrggbb or a name; all LEDs, or the bar's / effect's colour
barnumber0..1: light both strips from the screen end, this far
pixelsarray12 colours: left strip 0-5 from the screen end back, right strip 6-11 from the back to the screen end
effectstring: rainbow, breathe, chase, blink, offan animation on the robot
speednumberthe effect's speed, 0.2..5 (default 1)
secondsnumberthe effect's length (0 = until the next leds)
offbooleanall off
curl
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/leds" \
  -H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
  -d '{"bar":0.5,"color":"green"}'
Python
print(act("leds", bar=0.5, color="green"))
JavaScript
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
ArgumentTypeWhat it does
chimestring: ask, break_over, chirp, done, error, hello, lullaby, morning, munch, no, start, tada, whee, yawn, yesa ready-made sound
notesstringa melody: note:ms ..., up to 30 s
bellbooleanthe notes struck like a bell
volumenumberthe speaker's volume first, 0..100
curl
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/sound" \
  -H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
  -d '{"chime":"tada"}'
Python
print(act("sound", chime="tada"))
JavaScript
console.log(await act("sound", { chime: "tada" }));
Go
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.

The arguments of ask
ArgumentTypeWhat it does
questionstring, requiredshort: it is shown big
answersarray, required2-4 short answers
timeout_snumberhow long to wait (default 30, at most 120)
curl
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/ask" \
  -H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
  -d '{"question":"Deploy now?","answers":["Yes","Not yet"]}'
Python
print(act("ask", question="Deploy now?", answers=["Yes", "Not yet"]))
JavaScript
console.log(await act("ask", { question: "Deploy now?", answers: ["Yes", "Not yet"] }));
Go
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"]}

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
ArgumentTypeWhat it does
curl
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/menu" \
  -H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
  -d '{"title":"Snack?","items":["Apple","Cake","Carrot","Milk"]}'
Python
print(act("menu", title="Snack?", items=["Apple", "Cake", "Carrot", "Milk"]))
JavaScript
console.log(await act("menu", { title: "Snack?", items: ["Apple", "Cake", "Carrot", "Milk"] }));
Go
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
ArgumentTypeWhat it does
imagestringa JPEG or PNG, base64, shown first (fitted to 320x240); without it the regions lie over what the screen shows now
regionsarray, required1-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
timeout_snumberhow long to wait (default 30, at most 120)
curl
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/clickmap" \
  -H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
  -d '{"regions":[{"id":"left","rect":[0,0,160,240]},{"id":"right","rect":[160,0,160,240]}]}'
Python
print(act("clickmap", regions=[{"id": "left", "rect": [0, 0, 160, 240]}, {"id": "right", "rect": [160, 0, 160, 240]}]))
JavaScript
console.log(await act("clickmap", { regions: [{ id: "left", rect: [0, 0, 160, 240] }, { id: "right", rect: [160, 0, 160, 240] }] }));
Go
var out map[string]any
err := robot.Do(ctx, "clickmap", map[string]any{"regions": []any{map[string]any{"id": "left", "rect": []any{0, 0, 160, 240}}, map[string]any{"id": "right", "rect": []any{160, 0, 160, 240}}}}, &out)
MCP
# 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
ArgumentTypeWhat it does
afternumberonly events after this seq (0: the last ones kept)
namesarrayonly these events
secondsnumberwait this long for one (default 0, at most 60)
curl
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/events" \
  -H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
  -d '{"after":0,"seconds":30}'
Python
print(act("events", after=0, seconds=30))
JavaScript
console.log(await act("events", { after: 0, seconds: 30 }));
Go
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 view readings).

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.

No arguments.

curl
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/state" \
  -H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
  -d '{}'
Python
print(act("state"))
JavaScript
console.log(await act("state"));
Go
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.

The arguments of command
ArgumentTypeWhat it does
commandstring, requirede.g. sticker, brightness, ir_send
argsobjectthe command's arguments
curl
curl -sS -X POST "$SDEV_URL/v1/robots/$SDEV_ROBOT/command" \
  -H "Authorization: Bearer $SDEV_KEY" -H "Content-Type: application/json" \
  -d '{"command":"sticker","args":{"name":"heart","seconds":5}}'
Python
print(act("command", command="sticker", args={"name": "heart", "seconds": 5}))
JavaScript
console.log(await act("command", { command: "sticker", args: { name: "heart", seconds: 5 } }));
Go
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}}