Skip to content

Transports

A transport determines how audio reaches the worker and how video gets back to you. It’s a tagged union on the session’s transport field, keyed by type.

class TransportType(str, Enum):
FAKE = "fake" # internal only — not a customer transport
LIVEKIT = "livekit" # available now (BYO)
WEBSOCKET = "websocket" # reserved, not yet available
PIPECAT = "pipecat" # reserved, not yet available

Protoface uses Bring Your Own LiveKit. You already own a LiveKit room; our worker joins it and publishes a video track. This is the same pattern every LiveKit Agents avatar plugin uses (Tavus, Simli, D-ID, Hedra, Beyond Presence, and others).

{
"type": "livekit",
"url": "wss://my-app.livekit.cloud",
"room_name": "demo-room",
"worker_token": "eyJ...",
"worker_identity": "protoface-worker",
"audio_source": "data_stream",
"subscribe_to_identity": null,
"synthetic_audio_if_no_input": false
}

| Field | Required | Notes | | ----------------------------- | -------- | ------------------------------------------------------------ | | url | yes | Your LiveKit server URL (wss://...). | | room_name | yes | The room the worker joins. You create/own it. | | worker_token | yes | A JWT you mint authorizing the worker to join room_name. | | worker_identity | no | Identity the worker uses in the room. Default protoface-worker. | | audio_source | no | data_stream (default) or track. See below. | | subscribe_to_identity | no | When audio_source: track, pin a specific participant. | | synthetic_audio_if_no_input | no | Demo aid — synthesize audio if none arrives. Off by default. |

audio_source selects how the worker receives audio:

  • data_stream (default) — uses LiveKit’s DataStreamAudioReceiver. This is canonical for LiveKit Agents customers and what the livekit-plugins-protoface package uses. It also gives you free interrupt semantics.
  • track — the worker subscribes to a participant’s published audio track. With track, set subscribe_to_identity to pin a specific participant; if null, the worker subscribes to the first audio track it sees.

You mint the token with your LiveKit server SDK and your own key/secret:

import { AccessToken } from "livekit-server-sdk";
const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, {
identity: "protoface-worker",
});
at.addGrant({ roomJoin: true, room: "demo-room" });
const workerToken = await at.toJwt();

Then pass it on session create — see the Quickstart and the Sessions guide.

On GET, a LiveKit transport echoes back the same fields with the secret redacted:

{
"type": "livekit",
"url": "wss://my-app.livekit.cloud",
"room_name": "demo-room",
"worker_token": "********",
"worker_identity": "protoface-worker",
"audio_source": "data_stream"
}

These are declared in the contract so we can add them without a breaking change. They are not implemented yet — sending them today returns 400 invalid_request with code transport.unsupported.

Customer-streamed audio in, server-streamed video out over signed, short-lived WebSocket URLs returned at session-create time. URLs are always returned by the server; clients never construct them.

Reserved for a future Pipecat-native integration. No request shape is committed yet.