Model Exchange Protocol
The Model Exchange Protocol enables your application to securely request and receive configured language models from FlowDown. Instead of asking users to copy-paste sensitive credentials, you can use the FlowDownModelExchange framework to handle the secure handshake and data transfer.

::: info Version Requirement This protocol requires FlowDown version 4.1.4 or later installed on the user's device. :::
Installation
Currently, FlowDownModelExchange is distributed as part of the FlowDown source tree and not hosted on a separate public repository. You need to integrate it manually.
- Locate the directory
Frameworks/FlowDownModelExchangein the FlowDown repository. - Copy the entire folder into your project workspace.
- Add it as a Local Package in Xcode (File -> Add Package Dependencies -> Add Local...), or directly include the source files in your target.
Usage
1. Initialization
First, initialize a key pair. You should persist this key pair if you want to maintain the same identity across sessions, or generate a new one for each session.
import FlowDownModelExchange
// Generate a new key pair for encryption and signing
let keyPair = ModelExchangeKeyPair()
// Initialize the request builder
let builder = ModelExchangeRequestBuilder(
callbackScheme: "my-app-scheme", // Your app's URL scheme
keyPair: keyPair
)
2. Start the Handshake
The protocol requires a handshake before requesting models. First, open FlowDown with the handshake URL to establish a verified session.
func startHandshake() {
guard let url = builder.makeHandshakeURL() else {
print("Handshake URL unavailable")
return
}
// Open FlowDown to begin verification
UIApplication.shared.open(url)
}
3. Handle Callbacks
Configure your app to handle the callback URL (e.g., in onOpenURL or AppDelegate). The protocol has three stages: verification, completed, and cancelled.
var sessionId: String?
func handleCallback(url: URL) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems else { return }
let params = Dictionary(uniqueKeysWithValues: queryItems.compactMap { item -> (String, String)? in
guard let value = item.value else { return nil }
return (item.name.lowercased(), value)
})
switch params["stage"]?.lowercased() {
case "verification":
handleVerification(params: params)
case "completed":
handleCompletion(params: params)
case "cancelled":
sessionId = nil
print("User cancelled in FlowDown")
default:
print("Unknown callback stage")
}
}
4. Handle Verification and Request Models
When FlowDown calls back with stage=verification, validate the session and public key, then send the actual exchange request.
func handleVerification(params: [String: String]) {
guard let session = params["session"] else {
print("Missing session in verification")
return
}
guard let pk = params["pk"], pk == keyPair.encodedPublicKey else {
print("Public key mismatch in verification")
return
}
sessionId = session
do {
let signed = try builder.makeExchangeURL(
session: session,
appName: "My Awesome App",
reason: "To assist with coding tasks",
capabilities: [.audio, .developerRole],
multipleSelection: false
)
UIApplication.shared.open(signed.url)
} catch {
print("Failed to build request: \(error)")
}
}
5. Decrypt Payload
Once you receive the completed callback, use the framework to decode and decrypt the payload.
func handleCompletion(params: [String: String]) {
guard let payloadString = params["payload"],
let session = params["session"],
session == sessionId else { return }
do {
// 1. Decode the encrypted payload wrapper
let encryptedPayload = try ModelExchangeEncryptedPayload.decode(from: payloadString)
// 2. Decrypt the data using your key pair
let decryptedData = try ModelExchangeCrypto.decrypt(
encryptedPayload,
with: keyPair
)
// 3. Parse the model data (format is indicated by params["format"], usually "plist")
let models = try PropertyListSerialization.propertyList(
from: decryptedData,
options: [],
format: nil
)
print("Received models: \(models)")
} catch {
print("Decryption failed: \(error)")
}
sessionId = nil
}
Security
The framework handles the security details for you:
- Signatures: Requests are signed with your private key to prevent tampering.
- Encryption: Data is encrypted using
ChaChaPoly(Symmetric) andCurve25519(Key Agreement). - Forward Secrecy: Ephemeral keys are used for the exchange session.
Data Format
The decrypted data is a serialized Property List (Plist) containing:
- Model display names
- Endpoint URLs
- Authentication headers (including sensitive credentials)
- Model capabilities
Treat this data with the highest level of security. Do not log sensitive fields in production.