Quick Start
Client-Side Authentication
For iOS and macOS applications, use ephemeral keys instead of embedding your permanent API key in the app bundle. Ephemeral keys are short-lived tokens safe to include in client applications.Learn more about client tokens and why they’re important for security.
Fetching an Ephemeral Key
Your app should fetch an ephemeral key from your backend server before connecting:Connecting with an Ephemeral Key
Camera Capture
Realtime media uses LiveKit tracks. Build aLocalVideoTrack from LiveKit’s camera-track factory, size it from the model registry, and attach a MirroringVideoProcessor to pre-flip the front camera.
Setting Up Capture
CameraCaptureOptions parameters:
position—.frontor.back(LiveKit’sAVCaptureDevice.Position)dimensions—Dimensions(width:height:). For portrait output, passmodel.heightas width andmodel.widthas height (swap for landscape).fps— target framerate; usemodel.fps
LocalVideoTrack.createCameraTrack(name:options:processor:) starts capture immediately. There’s no separate startCapture() call.
Switching Cameras
LiveKit’sCameraCapturer toggles between front and back. Keep the MirroringVideoProcessor in sync so .auto mirroring follows the active camera:
Stopping Capture
Front-camera mirroring
Pre-flipping the selfie input keeps server-baked pixels (watermarks, overlays) correctly oriented when you render the remote stream as-is.MirrorMode values:
.off(default) — never mirror..auto— mirror only when the active camera is.front. Updatemirror.cameraPosition = cameraCapturer.positionon camera switch..on— always mirror.
RTCMLVideoViewWrapper(track:) — no mirror: argument.
Connecting
Create aDecartRealtimeManager and connect with your local media stream:
RealtimeConfiguration parameters:
model(required) - Realtime model fromModels.realtime()resolution(optional) -.p720or.p1080. Omit for the server’s 720p default; pass.p1080to request a 1080p remote stream from supported models.initialPrompt(optional) - Initial transformation prompttext- Prompt textenrich- Whether to auto-enhance the promptreferenceImageData- Optional reference imageData
connection(optional) - Connection configurationiceServers- STUN/TURN server URLs (default: Google STUN)connectionTimeout- Connection timeout in seconds (default: 15)rtcConfiguration- CustomRTCConfigurationfor advanced WebRTC tuning
media(optional) - Media configurationvideo.maxBitrate- Max bitrate in bps (default: 2,500,000)video.minBitrate- Min bitrate in bps (default: 300,000)video.maxFramerate- Max framerate (default: 30)video.preferredCodec- Preferred video codec (default: “VP8”)
RealtimeMediaStream — the transformed remote stream containing an optional videoTrack you can render
For image-capable models, pass the reference image data on DecartPrompt:
Managing Prompts
Change the transformation style dynamically without reconnecting:DecartPrompt parameters:
text(required) - Text description of desired stylereferenceImageData(optional) - Reference image data (used withlucy-2.1)enrich(optional) - Whether to enhance the prompt (default:false)
Prompt enhancement uses Decart’s AI to expand simple prompts for better results. Only the
lucy-2.1 model supports reference images.Connection State
Monitor connection state, service status, generation ticks, and session ID using theevents AsyncStream:
DecartRealtimeState properties:
connectionState—.idle,.connecting,.connected,.generating,.reconnecting,.disconnected,.errorserviceStatus—.unknown,.enteringQueue,.readyqueuePosition— Current position in queue (nil if not queued)queueSize— Total queue size (nil if not queued)generationTick— Seconds elapsed during generation (nil when not generating)sessionId— Current session identifier (nil before session established)
DecartRealtimeConnectionState helpers:
.isConnected—truewhen connected or generating.isInSession—truewhen connected, connecting, generating, or reconnecting
Auto-Reconnect
The SDK automatically reconnects when an unexpected disconnection occurs (e.g., network interruption). During auto-reconnect, the connection state transitions to.reconnecting while the SDK retries with exponential backoff (up to 5 attempts, max 10s delay).
When auto-reconnect succeeds, a new RealtimeMediaStream is emitted via remoteStreamUpdates. You must rebind your UI to the new stream’s video track:
Auto-reconnect is not triggered on user-initiated
disconnect(), permanent errors (401/403, invalid key, expired session), or after all retries are exhausted. If all retries fail, the state moves to .error.Connection Quality
Two layers report network health on a shared.good | .fair | .poor | .critical scale: a preflight check before connecting, and an in-session signal while connected.
Preflight
A fast, network-only reachability check. No session, no cost. Never throws — degrades to.critical / .failed on any error:
In-session quality
While connected, the SDK derives a smoothed verdict from live connection stats (latency, packet loss, upstream bandwidth, frame rate) and tells you the limiting factor. The stream yields on debounced level changes;getConnectionQuality() returns the live snapshot whose metrics refresh on every poll:
observability: .init(connectionQualityEnabled: false) in RealtimeConfiguration.
Glass-to-glass latency (opt-in)
Network RTT alone doesn’t reflect the latency users actually feel — a session can read.good while still feeling laggy. Set debugQuality: true to measure the real camera→display latency: the SDK stamps a pixel marker into each outgoing frame and reads it back off the rendered output, surfacing startup (ttffMs), steady-state (g2gMs), and end-to-end frame drops (g2gDropRatio). When present, glass-to-glass drives the latency verdict instead of RTT.
Deep preflight
For a measured verdict before connecting, use the deep probe — it briefly opens a real session with a synthetic source, measures glass-to-glass, then tears it down. Requires amodel and costs a short session:
Error Handling
Errors are thrown from async methods and can also arrive through theevents stream when the connection state becomes .error:
.invalidAPIKey- API key is invalid or missing.invalidBaseURL(String?)- Base URL is malformed.webRTCError(String)- WebRTC connection failed.websocketError(String)- WebSocket connection error.connectionTimeout- Connection timed out.serverError(String)- Server returned an error.processingError(String)- Processing failed.invalidInput(String)- Invalid input parameters.modelNotFound(String)- Specified model doesn’t exist.networkError(Error)- Network request failed.queueError(String)- Queue operation failed
Cleanup
Always stop the camera track and disconnect the manager when done:Complete SwiftUI Example
Here’s a full SwiftUI application using the SDK’s built-inRTCMLVideoViewWrapper:
Best Practices
Use model properties for video constraints
Use model properties for video constraints
Size
CameraCaptureOptions from the model registry so the encoder doesn’t have to rescale.Enable prompt enrichment
Enable prompt enrichment
For best results, set
enrich: true to let Decart’s AI enhance your prompts. Only disable it if you need exact prompt control.Handle auto-reconnect streams
Handle auto-reconnect streams
Always observe
remoteStreamUpdates to rebind your video track when the SDK auto-reconnects after a network interruption.Observe state with AsyncStream
Observe state with AsyncStream
Use
for await state in manager.events to track connection state, generation ticks, session ID, and queue position in a structured concurrency context.Clean up properly
Clean up properly
Always call
manager.disconnect() and capture.stopCapture() when done to avoid memory leaks and unnecessary resource usage.Test on real devices
Test on real devices
Always test camera features on real iOS devices, as the simulator does not support WebRTC camera access.
Request permissions properly
Request permissions properly
Add camera and microphone usage descriptions to your Info.plist and handle permission denials gracefully in your UI.
API Reference
DecartClient.createRealtimeManager(options:)
Creates a realtime manager for a WebRTC session.
Parameters:
options: RealtimeConfiguration- Configuration for the realtime sessionmodel: ModelDefinition- Realtime model fromModels.realtime()initialPrompt: DecartPrompt- Initial transformation prompt (default: empty)text: String- Prompt textenrich: Bool- Whether to auto-enhance the promptreferenceImageData: Data?- Optional reference image data
connection: ConnectionConfig- Connection settings (default: standard)media: MediaConfig- Media settings (default: standard)
DecartRealtimeManager
Throws: DecartError if the signaling URL cannot be constructed
DecartRealtimeManager.connect(localStream:)
Connects to the realtime transformation service.
Parameters:
localStream: RealtimeMediaStream- Local media stream with camera video track
RealtimeMediaStream — the transformed remote stream
Throws: DecartError if connection fails or times out
DecartRealtimeManager.setPrompt(_:)
Changes the transformation style.
Parameters:
prompt: DecartPrompt- Prompt with text, optional reference image, and enrich flag
DecartRealtimeManager.disconnect()
Closes the connection and cleans up WebRTC resources.
DecartRealtimeManager.events
An AsyncStream<DecartRealtimeState> that emits state changes.
DecartRealtimeManager.remoteStreamUpdates
An AsyncStream<RealtimeMediaStream> that emits new remote streams after auto-reconnect.
LocalVideoTrack.createCameraTrack(name:options:processor:)
LiveKit factory that opens the camera and produces a LocalVideoTrack. Capture starts immediately — there is no separate startCapture() call.
Parameters:
name: String- Track identifier (e.g."video0")options: CameraCaptureOptions- Position, dimensions, fpsprocessor: VideoProcessor?- OptionalMirroringVideoProcessor(or any LiveKitVideoProcessor)
CameraCapturer.switchCameraPosition()
Toggles between front and back cameras. Access via videoTrack.capturer as? CameraCapturer. After switching, update mirror.cameraPosition = cameraCapturer.position to keep MirrorMode.auto in sync.
LocalVideoTrack.stop()
Async. Stops capture and releases the track. Call before manager.disconnect() during cleanup.
Next Steps
SDK Overview
Learn about installation, setup, and Swift SDK fundamentals
GitHub
Browse the SDK source code and contribute