VYSLO DEVELOPER PLATFORM
Back to vyslo.ai

IOS SDK · v0.1

iOS SDK

A Swift package with no third-party dependencies and the same surface as the Android SDK: the cloud path (authorization, the token store, recordings, insights, transcription and webhook verification) and a direct Bluetooth connection to the card: scan, pair, start and stop recording, card status and events, audio export, live audio, smart wake, voiceprint enrolment, firmware updates, and LED and haptic feedback.

零第三方依賴的 Swift 套件,能力面與 Android SDK 一致:雲通路(授權、令牌存儲、錄音、洞察、轉寫與 webhook 校驗),以及與卡片的藍牙直連:掃描、配對、起停錄音、卡片狀態與事件、音頻導出、實時音頻、智能喚醒、聲紋註冊、 固件升級,以及 LED 與振動反饋。

PlatformStatusNotes
iOSAVAILABLEiOS 15 and later, Swift 5.9. Swift Package or XCFramework.
BLE direct connectAVAILABLEScan, pair, start/stop capture, card events, audio export, Wi-Fi fast transfer.
Live audioAVAILABLEHold the card's button and speak; audio streams into your foreground app.
Device managementAVAILABLESmart wake, card name, voiceprint enrolment, firmware updates, delete and unbind with on-card confirmation.
LED and hapticsAVAILABLELED per card state, vibration per card event, saved on the card.
平台狀態說明
iOS可用iOS 15 及以上,Swift 5.9。Swift Package 或 XCFramework。
BLE 直連可用掃描、配對、起停錄音、卡片事件、音頻導出、Wi-Fi 快傳。
實時音頻可用按住卡片按鍵說話,音頻實時流入你的前台 App。
設備管理可用智能喚醒、卡片名稱、聲紋註冊、固件升級,刪除與解綁需在卡上確認。
LED 與振動可用按卡片狀態設置 LED、按卡片事件設置振動,保存在卡片上。

Install

Add the VysloOpen Swift package to your target, or drop in VysloOpen.xcframework; ask support@vyslo.ai for the current release. It uses platform frameworks only: Foundation, Security, CoreBluetooth, NetworkExtension and AudioToolbox.

把 VysloOpen Swift 套件加到你的 target,或直接放入 VysloOpen.xcframework;寫信到 support@vyslo.ai 索取當前版本。只用平台自帶框架:Foundation、Security、CoreBluetooth、NetworkExtension 與 AudioToolbox。

swift
import VysloOpen

// appId comes from POST /open/apps — see the Quickstart.
let vyslo = VysloOpen(appId: 42)

// Optional base URL for a self-hosted or staging instance:
// VysloOpen(appId: 42, baseURL: URL(string: "https://your-instance/api/v1/app")!)

vyslo.isAuthorized     // Bool — is a token stored?
vyslo.token            // String? — the raw capability token

The token is kept in the Keychain (service ai.vyslo.opensdk), so it survives restarts. Every network and Bluetooth call is async; call it from any task.

令牌保存在 Keychain(service ai.vyslo.opensdk),重啟後仍在。所有網絡與藍牙調用都是 async,在任意 Task 裡調用即可。

Info.plist and capabilities

xml · Info.plist
<!-- Open the VYSLO app for consent, and receive the callback -->
<key>LSApplicationQueriesSchemes</key>
<array><string>vyslo</string></array>
<key>CFBundleURLTypes</key>
<array><dict>
  <key>CFBundleURLSchemes</key><array><string>notesbot</string></array>
</dict></array>

<!-- Bluetooth to the card, and keep a connected card alive in the background -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Connect to your VYSLO card.</string>
<key>UIBackgroundModes</key>
<array><string>bluetooth-central</string></array>

<!-- Fast transfer over the card's Wi-Fi hotspot -->
<key>NSLocalNetworkUsageDescription</key>
<string>Copy recordings from your VYSLO card over its Wi-Fi.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Confirm that this iPhone joined the card's Wi-Fi.</string>

For fast transfer, also turn on the Hotspot Configuration capability for your app ID in Xcode.

要用 Wi-Fi 快傳,還需在 Xcode 裡為你的 App ID 打開 Hotspot Configuration 能力。

Authorization

swift
// 1. Send the user to the VYSLO app's consent screen.
let url = vyslo.authorizeURL(
    scopes: [.recordingsRead, .recordingsAudio],
    callbackURI: "notesbot://vyslo-auth"
)
UIApplication.shared.open(url) { opened in
    if !opened {
        // VYSLO app not installed — send them to install it.
    }
}

// 2. Handle the callback (SwiftUI shown; UIKit: scene(_:openURLContexts:)).
.onOpenURL { url in
    if vyslo.handleAuthorizationCallback(url) {
        // Token stored. Carry on.
    } else {
        // The user denied, or the callback carried no token.
    }
}

authorizeURL builds this; document it if you need to trigger authorization from somewhere the SDK cannot reach, such as a web page or another framework.

authorizeURL 構造的就是下面這條。如果你要從 SDK 碰不到的地方(網頁、其他框架)發起授權, 就按它拼。

deep link
vyslo://open/authorize
    ?appId=<your app id>
    &scopes=<scope bitmap>
    &callback=<url-encoded callback uri>

# approved  ->  <callback>?vyslo_token=<capability token>
# denied    ->  <callback>?vyslo_error=denied
THE CALLBACK MUST BE REGISTERED

callback has to match an entry in your app's callback allowlist — exactly, or with ?/# and your own parameters appended, so notesbot://vyslo-auth covers notesbot://vyslo-auth?state=abc. The server checks this before issuing, so nobody can borrow your appId and have the token delivered somewhere else. Register entries with callbackUris at creation or POST /open/apps/{appId}/callbacks later.

回跳地址必須先登記

callback 必須命中你應用的回跳白名單——完全相等,或在登記項後追加 ?/# 及你自己的參數,所以登記 notesbot://vyslo-auth 可命中 notesbot://vyslo-auth?state=abc。服務端在簽發前校驗,別人無法冒用你的 appId 把令牌送到別處。建立應用時用 callbackUris 登記,或之後調 POST /open/apps/{appId}/callbacks 更新。

Reading data

swift
let rec: VysloRecording = try await vyslo.fetchRecording(recordingID)

rec.recordingID
rec.title
rec.segments               // [VysloRecording.Segment] (speaker, text, timestamp, identity)
rec.summaryLines           // [String]
rec.identity               // VysloIdentity for the recording as a whole
rec.ownerVerified          // shorthand: identity.verdict == .owner

// Needs .recordingsAudio in the grant.
let audio: Data = try await vyslo.fetchRecordingAudio(recordingID)

Speaker identity

swift
let id: VysloIdentity = rec.segments[0].identity

id.verdict                 // .owner, .likelyOwner or .other
id.source                  // .device, .hostedReview or .selfHosted
id.evidence                // .verified, .unverified or .unavailable
id.serverID                // which VYSLO server issued this result
SHOW THE SOURCE WITH THE VERDICT

A verdict is only as strong as its source. .device with .verified evidence is a result the card signed for that recording; .hostedReview is the VYSLO cloud review; .selfHosted is a claim made by a server someone else runs. Voiceprint templates, embeddings and raw scores are never exposed.

展示判決時一併展示來源

判決的可信度取決於來源。.device 且證據為 .verified,表示由卡片為該錄音簽名的結果; .hostedReview 是 VYSLO 雲端複核;.selfHosted 是他人自行部署的服務器所作的聲明。 聲紋模板、embedding 與原始分數永不暴露。

Lists, insights and transcription

swift
// Needs .recordingsRead. Paged, newest first.
let page = try await vyslo.listRecordings(from: weekStart, to: .now, includesMe: true)
page.items                 // [VysloRecordingSummary]
page.nextCursor

// Needs .recordingsRead.
let hits = try await vyslo.searchTranscripts("budget", from: monthStart)

// Needs .insightsRead.
let week = try await vyslo.fetchWeeklyInsights()     // talk time, speaking share, topics, to-dos

// Needs .deviceStatusRead. Cloud snapshot, with observedAt.
let cards = try await vyslo.listDevices()

// Needs .transcriptionCreate. Any audio file, not only card recordings.
let job = try await vyslo.transcribe(fileURL, summarize: true)

Cloud status from listDevices() is the last snapshot the card reported through a phone, returned with observedAt; for live values connect over BLE.

listDevices() 返回的是卡片最近一次經手機上報的快照,附 observedAt;要實時值請經 BLE 連接。

BLE direct connection

The same VysloOpen instance talks to the card directly over Bluetooth LE with Core Bluetooth: scan, pair, start and stop recording, read card status, receive card events and pull audio off the card. The VYSLO app does not need to be running. The card checks your capability token itself, so the scopes the user approved are enforced on the card.

同一個 VysloOpen 實例經 Core Bluetooth 直接連接卡片:掃描、配對、起停錄音、讀取卡片狀態、接收卡片事件、從卡上導出音頻。運行時不需要 VYSLO App。卡片自己校驗你的能力令牌,用戶同意的權限在卡片上就被執行。

HOW IOS SHAPES THE CONNECTION

The first time your app reads protected data, iOS shows its own pairing prompt; if this iPhone is already paired with the card, for example through the VYSLO app, no prompt appears, and users remove a pairing in Settings → Bluetooth. With bluetooth-central enabled, a connected card keeps delivering events while your app is in the background, and the SDK restores the connection through Core Bluetooth state restoration when iOS relaunches your app. Background scans find cards by their service and run less often than in the foreground.

iOS 如何影響連接

你的 App 第一次讀取受保護數據時,iOS 會彈出系統配對框;若這台 iPhone 已與卡片配對(例如經 VYSLO App),則不會再彈。用戶在「設置 → 藍牙」裡移除配對。開啟 bluetooth-central 後,App 在後台時已連接的卡片仍會持續送達事件;iOS 重新拉起你的 App 時,SDK 會經 Core Bluetooth 狀態恢復重建連接。後台掃描按服務篩選卡片,頻率低於前台。

Scan and connect

swift
let scan = vyslo.ble.startScan { card in
    card.name                 // the name the user gave the card in the VYSLO app
    card.identifier           // stable per iPhone
    card.rssi
}
// … once the user has picked a card
scan.stop()

let session: VysloCardSession = try await vyslo.ble.connect(card)

// Needs .deviceStatusRead.
let s = try await session.status()
s.batteryPercent
s.isCharging
s.storageUsedBytes
s.firmwareVersion
s.isRecording
s.isListening               // smart wake is listening, nothing is being stored
s.smartWakeEnabled

session.close()               // disconnects and releases the control lease if you hold it

Start and stop recording

swift
// Needs .captureControl.
try await session.acquireControl()      // exclusive control lease; throws .leaseHeld if another app has it
try await session.startRecording()
try await session.pauseRecording()
try await session.resumeRecording()
try await session.stopRecording()
try await session.releaseControl()
ONE CONTROLLER AT A TIME

Several apps can be connected to the same card at once, and all of them can read status and receive events. Only the holder of the control lease can start or stop recording, and that includes the VYSLO app itself. The lease ends when you release it, close the session or lose the link. A recording the user starts with the card's own button is never blocked by a lease.

同一時間只有一個控制方

多個 App 可以同時連接同一張卡,都能讀狀態、收事件;只有持有控制權租約的一方能起停錄音——VYSLO App 自己也不例外。 主動釋放、關閉會話或連接斷開時,租約即結束。用戶用卡片按鍵發起的錄音,永遠不會被租約擋住。

Card events

swift
// Needs .eventsSubscribe.
for await event in session.events {
    switch event {
    case .recordingStarted:              break
    case .recordingStopped(let id):      _ = id
    case .smartWakeTriggered:            break
    case .listeningChanged(let on):      _ = on
    case .buttonPressed:                 break
    case .chargingChanged(let charging): _ = charging
    }
}

Events carry no audio and no transcript text.

事件裡不含音頻,也不含轉寫正文。

Pull audio off the card

swift
// Needs .recordingsRead to list, .recordingsAudio to export.
let onCard: [VysloCardRecording] = try await session.listRecordings()
// id, startedAt, duration, sizeBytes

try await session.exportAudio(
    recordingID: onCard[0].id,
    format: .wav,                          // .opus, .wav or .pcm
    to: documents.appendingPathComponent("recording.wav")
) { progress in /* 0.0 – 1.0 */ }
FormatWhat you get
.opusThe file exactly as the card stores it: Opus, 16 kHz, mono. Smallest and fastest
.wav16-bit PCM in a WAV container, 16 kHz, mono. Decoded on the phone
.pcmRaw 16-bit little-endian samples, 16 kHz, mono. Decoded on the phone
格式得到什麼
.opus卡上存儲的原樣檔案:Opus、16 kHz、單聲道。體積最小、最快
.wavWAV 封裝的 16 位 PCM,16 kHz、單聲道。在手機上解碼
.pcm原始 16 位小端採樣,16 kHz、單聲道。在手機上解碼

Hidden and rejected recordings are never listed or exported over BLE, the same rule as the cloud.

隱藏與被拒的錄音在 BLE 上同樣不會被列出、也不能導出,與雲端規則一致。

Fast transfer over Wi-Fi

swift
// Needs .recordingsAudio. For many or long recordings.
try await session.fastTransfer(
    recordingIDs: onCard.map(\.id),
    format: .opus,
    to: documents.appendingPathComponent("vyslo")
) { done, total in }

The card turns on its own Wi-Fi hotspot and the iPhone joins it through NEHotspotConfiguration; iOS asks the user to join each time, and the first transfer also asks for local network access. Bluetooth stays connected for control while the files move, and the card turns the hotspot off when the transfer ends.

卡片打開自己的 Wi-Fi 熱點,iPhone 經 NEHotspotConfiguration 加入;iOS 每次都會請用戶確認加入,第一次傳輸還會請求本地網絡權限。傳輸期間藍牙保持連接用於控制,傳完卡片自動關閉熱點。

Live audio

swift
// Needs .audioLive. Your app must be in the foreground.
let stream = try await session.startLiveAudio(format: .pcm) { frame in
    // 16 kHz mono frames while the user holds the card's button
}
stream.onPushToTalk = { pressed in }   // button down / up
stream.stop()

Hold the card's button and speak: audio streams into the app the user is looking at, for dictation, voice input or your own speech pipeline. While the stream is open the button works as push-to-talk instead of starting a recording; nothing is stored on the card, and the stream stops when your app leaves the foreground.

按住卡片按鍵說話,音頻實時流入用戶眼前的 App,可用於聽寫、語音輸入或你自己的語音處理鏈路。串流期間按鍵作為按住說話使用、不會發起錄音,卡上不存儲任何內容;你的 App 退到後台時串流自動停止。

What the card checks

BLE errors

BLE calls throw VysloBleError; switch on its reason:

BLE 調用拋出 VysloBleError,按 reason 分支:

reasonMeaning
.bluetoothOffBluetooth is off. Ask the user to turn it on
.permissionDeniedA runtime Bluetooth permission has not been granted
.notAuthorizedNo token is stored, or the card refused it: expired, revoked, another user's card, or a scope missing
.leaseHeldAnother app holds the control lease
.cardBusyThe card cannot take the request right now, for example during a firmware update
.disconnectedThe link dropped. Call connect(card) again
.notForegroundLive audio was requested while your app is not in the foreground
.confirmationTimeoutAn operation that needs a press on the card got none within 10 seconds, and was not carried out
reason含義
.bluetoothOff藍牙未開啟,請用戶打開
.permissionDenied運行時藍牙權限未授予
.notAuthorized沒有本地令牌,或卡片拒絕了它:過期、已撤銷、他人的卡,或缺少權限
.leaseHeld控制權租約在其他 App 手上
.cardBusy卡片暫時無法處理請求,例如正在升級固件
.disconnected連接斷開,重新調用 connect(card)
.notForeground你的 App 不在前台時請求了實時音頻
.confirmationTimeout需要在卡上按鍵確認的操作,10 秒內沒有收到按鍵,操作未執行

Device management

Everything the VYSLO app does to a card goes through these same calls: settings, voiceprint enrolment, firmware updates and deletion. All of them run over a BLE session and need .deviceManage.

VYSLO App 對卡片做的所有操作,走的都是這同一組調用:設置、聲紋註冊、固件升級與刪除。全部經 BLE 會話執行,需要 .deviceManage。

Smart wake and card settings

swift
try await session.updateSettings(smartWakeEnabled: true)
try await session.updateSettings(name: "Work card")

let s = try await session.status()
s.smartWakeEnabled
s.isListening

With smart wake on, the card listens for its owner's voice and starts recording only when it hears them; nothing is stored while it listens. With it off, the card records only when the button is pressed or an app starts a recording. Smart wake changes arrive as SmartWakeTriggered and ListeningChanged card events.

開啟智能喚醒後,卡片監聽機主的聲音,聽到機主說話才開始錄音;監聽期間不存儲任何內容。關閉後,卡片只在按鍵或 App 發起時錄音。智能喚醒的變化以 SmartWakeTriggered 與 ListeningChanged 卡片事件送達。

Voiceprint enrolment

swift
// About 12 seconds of the user's own voice. Needs network.
for try await step in session.enrollVoiceprint() {
    switch step {
    case .recording(let level): showLevel(level)   // the user speaks now
    case .processing:           showProgress()
    case .done:                 showDone()
    }
}

The card records the sample, the cloud builds the voiceprint and writes it back to the card, where smart wake and speaker identity use it. Replacing an existing voiceprint asks for a press on the card first.

卡片錄下樣本,雲端生成聲紋並寫回卡片,供智能喚醒與說話人身份使用。若卡上已有聲紋,替換前需要先在卡上按鍵確認。

Firmware updates

swift
if let update = try await session.checkFirmwareUpdate() {   // nil when up to date
    try await session.installFirmware(update, overWiFi: true) { progress in }
}

The card accepts only images VYSLO signed; the SDK fetches the release meant for this card. Over Wi-Fi the card downloads through its own hotspot, much faster than BLE. The new image takes over on the next restart, and if it does not come up healthy the card rolls back to the previous one by itself.

卡片只接受 VYSLO 簽名的鏡像;SDK 會取回適用於這張卡的版本。走 Wi-Fi 時卡片經自己的熱點下載,比 BLE 快得多。 新鏡像在下次重啟時接管;若啟動後不健康,卡片會自動回滾到上一個版本。

Deleting, unbinding and on-card confirmation

swift
try await session.deleteRecording(recordingID)

// These wait for a press on the card, then run.
try await session.deleteAllRecordings()
try await session.resetVoiceprint()
try await session.unbind()
THE USER CONFIRMS ON THE CARD

Deleting all recordings, resetting a voiceprint and unbinding put the card into a pending state: the LED shows the confirmation signal and the user has 10 seconds to press the button. No press, no action, and the call throws .confirmationTimeout. The rule is enforced by the card, so it applies to every app, the VYSLO app included.

由用戶在卡片上確認

刪除全部錄音、重置聲紋與解綁會讓卡片進入待確認狀態:LED 顯示確認提示,用戶需在 10 秒內按鍵。沒有按鍵就不執行, 調用拋出 .confirmationTimeout。這條規則由卡片執行,對所有 App 生效,包括 VYSLO App 自己。

LED and haptics

Choose how the card's LED looks in each state and how it vibrates on each event. Settings are saved on the card and keep working offline. Every connected app sees the same settings, and a change saved by one app is pushed to the others. Reading and writing both need .deviceManage.

按卡片狀態設置 LED 的樣子,按卡片事件設置振動方式。設置保存在卡片上,離線照樣生效。所有已連接的 App 看到的是同一份設置, 一個 App 保存後會推送給其他 App。讀取與寫入都需要 .deviceManage。

swift
let caps = try await session.feedback.capabilities()   // states, events, modes, ranges, effect catalogue
let current = try await session.feedback.settings()    // your overrides, defaults, resolved values, revision

try await session.feedback.updateSettings(
    FeedbackPatch()
        .setLED("recording.smartWakeOn",
                .breathe(color: RGB(0, 60, 100), brightnessPercent: 40,
                         periodMs: 2400, minBrightnessPercent: 0))
        .setLED("idle.smartWakeOff", .off)
        .setHaptic("recording.started", .effect("START_CONFIRM"))
        .setHaptic("recording.stopped.SILENCE",
                   .pulses(levelPercent: 60, onMs: 80, offMs: 120, count: 2)),
    expectedRevision: current.revision          // rejected if another app saved in between
)

// Try before saving. A preview never saves and never fakes a real event.
let preview = try await session.feedback.previewLED(profile, durationMs: 3000)
try await session.feedback.stopPreview(preview.id)
try await session.feedback.previewHaptic(pattern)

try await session.feedback.restoreDefaults(["idle.smartWakeOff"])   // or restoreDefaults() for all
for await change in session.feedback.changes { }   // saved elsewhere, defaults restored, state changed
try await session.feedback.status()                // the state being shown right now, and why

LED modes

ModeParameters
offNone. The LED is dark in this state; recording and power are unaffected
steadycolorRgb, brightnessPercent
breathecolorRgb, brightnessPercent, periodMs, minBrightnessPercent
blinkcolorRgb, brightnessPercent, onMs, offMs
模式參數
off無。該狀態下熄燈,不影響錄音與電源
steadycolorRgb、brightnessPercent
breathecolorRgb、brightnessPercent、periodMs、minBrightnessPercent
blinkcolorRgb、brightnessPercent、onMs、offMs

colorRgb takes 0–100 per channel and brightnessPercent 0–100. Times are multiples of timeQuantumMs from getCapabilities(). A value the card cannot show exactly is refused with the reason, never silently rounded.

colorRgb 每通道 0–100,brightnessPercent 0–100。時間須為 getCapabilities() 給出的 timeQuantumMs 的整數倍。卡片無法精確呈現的值會被拒絕並說明原因, 不會被悄悄取整。

LED states

StateWhenDefault
idle.smartWakeOn
idle.smartWakeOff
Standing bySteady, 2%
monitoringSmart wake is listeningSame as standby
recording.smartWakeOn
recording.smartWakeOff
RecordingBreathe, peak 30%, 2 s
recording.pausedRecording pausedSame as recording
enrollmentRecording a voiceprint sampleSame as recording
chargingChargingSteady amber
chargeCompleteCharge completeThe state underneath
lowBatteryBelow 10% and not chargingAmber blink, 500 ms on and off
pairingPairing window openThe state underneath
authorizationPendingWaiting for an on-card confirmationConfirmation signal; cannot be turned fully off
bondClearFeedbackUnbind completedRed blink, three times
powerOn
powerOff
Power-on and power-off windowsOff
狀態何時默認
idle.smartWakeOn
idle.smartWakeOff
待機常亮,2%
monitoring智能喚醒監聽中同待機
recording.smartWakeOn
recording.smartWakeOff
錄音中呼吸,峰值 30%,週期 2 秒
recording.paused錄音已暫停同錄音
enrollment正在錄聲紋樣本同錄音
charging充電中琥珀色常亮
chargeComplete充滿沿用下層狀態
lowBattery電量低於 10% 且未充電琥珀色閃爍,亮滅各 500 ms
pairing配對窗口打開沿用下層狀態
authorizationPending等待卡上確認確認提示;不能完全熄滅
bondClearFeedback解綁完成紅色閃爍三次
powerOn
powerOff
開機與關機窗口熄滅

The card picks the state from what is actually happening; your settings change only how that state looks. When several hold at once the higher one wins: unbind feedback, power off, confirmation pending, low battery, recording, power on, charging, charge complete, pairing, listening, standby. A state can also inherit another one, a specific key or @idle, @recording or @underlying (the next state down).

卡片按實際發生的事選擇狀態;你的設置只改變該狀態的呈現。多個狀態同時成立時取優先級高者:解綁反饋、關機、待確認、 低電量、錄音、開機、充電、充滿、配對、監聽、待機。狀態也可以繼承另一個狀態:具體的 key,或 @idle、 @recording、@underlying(下一層狀態)。

Haptic events

EventWhenDefault
recording.startedA recording actually starts, from the button, an app or smart wakeSTART_CONFIRM
recording.stoppedA recording stopsSTOP_CONFIRM
recording.stopped.<origin>Stopped for a specific reason: silence, smart wake turned off, a firmware update, and othersSame as recording.stopped
recording.paused
recording.resumed
Pause and resumeOff
monitoring.started
monitoring.ended
Smart wake starts or stops listeningOff
recording.discarded.l2Reject
recording.discarded.noSpeech
Smart wake heard someone else, or no speech, and kept nothingOff
recording.interrupted.<reason>A recording cut short by pairing, shutdown or a critical batteryOff
pairing.startedPairing window opensPAIRING
operation.rejectedThe card refused a request, per reason: low battery, busy, shutting downREJECT
power.on
power.off
power.off.criticalBattery
Power on and off by the button, or off on a critical batteryDouble buzz
事件何時默認
recording.started錄音真正開始:按鍵、App 或智能喚醒發起START_CONFIRM
recording.stopped錄音停止STOP_CONFIRM
recording.stopped.<origin>因特定原因停止:靜音、關閉智能喚醒、固件升級等同 recording.stopped
recording.paused
recording.resumed
暫停與繼續關閉
monitoring.started
monitoring.ended
智能喚醒開始或結束監聽關閉
recording.discarded.l2Reject
recording.discarded.noSpeech
智能喚醒聽到的是別人或沒有語音,未保留任何內容關閉
recording.interrupted.<reason>錄音因配對、關機或電量耗盡被中斷關閉
pairing.started配對窗口打開PAIRING
operation.rejected卡片拒絕了請求,可按原因區分:低電量、忙碌、正在關機REJECT
power.on
power.off
power.off.criticalBattery
按鍵開關機,或電量耗盡關機雙振

A haptic pattern is either an effect from the card's catalogue (getCapabilities() lists the ones this card's motor supports) or pulses: levelPercent 0–100, onMs, offMs and count. Limits come from getCapabilities(); anything outside them is refused with the reason, never silently changed.

振動方式可以是卡片效果目錄中的效果(getCapabilities() 列出這張卡的馬達支援哪些),也可以是脈衝: levelPercent 0–100、onMs、offMs 與 count。上下限由 getCapabilities() 給出;超出範圍的請求會被拒絕並說明原因,不會被悄悄改動。

FEEDBACK NEVER OVERRIDES PROTECTION

Battery protection, data protection and on-card confirmation cannot be switched off from feedback settings. A custom colour can make two states look alike, so show state in your own UI from status(), not from the LED.

反饋設置不會蓋過保護機制

電池保護、數據保護與卡上確認,都不能通過反饋設置關閉。自定義顏色可能讓兩個狀態看起來一樣,所以在你自己的界面裡請以 status() 顯示狀態,而不是看 LED。

Token lifecycle

swift
// Call on foreground start. Internally rate-limited:
// does nothing if the stored token is younger than minAgeDays.
try await vyslo.renewIfNeeded()                 // default 7 days
try await vyslo.renewIfNeeded(minAgeDays: 3)

vyslo.setToken(token)                           // adopt a token you obtained yourself
vyslo.clearToken()                              // forget it (e.g. on your own sign-out)
BehaviourValue
Token lifetime from issue60 days — the offline grace period
Renewal cadencerenewIfNeeded() acts once the token is 7 days old
User revoked, or app banned403 — the SDK clears the stored token, then throws
Invalid or expired token401 — the SDK clears the stored token, then throws
Where the user revokesVYSLO app → 我的 → 已授權的應用
行為取值
令牌自簽發起的有效期60 天——離線寬限期
續簽節奏renewIfNeeded() 在令牌滿 7 天後才動作
用戶撤銷或應用被封禁403——SDK 先清除本地令牌再拋異常
令牌無效或過期401——SDK 先清除本地令牌再拋異常
用戶在哪裡撤銷VYSLO App → 我的 → 已授權的應用

Errors

swift
do {
    let rec = try await vyslo.fetchRecording(recordingID)
} catch let error as VysloOpenError {
    switch error.statusCode {
    case 401, 403: reauthorize()     // token already cleared by the SDK
    case 404:      dropLocalCopy()   // hidden, deleted, or not this user's recording
    default:       retryLater(error.body)
    }
}

VysloOpenError carries statusCode and the raw response body. A 404 is deliberately indistinguishable between "does not exist" and "exists but is hidden or belongs to someone else".

VysloOpenError 帶 statusCode 與原始回應 body。404 故意不區分「不存在」與「存在但被隱藏或屬於他人」。

Webhook signature helper

swift
// Reference implementation — your server does the same thing in its own language.
let ok = VysloOpen.verifyWebhookSignature(
    webhookSecret: secret,
    rawBody: body,                         // exactly the bytes you received
    signature: request.value(forHTTPHeaderField: "X-Vyslo-Signature") ?? ""
)

Reference

Member Meaning
VysloOpen(appId:baseURL:)Construct; baseURL defaults to the official cloud
authorizeURL(scopes:callbackURI:)URL that opens the VYSLO consent screen
handleAuthorizationCallback(_:)Extract and store the token from the callback URL; true on success
renewIfNeeded(minAgeDays:)Rate-limited renewal; true if it actually renewed
fetchRecording(_:) / fetchRecordingAudio(_:)Transcript, summary, speaker identity / original audio
listRecordings(…) / searchTranscripts(…)Paged recording list / search across transcripts
fetchWeeklyInsights() / listDevices() / transcribe(_:summarize:)Weekly insights / cards on the account / transcribe any audio file
ble.startScan(_:) / ble.connect(_:)Scan for nearby cards / connect and present the token, returning a VysloCardSession
session.status() / session.eventsLive card status / card events as an AsyncStream
session.acquireControl() … stopRecording()Control lease and capture: start, pause, resume, stop
session.listRecordings() / exportAudio(…) / fastTransfer(…)Recordings on the card / export one over BLE / bulk over Wi-Fi
session.startLiveAudio(format:onFrame:)Push-to-talk live audio stream
session.updateSettings(…) / enrollVoiceprint() / installFirmware(…)Smart wake and name / voiceprint enrolment / signed firmware
session.deleteAllRecordings() / resetVoiceprint() / unbind()Need a press on the card
session.feedbackLED and haptics: capabilities, settings, preview, restore defaults, changes
VysloOpen.verifyWebhookSignature(…)HMAC-SHA256 verification reference
VysloScope.recordingsRead … .transcriptionCreate — see Scopes & tokens