What this guide is

This guide takes you from nothing to a Furhat robot that holds a real spoken conversation. It covers the Furhat Realtime API, the WebSocket API shown in the Furhat demo video. The API streams audio, video, and robot commands back and forth in real time between the robot and your Python code.

Whoever reads this page gets: the full setup, the starter code files, and an in-depth explanation of all five workflows from the video (connecting, LLM dialogue with lip-sync, local models, computer vision, and telepresence).

Honest note: the Python API calls in this guide were verified against the official furhat-realtime-api package (version 0.1.3). Anything that could not be verified is marked UNVERIFIED. Do not build on unverified items until you confirm them on the real robot.
Intro slide: Furhat Realtime API is a WebSocket API for any programming language, with two-way asynchronous real-time streaming
The Realtime API as introduced in the demo video: a WebSocket API for any language, with two-way real-time streaming.

What you need before you start

  1. The Furhat SDK installed and running. It includes a free virtual Furhat robot. (Your Furhat guide covers the install.)
  2. Python 3.10 or newer. Check with python --version in a terminal.
  3. An OpenAI API key, only needed for the chatbot script (02_chatbot.py).
  4. Realtime API access. The Realtime API is part of the FurhatAI subscription, so the lab's physical Furhat needs that enabled. The virtual robot in the SDK works for development.

Setup, step by step (do this once)

Open a terminal in the folder that holds the starter files (the files folder linked at the bottom of this page), then follow each step in order.

Step 1: Create a virtual environment.
This gives the project its own private copy of Python packages, so it never clashes with other projects on your computer.
python -m venv venv
Step 2: Activate the virtual environment.
On Windows:
.\venv\Scripts\activate
On Mac or Linux:
source venv/bin/activate
You will see (venv) appear at the start of your terminal line. That means it worked.
Step 3: Install the packages.
pip install -r requirements.txt
This installs three things: furhat-realtime-api (the official Furhat client), openai (for the chatbot), and python-dotenv (for reading the .env settings file).
Step 4: Create your settings file.
On Windows:
copy files\.env.example .env
On Mac or Linux:
cp files/.env.example .env
(If you downloaded the files separately, just copy .env.example to .env in the same folder as the scripts.)
Step 5: Fill in your settings.
Open .env in any text editor. It looks like this:
OPENAI_API_KEY=put-your-key-here
FURHAT_HOST=127.0.0.1
FURHAT_AUTH_KEY=
Replace put-your-key-here with your real OpenAI key. Leave FURHAT_HOST as 127.0.0.1 while you use the virtual robot. Leave FURHAT_AUTH_KEY empty unless the robot asks for a key. Save the file.
Setup is done. You never need to repeat these five steps on this computer. Now start the Furhat SDK (the virtual robot must be running), and move to the next section.

Run it

First test:
python 01_hello_world.py
Furhat says hello, turns its neck light blue, nods, then asks you to say something and repeats it back. If this works, your connection is good.
The chatbot:
python 02_chatbot.py
Talk to Furhat. It listens, sends your words to OpenAI, and speaks the answer. Say "goodbye" when you are done.
Point at the physical robot without editing .env:
python 01_hello_world.py --host=192.168.0.52 --auth_key=mykey123
Replace the IP with the robot's real address. Drop --auth_key if the robot does not need one.

Part 1: Connecting to the robot (in depth)

The idea. The Realtime API is a WebSocket server that runs on the robot (or on the virtual robot inside the SDK). Your Python script opens a connection to it, and from then on the two sides exchange messages in real time: your script sends commands (speak, move the face, change the light), and the robot sends back events (I heard speech, here is a camera frame, the user moved).

How the connection works, step by step

1. You create a client object with the robot's address. 127.0.0.1 means "this computer", which is where the virtual robot lives. A physical robot has its own IP address on the network.
2. You call connect(). The client opens the WebSocket and shakes hands with the robot. If the robot requires authentication, the client sends the auth key you passed in.
3. You call methods like request_speak_text(...). Each method sends one JSON message over the WebSocket and, for the synchronous client, waits for the robot's answer.
4. You call disconnect() when done. Always do this, even if something crashed (the starter scripts use try/finally for exactly this reason).

The code (verified)

from furhat_realtime_api import FurhatClient

furhat = FurhatClient("127.0.0.1")   # or the robot's IP
# furhat = FurhatClient("192.168.1.50", "YOUR_AUTH_KEY")  # if a key is needed
furhat.connect()
furhat.request_speak_text("Hello, I am Furhat.")
furhat.disconnect()
Python client library slide: pip install furhat-realtime-api, FurhatClient and AsyncFurhatClient connect examples
The official onboarding from the video's closing slides: pip install, connect with the robot's IP, one call to speak.

Sync vs async client

The package ships two clients. FurhatClient is synchronous: each call blocks until the robot answers, which makes simple scripts easy to read. AsyncFurhatClient is asynchronous: calls return immediately and events arrive as they happen, which is what you want when several streams (audio in, audio out, camera) must run at the same time. Start with the sync client; move to async when you add streaming.

UNVERIFIED: the web UI address (http://<ROBOT_IP>:9000/), a Realtime API settings block, per-network auth scopes, the raw endpoint ws://<ROBOT_IP>:9000/v1/events, and the raw request.auth payload shape were not confirmed. If you ever need the raw WebSocket protocol instead of the Python client, confirm these on the real robot first.

Part 2: LLM dialogue and real-time lip-sync (in depth)

The idea. A talking robot needs three jobs done: hear the user (speech-to-text), think of an answer (the LLM), and speak it (text-to-speech). The Realtime API lets you arrange these jobs however you like, and the robot's mouth moves in sync with whatever audio comes out.

Option A: let the robot do the hearing (simplest, verified)

The robot has built-in speech recognition. request_listen_start() waits until the user finishes speaking and returns the recognized text as a plain string. Your loop is then: listen, send the text to the LLM, speak the answer.

LLM dialogue diagram: LLM, Python, and Furhat with user speech-to-text in and robot text-to-speech out
The LLM dialogue pipeline from the video: LLM, Python, and Furhat, with user speech-to-text streaming in and robot text-to-speech streaming out.
furhat.request_speak_text("What would you like to talk about?")
while True:
    user_text = furhat.request_listen_start()  # blocks, returns text
    if not user_text.strip():
        furhat.request_speak_text("Sorry, I did not catch that.")
        continue
    if user_text.lower() in {"goodbye", "bye"}:
        furhat.request_speak_text("Goodbye!")
        break
    reply = ask_llm(user_text)        # your OpenAI / local LLM call
    furhat.request_speak_text(reply)  # robot speaks, lips move automatically

You can tune the listening behavior (all verified parameters):

furhat.request_listen_config(languages=["en-US"])  # recognition language
furhat.request_listen_start(
    partial=False,           # True also returns in-progress partial results
    no_speech_timeout=8.0,   # give up after 8 seconds of silence
    end_speech_timeout=1.0,  # user is done after 1 second of silence
)

This is exactly what 02_chatbot.py does, with OpenAI as the LLM and the full conversation history passed along so the robot remembers context.

Option B: stream your own audio with lip-sync (verified)

When your text-to-speech runs outside the robot (a cloud voice or a local model), you stream the audio file and the face lip-syncs by itself. This is the mechanism behind the video's karaoke demo: any audio you send plays through the robot's speaker with only about 0.2 seconds of delay, and the mouth matches it.

Realtime lip-sync diagram: audio flows through the Realtime API to the robot head and out the speaker with 0.2 second delay
Realtime lip-sync from the video: streamed audio plays through the robot's speaker with about 0.2 seconds of delay while the face moves with it.
furhat.request_speak_audio(
    url="https://your-server/audio.wav",  # any audio URL the robot can reach
    text="transcript for context",        # optional
    lipsync=True,   # mouth moves with the audio
    abort=False,    # True cuts off whatever is currently playing
    wait=False,
)

Karaoke: one way to build it (your design, not shown in the video)

1. Split a song into vocals and instruments with Spleeter: spleeter separate -i song.mp3 -p spleeter:2stems -o out/
2. Put vocals.wav on a small web server your robot can reach.
3. Call request_speak_audio(url=<vocals URL>, lipsync=True). The face sings the vocals in real time.
4. Play the instrumental track through your own speakers at the same time.

Simpler alternative: stream the full mixed song the same way. The lip-sync still follows the vocal frequencies, and you skip the splitting step entirely.

Karaoke demo: a developer at a laptop next to the physical Furhat robot performing with synced lip movement
The karaoke demo from the video: the laptop streams audio while the physical Furhat performs with synced lip movement.

Part 3: Local models, no cloud (in depth)

The idea. Instead of sending the user's voice to cloud services, you run everything on your own computer: speech-to-text, the LLM, and text-to-speech. Nothing leaves the lab. This is exactly what the video demonstrates: the whole pipeline on a MacBook next to the robot.

The key architectural point. In the video's diagram, user speech flows from the robot to the computer. You use the robot's microphone, not your laptop's. The robot streams raw audio to your script, your script runs its models, and the answer streams back to the robot's speaker.

Local models diagram: speech-to-text, LLM, and text-to-speech run on the local computer; user speech flows from robot to computer, robot speech flows back
The local-models setup from the video: speech-to-text, LLM, and text-to-speech all run on your own computer. User speech flows from the robot to the computer; robot speech flows back.
Robot mic --(request.audio.start)--> your computer --(request.speak.audio)--> Robot speaker
                                     |- STT: faster-whisper (local)
                                     |- LLM: Ollama, e.g. Llama 3 or Mistral
                                     |- TTS: Piper or Coqui (local WAV)

Step by step

1. Stream the robot's microphone to your computer (verified events, async client):
await furhat.request_audio_start(sample_rate=16000, microphone=True, speaker=False)
# audio chunks arrive as response.audio.data events
# ...
await furhat.request_audio_stop()
2. Turn the audio into text locally. Feed the chunks into faster-whisper running on your machine. No audio ever goes to the cloud.
3. Think locally. Send the text to Ollama (ollama run llama3) and get the answer back.
4. Speak locally, play on the robot. Synthesize the answer to a WAV file with Piper or Coqui TTS, serve it over HTTP from your machine, and play it through the robot with lip-sync:
furhat.request_speak_audio(url="http://<your-pc>/reply.wav", lipsync=True)
Local models live demo: a developer converses with the physical Furhat robot while the laptop runs the pipeline
Live in the video: a full spoken conversation with the robot while the laptop on the desk runs the entire pipeline locally. No cloud involved.
Simpler hybrid (less plumbing, LLM still private): skip local speech recognition and use the robot's built-in request_listen_start() for the hearing step. Run only the LLM and TTS locally. You keep the brain on-premise with a third of the code.

Part 4: Computer vision (in depth)

The idea. The robot's camera feed streams out through the API into your Python code, where you run real vision models (the video uses YOLO object detection on a GPU). What the model sees then drives what the robot does: look at the person, react, change its light.

Computer vision diagram: YOLO in Python processes the robot's camera feed
The computer vision pipeline from the video: the robot's camera feed streams to Python, where YOLO processes it and the results drive the robot.

Step 1: get camera frames (verified, corrected)

There is no request.vision.stream event. The real API is:

frame = furhat.request_camera_once()  # one frame, returned directly

# Continuous feed (async client): frames arrive as response.camera.data
await furhat.request_camera_start()
# ... process frames as they arrive ...
await furhat.request_camera_stop()

Similarly, request_users_once() (or the request.users.start stream) gives you tracked-user data: who is in front of the robot and where they are.

Step 2: run YOLO (model name corrected)

The video's dashboard runs yolov8m.pt (YOLOv8-medium) on a CUDA GPU, with the confidence threshold set to 0.25 and classes like person, bicycle, car, and motorcycle selected.

from ultralytics import YOLO

model = YOLO("yolov8m.pt")  # same model family as the video's demo

def process_frame(frame):
    results = model(frame, conf=0.25)
    for r in results:
        for box in r.boxes:
            label = model.names[int(box.cls)]  # e.g. "person"
            x1, y1, x2, y2 = box.xyxy[0]       # where it is in the frame
Live vision dashboard: robot camera feed with detection boxes, YOLOv8 settings panel
The live vision dashboard from the video: the robot's camera feed with detection boxes, YOLOv8-medium on a GPU, and a settings panel for model, confidence, and object classes.
Not in the video: no emotion model was shown. Adding one (for example, a facial-expression classifier run on each detected face) is your own extension, and a natural one for HRI work.

Step 3: make the robot react (verified)

furhat.request_attend_user(user_id="closest")  # look at the nearest person
furhat.request_attend_location(x=0.0, y=0.0, z=1.0)  # or look at a 3D point
furhat.request_gesture_start("Nod")                  # react with a gesture
furhat.request_led_set("green")                      # signal state with the neck light

Example behavior: when a "person" box appears, call request_attend_user("closest") so the robot turns to face them, then greet them with request_speak_text(...).

Part 5: Telepresence (in depth)

The idea. A remote human operator "becomes" the robot: their voice comes out of its speaker, their facial expressions and gaze drive its face, and they see and hear the room through its camera and microphones. The video shows this working from a VR headset (C# client) and from a plain web browser (JavaScript client). Any language with WebSocket support can do it.

The four streams (as shown in the video)

DirectionStreamWhat it doesAPI piece (verified)
Operator to robotOperator speechThe operator's voice plays through the robot with lip-syncrequest.speak.audio with lipsync=True
Operator to robotFacial and gaze parametersThe operator's expressions drive the robot's face liverequest.face.params, request.face.headpose
Robot to operatorRobot microphoneThe operator hears the roomrequest.audio.start (microphone=True)
Robot to operatorCamera feedThe operator sees through the robot's eyesrequest.camera.start
Telepresence diagram: C# headset client and JS browser client stream operator speech and facial/gaze parameters to the robot
The telepresence setup from the video: a C# headset client or a JavaScript browser client streams the operator's speech and facial/gaze parameters to the robot, which streams its microphone and camera back.

Driving the face (verified client methods)

# Continuous facial parameters from the operator's face tracker
# (e.g. MediaPipe landmarks mapped to Furhat's parameter set)
furhat.request_face_params(params={...})

# Head orientation follows the operator's head
furhat.request_face_headpose(yaw=0.1, pitch=0.0, roll=0.0, relative=True)

In the video's live demo, the operator's video feed carries a face-detection box, and a second view shows the facial landmark mesh (eyes, brows) being tracked. Those landmarks are exactly what gets streamed as facial parameters.

How to build it, in order

1. On the operator's machine, capture microphone audio and webcam video of the operator.
2. Track the operator's face (MediaPipe or similar) to get expression and head-pose values each frame.
3. Open the Realtime API connection to the robot and run all four streams at once (this is where the async client earns its keep).
4. Map the tracked values to request_face_params / request_face_headpose every frame, and stream the operator's voice with request_speak_audio(..., lipsync=True).
UNVERIFIED: a ready-made telepresence folder in the examples repo was not confirmed. Start from the verified examples at github.com/FurhatRobotics/realtime-api-examples; openai_realtime.py shows the audio-bridging pattern that telepresence builds on.

The demo video

"Furhat Realtime API Demo | Gabriel Skantze" (Furhat Robotics, 12:07). This is the video all five workflows above come from. Watch it here:

Direct link: https://youtu.be/WtL7tHFHFJo

Download the starter files

Everything you need to run this guide. Click to open or download each file:

Keep your real .env private. It holds your OpenAI key. Never share it, upload it, or put it in a public folder.

Verified method reference (package v0.1.3)

MethodWhat it does
FurhatClient(host, auth_key)Create the sync client
AsyncFurhatClient(host, auth_key)Create the async client
connect() / disconnect()Open / close the connection
request_speak_text(text, wait, abort)Speak text with the robot's voice
request_speak_audio(url, text, lipsync, abort, wait)Stream audio with automatic lip-sync
request_speak_stop()Stop speaking immediately
request_listen_config(languages, phrases)Set the speech recognition language
request_listen_start(...)Listen once, returns recognized text
request_voice_config(...)Choose the voice (id, name, gender, language)
request_attend_user(user_id)Look at a user ("closest" by default)
request_attend_location(x, y, z)Look at a point in space
request_gesture_start(name, ...)Play a gesture, e.g. "Nod"
request_face_params(params)Set facial parameters directly
request_face_headpose(yaw, pitch, roll, relative)Turn the head
request_face_reset()Reset the face to neutral
request_led_set(color)Set the neck LED color
request_users_once()Get current tracked-user data
request_camera_once()Grab a single camera frame
request_audio_start(sample_rate, microphone, speaker) (async)Stream raw audio from the robot
request_camera_start() / request_camera_stop() (async)Start / stop the camera stream

Troubleshooting

"Connection refused" or it hangs on connect. The Furhat SDK is not running. Start it, wait for the virtual robot to appear, then run your script again.
"Authentication failed". The robot needs an auth key. Ask whoever set up the robot, or check the Realtime settings in the Furhat web interface, then pass it as the second argument to FurhatClient.
The chatbot gives empty replies. Your OpenAI key is missing, wrong, or out of credit. Open .env and check OPENAI_API_KEY.
Furhat does not hear you. With the virtual robot, sound comes through your computer's microphone. Check the mic works and is selected as the input device.
pip install fails. Make sure the virtual environment is activated (you see (venv) in the terminal) and that you have Python 3.10 or newer.
Camera or audio streaming does nothing. Those use the async client and event streams. Get the sync scripts working first, then move to AsyncFurhatClient.

How this maps to your trust study