Skip to main content

Configuring the SDK

AppActor must be configured once when your app launches. There are two modes: Payment Mode (server-backed) and Local Mode (client-only).

Payment mode connects to the AppActor backend for server-side receipt validation, entitlement management, remote config, and experiments. You only need your public API key.

import AppActor

@main
struct MyApp: App {
init() {
// Payment mode โ€” server-authoritative
AppActor.configure(apiKey: "pk_live_your_key_here")
}

var body: some Scene {
WindowGroup {
ContentView()
}
}
}

Your API key (pk_*) is safe to include in client-side code. It can only be used to make purchases and fetch offerings โ€” not to modify data. See API Authentication for details.

Payment Mode Optionsโ€‹

AppActor.configure(
apiKey: "pk_live_your_key_here",
options: .init(
logLevel: .debug, // Enable verbose payment logs
verifyResponseSignatures: true // Ed25519 signature verification (default: true)
)
)

Waiting for Bootstrapโ€‹

The SDK bootstraps asynchronously (identifies user, fetches offerings, syncs purchases). If you need to wait for it to complete:

// Wait for bootstrap to complete before showing paywall
await AppActor.shared.awaitBootstrap()
let offerings = try await AppActor.shared.offerings()

Local Modeโ€‹

Local mode runs entirely on-device with no backend. You define offerings and entitlements in code using a declarative DSL.

import AppActor

@main
struct MyApp: App {
init() {
AppActor.configure(
projectKey: "myapp",
offerings: {
AppActorOfferingDefinition("default", displayName: "Standard") {
AppActorPackageDefinition(.monthly, productID: "com.myapp.monthly")
AppActorPackageDefinition(.annual, productID: "com.myapp.annual")
}
AppActorOfferingDefinition("premium", displayName: "Premium") {
AppActorPackageDefinition(.lifetime, productID: "com.myapp.lifetime")
}
},
entitlements: {
AppActorEntitlementDefinition("premium", productIDs: [
"com.myapp.monthly",
"com.myapp.annual",
"com.myapp.lifetime"
])
}
)
}

var body: some Scene {
WindowGroup {
ContentView()
}
}
}

Local Mode Optionsโ€‹

AppActor.configure(
projectKey: "myapp",
grantAccessDuringGracePeriod: true, // Default: true
grantAccessDuringBillingRetry: false, // Default: false
logLevel: .debug, // Enable verbose SDK logging
offerings: { /* ... */ },
entitlements: { /* ... */ }
)

Async Configurationโ€‹

If you need to await configuration completion (e.g., to handle errors):

do {
try await AppActor.configureAsync(
projectKey: "myapp",
offerings: { /* ... */ },
entitlements: { /* ... */ }
)
} catch {
print("Configuration failed: \(error)")
}

Foreground Refreshโ€‹

The SDK does not automatically detect passive subscription expirations. Call onAppForeground() when your app returns to the foreground to refresh entitlement state:

@main
struct MyApp: App {
@Environment(\.scenePhase) var scenePhase

var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { phase in
if phase == .active {
Task { await AppActor.shared.onAppForeground() }
}
}
}
}
Important

Always call onAppForeground() when the app becomes active. Without this, expired subscriptions may still appear active until the 5-minute cache expires.

Next Stepsโ€‹