Features & Entitlements
Check Feature Access and consume server-authoritative usage
Features are the app-facing access contract. Products and Entitlements determine why a Customer has access; app code checks the Feature external ID.
Check access
hasFeature() returns cached state while it is fresh and asks the server otherwise:
// nuxie-doc-fragment: basic-feature-access harness=async-body
let access = try await NuxieSDK.shared.hasFeature("premium_content")
if access.allowed {
showPremiumContent()
} else {
showUpgradePrompt()
}
For a fixed quota or credit-backed action, include the amount that the action requires:
// nuxie-doc-fragment: metered-feature-access harness=async-body
let access = try await NuxieSDK.shared.hasFeature(
"ai_generations",
requiredBalance: 5,
entityId: "project-456"
)
if access.allowed {
// This is presentation state only. Consume usage before the side effect.
showEnabledGenerateButton()
}
Use the explicit policy when the distinction matters:
// nuxie-doc-fragment: feature-access-policies harness=async-body
let cachedFirst = try await NuxieSDK.shared.hasFeature(
"premium_content",
policy: .cacheFirst
)
let authoritative = try await NuxieSDK.shared.hasFeature(
"premium_content",
policy: .remote
)
.remote always asks the server. It is the appropriate check for a critical decision
that cannot tolerate a cached profile.
Reactive SwiftUI access
features is one stable FeatureInfo observable for the SDK lifetime:
// nuxie-doc-snippet: observable-feature-view
import SwiftUI
import Nuxie
struct PremiumView: View {
@ObservedObject private var features = NuxieSDK.shared.features
var body: some View {
if features.isAllowed("premium_content") {
PremiumContent()
} else {
UpgradePrompt()
}
}
}
FeatureInfo provides synchronous reads of authenticated cached state:
| Method or property | Meaning |
|---|---|
all |
All cached access values keyed by Feature external ID. |
isAllowed(_:) |
Whether the cached Feature allows access. |
hasBalance(_:) |
Whether access is unlimited or has positive balance. |
balance(_:) |
Cached metered balance, when present. |
feature(_:) |
Full cached FeatureAccess, when present. |
Server-authoritative quotas and credits
Boolean Features and unlimited metered Features can become available immediately from authenticated local purchase state. Fixed quotas and credits are always server-authoritative. The SDK never invents, decrements, or exposes a temporary local balance as authority.
Call useFeatureAndWait() before the protected side effect:
// nuxie-doc-fragment: consume-feature-and-wait harness=async-body
let result = try await NuxieSDK.shared.useFeatureAndWait(
"ai_generations",
amount: 1,
entityId: "project-456"
)
guard result.success else { return }
let remaining = result.authoritativeAccess?.balance
?? result.usage?.remaining
proceedWithGeneration(remaining: remaining)
Immediately after a native App Store purchase, this command may attach the one safely matching protected transaction. The server verifies the purchase, grants the allowance, and consumes the requested amount as one idempotent operation. A timeout retains the protected evidence and reuses the same idempotency identity on retry.
If no unique transaction matches, the SDK submits an ordinary authoritative usage command without exposing receipt material. This includes provider-owned checkout, Test Store purchases, another Customer’s transaction, and ambiguous pending transactions. RevenueCat, Superwall, and custom-provider balances arrive through the reviewed Connector synchronization path.
Fire-and-forget reporting
useFeature() starts the same authoritative request in a background task and logs any
failure:
// nuxie-doc-fragment: enqueue-feature-usage harness=sync-body
NuxieSDK.shared.useFeature(
"analytics_event",
amount: 1,
metadata: ["source": "settings"]
)
Use it only when the protected side effect does not depend on the result. It cannot authorize quota- or credit-backed work because the caller cannot observe whether the server accepted the deduction.
Change notifications
Set NuxieSDK.shared.delegate to receive featureAccessDidChange after a profile,
Feature check, identity change, or purchase synchronization updates cached state:
// nuxie-doc-snippet: feature-access-observer
import Nuxie
@MainActor
final class FeatureObserver: NuxieDelegate {
func featureAccessDidChange(
_ featureId: String,
from oldValue: FeatureAccess?,
to newValue: FeatureAccess
) {
print("Feature \(featureId) allowed: \(newValue.allowed)")
}
}