SDK Design
1. SDK strategy
SDKs are the primary developer experience.
The public SDK should present generic RTC concepts and hide the underlying LiveKit implementation.
Initial SDKs:
- Web/TypeScript client SDK
- Flutter client SDK
- Node.js server SDK
- Go server SDK
Native Swift/Kotlin and React Native are post-v0.1 unless demanded by a paying customer.
Install
All SDKs are MIT-licensed public packages; none is installed from git.
| SDK | Install |
|---|---|
| Web | npm install @kallolive/web |
| Flutter | flutter pub add kallolive |
| Node.js | npm install @kallolive/server |
| Go | go get go.kallo.live/kallo |
If go get cannot resolve the module (for example behind a strict module
proxy or checksum database), fetch it from our module proxy directly:
go env -w GOPROXY=https://go.kallo.live,https://proxy.golang.org,direct
go env -w GONOSUMDB=go.kallo.live
2. Client SDK responsibilities
Client SDKs own:
- participant-token exchange
- media-engine connection
- microphone control
- camera control
- device switching
- remote participant state
- remote track state
- reconnect handling
- network quality events
- screen sharing where platform permits
- normalized error mapping
- SDK telemetry
They do not own:
- customer authentication
- room authorization business logic
- payment
- customer booking logic
- platform secret keys
3. Web SDK
Package:
@kallolive/web
Example:
import { RTCClient } from "@kallolive/web";
const rtc = new RTCClient({
apiBaseUrl: "https://api.kallo.live" // required; no default host
});
const result = await rtc.connect({
token: "rtc_pt_xxxxx"
});
// result: { sessionId, roomId, participantId, expiresAt }
await rtc.microphone.enable();
await rtc.camera.enable();
Core API
interface RTCClient {
connect(options: RTCConnectOptions): Promise<RTCConnectResult>;
disconnect(): Promise<void>; // also cancels an in-flight connect()
readonly connectionState: RTCConnectionState;
readonly features: RTCFeatures | null;
readonly connectResult: RTCConnectResult | null;
readonly localParticipant: RTCParticipant | null;
readonly participants: RTCParticipant[];
readonly tracks: RTCTrack[];
readonly microphone: RTCMicrophoneControl;
readonly camera: RTCCameraControl;
readonly screenShare: RTCScreenShareControl;
attachTrack(trackId: string, element: HTMLMediaElement): HTMLMediaElement;
detachTrack(trackId: string, element?: HTMLMediaElement): void;
getLocalCameraTrack(): MediaStreamTrack | null;
getLocalMicrophoneTrack(): MediaStreamTrack | null;
getMediaDevices(kind?: RTCMediaDeviceKind): Promise<RTCMediaDevice[]>;
switchAudioOutput(deviceId: string): Promise<void>;
on<T extends RTCEvent>(event: T, handler: RTCEventHandler<T>): RTCUnsubscribe;
}
Events
connection.state_changed
connection.quality_changed
connection.reconnecting
connection.reconnected
connection.disconnected
participant.joined
participant.updated
participant.left
track.subscribed
track.unsubscribed
error
Connection states
idle
connecting
connected
reconnecting
disconnecting
disconnected
failed
Quality levels
unknown
excellent
good
poor
lost
Keep these generic even if the media engine uses different native quality enums.
4. Flutter SDK
Package:
kallo
Example:
final rtc = KalloClient(
apiBaseUrl: 'https://api.kallo.live', // required
);
final result = await rtc.connect(
token: participantToken,
);
await rtc.microphone.enable();
await rtc.camera.enable();
Video widget:
KalloVideoView(
track: videoTrack, // KalloVideoTrack, e.g. rtc.localCameraTrack
fit: BoxFit.cover,
)
Expected API:
class KalloClient {
KalloClient({required String apiBaseUrl, http.Client? httpClient});
Future<KalloConnectResult> connect({required String token});
Future<void> disconnect(); // also cancels an in-flight connect()
Future<void> dispose();
KalloConnectionState get connectionState;
KalloFeatures? get features;
KalloParticipant? get localParticipant;
List<KalloParticipant> get participants;
List<KalloTrack> get tracks;
KalloMicrophoneControl get microphone;
KalloCameraControl get camera;
KalloMediaControl get screenShare;
Stream<KalloEvent> get events;
}
Mobile requirements
Flutter v0.1 must test:
- iOS camera/microphone permission flow
- Android camera/microphone permission flow
- speaker/earpiece routing
- Bluetooth headset
- wired headset
- front/back camera switching
- app background/foreground
- incoming phone interruption
- screen lock behavior
- Wi-Fi to cellular transition
- cellular to Wi-Fi transition
- reconnect after temporary network loss
5. Client errors
Normalized SDK errors:
authentication_failed
token_expired
room_not_found
room_ended
participant_limit_reached
permission_denied
device_not_found
device_in_use
media_permission_denied
connection_failed
connection_lost
network_unavailable
service_unavailable
unsupported_platform
invalid_request
insufficient_credit (HTTP 402; project is out of credit)
rate_limited (HTTP 429; see error.retryAfter)
timeout (connect() exceeded 15s, token exchange plus media connect)
unknown
Flutter uses the camelCase enum names (KalloErrorCode.insufficientCredit, ...).
retryAfter is in seconds when the API sent a Retry-After header.
Do not leak raw LiveKit errors as the only documented contract.
Raw engine errors may be attached under debug metadata.
6. Server SDKs
Server SDKs are thin typed wrappers around the public REST API.
They should not connect directly to LiveKit.
Node.js
Package:
@kallolive/server
Example:
import { Kallo } from "@kallolive/server";
const rtc = new Kallo({
secretKey: process.env.KALLO_SECRET!,
baseURL: "https://api.kallo.live/v1" // required
});
// Request bodies use the API's snake_case field names.
const room = await rtc.rooms.create({
max_participants: 2,
metadata: {
booking_id: "BK000123"
}
});
const participant = await rtc.participants.create(room.id, {
external_id: "user_123"
});
// participant: { id, room_id, token, expires_at, join_url? } (ParticipantCreated;
// Go returns the same fields as kallo.ParticipantCreated)
Go
Module: go.kallo.live/kallo, served by our Go module proxy at
https://go.kallo.live (install: go get go.kallo.live/kallo).
Example:
client, err := kallo.NewClient(
os.Getenv("KALLO_SECRET"),
kallo.WithBaseURL("https://api.kallo.live/v1"), // required
)
if err != nil {
return err
}
room, err := client.Rooms.Create(ctx, kallo.CreateRoomRequest{
MaxParticipants: 2,
}, "" /* optional Idempotency-Key */)
Base URL
Client SDKs (Web apiBaseUrl, Flutter apiBaseUrl) take the API origin and add
/v1/connect themselves; a trailing /v1 and trailing slashes are stripped.
Server SDKs (Node baseURL, Go WithBaseURL) call resource paths under
/v1; they append /v1 when the URL has no version segment (/v1, /v2, ...
are kept as given). One value, for example https://api.kallo.live, therefore
works for every SDK, and so does https://api.kallo.live/v1.
7. SDK transport
Client SDK:
Customer backend -> creates rtc_pt token
Client SDK -> exchanges rtc_pt
RTC API -> returns endpoint + short-lived media token
SDK -> connects to media engine
Server SDK:
Customer backend -> REST API only
8. Engine adapter inside client SDK
For v0.1:
Public Web SDK
|
Generic SDK types
|
LiveKit adapter
|
LiveKit JS client
and:
Public Flutter SDK
|
Generic SDK types
|
LiveKit adapter
|
LiveKit Flutter client
Keep adapter-specific files isolated.
Suggested Web layout:
packages/sdk-web/src/
client.ts
types.ts
events.ts
errors.ts
media/
media-engine.ts
livekit/
adapter.ts
mapper.ts
Flutter equivalent:
packages/sdk-flutter/lib/src/
client.dart
models/
events/
errors/
media/
media_engine.dart
livekit/
Endpoint parity
Every public /v1 route is covered by both server SDKs. The route tables in
packages/sdk-node/test/client.test.js and packages/sdk-go/routes_test.go
fail if a method stops building the request below.
| Route | Node (Kallo) | Go (*Client) |
|---|---|---|
POST /v1/rooms | rooms.create | Rooms.Create |
GET /v1/rooms | rooms.list | Rooms.List |
GET /v1/rooms/{room_id} | rooms.get | Rooms.Get |
DELETE /v1/rooms/{room_id} | rooms.end | Rooms.End |
POST /v1/rooms/{room_id}/participants | participants.create | Participants.Create |
GET /v1/rooms/{room_id}/participants | participants.list | Participants.List |
GET /v1/rooms/{room_id}/participants/{participant_id} | participants.get | Participants.Get |
DELETE /v1/rooms/{room_id}/participants/{participant_id} | participants.remove | Participants.Remove |
GET /v1/usage/summary | usage.summary | Usage.Summary |
GET /v1/usage/daily | usage.daily | Usage.Daily |
GET /v1/billing/balance | billing.balance | Billing.Balance |
GET /v1/billing/pricing | billing.pricing | Billing.Pricing |
GET /v1/billing/ledger | billing.ledger | Billing.Ledger |
POST /v1/billing/topups | billing.createTopUp | Billing.CreateTopUp |
GET /v1/billing/topups | billing.topUps | Billing.TopUps |
GET /v1/billing/topups/{payment_id} | billing.topUp | Billing.TopUp |
POST /v1/api-keys | apiKeys.create | APIKeys.Create |
GET /v1/api-keys | apiKeys.list | APIKeys.List |
DELETE /v1/api-keys/{key_id} | apiKeys.revoke | APIKeys.Revoke |
GET /v1/project/limits | project.limits | Project.Limits |
GET /v1/audit-logs | audit.list | Audit.List |
POST /v1/webhook-endpoints | webhooks.create | Webhooks.Create |
GET /v1/webhook-endpoints | webhooks.list | Webhooks.List |
DELETE /v1/webhook-endpoints/{endpoint_id} | webhooks.disable | Webhooks.Disable |
GET /v1/system/info | system.info | System.Info |
POST /v1/connect | client SDKs only | client SDKs only |
| Webhook signature | verifyWebhookSignature | VerifyWebhookSignature |
Both return the API error envelope as a typed error (KalloError /
*APIError) including retryAfter / RetryAfter from Retry-After.
Client SDK parity
| Capability | Web (RTCClient) | Flutter (KalloClient) |
|---|---|---|
| Connect / disconnect (one 15 s deadline for the whole connect, cancellable) | connect, disconnect | connect, disconnect, dispose |
| Microphone / camera / screen share | microphone, camera, screenShare | microphone, camera, screenShare |
| Device list / switch | getMediaDevices, *.switchDevice | getMediaDevices, *.switchDevice |
| Audio output | switchAudioOutput | setSpeakerPreferred (mobile routing) |
| Participants / tracks | participants, tracks, localParticipant | same |
| Local preview | getLocalCameraTrack | localCameraTrack + KalloVideoView |
| Remote rendering | attachTrack / detachTrack | KalloVideoView |
| Events | on(event, handler) | events stream |
| Errors | RTCError | KalloError (same codes) |
9. Versioning
Use semantic versioning.
Before 1.0:
- patch: fixes without contract changes
- minor: additive APIs and potentially announced pre-1.0 changes
- major: reserved for major compatibility reset
After 1.0, normal semantic versioning applies.
10. Telemetry
SDK should send minimal technical telemetry, controlled by project/privacy settings:
- SDK name/version
- OS/platform
- connect attempt time
- connect success/failure
- reconnect count
- quality category
- anonymized technical error
Do not collect customer business content or media.
Never record audio/video unless an explicit recording feature is later introduced with separate consent and configuration.
11. SDK release requirements
Packages: @kallolive/web and @kallolive/server on npm, kallolive on
pub.dev, and go.kallo.live/kallo on the Kallo Go module proxy. All are MIT
licensed, versioned independently with semantic versioning, and each has a
CHANGELOG.md. A published version is never changed; fixes ship as a new
version.
Every client SDK release must pass:
- connect/disconnect
- publish/unpublish microphone
- publish/unpublish camera
- switch camera/device
- second participant join/leave
- reconnect
- expired token
- ended room
- permission denied
- network interruption
- SDK version compatibility against current API
12. Example applications
Maintain small reference apps:
examples/
web-basic/
flutter-basic/
node-token-server/
go-token-server/
The examples should be used as end-to-end CI smoke tests where practical.