PhosraSDK Swift package provides on-device child safety enforcement for iOS using Apple’s FamilyControls, ManagedSettings, and DeviceActivity frameworks.
Overview
The SDK has a two-layer architecture:- API Client — Communicates with the Phosra API to register devices, fetch compiled policies, submit enforcement reports, and acknowledge policy versions.
- Enforcement Engine — Translates the compiled policy document into native Apple framework calls to enforce content filters, screen time limits, web filtering, purchase controls, and more.
API Client
Handles device registration, policy sync, and reporting via the Phosra REST API.
Enforcement Engine
Maps policy rules to FamilyControls, ManagedSettings, and DeviceActivity calls.
Requirements
| Requirement | Version |
|---|---|
| iOS | 16.0+ |
| Swift | 5.9+ |
| Xcode | 15+ |
| FamilyControls Entitlement | Required (request here) |
Installation
The
PhosraSDK Swift package is in private preview. Contact developers@phosra.com to receive the repository URL and access credentials. Once you have access, add it via Swift Package Manager:Xcode
Quick Start
import FamilyControls
let center = AuthorizationCenter.shared
do {
try await center.requestAuthorization(for: .individual)
print("FamilyControls authorized")
} catch {
print("Authorization denied: \(error)")
}
The parent initiates device registration from their authenticated session. This requires a parent JWT token:
import PhosraSDK
let config = PhosraConfiguration(
parentToken: parentJWT,
childID: childUUID
)
let client = PhosraAPIClient(configuration: config)
let request = RegisterDeviceRequest(
deviceName: "Emma's iPad",
deviceModel: UIDevice.current.model,
osVersion: UIDevice.current.systemVersion,
appVersion: "1.0.0",
capabilities: ["FamilyControls", "ManagedSettings", "DeviceActivity"]
)
let response = try await client.registerDevice(request)
The API key is returned exactly once during registration. The server only stores a SHA-256 hash; the plaintext is never returned again:
let deviceKey = try KeychainHelper.load()!
let deviceConfig = PhosraConfiguration(deviceKey: deviceKey)
let deviceClient = PhosraAPIClient(configuration: deviceConfig)
let policy = try await deviceClient.fetchPolicy()
print("Applying policy v\(policy.version) for \(policy.ageGroup)")
// Apply via enforcement engine
let engine = EnforcementEngine()
let results = try await engine.apply(policy)
// Report results back to Phosra
let report = DeviceReport(
reportType: .enforcementStatus,
payload: .enforcementStatus(EnforcementStatusPayload(
policyVersion: policy.version,
results: results
))
)
try await deviceClient.submitReport(report)
try await deviceClient.ackPolicyVersion(policy.version)
Device Registration
The registration flow requires parent authentication and produces a device-specific API key:RegisterDeviceRequest includes device metadata and capability declarations:
Policy Sync
Automatic Polling with PolicySyncManager
ThePolicySyncManager handles periodic policy fetching with conditional requests:
Push-Based Sync (APNs)
For immediate policy refresh when a parent modifies rules, handle APNs silent push notifications:Enforcement Engine
Each enforcer handles a specific policy section and maps it to the appropriate Apple framework.Content Filter Enforcer
UsesManagedSettings to control app access based on age ratings and block/allow lists.
| Policy Field | Apple Framework | API |
|---|---|---|
contentFilter.ageRating | ManagedSettings | ManagedSettingsStore.application |
contentFilter.blockedApps | ManagedSettings | blockedApplications |
contentFilter.allowlistMode | ManagedSettings | denyAppInstallation |
Screen Time Enforcer
UsesDeviceActivity to monitor and limit screen time.
| Policy Field | Apple Framework | API |
|---|---|---|
screenTime.dailyLimitMinutes | DeviceActivity | DeviceActivitySchedule |
screenTime.perAppLimits | DeviceActivity | Per-token schedule |
screenTime.downtimeWindows | DeviceActivity | DeviceActivitySchedule |
screenTime.schedule | DeviceActivity | DateComponents ranges |
Web Filter Enforcer
UsesManagedSettings web content filtering.
| Policy Field | Apple Framework | API |
|---|---|---|
webFilter.safeSearch | ManagedSettings | webContent.autoFilter |
webFilter.blockedDomains | ManagedSettings | webContent.blockedDomains |
webFilter.allowedDomains | ManagedSettings | webContent.allowedDomains |
webFilter.level | ManagedSettings | webContent filter mode |
Purchase Enforcer
| Policy Field | Apple Framework | API |
|---|---|---|
purchases.requireApproval | ManagedSettings | appStore.requirePasswordForPurchases |
purchases.blockIAP | ManagedSettings | appStore.denyInAppPurchases |
purchases.spendingCapUSD | None | Tracked via StoreKit observer |
Social Enforcer
| Policy Field | Apple Framework | API |
|---|---|---|
social.multiplayerMode | ManagedSettings | gameCenter.denyMultiplayerGaming |
social.chatMode | None | Block messaging apps via ManagedSettings |
social.dmRestriction | None | Block messaging apps via ManagedSettings |
Notification Enforcer
| Policy Field | Apple Framework | API |
|---|---|---|
notifications.curfewStart/End | ManagedSettings | notification (iOS 16.4+) |
notifications.usageTimerMinutes | DeviceActivity | DeviceActivityMonitor events |
Reporting
The SDK submits two types of reports to the Phosra API:Enforcement Status Report
Sent after applying a policy, contains per-category results:Screen Time Report
Aggregated daily usage data:CompiledPolicy Structure
The compiled policy is fetched fromGET /device/policy and contains all enforcement rules organized by section:
Supported Rule Categories
The 45 OCSS rule categories the iOS SDK maps to native framework primitives (of the 123 categories in the OCSS rule registry; the remainder are policy-only or census-enforced and carry no client-side Apple verb):Content Rules
| Category | Apple Framework | API Class | Min iOS |
|---|---|---|---|
content_rating | ManagedSettings | ManagedSettingsStore.application | 16.0 |
content_block_title | ManagedSettings | blockedApplications | 16.0 |
content_allow_title | ManagedSettings | denyAppInstallation | 16.0 |
content_allowlist_mode | ManagedSettings | denyAppInstallation | 16.0 |
content_descriptor_block | ManagedSettings | ManagedSettingsStore.application | 16.0 |
Time Rules
| Category | Apple Framework | API Class | Min iOS |
|---|---|---|---|
time_daily_limit | DeviceActivity | DeviceActivitySchedule | 16.0 |
time_scheduled_hours | DeviceActivity | DeviceActivitySchedule | 16.0 |
time_per_app_limit | DeviceActivity | DeviceActivitySchedule | 16.0 |
time_downtime | DeviceActivity | DeviceActivitySchedule | 16.0 |
Purchase Rules
| Category | Apple Framework | API Class | Min iOS |
|---|---|---|---|
purchase_approval | ManagedSettings | appStore.requirePasswordForPurchases | 16.0 |
purchase_block_iap | ManagedSettings | appStore.denyInAppPurchases | 16.0 |
purchase_spending_cap | None | StoreKit observer (tracked in-app) | — |
Social Rules
| Category | Apple Framework | API Class | Min iOS |
|---|---|---|---|
social_contacts | FamilyControls | AuthorizationCenter | 16.0 |
social_chat_control | None | Block messaging apps via ManagedSettings | — |
social_multiplayer | ManagedSettings | gameCenter.denyMultiplayerGaming | 16.0 |
Web Rules
| Category | Apple Framework | API Class | Min iOS |
|---|---|---|---|
web_safesearch | ManagedSettings | webContent.autoFilter | 16.0 |
web_category_block | ManagedSettings | webContent.blockedByFilter | 16.0 |
web_custom_allowlist | ManagedSettings | webContent.allowedDomains | 16.0 |
web_custom_blocklist | ManagedSettings | webContent.blockedDomains | 16.0 |
web_filter_level | ManagedSettings | webContent | 16.0 |
Privacy Rules
| Category | Apple Framework | Notes |
|---|---|---|
privacy_location | None | CLLocationManager delegation in-app |
privacy_profile_visibility | None | App-level concern |
privacy_data_sharing | None | App-level logic; ATT covers ads only |
privacy_account_creation | None | Gate via FamilyControls auth check |
Monitoring Rules
| Category | Apple Framework | API Class | Min iOS |
|---|---|---|---|
monitoring_activity | DeviceActivity | DeviceActivityReport | 16.0 |
monitoring_alerts | DeviceActivity | DeviceActivityMonitor | 16.0 |
Engagement Rules
| Category | Apple Framework | Notes |
|---|---|---|
algo_feed_control | None | Block/limit social apps via ManagedSettings |
addictive_design_control | None | Block offending apps via ManagedSettings |
Notification Rules
| Category | Apple Framework | API Class | Min iOS |
|---|---|---|---|
notification_curfew | ManagedSettings | notification | 16.4 |
usage_timer_notification | DeviceActivity | DeviceActivityMonitor | 16.0 |
Legislation-Driven Rules
| Category | Apple Framework | Notes |
|---|---|---|
targeted_ad_block | None | ATT is per-prompt; no blanket block |
dm_restriction | None | Block messaging apps as proxy |
age_gate | None | Enforce in-app |
data_deletion_request | None | App-level support flow |
geolocation_opt_in | None | CLLocationManager per-app |
Compliance Rules
| Category | Apple Framework | Notes |
|---|---|---|
csam_reporting | None | Apple handles via iCloud Photos |
library_filter_compliance | ManagedSettings | Same as web content filtering |
ai_minor_interaction | None | Block AI apps via ManagedSettings |
social_media_min_age | None | Block social BundleIDs for underage |
image_rights_minor | None | App-level enforcement |
Parental / Legislative Rules
| Category | Apple Framework | API Class | Min iOS |
|---|---|---|---|
parental_consent_gate | FamilyControls | AuthorizationCenter | 16.0 |
parental_event_notification | DeviceActivity | DeviceActivityMonitor | 16.0 |
screen_time_report | DeviceActivity | DeviceActivityReport | 16.0 |
commercial_data_ban | None | App/server-level policy | |
algorithmic_audit | None | Platform transparency requirement |
Troubleshooting
FamilyControls authorization fails
FamilyControls authorization fails
ManagedSettingsStore changes not applying
ManagedSettingsStore changes not applying
- Verify FamilyControls authorization status is
.approved ManagedSettingsStorechanges are transactional; create a new store instance if the previous one has stale state- Check the device logs:
log stream --predicate 'subsystem == "com.apple.ManagedSettings"'
DeviceActivity monitoring not starting
DeviceActivity monitoring not starting
- The
DeviceActivityMonitorextension must be a separate target in your Xcode project - Verify the extension’s bundle ID matches the App Group
- Maximum of 20 concurrent
DeviceActivityScheduleinstances per app
Policy fetch returns 304 Not Modified
Policy fetch returns 304 Not Modified
This is expected behavior. When you pass
sinceVersion, the server returns 304 if the policy has not changed. The SDK returns nil in this case — no action is needed.Keychain errors on first launch
Keychain errors on first launch
- Ensure Keychain Sharing capability is enabled if using app extensions
- Use
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly(the SDK default) for device keys - On simulator, Keychain behavior may differ from physical devices
App Store rejection for FamilyControls usage
App Store rejection for FamilyControls usage
- Provide a detailed description of your child safety use case in App Store Connect
- Include screenshots showing the parent consent flow
- Reference applicable legislation (COPPA, KOSA, etc.) in your review notes
- Ensure your privacy policy covers data collected from child devices