For the complete documentation index, see llms.txt. This page is also available as Markdown.

Python SDK

Drive Ojin's face models (Human Presence and Human Portrait) straight from Python. Feed in your TTS audio; get back a synchronized, lip-synced talking-avatar stream.

The ojin-client SDK is the fastest way to add a real-time talking avatar to a Python app or voice agent. You bring the words (any TTS); the SDK streams back a face that speaks them (perfectly in sync) handling buffering, the playback clock, resampling, and barge-in for you.

Just want a complete agent? If you'd rather not assemble a pipeline, the Human Agent gives you a full speech-in, speech-out avatar with a single line of embed code. Already on Pipecat? Jump to the Pipecat integration.

Requirements

  • Python 3.10+

  • An Ojin API key, get yours here

  • A face-model config ID, a Presence or Portrait configuration from your dashboard

New accounts start with $10 in free credits.

Install

With uv (recommended):

uv add "ojin-client[stv]"

Or with pip:

pip install "ojin-client[stv]"

The [stv] extra pulls in the speech-to-video helpers (numpy, opencv-python-headless, soxr) that handle resampling, decoding, and the sync math for you.

Authenticate

The client needs your API key and a configuration ID. Keep both out of source. Read them from the environment:

Or let the SDK load and validate them for you. resolve_credentials() reads an optional .env file and raises a clear error if either value is missing:

By default the client connects to wss://models.ojin.ai/realtime.

Pick your model

The SDK is identical for both face models; the config_id you pass selects which one runs.

A config_id from…

You get

Choose it when

a Human Presence config

the flagship, fully expressive generative presence, rich expressions and natural movement

quality and expressiveness matter most

a Human Portrait config

a cost-effective, real-time lip-synced talking face

you want a great talking face at the best price

Create configurations in the dashboard. The code below is the same either way; only the config_id changes.

Two clients, one package

OjinSTVClient (high-level)

OjinClient (low-level)

Import

from ojin.stv import OjinSTVClient

from ojin.ojin_client import OjinClient

You give it

TTS audio at any sample rate

16 kHz mono int16 PCM

You get

synced STVAudioFrame + STVVideoFrame on a 25 fps clock

raw protocol messages (JPEG + PCM)

It handles

buffering, playback clock, resampling, barge-in re-sync

just the WebSocket + wire protocol

Most applications want OjinSTVClient. Reach for OjinClient only when you're writing your own playback/sync layer.

Choose how the avatar reaches your viewers. By default OjinSTVClient streams frames back to your process over the WebSocket. If your viewers are in a LiveKit or Daily room, pass webrtc=WebRTCSettings(...) and Ojin publishes the avatar straight into that room instead. See Direct WebRTC (LiveKit & Daily).

Quickstart

Feed the avatar one utterance of TTS audio and consume the synchronized stream it speaks back.

say() is a one-shot helper for start_turn() + send_tts_audio(). For a live agent, call start_turn() once per utterance and stream chunks with send_tts_audio() as your TTS produces them, the user hears the exact audio you sent, lip-synced to the avatar.

Events you'll use

Register handlers with @client.on(STVEvent.X) or client.add_listener(STVEvent.X, cb). Handlers may be sync or async; a failing handler never breaks the playback loop.

Event
Fires when

SESSION_READY

the session is live and ready for audio

FIRST_FRAME

the first avatar frame is out (frame_type)

WEBRTC_CONNECTED

direct WebRTC only: the avatar has joined your room (participant_id)

BOT_STARTED_SPEAKING

the first speech frame of a turn is played

BOT_STOPPED_SPEAKING

a turn has finished playing

INTERRUPTED

interrupt() took effect (check its return value to know whether a server-side cancel was actually sent)

ERROR

a transport or server error occurred

CLOSED

the session has been torn down

Where to go next

Last updated

Was this helpful?