Migrate from Lemon Slice
Lemon Slice does impressive real-time image animation, and we respect their work. If you're moving to Lookalike, the biggest change is how much of the pipeline you have to assemble. This guide maps the two APIs side by side. The Lemon Slice snippets reflect its self-managed real-time API and are shown for orientation.
Two things are worth knowing up front:
- We run the pipeline; you don't assemble it. With Lemon Slice's self-managed sessions you stand up the transport and bring your own speech-to-text, LLM, and text-to-speech, then publish audio for the avatar to lip-sync. With Lookalike, one call starts a session where we run the agent (STT, LLM, TTS) and stream the avatar — you just join and talk.
- A trained identity, not an image per call. Lemon Slice animates a still image you pass each session (or a dashboard agent). Lookalike creates a reusable identity — face, voice, and personality — once, and you reference it by
identity_id.
Both run over LiveKit, so your client-side join code barely changes.
How the concepts map
| Lemon Slice | Lookalike | Notes |
|---|---|---|
agent_image_url (image per session) or dashboard agent_id | trained identity (identity_id) | Create once, reuse |
| Your own STT + LLM + TTS pipeline | run for you inside the session | You can delete this layer |
livekit_url + livekit_token you pass in | room_name + token + livekit_url we return | The direction flips |
| Self-managed session | session | POST /v1/sessions/start |
X-API-Key header | Authorization: Bearer or X-API-Key | Keys start withlk_ |
| Credits | credits | One balance covers onboarding and realtime |
Authenticate
Lemon Slice sends X-API-Key against https://lemonslice.com/api. Lookalike sends a bearer token (or X-API-Key) against https://api.lookalike.com, and keys start with lk_. Get one in the console.
Lemon Slice
curl https://lemonslice.com/api/liveai/sessions -H "X-API-Key: $LEMONSLICE_API_KEY"
Lookalike
curl https://api.lookalike.com/v1/identities -H "Authorization: Bearer $LOOKALIKE_API_KEY"
Create the avatar
Lemon Slice has no train/clone step — you point at a public image URL (or a dashboard agent) when you start a session, and there's no voice attached to a self-managed avatar. Lookalike creates a reusable identity once, bundling the face, an optional voice clip, and the personality prompt, and trains it inline.
Lookalike
- cURL
- Python
- Node.js
curl -sS -X POST https://api.lookalike.com/v1/identities \
-H "Authorization: Bearer $LOOKALIKE_API_KEY" \
-F "name=Ada" \
-F "prompt=You are Ada, a warm and concise product guide." \
-F "image=@ada.jpg;type=image/jpeg" \
-F "audio=@ada.wav;type=audio/wav"
# -> { "identity_id": "id_...", "status": "ready" }
import httpx
with open("ada.jpg", "rb") as img, open("ada.wav", "rb") as aud:
resp = httpx.post(
"https://api.lookalike.com/v1/identities",
headers={"Authorization": f"Bearer {API_KEY}"},
data={
"name": "Ada",
"prompt": "You are Ada, a warm and concise product guide.",
},
files=[
("image", ("ada.jpg", img, "image/jpeg")),
("audio", ("ada.wav", aud, "audio/wav")),
],
)
resp.raise_for_status()
identity = resp.json() # { identity_id, status: "ready", ... }
import { openAsBlob } from 'node:fs';
const form = new FormData();
form.set('name', 'Ada');
form.set('prompt', 'You are Ada, a warm and concise product guide.');
form.set('image', await openAsBlob('ada.jpg', { type: 'image/jpeg' }), 'ada.jpg');
form.set('audio', await openAsBlob('ada.wav', { type: 'audio/wav' }), 'ada.wav');
const resp = await fetch('https://api.lookalike.com/v1/identities', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.LOOKALIKE_API_KEY}` },
body: form,
});
const identity = await resp.json(); // { identity_id, status: "ready", ... }
See Create an Identity for the full field list.
Start a session
This is where the models differ most. A Lemon Slice self-managed session expects you to have already created a LiveKit room and token, which you pass in; it returns only a session_id, and you run your own STT/LLM/TTS into that room. A Lookalike session takes just the identity_id, mints the room for you, and brings in the agent and avatar.
Lemon Slice (you supply the transport and pipeline)
curl -sS -X POST https://lemonslice.com/api/liveai/sessions \
-H "X-API-Key: $LEMONSLICE_API_KEY" -H "Content-Type: application/json" \
-d '{
"transport_type": "livekit",
"properties": { "livekit_url": "wss://your-livekit", "livekit_token": "YOUR_TOKEN" },
"agent_image_url": "https://example.com/ada.png"
}'
# -> { "session_id": "..." } then publish your own TTS audio into the room
Lookalike (we supply the transport and pipeline)
curl -sS -X POST https://api.lookalike.com/v1/sessions/start \
-H "Authorization: Bearer $LOOKALIKE_API_KEY" -H "Content-Type: application/json" \
-d '{ "identity_id": "id_3f9a2b7c4d1e8a05f6b3" }'
# -> { "room_name": "...", "token": "...", "livekit_url": "wss://..." }
The token is minted server-side and already encodes the room. See Realtime API for the full response.
Join and talk
Both sides run over LiveKit, so your join code is the same shape — point your LiveKit client at the livekit_url and token from the session response. The difference is what happens next: with Lemon Slice you drive the avatar by publishing audio from your own STT/LLM/TTS pipeline; with Lookalike the agent listens and speaks on its own, so you can retire that pipeline entirely.
import { Room, RoomEvent, Track } from 'livekit-client';
// livekitUrl and token come from the sessions/start response
const room = new Room();
room.on(RoomEvent.TrackSubscribed, (track) => {
if (track.kind === Track.Kind.Video) {
document.getElementById('avatar').appendChild(track.attach());
} else if (track.kind === Track.Kind.Audio) {
document.body.appendChild(track.attach());
}
});
await room.connect(livekitUrl, token);
await room.localParticipant.setMicrophoneEnabled(true); // so the avatar can hear you
When an avatar is enabled it publishes both audio and video. Attach both, but don't also play the agent's raw voice track, or you'll hear it twice.
End the session
Lemon Slice ends a session with a terminate control event (or an idle timeout). On Lookalike you can do either of two things: disconnect from the room, or call the End Session endpoint.
Lemon Slice
curl -sS -X POST https://lemonslice.com/api/liveai/sessions/SESSION_ID/control \
-H "X-API-Key: $LEMONSLICE_API_KEY" -H "Content-Type: application/json" \
-d '{ "event": "terminate" }'
Lookalike
Disconnect the client:
await room.disconnect();
Or end it from your backend, passing the room_name from sessions/start as the session_id:
curl -sS -X POST https://api.lookalike.com/v1/sessions/ROOM_NAME/end \
-H "Authorization: Bearer $LOOKALIKE_API_KEY"
Billing
Both platforms meter credits. On Lookalike, onboarding an identity is a one-time charge and a realtime session is charged per minute while the avatar is streaming. Buy credits and view usage in the Console; full rates are on the Billing & credits page. A request that would spend credits you don't have returns 402.
What's different
- The pipeline is included. STT, LLM, and TTS run inside the session, so you don't wire up or pay separate speech vendors.
- We mint the room. You don't provision LiveKit credentials and pass them in — you get them back from one call.
- A reusable trained identity. Face, voice, and personality live in one
identity_idyou create once, rather than an image passed per session.
Next steps
- Core concepts — how identities and sessions fit together.
- Create an Identity — the full create/train reference.
- Realtime API — the full session and join reference.
- Billing & credits — rates and how credits are charged.