Skip to main content

Installation

Add the Plaud SDK to your iOS project using the Swift Package Manager.
1

Add Package Dependency in Xcode

  1. With your project open in Xcode, navigate to File > Add Package Dependencies…
  2. In the search bar that appears, enter the repository URL: https://github.com/Plaud-AI/plaud-sdk.git
  3. Click Add Package and wait for Xcode to resolve the package.
Add Swift Package Dependency in Xcode
2

Import the SDK

Now, in any Swift file where you need to use the SDK, import the necessary modules.
import PenBleSDK
import PlaudDeviceBasicSDK
import PenWiFiSDK

Requirements

  • Supported Platforms: iOS 13.0+
  • Development Environment: Xcode 15.0+ and Swift 5.7+
  • Permissions: Your app’s Info.plist file must include keys for Bluetooth usage, such as NSBluetoothAlwaysUsageDescription.

Start Development

Initialization

The best place to initialize the SDK is within your AppDelegate or in a dedicated singleton manager class. This ensures it’s configured once when your app starts.

Parameters

ParameterTypeRequiredDescription
hostNameStringYour app identifier (e.g., “YourAppName”)
appKeyStringYour application key from developer platform
appSecretStringYour application secret from developer platform
bindTokenStringUnique identifier for user/session binding
extraDictionaryOptional extra parameters (e.g., language, customDomain)
partnerTokenStringUser-level access token from Partner API
Device-specific authentication:
  • NotePro / NotePins: Use partnerToken for authentication. For appKey/appSecret/bindToken, you can pass any non-empty placeholder value (e.g., “placeholder”)
  • Note / NotePin: Depends on firmware version. Newer firmware supports partnerToken, older firmware requires appKey + appSecret + bindToken
About partnerToken: The partnerToken is a user-level access token obtained from the Partner API. Each token is associated with a specific end-user in your application.
import UIKit

// In your AppDelegate or a central manager class
class YourAppManager {
    // Hold a strong reference to the device agent
    var deviceAgent: PlaudDeviceAgent?

    // Initialize with appKey/appSecret (for older Note/NotePin firmware)
    func initializeWithAppKey() {
        let agent = PlaudDeviceAgent.shared()
        agent.delegate = self 
        
        agent.initSDK(
            hostName: "YourAppName",
            appKey: "YOUR_APP_KEY",
            appSecret: "YOUR_APP_SECRET",
            bindToken: "YOUR_BIND_TOKEN",
            extra: [:],
            partnerToken: nil
        )
        
        self.deviceAgent = agent
    }
    
    // Initialize with partnerToken (for NotePro, NotePins, and newer Note/NotePin firmware)
    func initializeWithPartnerToken() {
        let agent = PlaudDeviceAgent.shared()
        agent.delegate = self 
        
        agent.initSDK(
            hostName: "YourAppName",
            appKey: "placeholder",        // Can be any non-empty value
            appSecret: "placeholder",     // Can be any non-empty value
            bindToken: "placeholder",     // Can be any non-empty value
            extra: [:],
            partnerToken: "YOUR_PARTNER_TOKEN"
        )
        
        self.deviceAgent = agent
    }
}

Delegate Callbacks Overview

To receive events from the SDK, you must implement the PlaudDeviceAgentDelegate protocol. This protocol provides feedback on everything from connection status to recording events.
It’s best practice to make your central manager class or a relevant view controller conform to this delegate protocol.
import Foundation
import PlaudDeviceBasicSDK // For BleDevice, BleFile types
import PenBleSDK

@objc public protocol PlaudDeviceAgentDelegate: AnyObject {
    
    // MARK: - Initialization & Connection
    
    /// Called with the result of the AppKey verification.
    @objc optional func plaud(didVerifyAppKeyWith result: Int)
    
    /// Reports the Bluetooth connection state.
    @objc optional func plaud(didUpdateConnectionState state: Int)
    
    /// Callback for the device binding (pairing) process.
    @objc optional func plaud(didBindDevice sn: String?, status: Int, protVersion: Int, timezone: Int)
    
    /// Unbind result.
    @objc optional func plaud(didUnbind status: Int)
    
    
    // MARK: - Device Discovery
    
    /// Called when Bluetooth devices are discovered during a scan.
    @objc optional func plaud(didDiscover devices: [BleDevice])
    
    /// Called when the device scan times out.
    @objc optional func plaudDidTimeoutScan()
    

    // MARK: - Device Status
    
    /// Provides the current state of the PLAUD device.
    @objc optional func plaud(deviceDidUpdateState state: Int, privacy: Int, keyState: Int, uDisk: Int, findMyToken: Int, hasSndpKey: Int, deviceAccessToken: Int)
    
    /// Provides the device's name.
    @objc optional func plaud(didReceiveDeviceName name: String?)
    
    /// Reports the device's storage capacity.
    @objc optional func plaud(didReceiveStorageInfo total: Int, free: Int, duration: Int)
    
    /// Reports a change in the device's battery level.
    @objc optional func plaud(didUpdateBatteryLevel power: Int, oldPower: Int)
    
    /// Reports the device's charging status.
    @objc optional func plaud(didUpdateChargingState isCharging: Bool, level: Int)
    
    /// Provides the microphone's current gain value.
    @objc optional func plaud(didReceiveMicGain value: Int)
    
    
    // MARK: - Recording Control
    
    /// Confirms that recording has started.
    @objc optional func plaud(didStartRecordingWithSessionId sessionId: Int, start: Int, status: Int, scene: Int, startTime: Int, reason: Int)
    
    /// Confirms that recording has stopped.
    @objc optional func plaud(didStopRecordingWithSessionId sessionId: Int, reason: Int, fileExist: Bool, fileSize: Int)
    
    /// Confirms that recording has been paused.
    @objc optional func plaud(didPauseRecordingWithSessionId sessionId: Int, reason: Int, fileExist: Bool, fileSize: Int)
    
    /// Confirms that a paused recording has resumed.
    @objc optional func plaud(didResumeRecordingWithSessionId sessionId: Int, start: Int, status: Int, scene: Int, startTime: Int)
    
    
    // MARK: - File Management
    
    /// Provides the list of files stored on the device.
    @objc optional func plaud(didReceiveFileList files: [BleFile])
    
    /// Reports the result of a file deletion attempt.
    @objc optional func plaud(didDeleteFileWithSessionId sessionId: Int, status: Int)
    
    
    // MARK: - Data Transfer (Download)
    
    /// Indicates the start of a file download.
    @objc optional func plaud(didStartSyncingFileHead sessionId: Int, status: Int)
    
    /// Indicates the end of a file download.
    @objc optional func plaud(didFinishSyncingFileTail sessionId: Int, crc: Int)
    
    /// Delivers chunks of file data during a download.
    @objc optional func plaud(didReceiveDataChunk sessionId: Int, start: Int, data: Data)

    /// Reports download progress for a composite file.
    @objc optional func plaud(didDownloadFile sessionId: Int, desiredOutputPath: String, status: Int, progress: Int, tips: String)
    
    /// Called when a file download is manually stopped.
    @objc optional func plaudDidStopDownloadingFile()
    
    
    // MARK: - Wi-Fi Configuration
    
    /// Reports the result of setting a Wi-Fi configuration.
    @objc optional func plaud(didSetConfigForWifiSync result: Int)

    /// Provides the list of saved Wi-Fi networks.
    @objc optional func plaud(didReceiveWifiSyncList list: [UInt32])
    
    /// Reports the result of a Wi-Fi connection test.
    @objc optional func plaud(didReceiveWifiTestResult index: UInt32, result: Int, rawCode: Int)
    
    
    // MARK: - Firmware Updates (OTA)
    
    /// Reports the result of a firmware update (OTA).
    @objc optional func plaud(didFinishOtaWithResult uid: Int, status: Int, errmsg: String?)
    
    /// The device is requesting a specific chunk of the firmware file.
    @objc optional func plaud(deviceDidRequestOtaPacket uid: Int, start: Int, end: Int)
}

Methods

Here are some of the most common methods you’ll use to interact with the device.

Device Scanning

To discover nearby PLAUD devices, start a scan. The results will be delivered to the plaud(didDiscover:) delegate method.
// Get the shared agent instance
guard let deviceAgent = self.deviceAgent else { return }

// Start scanning for devices
deviceAgent.startScan()

// Implement the delegate method in your class to receive the results
// extension YourViewController: PlaudDeviceAgentDelegate {
//     func plaud(didDiscover devices: [BleDevice]) {
//         // Sort devices by signal strength (strongest first)
//         let sortedDevices = devices.sorted { $0.rssi > $1.rssi }
        
//         // Update your UI with the sorted list of devices
//         self.discoveredDevices = sortedDevices
//         self.tableView.reloadData()
//         print("Discovered devices: \(sortedDevices.map { $0.name ?? "Unknown" })")
//     }
// }

Connect to a Device

Once a device has been discovered, you can connect to it using its BleDevice object.
// Assume `selectedDevice` is a `BleDevice` object from the scan results
func connect(to device: BleDevice) {
    guard let deviceAgent = self.deviceAgent else { return }
    deviceAgent.connect(device)
}

// Implement the delegate method to handle connection state changes
// func plaud(didUpdateConnectionState state: Int) {
//     let message: String
//     switch state {
//     case 0:
//         message = "Device disconnected or failed to connect."
//     case 1:
//         message = "Device connected successfully!"
//     case 2:
//         message = "Device connection failed."
//     default:
//         message = "Unknown connection state."
//     }
//     print(message)
//     // Update your UI to reflect the new connection status
// }

Disconnect Device

Disconnect from the currently connected device while keeping the binding relationship.
guard let deviceAgent = self.deviceAgent else { return }
deviceAgent.disconnect()

Unbind Device

Unbind (depair) the device, removing the binding relationship. After unbinding, the device needs to be re-paired.
guard let deviceAgent = self.deviceAgent else { return }
deviceAgent.unbind()

// Implement the delegate method to handle unbind result
// func plaud(didUnbind status: Int) {
//     if status == 0 {
//         print("Device unbound successfully")
//     } else {
//         print("Failed to unbind device. Status: \(status)")
//     }
// }
Disconnect vs Unbind:
  • disconnect(): Only disconnects Bluetooth connection. The device remains bound and can reconnect without re-pairing.
  • unbind(): Removes the binding relationship. The device must go through the pairing process again to connect.

Get File List

Retrieve the list of recording files stored on the device.
guard let deviceAgent = self.deviceAgent else { return }

// Get all files (startSessionId = 0)
deviceAgent.getFileList(startSessionId: 0)

// Get files after a specific sessionId
deviceAgent.getFileList(startSessionId: 1234567890)

// Get a single file by sessionId
deviceAgent.getFile(sessionId: 1234567890)

// Implement the delegate method to receive the file list
// func plaud(didReceiveFileList files: [BleFile]) {
//     for file in files {
//         print("File: sessionId=\(file.sessionId), size=\(file.fileSize)")
//     }
// }

Start Recording

Send a command to the connected device to begin recording.
guard let deviceAgent = self.deviceAgent else { return }
deviceAgent.startRecord()

// Implement the delegate method to confirm recording has started
// func plaud(didStartRecordingWithSessionId sessionId: Int, status: Int, ...) {
//     if status == 0 {
//         print("Recording started successfully with session ID: \(sessionId)")
//     } else {
//         print("Failed to start recording. Status code: \(status)")
//     }
//     // Update your recording UI
// }

Stop Recording

Send a command to the connected device to stop the current recording.
guard let deviceAgent = self.deviceAgent else { return }
deviceAgent.stopRecord()

// Implement the delegate method to confirm recording has stopped
// func plaud(didStopRecordingWithSessionId sessionId: Int, reason: Int, ...) {
//     // Reason codes indicate why recording stopped:
//     // 1: Stopped by device button
//     // 2: Stopped by app command
//     // 3: Stopped due to auto-split feature
//     // 4: Stopped by physical mode switch
//     print("Recording stopped for session \(sessionId). Reason code: \(reason)")
//     // Update your UI and potentially start file download
// }

Export Audio

Export a recording file to a specified format. This method handles downloading from device, E2EE decryption (if needed), and format conversion automatically.
guard let deviceAgent = self.deviceAgent else { return }

let sessionId = 1234567890
let outputDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].path

deviceAgent.exportAudio(
    sessionId: sessionId,
    outputDir: outputDir,
    format: .wav,  // .wav (recommended) or .pcm
    channels: 1,   // 1 = mono, 2 = stereo
    callback: self
)

// Implement AudioExportCallback protocol
// extension YourViewController: AudioExportCallback {
//     func onProgress(_ progress: Int, message: String) {
//         print("Export progress: \(progress)% - \(message)")
//     }
//     
//     func onComplete(outputPath: String) {
//         print("Export completed: \(outputPath)")
//     }
//     
//     func onError(_ error: String) {
//         print("Export failed: \(error)")
//     }
// }

// Get supported export formats
let formats = PlaudDeviceAgent.getSupportedExportFormats()
// Returns: [.wav, .pcm]
Export process:
  1. Check if local cache exists
  2. If not, download from device via BLE
  3. Decrypt E2EE encryption (if applicable)
  4. Convert to target format (WAV/PCM)

Export Audio via Wi-Fi

Export a recording file using Wi-Fi Fast Transfer for faster download speeds. The API signature is identical to exportAudio, making it easy to switch between BLE and Wi-Fi transfer channels.
Prerequisites: Wi-Fi must be connected and handshake completed before calling this method. See the Wi-Fi Fast Transfer section below for connection setup.
// Ensure Wi-Fi is connected and ready
guard PlaudWiFiAgent.shared.isConnected else {
    print("WiFi not connected")
    return
}

let sessionId = 1234567890
let outputDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].path

PlaudWiFiAgent.shared.exportAudioViaWiFi(
    sessionId: sessionId,
    outputDir: outputDir,
    format: .wav,  // .wav (recommended) or .pcm
    channels: 1,   // 1 = mono, 2 = stereo
    callback: self
)

// Implement AudioExportCallback protocol (same as exportAudio)
// extension YourViewController: AudioExportCallback {
//     func onProgress(_ progress: Int, message: String) {
//         print("Export progress: \(progress)% - \(message)")
//     }
//     
//     func onComplete(outputPath: String) {
//         print("Export completed: \(outputPath)")
//     }
//     
//     func onError(_ error: String) {
//         print("Export failed: \(error)")
//     }
// }
Export via Wi-Fi process:
  1. Check if local cache exists
  2. If not, download from device via Wi-Fi (high-speed)
  3. Decrypt E2EE encryption (if applicable)
  4. Convert to target format (WAV/PCM)
The only difference from exportAudio is that the download step uses Wi-Fi instead of BLE, resulting in much faster transfer speeds.

Delete File

Delete a recording file from the device.
guard let deviceAgent = self.deviceAgent else { return }

// Delete a file by sessionId
deviceAgent.deleteFile(sessionId: 1234567890)

// Implement the delegate method to handle deletion result
// func plaud(didDeleteFileWithSessionId sessionId: Int, status: Int) {
//     if status == 0 {
//         print("File deleted successfully")
//     } else {
//         print("Failed to delete file. Status: \(status)")
//     }
// }

Wi-Fi Fast Transfer

Wi-Fi Fast Transfer enables high-speed file transfer between the Plaud device and your app via the device’s built-in Wi-Fi hotspot.
Wi-Fi Fast Transfer requires the PenWiFiSDK module (already included in import PenWiFiSDK above). Ensure the device is connected via Bluetooth before initiating Wi-Fi Fast Transfer. On iOS 13+, location permission (NSLocationWhenInUseUsageDescription) is required to detect the current Wi-Fi SSID.

Integration Overview

1

Open Device Wi-Fi Hotspot

Use the Bluetooth connection to instruct the device to enable its Wi-Fi hotspot.
2

Receive Hotspot Credentials

The device returns the hotspot name (SSID) and password via the bleWiFiOpen callback.
3

Connect to Device Hotspot

Use PlaudWiFiAgent to connect your app to the device’s Wi-Fi hotspot.
4

Transfer Files

Once connected and handshake is complete, use file operation APIs to list, download, and manage files.

Step 1: Open Device Wi-Fi Hotspot

After the device is connected via Bluetooth, enable its Wi-Fi hotspot:
// Ensure Bluetooth is connected first
guard PlaudDeviceAgent.shared().isConnected() else {
    print("Bluetooth not connected")
    return
}

// Open the device Wi-Fi hotspot
PlaudDeviceAgent.shared().setDeviceWiFi(open: true)
Receive the hotspot credentials in the PlaudDeviceAgentProtocol delegate:
/// Device Wi-Fi hotspot opened callback
/// - Parameters:
///   - status: 0 = success, 1 = recording in progress, 2 = USB disk mode active
///   - wifiName: Device hotspot SSID
///   - wholeName: Full device name (with SN suffix)
///   - wifiPass: Device hotspot password
func bleWiFiOpen(_ status: Int, _ wifiName: String, _ wholeName: String, _ wifiPass: String) {
    if status == 0 {
        // Pass the BLE device reference to WiFi agent
        PlaudWiFiAgent.shared.bleDevice = BleAgent.shared.bleDevice
        // Proceed to connect WiFi
        connectToDeviceWiFi(wifiName, wifiPass)
    }
}

Step 2: Set Up PlaudWiFiAgent

Set the delegate to receive Wi-Fi transfer callbacks:
// Set delegate
PlaudWiFiAgent.shared.delegate = self

// Implement PlaudWiFiAgentProtocol
class YourViewController: UIViewController, PlaudWiFiAgentProtocol {
    // Implement delegate methods below...
}

Step 3: Connect to Device Wi-Fi

/// Connect to the device's Wi-Fi hotspot
/// - Parameters:
///   - ssid: Wi-Fi hotspot name (from bleWiFiOpen callback)
///   - passphrase: Wi-Fi hotspot password (from bleWiFiOpen callback)
///   - overtimeSec: Connection timeout in seconds (default: 60)
if #available(iOS 11.0, *) {
    PlaudWiFiAgent.shared.connectWifi(wifiName, wifiPass, 60)
} else {
    // For iOS < 11.0, use listenPort and guide user to Settings
    PlaudWiFiAgent.shared.listenPort(wifiName, 30)
}

Step 4: Handle Connection Callbacks

/// Wi-Fi connection status change
func wifiConnectionStatus(_ ssid: String, _ connected: Bool) {
    if connected {
        print("Connected to device Wi-Fi: \(ssid)")
    } else {
        print("Wi-Fi connection failed: \(ssid)")
    }
}

/// Handshake result - connection is ready for file operations when status == 0
func wifiHandshake(_ status: Int) {
    if status == 0 {
        print("Wi-Fi handshake successful, ready for file transfer")
    }
}

PlaudWiFiAgentProtocol Reference

The key delegate callbacks for Wi-Fi transfer:
CallbackDescription
wifiConnectionStatus(_ ssid:, _ connected:)Wi-Fi connection status changed
wifiHandshake(_ status:)Handshake result (0 = success)
wifiFileList(_ files:)File list received
wifiFileListFail(_ status:)Failed to get file list
wifiFileDelete(_ sessionId:, _ status:)File deletion result (0 = success)
wifiPower(_ power:, _ voltage:)Device battery level and voltage
wifiCommonErr(_ cmd:, _ status:)General error occurred
wifiClientFail()WebSocket connection lost unexpectedly
wifiClose(_ status:)Wi-Fi closed (-1 = error, -2 = timeout, -3 = system error)

Wi-Fi Status Properties

// Check if Wi-Fi is connected
let connected = PlaudWiFiAgent.shared.isConnected

// Check if currently downloading a file
let downloading = PlaudWiFiAgent.shared.isDownloading

// Get current download speed (KB/s)
let speedKBps = PlaudWiFiAgent.shared.currentDownloadSpeedKBps

// Get formatted download speed string (e.g., "1.5 MB/s" or "500.0 KB/s")
let speedString = PlaudWiFiAgent.shared.getFormattedDownloadSpeed()

// Get current connected Wi-Fi name
let wifiName = PlaudWiFiAgent.shared.getCurrentWiFiName()

// Check WebSocket connection status
let wsConnected = PlaudWiFiAgent.shared.isWebSocketConnected()

Get File List via Wi-Fi

Prerequisite: Wi-Fi must be connected and handshake completed. See Wi-Fi Fast Transfer above.
PlaudWiFiAgent.shared.getFileList(Int(Date().timeIntervalSince1970), 0)

// Callback - file list received
func wifiFileList(_ files: [BleFile]) {
    for file in files {
        print("File sessionId: \(file.sessionId), size: \(file.size) bytes")
    }
}

// Callback - file list retrieval failed
func wifiFileListFail(_ status: Int) {
    print("Failed to get file list, error: \(status)")
}

Delete File via Wi-Fi

Prerequisite: Wi-Fi must be connected and handshake completed. See Wi-Fi Fast Transfer above.
// Delete a recording file on the device
PlaudWiFiAgent.shared.deleteFile(bleFile.sessionId)

// Delete result callback
func wifiFileDelete(_ sessionId: Int, _ status: Int) {
    // status 0 = deleted successfully
}

Disconnect Wi-Fi and Reconnect BLE

After Wi-Fi file transfer is complete, the device will automatically close its Wi-Fi hotspot after approximately 15 seconds of inactivity. You should handle this transition by disconnecting from the hotspot and reconnecting via BLE.
Do not call PlaudWiFiAgent.shared.disconnect() directly. This may cause certain device models to enter an abnormal state. Use NEHotspotConfigurationManager to disconnect from the hotspot instead.
import NetworkExtension

// Step 1: Remove the Wi-Fi hotspot configuration to disconnect the phone from the device hotspot
if #available(iOS 11.0, *) {
    NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: wifiName)
}

// Step 2: Wait ~15 seconds for the device to close Wi-Fi and resume BLE advertising
DispatchQueue.main.asyncAfter(deadline: .now() + 15.0) {
    // Step 3: Start a BLE scan to find the device
    PlaudDeviceAgent.shared().startScan()
}

// Step 4: In the scan callback, find the target device by serial number and reconnect
// func bleScanResult(bleDevices: [BleDevice]) {
//     for device in bleDevices {
//         if device.serialNumber == targetSerialNumber {
//             PlaudDeviceAgent.shared().stopScan()
//             PlaudDeviceAgent.shared().connectBleDevice(bleDevice: device)
//             return
//         }
//     }
// }
After Wi-Fi transfer, you must perform a BLE scan and use the newly discovered BleDevice object to reconnect. Do not reuse the BleDevice object from before the Wi-Fi transfer.

Example Implementation

A complete example iOS app using this SDK can be found in the official repository.
For a full, working example, please refer to the demo app in the Plaud iOS SDK repository. The app demonstrates:
  • Device scan, connect, and disconnect flows
  • Recording control (start, stop, pause, resume)
  • Recording file management (list, download, delete)
  • Device Wi-Fi configuration and testing
  • Triggering transcription and summary services