standin-sdk is the worker side of a StandIn call. StandIn is the hosted bridge that joins the Microsoft Teams call: it owns the Microsoft side entirely, the bot registration, Graph, media negotiation and the avatar tile, and it talks to your worker over one authenticated WebSocket per call. This package is that socket’s other end.
You write a handler. The SDK does everything else on the wire.
standin, and every plugin ships inside it. That line alone is
already enough for ElevenLabs,
Deepgram and Cartesia: they are
reached over an ordinary WebSocket, so they need nothing beyond aiohttp.
A capability lands once in the core and reaches every plugin, which is why there is no
second package to keep in step.
The four extras
An extra is either a framework that runs inside your process or a capability that needs a wheel most deployments do not want. Both kinds are off by default, and the two capability extras are designed to stay off rather than to fail.pip install "standin-sdk[all]" takes all three wheels at once.
Plugins are resolved on first touch and never at import standin, so the base install is aiohttp and
nothing else. standin.plugins.livekit is the stable spelling, because the path says which layer a
name comes from; standin.livekit works as a shorthand and is not the documented one.
How it fits together
One call is one socket. StandIn dialswss://<your-host>/msteams/calling/{callId}, your CallServer answers it, verifies the handshake, and builds one handler for that call. When the call ends, the handler is closed and the slot is freed.
What the SDK owns, and what you own
Everything that is the same whichever agent framework you use belongs toCallServer. That split is why a StandIn plugin is small.
A handler never tracks a sequence number, never builds a frame, and never sees a signature.
The three things you import
CallServer
Answers the dial, authenticates it, speaks the wire protocol, drives one handler per call.
CallHandler
The five-method seam your plugin implements. Every method is optional.
ChatChannel
The Microsoft Teams messages lane. Dialed out from your worker, so chat needs no listener and no bot credential.
The whole contract
CallHandler is a typing.Protocol, not a base class. Nothing inherits from anything, a missing method is a no-op, and a synchronous method is accepted where there is nothing to await.
The whole surface, by lane
Those three names are the seam. Everything else in the package is a lane you can ignore until you need it. Every name below resolves fromimport standin unless the row says otherwise, and the page
in the last column is the lane’s page rather than a per-name reference: it is where the reasoning
lives, and this table is how you find which page that is.
The two SDKs are one API
The wire protocol, the audio format and the HMAC are byte-identical between the languages, and shared conformance vectors prove it rather than promising it: both protocol modules are generated from one schema and gated against drift. The core seam is the same in both. Same handler methods, same session, same watchdogs, same defaults. A handler ported between the languages changes only its method names: Python is snake_case, TypeScript is camelCase. Around the seam a few peripheral helpers exist in one language only, and the page each one belongs to says so. Three that matter here:- Timers are seconds in Python and milliseconds in TypeScript.
pre_start_timeout=10.0againstpreStartTimeoutMs: 10000. The names differ too, so a straight port does not compile, but a configuration file copied between two workers does not complain. - The two optional video callbacks sit differently.
on_video_frameandon_speaker_changelive on separateVideoHandlerandSpeakerHandlerprotocols here, becauseCallHandlerisruntime_checkableand a sixth member would breakisinstancefor every handler written before the vision lane existed. In TypeScript every handler member is optional, so they are simply two more of them. - Who may call the agent is a TypeScript module.
isInboundCallAllowedand its companions ship in that SDK only. A Python worker writes the same check in its own handler, which is the refusing a call snippet.
Why the plugin lists differ per language
Most plugins exist in both languages, and the ones that do not are settled by the same rule: which language a plugin lives in is decided by the framework it integrates, not by preference. ElevenLabs, Deepgram, Cartesia and LiveKit are in both trees. Hermes Agent has to be Python, because Hermes loads the adapter into its own process. OpenClaw is TypeScript for the same reason in reverse, and OpenAI Realtime is a TypeScript plugin today. Nothing is missing from either side of the wire: both SDKs speak the same protocol, run the same conformance vectors, and answer the same calls.Where the SDK stops
Three boundaries, and each is a real line rather than a disclaimer. Speech and reasoning are your framework’s. Speech recognition, speech generation and the model that decides what to say belong to your provider. The SDK carries PCM in both directions and gives you the turn-taking pieces if you are assembling three vendors yourself, but nothing in this package transcribes, synthesizes or thinks. The Microsoft side is StandIn’s. Bot registration, Graph, the media negotiation and the call itself. Your worker never holds a Bot Framework credential and never talks to Microsoft. StandIn draws the avatar tile; the SDK sends it hints. This is the boundary most often described wrongly, so it is worth stating exactly. The SDK does carry the vision and avatar lane:session.express names an emotion, session.send_speech_marks sends the viseme timeline that drives
lip-sync, session.display_image puts a picture on the tile, session.send_tile_frame puts one frame
of your own video on it, and session.latest_video_frame and CallHandler.on_video_frame are how the
caller’s camera and screen share reach you. Whole modules back those: avatar, lipsync, vision,
vision_tools, tile and ambient, all on the barrel above. What the SDK does not do is render
anything. It never composites a face, never encodes the avatar and never knows how the tile is drawn.
See Vision and the avatar and The avatar.
A message type this SDK does not recognise is ignored by contract, which is what lets an older worker
and a newer StandIn interoperate. That rule is about unknown frames, not about the avatar: those are
frames the SDK itself sends.
Audio format
PCM16, 16 kHz, mono, little-endian, in both directions.on_caller_audio receives it and send_audio expects it back. Realtime models usually speak 24 kHz, which is why the SDK ships a resampler and a frame aligner. See Audio.
Requirements
- Python 3.10 or newer
aiohttp3.9 or newer, the only runtime dependency- A StandIn identity and its connection secret, from standin.komaa.com
- A public
wss://route to your worker, so StandIn can dial in
Next
Quickstart
Install the package, run the echo plugin, and hear yourself on a real Microsoft Teams call.
Call handler
The five methods, one at a time, with a full worked handler.
Audio helpers
resample_pcm16, FrameAligner, and the 16 kHz against 24 kHz problem.Security
The HMAC handshake, the two signing lanes, and what the SDK refuses.