Skip to main content
This is the lookup reference for the FlowPilot iOS SDK. Every symbol here is declared public in the SDK source. The guide pages (Getting started through Offline and performance) teach how to use them in context; this page is the index. Internal types are not listed, because you cannot call them.
This reference reflects FlowPilot iOS SDK v1.0.0 (FlowPilotSDK.version). Public symbols can change between versions, so check the version string in your installed package if a signature here does not match.

Setup

Configure the SDK once at launch, then reach everything else through FlowPilot.shared. See Configuration.
// Configure the SDK. Pass a FlowPilotConfiguration. No-ops (logs an error) if
// the API key does not start with "fp_", leaving FlowPilot.shared nil.
public static func configure(_ configuration: FlowPilotConfiguration)

// The shared instance. nil until configure(_:) succeeds.
public private(set) static var shared: FlowPilot?

// Merge new key/value pairs into the SDK context for future resolves.
public func updateContext(_ context: SDKContext)

FlowPilotConfiguration

A public struct. Only apiKey and appId are required; everything else has a default.
ParameterTypeDefaultNotes
apiKeyStringrequiredWorkspace SDK key. Must start with fp_.
appIdStringrequiredThe app’s FlowPilot App ID.
environmentFlowPilotEnvironment.productionPicks the base URL.
contextSDKContext?nilInitial SDK context for variable resolution and targeting.
cachingEnabledBooltrueDisk cache for resolved flows.
cacheDirectoryString?nilCustom flow cache directory.
resolveTimeoutTimeInterval4.0Hard wall-clock deadline for a resolve. Clamped to a minimum of 0.5.
bundledFlows[String: String][:]Placement key to main-bundle JSON resource name.
bundledFlowAssets[String: BundledFlowAssets][:]Offline image/font assets per placement.
mediaPreloadingEnabledBooltruePreload images in screen order when a session starts.
imageMemoryCacheSizeInt50 * 1024 * 1024Image memory cache cap, in bytes (50MB).
imageDiskCacheSizeInt200 * 1024 * 1024Image disk cache cap, in bytes (200MB).
debugModeBool?nilDebug override.
logLevelFlowPilotLogLevel.errorSDK log verbosity.
prefetchOnLaunch[String][]Placement keys to warm in the background after configure(_:). No-op when caching is off.
prefetchMediaStrategyPrefetchMediaStrategy.firstScreenHow aggressively launch prefetch (and prefetch(_:warmMedia: true)) warms images.

FlowPilotEnvironment

public enum FlowPilotEnvironment: Sendable, Equatable {
    case development   // https://dev-api.flowpilot.io/v1
    case staging       // https://staging-api.flowpilot.io/v1
    case production    // https://api.flowpilot.io/v1
    case custom(url: String)
}

FlowPilotLogLevel

public enum FlowPilotLogLevel: Int, Comparable, Sendable {
    case none = 0
    case error = 1
    case warn = 2
    case info = 3
    case debug = 4
    case verbose = 5
}

PrefetchMediaStrategy

public enum PrefetchMediaStrategy: Sendable {
    case none          // JSON + fonts only
    case firstScreen   // + first-screen & persistent-zone images (default)
    case allScreens    // + every screen's images
}
Set via FlowPilotConfiguration.prefetchMediaStrategy. Governs launch prefetch (prefetchOnLaunch) and bounds the screen window when a prefetch(_:warmMedia: true) call opts into media warming. See Prefetching.

SDKContext

// Host-provided key/value context for variable resolution and targeting.
public typealias SDKContext = [String: Any]

Presenting

Methods on FlowPilot.shared. The presentPlacement overloads are UIKit-only (#if canImport(UIKit)). See Presenting placements, SwiftUI integration, and Prefetching.
// Resolve and present (UIKit). Returns the result.
@MainActor
public func presentPlacement(
    _ placementKey: String,
    from viewController: UIViewController,
    options: PresentationOptions? = nil
) async throws -> FlowResult

// Resolve and present (UIKit), with a completion closure instead of a return.
@MainActor
public func presentPlacement(
    _ placementKey: String,
    from viewController: UIViewController,
    options: PresentationOptions? = nil,
    completion: ((FlowResult) -> Void)? = nil
) async throws

// Never-throwing present (UIKit). Walks the full fallback chain, then presents
// your `fallback` view controller if FlowPilot has nothing to show.
@MainActor
@discardableResult
public func presentPlacement(
    _ placementKey: String,
    from viewController: UIViewController,
    fallback: @escaping @MainActor () -> UIViewController,
    options: PresentationOptions? = nil
) async -> FlowResult

// SwiftUI: build a session you drive with FlowPresenterView / .flowPresenter.
@MainActor
public func createSession(
    placementKey: String,
    additionalContext: SDKContext? = nil,
    preloadMedia: Bool? = nil
) async throws -> FlowSession

// SwiftUI: non-throwing counterpart. Returns nil when nothing is presentable.
@MainActor
public func resolveSession(
    placementKey: String,
    additionalContext: SDKContext? = nil,
    preloadMedia: Bool? = nil
) async -> FlowSession?

// True if a flow is cached or resolves for this placement. Not @MainActor.
// Routes through the cache-populating path, so the resolve is not wasted: a
// following present hits the cache.
public func isPlacementReady(_ placementKey: String) async -> Bool

// Warm the cache for one or more placements. Best-effort, concurrent, never
// throws; returns a per-placement outcome map. Pass warmMedia: true to also warm
// images (bounded by prefetchMediaStrategy). @discardableResult.
@discardableResult
public func prefetch(
    _ placementKeys: [String],
    warmMedia: Bool? = nil
) async -> [String: PrefetchOutcome]

PrefetchOutcome

public struct PrefetchOutcome: Sendable {
    public enum State: Sendable {
        case warmed(fromCache: Bool)   // a presentable flow is cached and ready
        case noFlow                    // resolve ok, backend has nothing to show
        case failed(FlowPilotError)    // resolve failed; nothing warmed
    }
    public let placementKey: String
    public let state: State
    public let mediaWarmed: Bool       // first/all-screen images warmed
    public var isWarmed: Bool          // convenience: true when state is .warmed
}
Returned per placement by prefetch(_:warmMedia:). See Prefetching.

PresentationOptions

public struct PresentationOptions: Sendable {
    public let additionalContext: SDKContext?
    public let presentationStyle: PresentationStyle?
    public let animated: Bool

    public init(
        additionalContext: SDKContext? = nil,
        presentationStyle: PresentationStyle? = nil,
        animated: Bool = true
    )
}

PresentationStyle

public enum PresentationStyle: String, Sendable {
    case fullScreen
    case modal
    case bottomSheet
}
presentationStyle is accepted by PresentationOptions but is not applied in the current SDK build. Flows present full screen (FlowHostingController forces .fullScreen). Setting .modal or .bottomSheet has no effect today. See Presenting placements.

PlacementInfo

public struct PlacementInfo: Sendable {
    public let placementId: String
    public let placementKey: String
    public let willShow: Bool
    public let flowId: String?
    public let experimentId: String?
    public let variantId: String?
}
PlacementInfo is declared public, but no public API returns it in v1.0.0. To check whether a placement will show, use isPlacementReady(_:) instead.

Session

FlowSession is the live, observable handle to a presented flow. It is a public final class conforming to ObservableObject and Identifiable. See SwiftUI integration, Variables and SDK context, and Media preloading.

Published state

public private(set) var flow: ResolvedFlow
public let placementId: String?

@Published public private(set) var currentScreen: ScreenNode?
@Published public private(set) var isActive: Bool
@Published public private(set) var mediaPreloadProgress: Double   // 0.0 ... 1.0
@Published public private(set) var isMediaPreloaded: Bool

public var id: String   // Identifiable
currentScreen exposes a ScreenNode whose public fields are id, kind, name, screenType, props, and layout.

Methods

// Variables
public func getVariable(_ key: String) -> VariableValue?
public func getAllVariables() -> [String: VariableValue]
@discardableResult public func setVariable(_ key: String, value: VariableValue) -> Bool
public func resetVariables(sdkContext: SDKContext? = nil)

// Media preloading
public func waitForMediaPreload() async
public func cancelMediaPreload()

// Conversions
public func trackConversion(
    amount: Double,
    currency: String,
    productId: String? = nil,
    metadata: [String: Any]? = nil
)

// Lifecycle
public func startNavigation()
public func waitForCompletion() async -> FlowResult
public func dismiss()
public func failPresentation()
public func navigate(to screenNodeId: String)

// Live mirror hot-replace (used by the preview tooling)
@MainActor public func replaceFlow(_ newDefinition: FlowDefinition, fonts: [FontFile]? = nil)

// Preview-only initializer (skips resolution, suppresses analytics)
public convenience init(
    flow: ResolvedFlow,
    suppressAnalytics: Bool = true,
    sdkContext: SDKContext? = nil
)

// Deprecated: use startNavigation() then waitForCompletion()
@available(*, deprecated)
public func start() async -> FlowResult
The engine objects inside a session (variableStore, navigationController, actionExecutor, and so on) are internal. Use the delegating methods above instead of reaching into them.

Presenter (SwiftUI / UIKit)

How a session becomes on-screen UI. See SwiftUI integration.
// SwiftUI view that renders a session.
@MainActor
public struct FlowPresenterView: View {
    public init(session: FlowSession)
}

// View modifier: present when the bound session is non-nil, clear it on result.
extension View {
    public func flowPresenter(
        session: Binding<FlowSession?>,
        onResult: ((FlowResult) -> Void)? = nil
    ) -> some View
}

// UIKit hosting controller that wraps FlowPresenterView (UIKit only).
@MainActor
public class FlowHostingController: UIHostingController<FlowPresenterView> {
    public init(session: FlowSession)
    public func onCompletion(_ handler: @escaping (FlowResult) -> Void)
}
FlowPresenterModifier (the type behind .flowPresenter) is also public, but use the .flowPresenter(session:onResult:) modifier rather than constructing it directly.

Results

Returned by the presenting and waitForCompletion() APIs. See Results and outcomes.
public struct FlowResult: Sendable {
    public let outcome: FlowOutcome
    public let finalVariables: [String: VariableValue]
    public let screensVisited: [String]
    public let durationMs: Int
    public let experimentAssignments: [String: String]
    public let error: FlowPilotError?
}

public enum FlowOutcome: String, Sendable {
    case completed
    case dismissed
    case error
}

Variables

See Variables and SDK context.
public enum VariableValue: Sendable, Equatable, Codable {
    case string(String)
    case number(Double)
    case boolean(Bool)
    case stringList([String])
    case numberList([Double])
    case booleanList([Bool])

    // Typed accessors (coercing where noted)
    public var stringValue: String?
    public var numberValue: Double?        // coerces a numeric string
    public var intValue: Int?              // coerces a numeric string
    public var boolValue: Bool?            // coerces "true"/"1"/"yes" and numbers
    public var stringListValue: [String]?
    public var numberListValue: [Double]?
    public var booleanListValue: [Bool]?

    // Inspection
    public var isEmpty: Bool
    public var isString: Bool
    public var isNumber: Bool
    public var isBoolean: Bool
    public var isList: Bool
    public var typeName: String
    public var displayString: String
}
VariableValue also conforms to ExpressibleBy{String,Integer,Float,Boolean,Array}Literal, so you can write session.setVariable("plan", value: "premium") or value: 3.
public enum VariableType: String, Codable, Sendable {
    case string
    case number
    case boolean
    case list
}

// Used by custom components: an input that is either a bound variable or a constant.
public enum ComponentInputValue: Codable, Sendable {
    case bind(String)
    case value(VariableValue)
}

Custom components and screens

Registration methods on FlowPilot.shared, plus the definition types. See Custom components and Custom screens.
// Register / unregister a custom component by key (+ version, default 1).
public func registerCustomComponent(
    key: String,
    version: Int = 1,
    definition: CustomComponentDefinition
)
public func unregisterCustomComponent(key: String, version: Int = 1)

// Deprecated bare-key overloads (use version 1).
@available(*, deprecated)
public func registerCustomComponent(_ typeId: String, definition: CustomComponentDefinition)
@available(*, deprecated)
public func unregisterCustomComponent(_ typeId: String)

// Custom screens.
public func registerCustomScreen(_ screenId: String, definition: CustomScreenDefinition)
public func unregisterCustomScreen(_ screenId: String)
public struct CustomComponentDefinition: Sendable {
    public let inputs: [String: VariableType]?
    public let outputs: [String: OutputSchema]?
    public let factory: @Sendable @MainActor (CustomComponentProps, CustomComponentContext) -> AnyView

    public init(
        inputs: [String: VariableType]? = nil,
        outputs: [String: OutputSchema]? = nil,
        factory: @escaping @Sendable @MainActor (CustomComponentProps, CustomComponentContext) -> AnyView
    )
}

public struct OutputSchema: Sendable {
    public let description: String?
    public let payload: [String: VariableType]?
    public init(description: String? = nil, payload: [String: VariableType]? = nil)
}

public struct CustomComponentProps: Sendable {
    public let inputs: [String: VariableValue]
    // Read-only validated accessors (warn in debug on type mismatch):
    public func string(_ key: String) -> String?
    public func string(_ key: String, default defaultValue: String) -> String
    public func number(_ key: String) -> Double?
    public func number(_ key: String, default defaultValue: Double) -> Double
    public func bool(_ key: String) -> Bool?
    public func bool(_ key: String, default defaultValue: Bool) -> Bool
    public func int(_ key: String) -> Int?
    public func int(_ key: String, default defaultValue: Int) -> Int
}

public final class CustomComponentContext: @unchecked Sendable {
    public let containerSize: CGSize
    public let containerConstraints: ContainerConstraints
    public func emit(_ eventName: String, payload: [String: Any]? = nil)
    public func emit(_ eventName: String)

    public struct ContainerConstraints: Sendable {
        public let minWidth: Double?
        public let maxWidth: Double?
        public let minHeight: Double?
        public let maxHeight: Double?
        public let supportsIntrinsicSize: Bool
    }
}
public struct CustomScreenDefinition: Sendable {
    public let inputs: [String: VariableType]?
    public let outputs: [String: OutputSchema]?
    public let factory: @Sendable @MainActor (CustomScreenParams, CustomScreenContext) -> AnyView
    public init(/* same shape as CustomComponentDefinition */)
}

public struct CustomScreenParams: Sendable {
    public let inputs: [String: VariableValue]
    public func string(_ key: String) -> String?
    public func string(_ key: String, default defaultValue: String) -> String
    public func number(_ key: String) -> Double?
    public func number(_ key: String, default defaultValue: Double) -> Double
    public func bool(_ key: String) -> Bool?
    public func bool(_ key: String, default defaultValue: Bool) -> Bool
}

public final class CustomScreenContext: @unchecked Sendable {
    public func emit(_ eventName: String, payload: [String: Any]? = nil)
    public func emit(_ eventName: String)
    public func setZonesVisible(_ visible: Bool)
    @available(*, deprecated) public func setChromeVisible(_ visible: Bool)
}
Custom components render today. Custom screens do not: the SDK accepts registerCustomScreen and stores the definition, but the renderer never looks it up or invokes the factory in v1.0.0. Treat the custom-screen types as a roadmap API. See Custom screens.

Analytics

See Analytics integration and the Event taxonomy.
// Set an additive callback that receives every analytics event the SDK emits.
public func setAnalyticsCallback(_ callback: @escaping AnalyticsCallback)

public typealias AnalyticsCallback = (AnalyticsEvent) -> Void
public struct AnalyticsEvent: Codable, Sendable {
    public let eventId: String          // wire: event_id
    public let appId: String            // wire: app_id
    public let eventName: String        // wire: event_type
    public let timestamp: Date
    public let placementId: String?
    public let flowId: String
    public let flowVersionId: String
    public let userId: String
    public let sessionId: String
    public let devicePlatform: String   // "ios"
    public let sdkVersion: String
    public let flowVersion: Int?
    public let experimentId: String?
    public let variantId: String?
    public let variantName: String?
    public let screenId: String?
    public let screenName: String?
    public let screenIndex: Int?
    public let elementId: String?
    public let elementType: String?
    public let interactionType: String?
    public let revenue: Double?
    public let currency: String?
    public let timeSinceFlowStartMs: Int?
    public let timeOnScreenMs: Int?
    public let appVersion: String?
    public let country: String?
    public let properties: [String: AnyCodable]?
}

// Events the SDK fires automatically.
public enum AutomaticEventType: String, Sendable {
    case flowStarted = "flow_start"
    case flowCompleted = "flow_complete"
    case flowDismissed = "flow_exit"
    case screenViewed = "screen_view"
    case screenExited = "screen_exit"
    case experimentAssigned = "experiment_exposure"
    case elementInteraction = "element_interaction"
}
Conversions are emitted with event_type conversion via trackConversion(...), not through AutomaticEventType.

Errors

See Error handling.
public func setErrorCallback(_ callback: @escaping ErrorCallback)
public typealias ErrorCallback = (FlowPilotError) -> Void

public struct FlowPilotError: Error, Sendable, CustomStringConvertible {
    public let code: FlowPilotErrorCode
    public let message: String
    public let underlyingError: Error?
    public let context: [String: String]?
    public var description: String
    public var localizedDescription: String
    public var isClientError: Bool     // true for a 4xx status in context
    public var isServerError: Bool     // true for a 5xx status in context
}
FlowPilotErrorCode is a String enum with these cases (raw values in the Error handling table): invalidApiKey, invalidAppId, sdkNotInitialized, networkError, apiError, timeout, rateLimited, placementNotFound, flowNotFound, targetingNotMet, frequencyLimitReached, unsupportedSchemaVersion, invalidFlowSchema, componentRenderError, customComponentNotFound, customScreenNotFound, navigationError, variableError, actionError, fatalActionError, actionChainTimeout, internalError.
setErrorCallback(_:) stores the closure but the SDK never invokes it in v1.0.0. Observe errors through the thrown FlowPilotError (from the throwing present/create APIs) and through FlowResult.error. See Error handling.

Caching and offline

See Caching and Offline and bundled flows.
// Clear cached flows + fonts + icons.
public func clearCache() async

// Clear cached images + icons.
public func clearImageCache()

// The shared image cache, for advanced use.
public var imageCache: ImageCache { get }

// Register a bundled (offline) default flow for a placement. Four overloads:
public func registerBundledFlow(placementKey: String, json: Data, assets: BundledFlowAssets? = nil)
public func registerBundledFlow(
    placementKey: String,
    resource: String,
    withExtension ext: String = "json",
    in bundle: Bundle = .main,
    assets: BundledFlowAssets? = nil
)
public func registerBundledFlow(
    placementKey: String,
    assetBundle subdirectory: String,
    in bundle: Bundle = .main,
    flowResource: String = "flow",
    manifestResource: String = "manifest"
)
public func registerBundledFlowAssets(placementKey: String, assets: BundledFlowAssets)
public final class ImageCache: @unchecked Sendable {
    public static let shared: ImageCache
    public static let bundledAssetTTL: TimeInterval   // effectively non-expiring
    public var maxMemoryCacheSize: Int                // default 50MB
    public var maxDiskCacheSize: Int                  // default 200MB
    public var defaultTTL: TimeInterval               // default 7 days

    public init(cacheDirectory: String? = nil)
    public func getImage(for url: URL) -> UIImage?
    public func hasImage(for url: URL) -> Bool
    public func setImage(_ image: UIImage, for url: URL, ttl: TimeInterval? = nil)
    public func setImageData(_ data: Data, for url: URL, ttl: TimeInterval? = nil)
    public func removeImage(for url: URL)
    public func clearAll()
    public func cleanExpired()
}
public struct BundledFlowAssets: @unchecked Sendable {
    // Assets in a plain filesystem directory (manifest + files).
    public init(directoryURL: URL, manifest: String = "manifest")
    // Assets in an app bundle, optionally inside a folder-reference subdirectory.
    public init(bundle: Bundle = .main, subdirectory: String? = nil, manifest: String = "manifest")
}
On non-UIKit platforms (macOS without UIKit), ImageCache is a reduced stub: getImage, setImage, and setImageData are not available there.

Callbacks and typealiases

public typealias AnalyticsCallback = (AnalyticsEvent) -> Void
public typealias ErrorCallback = (FlowPilotError) -> Void
public typealias FlowCompletionCallback = (FlowResult) -> Void
public typealias FlowDismissalCallback = (String) -> Void
FlowCompletionCallback and FlowDismissalCallback are declared public for source compatibility, but there is no setter for them and the SDK does not invoke them in v1.0.0. Get the result from the present APIs’ return value, the completion: closure, or FlowResult instead.

Deprecated aliases

Short-prefix names kept for source compatibility with v1.0 integrators. Each is marked @available(*, deprecated, renamed:); migrate to the FlowPilot* name.
Deprecated aliasUse instead
FPConfigurationFlowPilotConfiguration
FPEnvironmentFlowPilotEnvironment
FPLogLevelFlowPilotLogLevel
FPErrorFlowPilotError
FPErrorCodeFlowPilotErrorCode

Debug

// Draw debug borders around rendered components.
public static var debugBordersEnabled: Bool

// Raise the log level to .verbose (development only).
public func enableDebugOverlay()
// Restore the configured log level.
public func disableDebugOverlay()
// Log user id, session id, environment, and registry counts.
public func logState()
createTestSession() also exists but is compiled only in #if DEBUG builds and is for rendering tests, not production use.

The FlowPilotDelegate protocol

public protocol FlowPilotDelegate: AnyObject {
    func flowPilotDidComplete(_ flowId: String, result: FlowResult)
    func flowPilotDidDismiss(_ flowId: String)
    func flowPilotDidFail(_ error: FlowPilotError)
}
FlowPilotDelegate is declared public, but the SDK has no setter for it and never calls its methods in v1.0.0. It is not usable today. Use the closure-based APIs, the present return values, and the never-throwing overloads instead. See Error handling.