Skip to main content

Sleep Cycle SDK - iOS Documentation

Overview

The Sleep Cycle SDK for iOS enables developers to integrate advanced sleep analysis capabilities into their applications. The SDK provides real-time sleep tracking using audio and motion sensors, delivering detailed sleep insights, stage transitions, and detected events throughout the night.

System Requirements

Minimum iOS Version:
  • iOS: 16.0+
  • macOS: 13.0+
Swift:
  • Swift Version: 5.9+
  • The SDK is written in Swift and provides a Swift-native API with async/await support

Installation

Swift Package Manager

Add the Sleep Cycle SDK to your project using Swift Package Manager:
  1. In Xcode, select FileAdd Package Dependencies
  2. Enter the package URL: https://github.com/MDLabs/sleepcycle-sdk-swift
  3. Select the version you want to use (Semantic Versioning)
  4. Add the package to your target
Alternatively, add it to your Package.swift:
dependencies: [
    .package(url: "https://github.com/MDLabs/sleepcycle-sdk-swift", from: "1.2.0")
]
The SDK requires an API key for authorization. Contact Sleep Cycle to obtain credentials.

Prerequisites

Permissions

The SDK requires microphone access for audio-based sleep analysis. Add the following to your Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>We need access to the microphone to analyze your sleep patterns and detect snoring.</string>
For motion-based analysis, you may also need:
<key>NSMotionUsageDescription</key>
<string>We use motion data to track your sleep movements.</string>

Background modes

To ensure continuous sleep analysis throughout the night, enable the appropriate background modes in your app’s capabilities:
  1. Open your project in Xcode
  2. Select your app target
  3. Go to “Signing & Capabilities”
  4. Add “Background Modes” capability
  5. Enable “Audio”
Alternatively, add this to your Info.plist:
<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
</array>
The SDK uses the audio background mode to maintain continuous audio processing during sleep analysis. Your app should also implement proper session management to prevent iOS from suspending the analysis process.

General

The SDK is thread-safe and uses Swift concurrency (async/await) for all asynchronous operations.

Initialize the SDK

The SDK requires authentication before use. The initialization process validates your credentials and determines available features.
import SleepCycleSDK

Task {
    do {
        let features = try await SleepCycleSdk.initialize(
            logLevel: .info,
            apiKey: "your-api-key-here"
        )
        print("Authorized with features: \(features)")
    } catch {
        print("Initialization error: \(error)")
    }
}
Parameters:
logLevel LogLevel - SDK log verbosity (.debug, .info, .warning, .error, .noLog)
logger Logger? - Optional custom logger (defaults to the system logger)
apiKey String - Your Sleep Cycle API key
forceTokenRefresh Bool - Force a fresh token fetch even if a cached token exists (defaults to false)
The returned SleepAnalysisFeatures indicates which capabilities are available for your API key:
sleepStaging Bool - Sleep staging analysis
smartAlarm Bool - Smart alarm functionality
audioEvents Bool - Audio event detection
snoringDetection Bool - Snoring detection
realTimeSleepStaging Bool - Real-time sleep staging
multiChannelAnalysis Bool - Multi-channel analysis (stereo, two channels)
extendedAudioEvents Bool - Extended audio event types beyond the standard set
Access feature flags at runtime:
if SleepCycleSdk.isFeatureEnabled(\.audioEvents) {
    // Present snore/talk event UI
}

Get the SDK state

Monitor SDK state changes using the AsyncStream:
import SleepCycleSDK

Task {
    for await state in await SleepCycleSdk.stateStream {
        switch state {
        case .uninitialized:
            print("SDK not initialized")
        case .initialized:
            print("SDK ready")
        case .running:
            print("Analysis in progress")
        }
    }
}
Get the current state:
let currentState = await SleepCycleSdk.currentState

Start a sleep analysis session

Once initialized, you can start a sleep analysis session. The method returns a UUID that identifies the session.
import SleepCycleSDK

Task {
    do {
        let sessionId: UUID = try await SleepCycleSdk.startAnalysis(
            config: SleepAnalysisConfig(
                useAudio: true,
                useAccelerometer: true
            )
        )
        print("Analysis started with session ID: \(sessionId)")
    } catch {
        print("Failed to start analysis: \(error)")
    }
}
Parameters:
config SleepAnalysisConfig - Configuration object specifying which sensors to use
at Date - The start time for the analysis (defaults to current time)
using DataSource? - Optional data source (e.g., live or file replay)
eventListeners [AudioEventListener] - Optional array of listeners that receive callbacks during audio analysis. Use this to capture audio samples and events in real-time

Resume a session

The SDK supports resuming a previously started analysis session. This is useful when your app restarts or the background task is terminated by the system.
if await SleepCycleSdk.isResumePossible() {
    let resumedSessions: [ResumedSession] = try await SleepCycleSdk.resumeAnalysis()
    for session in resumedSessions {
        print("Resumed \(session.sessionId) on channel \(session.channel)")
    }
}
resumeAnalysis() returns an array of ResumedSession, each containing the sessionId, channel, and any persisted smartAlarmConfig for the resumed session.

Stop a session

To stop an active analysis session and retrieve the results:
Task {
    do {
        let result = try await SleepCycleSdk.stopAnalysis()

        print("Session ID: \(result.sessionId)")

        if let statistics = result.statistics {
            print("Total sleep duration: \(statistics.totalSleepDuration ?? 0)")
            print("Sleep efficiency: \(statistics.sleepEfficiency ?? 0)")

            if let snoreSessions = statistics.snoreSessions {
                for session in snoreSessions {
                    print("Snoring session: \(session.interval)")
                }
            }
        }

        for event in result.events {
            print("\(event.type) detected: \(event.interval), p=\(event.probability)")
        }

        for breathingRate in result.breathingRates {
            print("Breathing rate: \(breathingRate.bpm) bpm at \(breathingRate.timestamp)")
        }

        for stageInterval in result.sleepStageIntervals {
            print("\(stageInterval.stage): \(stageInterval.interval)")
        }

        if let audioStats = result.audioStatistics {
            for interval in audioStats.healthIntervals {
                print("Audio \(interval.status): \(interval.interval)")
            }
        }
    } catch {
        print("Failed to stop analysis: \(error)")
    }
}

Analysis result

The AnalysisResult contains the complete output of a sleep analysis session:
sessionId UUID - Unique session identifier
startTime Date - Session start time
endTime Date - Session end time
timeZone TimeZone - Time zone captured at session start
events [Event] - Detected sleep events
breathingRates [BreathingRate] - Breathing rate measurements
sleepStageIntervals [SleepStageInterval] - Sleep stage data
realTimeSleepStageIntervals [SleepStageInterval] - Sleep stages emitted live during the session
statistics SleepStatistics? - Aggregated sleep statistics (optional)
audioStatistics AudioStatistics? - Audio health statistics (optional)

SleepStatistics

When available, statistics contains aggregated metrics about the sleep session:
totalSleepDuration Double? - Total time spent sleeping
sleepOnsetLatency Double? - Time to fall asleep
sleepEfficiency Double? - Ratio of sleep to time in bed (0.0 to 1.0)
finalWakeTime Date? - Time of final awakening
numberOfAwakenings Int? - Number of awakenings during the night
snoreTime Double? - Total time spent snoring
snoreSessions [SnoreSession]? - Individual snoring sessions
sleepStageDurations [SleepStage: Double]? - Duration per sleep stage

AudioStatistics

When available, audioStatistics contains information about audio input health throughout the session.

Sleep score

After a session completes, you can compute a sleep score for the night with SleepScoring.compute(result:history:dateOfBirth:chronoType:). The score combines the night’s result with a short history of previous nights.
import SleepCycleSDK

// Build a history entry from each completed result and persist it for future nights
let historyEntry = result.toSleepScoreHistoryEntry()

let score = SleepScoring.compute(
    result: result,
    history: recentHistoryEntries,
    dateOfBirth: userDateOfBirth,   // optional, age-adjusts the quality subscore
    chronoType: userChronoType      // optional, selects the timing subscore window
)

print("Sleep score: \(score.total) (duration=\(score.duration), quality=\(score.quality), routine=\(score.routine))")
Each SleepScore field is in the range 0.0–1.0, where higher is better:
total Float - Overall sleep score for the night
duration Float - How much the user slept
quality Float - How well the user slept
routine Float - The user’s sleep schedule
SleepScoring.identifyChronoType(history:) derives the user’s ChronoType (.extremeMorning, .morning, .intermediate, .evening, .extremeEvening) from recent history, or returns nil when there isn’t enough data. The result can be passed back into compute(...) as the chronoType argument.

Real-time events

The SDK provides real-time event updates during analysis through an AsyncStream API:
Task {
    for await events in await SleepCycleSdk.eventStream {
        for event in events {
            switch event.type {
            case .movement:
                handleMovement(event)
            case .snoring:
                handleSnoring(event)
            case .talking:
                handleTalking(event)
            case .coughing:
                handleCoughing(event)
            default:
                handleOtherEvent(event)
            }
        }
    }
}
When the extendedAudioEvents feature is enabled for your API key, additional EventType values are reported alongside the standard ones: .bird, .cat, .digestive, .dog, .fart, .music, .sneeze, .throatClearing, .traffic, .water, and .wind. Use EventType.standardTypes and EventType.extendedTypes to distinguish the two sets. Each Event contains:
type EventType - The type of event
interval DateInterval - Time interval of the event
probability Double - Confidence score (0.0 to 1.0)
source EventSource - Source of detection
sessionId UUID - The session this event belongs to
signature [Float]? - Optional feature vector (for snoring events)

Real-time breathing rate

The SDK provides real-time breathing rate measurements during analysis:
Task {
    for await breathingRate in await SleepCycleSdk.breathingRateStream {
        print("Breathing rate: \(breathingRate.bpm) bpm (confidence: \(breathingRate.confidence))")
    }
}
Each BreathingRate contains:
timestamp Date - The time when the measurement was recorded
bpm Double - Breathing rate in breaths per minute
confidence Double - Confidence score of the measurement (0.0 to 1.0)
sessionId UUID - The session this measurement belongs to

Real-time sleep staging (Experimental)

This feature is experimental and may change in future releases. The API and behavior are subject to modification without notice.
The SDK can provide real-time sleep stage predictions during analysis. This feature requires the realTimeSleepStaging capability to be enabled for your API key.
Task {
    for await stageInterval in await SleepCycleSdk.sleepStageStream {
        switch stageInterval.stage {
        case .awake:
            print("Awake: \(stageInterval.interval)")
        case .light:
            print("Light sleep: \(stageInterval.interval)")
        case .deep:
            print("Deep sleep: \(stageInterval.interval)")
        case .rem:
            print("REM sleep: \(stageInterval.interval)")
        }
    }
}
The stream emits SleepStageInterval objects approximately every 30 seconds during analysis, providing near real-time feedback on sleep state transitions.

Real-time audio health

The SDK monitors the health of the audio input during analysis and emits status updates when the audio state changes:
Task {
    for await update in await SleepCycleSdk.audioHealthStream {
        switch update.status {
        case .healthy:
            print("Audio input healthy")
        case .flatline:
            print("Audio flatline detected")
        case .missingInput:
            print("Audio input missing")
        }
    }
}
AudioHealthStatus values:
.healthy - Audio input contains a varying signal
.flatline - Constant value detected (non-functional microphone or muted input)
.missingInput - No audio input received for an extended period

Smart alarm

The smart alarm monitors movement during a wake-up window and emits events when the user is in a light sleep phase, allowing you to wake them at an optimal moment. This requires the smartAlarm feature to be enabled for your API key and is only supported on the primary channel. Configure a wake-up window with SmartAlarmConfig and pass it when starting analysis:
import SleepCycleSDK

let wakeupWindow = DateInterval(start: windowStart, end: windowEnd)

try await SleepCycleSdk.startAnalysis(
    config: SleepAnalysisConfig(useAudio: true, useAccelerometer: true),
    smartAlarmConfig: SmartAlarmConfig(wakeupWindow: wakeupWindow)
)
Observe alarm lifecycle events through smartAlarmStream:
Task {
    for await event in await SleepCycleSdk.smartAlarmStream {
        switch event.type {
        case .armed:
            print("Smart alarm armed")
        case .triggered(let reason):
            switch reason {
            case .optimalWakeUp, .userInteraction, .windowEnd:
                ringAlarm()
            }
        }
    }
}
The smartAlarmConfig parameter is also available on startMultiChannelAnalysis(...).

Event signatures

For snoring events, the Event.signature property contains a 16-dimensional feature vector that represents unique characteristics of the detected snore. Snore events from the same person are grouped close to each other in the signature space, allowing clustering of events by person.

Audio event listener

The AudioEventListener protocol allows you to receive real-time audio analysis updates during a session. Implement this protocol to access raw audio samples, event detection, and volume information as analysis progresses.
class MyAudioListener: AudioEventListener {
    func onAudioAnalysisBatchCompleted(
        sessionId: UUID,
        audioSamples: [Float],
        audioSampleRate: Int,
        audioStartTime: Date,
        audioEndTime: Date,
        audioProbability: [EventType: Float],
        eventsStarted: [EventStartedInfo],
        eventsEnded: [EventEndedInfo],
        dbSpl: [Float]
    ) {
        // Process audio samples and events
    }
}

let listener = MyAudioListener()
try await SleepCycleSdk.startAnalysis(
    config: SleepAnalysisConfig(useAudio: true),
    eventListeners: [listener]
)
The audioSamples parameter contains all processed audio data in sequence, without any gaps or overlap between batches. Each batch continues exactly where the previous batch ended, ensuring complete coverage of all analyzed audio. The audioProbability parameter provides the per-batch probability for each EventType in the analyzed time window. The dbSpl parameter provides the A-weighted sound volume in dB SPL for each time frame in the batch.

Audio clips

The SDK can capture short audio recordings when specific sleep events are detected, such as snoring, sleep talking, or coughing. Create an AudioEventListener using AudioClipsConfig and AudioClipsReceiver:
import SleepCycleSDK

let listener = SleepCycleSdk.createAudioClipsProducer(
    audioClipsConfig: AudioClipsConfig(
        activeTypes: [
            .snoring: EventTypeConfig(minDuration: 0.5),
            .talking: EventTypeConfig(minDuration: 0.5)
        ],
        clipLength: 5.0
    ),
    receiver: MyAudioHandler()
)

try await SleepCycleSdk.startAnalysis(
    config: SleepAnalysisConfig(useAudio: true),
    eventListeners: [listener]
)
Each AudioClip contains:
startTime Date - Start timestamp
type EventType - The event type that triggered the capture
samples [Float] - Raw audio samples
sampleRate Int - Sample rate in Hz
sessionId UUID - The session this clip belongs to

Multi-channel analysis

The SDK supports analyzing two channels simultaneously using a stereo audio source. Multi-channel analysis separates the data source lifecycle from individual session lifecycles, allowing you to start and stop sessions on each channel independently. The stereo stream is expected to come from two separate mono microphones combined into a single stereo stream, with one microphone per channel. This requires the multiChannelAnalysis feature to be enabled for your API key.

Channel separation

When using stereo input, ChannelSeparationConfig controls how audio events are assigned to channels. Built-in presets:
  • .bedSideMics — bedside microphones placed apart (default)
  • .centeredMicArray — closely spaced microphone array
  • .detectionStrengthOnly() — no spatial filtering, uses only detection confidence
All parameters (mic distance, ambiguous zone, confidence threshold, per-event-type settings) can be tuned individually to fit your specific hardware setup and use case.

Data source lifecycle

Start the data source with a stereo audio configuration before starting individual sessions:
import SleepCycleSDK

let dataSource = SleepCycleSdk.createLiveDataSource()

try await SleepCycleSdk.startDataSource(
    using: dataSource,
    channelSeparationConfig: .bedSideMics
)

Starting sessions on each channel

Once the data source is running, start a session on each channel:
let primarySessionId: UUID = try await SleepCycleSdk.startMultiChannelAnalysis(
    channel: .primary,
    config: SleepAnalysisConfig(useAudio: true, useAccelerometer: true)
)

let secondarySessionId: UUID = try await SleepCycleSdk.startMultiChannelAnalysis(
    channel: .secondary,
    config: SleepAnalysisConfig(useAudio: true, useAccelerometer: false)
)
AnalysisChannel values:
.primary - First audio channel (or mono)
.secondary - Second audio channel in stereo

Stopping sessions independently

Each session can be stopped independently to retrieve its result:
let primaryResult = try await SleepCycleSdk.stopAnalysis(sessionId: primarySessionId)
let secondaryResult = try await SleepCycleSdk.stopAnalysis(sessionId: secondarySessionId)

Stopping the data source

After all sessions have been stopped, stop the data source:
await SleepCycleSdk.stopDataSource()
Calling stopDataSource() while sessions are still active will force-stop them and discard their results. To retrieve results, stop each session first.