Skip to main content

HealthSync iOS app

info

This page covers the HealthSync iOS app that sends health data directly from your iPhone to the stats dashboard API, and the Apple Watch companion app that displays Flow Control data on your wrist.

Flow Control series

  1. Flow Control
  2. Architecture
  3. Cursor sync daemon
  4. Music sync daemon
  5. Health sync daemon (disabled)
  6. HealthSync iOS app - You are here
  7. Deployment

Why a native iOS app?

The original health data pipeline required multiple components:

This pipeline had 2-3 days latency and multiple failure points. The new approach is direct:

Old PipelineNew Pipeline
6+ components2 components
2-3 day latencyReal-time
Multiple failure pointsSingle sync path
Mac requirediPhone only
Google Sheets dependencyDirect API
HealthSync iOS app Health page with readiness and sleep debt

Prerequisites

Before starting, ensure you have:

RequirementVersionNotes
Mac with Xcode15.0+Download from App Store
Apple Developer accountFree or paidFor device testing
iPhoneiOS 17.0+With Apple Watch paired
Apple WatchwatchOS 11.0+Source of health data AND Flow Control display
stats-dashboardDeployedWith /api/import/health-app endpoint

Part 1: Xcode project setup

1.1 Create new project

  1. Open Xcode
  2. Select File > New > Project
  3. Choose iOS > App
  4. Configure the project:
SettingValue
Product NameHealthSync
TeamYour Apple Developer team
Organization Identifiercom.yourname
Bundle IdentifierAuto-generated (e.g., com.yourname.HealthSync)
InterfaceSwiftUI
LanguageSwift
StorageNone
Testing SystemNone
  1. Choose a location (e.g., ~/Projects/tools/health-sync-ios)
  2. Create Git repository: Check this box

1.2 Add HealthKit capability

  1. Select your project in the navigator
  2. Select the HealthSync target
  3. Go to Signing & Capabilities tab
  4. Click + Capability
  5. Search for and add HealthKit

1.3 Add Background Modes capability

  1. Click + Capability again
  2. Search for and add Background Modes
  3. Check Background fetch

1.4 Configure Info.plist

In the Info tab of your target settings, add these keys:

KeyTypeValue
Privacy - Health Share Usage DescriptionStringSync health data to your personal dashboard for tracking sleep, activity, and workouts.
Privacy - Health Update Usage DescriptionStringRecord sync timestamps to avoid duplicate imports.
Permitted background task scheduler identifiersArray(see below)

For the background task identifier array, add one item:

ItemValue
Item 0com.yourname.HealthSync.refresh

Replace com.yourname with your actual bundle identifier prefix.

Part 2: iPhone setup

2.1 Enable Developer Mode

On your iPhone (iOS 16+):

  1. Open Settings
  2. Go to Privacy & Security
  3. Scroll down and tap Developer Mode
  4. Toggle Developer Mode on
  5. Tap Restart when prompted
  6. After restart, tap Turn On to confirm

2.2 Trust your Mac

  1. Connect iPhone to Mac via USB cable
  2. On iPhone, tap Trust when prompted
  3. Enter your passcode

2.3 Verify wireless debugging (optional)

For cable-free development:

  1. In Xcode, go to Window > Devices and Simulators
  2. Select your iPhone
  3. Check Connect via network
  4. Ensure your Mac and iPhone are on the same Wi-Fi network

Part 2.5: Sleep preferences

After initial setup, configure sleep preferences in the app's Settings screen:

SettingDefaultDescription
Sleep TrackerOuraAuthoritative sleep data source. Options: Oura, Apple Watch, iPhone, AutoSleep. Prevents multi-source data corruption by filtering HealthKit queries to a single source.
Sleep Debt Threshold7.5hTarget sleep hours per night for sleep debt calculation
No "All Sources" option

The app deliberately excludes an "All Sources" option. Combining sleep data from multiple devices (e.g., Oura ring + Apple Watch) leads to duplicate and inflated sleep durations. Always select the single device you sleep with.

The selected tracker filters HealthKit sleep queries using HKSourceQuery to discover matching sources by bundle ID pattern, then constructs a compound predicate with predicateForObjects(from:).

Part 3: Swift code files

Create these files in your Xcode project. Right-click on the HealthSync folder in the navigator and select New File > Swift File.

GroupFilePurpose
ModelsHealthModels.swiftData models for API payloads (SleepRecord, DailyMetrics, WorkoutRecord, HealthPayload), SleepTracker enum (Oura/AppleWatch/iPhone/AutoSleep with bundleIdPattern), SleepPreferences struct
ManagersHealthKitManager.swiftHealthKit auth, queries, sleep processing with session-based grouping and source filtering via discoverSleepSources()
ManagersAPIClient.swiftDashboard API communication with Bearer auth
ManagersNetworkMonitor.swiftWiFi detection for background sync gating (prevents cellular timeout penalties)
ViewsSettingsView.swiftAPI configuration UI with connection test, Sleep Preferences section (Tracker picker, Threshold stepper)
(root)ContentView.swiftMain sync status and controls
(root)HealthSyncApp.swiftApp entry point with background task setup, observer debounce, WiFi gating
Critical

All background setup (observer queries, background delivery, BGTask scheduling) must happen in HealthSyncApp.init(), NOT in ContentView.onAppear. Observer queries set up in a view's onAppear are lost when the app is suspended. The singleton HealthKitManager.shared ensures queries persist across foreground/background transitions.

Important

The ContentView uses HealthKitManager.shared (the singleton) -- do NOT create a new @State instance. Background setup is handled by HealthSyncApp.init(), not the view.

View complete source code -- all 7 Swift files and the dashboard API endpoint.

Part 4: Dashboard API endpoint

The iOS app sends data to /api/import/health-app. The endpoint accepts a JSON payload with sleep, metrics, and workouts arrays, validates with Zod schemas, and upserts into PostgreSQL via Prisma.

Key details:

  • Authentication via Bearer token (matches DAEMON_API_KEY Kubernetes secret)
  • Rate limited to 30 requests per minute
  • Sleep records use date-based upsert (one record per night)
  • Workouts use composite key (date + startTime) for deduplication; optional distanceKm from HKWorkout.totalDistance stored for running, cycling, walking
  • Invalidates AI insight cache when new sleep data arrives

View complete source code -- full TypeScript endpoint implementation.

Part 5: Configuration

5.1 iOS app settings

In the app, tap the gear icon and configure:

SettingValue
Dashboard URLhttps://stats.example.com
API KeyYour DAEMON_API_KEY value

5.2 Get API key from Kubernetes

kubectl get secret stats-daemon-secret -n stats \
-o jsonpath='{.data.daemon-api-key}' | base64 -d

Part 6: Testing

6.1 Build and run

  1. In Xcode, select your iPhone from the device dropdown
  2. Press Cmd+R to build and run
  3. Wait for the app to install on your iPhone

6.2 Grant permissions

  1. Tap Request Health Access
  2. Grant access to all requested health data types
  3. Verify the HealthKit status shows green

6.3 Configure API

  1. Tap the gear icon
  2. Enter your dashboard URL
  3. Enter your API key
  4. Tap Test Connection - verify green checkmark

6.4 Manual sync

  1. Select lookback period (e.g., 7 days)
  2. Tap Sync Now
  3. Verify "Last sync successful" message

6.5 Verify in database

kubectl exec -n stats pod/stats-db-xxx -- \
psql -U stats -d stats -c \
"SELECT source, COUNT(*) FROM import_logs WHERE source = 'health-app-ios' GROUP BY source;"

Part 7: Apple Watch companion app

The HealthSync project includes an Apple Watch companion app that displays Flow Control data directly on your wrist. The watch app is a separate target within the same Xcode project, deployed alongside the iPhone app.

The watch app presents 6 horizontally-paged screens and a watch face complication.

Flow dashboard

Circular gauge (0-10) with score-based ring gradient. Below the gauge, a quadrant label shows your current creative state: Flow (green), Stuck (red), Autopilot (orange), or Resting (grey) -- derived from the 2D input/output grid (same as the dashboard's flow badge). Readable at a glance.

Watch flow dashboard

Health card

Readiness score displayed as a 10-segment bar, last night's sleep duration and efficiency, plus a breakdown of REM, deep, and core sleep stages.

Watch health card

Today pulse

Active hours shown prominently, with current session duration, pipeline count, and Cursor request count below.

Watch today pulse

Decision nudge

Continue, pause, or stop recommendation with contextual stats: hours worked, flow score, and sleep debt. Helps decide whether to keep going or wrap up.

Watch decision nudge

Budget

Cursor on-demand usage shown as a large percentage ring with remaining dollars and forecast message.

Watch budget

Status

Server health indicator, version number, last successful sync and last attempt times, plus 5 per-endpoint connectivity indicators (Health, Flow, Sources, Insights, Music). Shows a "Using cached data" badge when offline.

Watch status

Watch face complication

A WidgetKit complication shows flow score, quadrant state (Flow/Stuck/Autopilot/Resting), sleep debt, and active hours directly on the watch face. Supports circular, corner, rectangular, and inline complication families.

Watch face with Flow Control complication

How config reaches the watch

The iPhone app pushes the dashboard URL to the watch via WatchConnectivity.updateApplicationContext. The watch stores it in a shared App Group (group.com.muppit.HealthSync) so both the watch app and widget extension can access it.

No manual configuration is needed on the watch -- just configure the iPhone app and the URL propagates automatically.

Offline behaviour

The dashboard server is only reachable on the local WiFi network. When the watch leaves WiFi range:

  • Watch app screens show the last successfully fetched data (cached in App Group UserDefaults)
  • Watch face complication falls back to cached values instead of showing zeros
  • Status screen shows "Using cached data" with the last sync timestamp so you know the data freshness

Adding the watch target

The watch app is built as a separate target in the existing Xcode project:

  1. In Xcode, select File > New > Target
  2. Choose watchOS > App
  3. Set the bundle identifier to com.yourname.HealthSync.watchkitapp
  4. For the widget extension, add another target: watchOS > Widget Extension
  5. Add an App Groups capability to all three targets (iOS app, watch app, widget extension) with the same group identifier

Troubleshooting

HealthKit shows red X after authorization

HealthKit does not reveal read permission status for privacy. If authorization completed without error but shows red, the app may need updating. The @Observable state must use stored properties, not computed properties from UserDefaults.

Sync Now is greyed out

Both conditions must be met:

  • HealthKit authorized (green checkmark)
  • Dashboard API configured (URL and key entered)

Background sync not working

  1. Verify BGTaskSchedulerPermittedIdentifiers in Info.plist contains com.yourname.HealthSync.refresh
  2. Check that Background Modes capability is enabled with "Background fetch" checked
  3. Critical: Background setup must happen in HealthSyncApp.init(), NOT in ContentView.onAppear. Observer queries set up in a view's onAppear are lost when the app is suspended. The singleton HealthKitManager.shared ensures queries persist.
  4. Open the app at least once after install/update to trigger init() and register everything
  5. iOS limits background refreshes to approximately 4 per hour
  6. If your dashboard is only reachable on local WiFi, ensure NetworkMonitor is gating background syncs — cellular attempts will always timeout and iOS may penalise the app

API returns 401 Unauthorized

  • Verify API key matches DAEMON_API_KEY in Kubernetes secret
  • Check for trailing whitespace when copying the key
  • Ensure Bearer prefix is included (the app handles this)

No data in dashboard

  1. Check import_logs table for errors
  2. Verify data sources in HealthKit (Apple Watch must be synced)
  3. Ensure lookback period covers dates with data

Sleep duration seems too high

Both Apple Watch and iPhone's Clock app (Sleep schedule) write overlapping sleep stage samples to HealthKit for the same night. The processSleepSamples function uses an interval merge algorithm to handle this — if you see inflated sleep times, check the Xcode console for Sleep merged log lines which show raw sample count vs merged minutes per stage. Compare with Athlytic or Apple Health's sleep summary to validate.

App Store submission

HealthSync has been approved and is live on the App Store (February 2026). The watch app is included in the same App Store listing -- no separate submission is required.

If you want to publish your own version, the following resources are available.

Important

HealthKit apps are frequently rejected for not "clearly identifying HealthKit functionality" (Guideline 2.5.1). Follow this guide carefully to avoid common pitfalls.

Required pages

PageURLPurpose
Privacy policyFor App Store + HealthKit compliance
SupportSupport URL for App Store listing
Terms of serviceLegal terms for users

App Store Connect URLs

Use these URLs when submitting to App Store Connect:

FieldURL
Privacy Policy URLhttps://sphere.muppit.au/applications/flow/health-sync-ios/privacy
Support URLhttps://sphere.muppit.au/applications/flow/health-sync-ios/support

Use this description (or similar) that explicitly mentions the Health app:

HealthSync reads your sleep, activity, and workout data from the Health app 
and syncs it to your personal dashboard API.

HEALTH APP INTEGRATION
• Reads sleep analysis data including sleep stages (REM, Core, Deep)
• Reads daily steps and active energy burned
• Reads resting heart rate and heart rate variability (HRV)
• Reads workout sessions with duration, distance (km), and calories

APPLE WATCH COMPANION
• Flow score dashboard with real-time gauge
• Health readiness indicator and today's activity pulse
• Cursor AI usage and budget tracking
• Server status monitor with version info
• Watch face complication for at-a-glance flow score
• Offline caching — data persists when away from your network

SLEEP PREFERENCES
• Select your sleep tracker (Oura, Apple Watch, iPhone, AutoSleep)
• Configurable sleep debt target (default 7.5 hours)
• Single-source filtering prevents duplicate sleep data

FEATURES
• Connects directly to the Health app on your iPhone
• Syncs data to your self-hosted API endpoint
• Background sync keeps your dashboard up-to-date
• Apple Watch app syncs automatically from your iPhone
• Privacy-focused: your health data goes only to your own server
• No accounts required - configure your API URL and sync

REQUIREMENTS
• Requires a compatible dashboard API endpoint
• You must host your own server to receive health data
• Apple Watch requires paired iPhone with HealthSync installed
• See documentation for API setup instructions

Your health data is never sent to the app developer or any third parties.
Data flows directly from the Health app to your configured server.
health,sync,sleep,fitness,activity,workout,dashboard,apple health,heart rate,hrv,apple watch,watchos

Submission checklist

In Xcode:

  1. Set app to iPhone-only - General tab > Supported Destinations > remove iPad/Mac/Vision
  2. Add app icon - Assets > AppIcon > drag single 1024x1024 PNG into "Any Appearance" slot
  3. Increment Build number - General tab > Build (must be unique for each upload)
  4. Archive - Product > Archive
  5. Upload - Distribute App > App Store Connect > Upload

In App Store Connect: 6. Complete encryption compliance - Select "Standard encryption algorithms" (for HTTPS) 7. Set up Content Rights - "Does not contain third-party content" 8. Complete Age Rating - All "NONE/NO" for utility apps (results in 4+ rating) 9. Configure App Privacy - Select "No, we do not collect data" (you don't receive user data) 10. Set Pricing and Availability - Free, uncheck Apple Silicon Mac and Vision Pro 11. Add screenshots - iPhone screenshots (Side button + Volume Up, use 6.9" Display tab) and Apple Watch screenshots (from Xcode simulator or physical watch) 12. Add description and keywords - Use templates above 13. Add URLs - Privacy policy and support URLs 14. Add reviewer notes - Critical for HealthKit apps (see below) 15. Select build and submit - Click "Add for Review" then "Submit to App Review"

Timeline: Usually 24-48 hours for review

iPhone-only configuration

HealthKit and the Health app are iPhone only - iPads don't have the Health app. Setting your app to iPhone-only eliminates the iPad screenshot requirement in App Store Connect.

In Xcode:

  1. Select your project in the navigator
  2. Select the HealthSync target
  3. Go to General tab > Supported Destinations
  4. Click the "-" button to remove:
    • iPad
    • Mac (Designed for iPad)
    • Apple Vision (Designed for iPad)
  5. Keep only iPhone

App Store Connect configuration

Content Rights:

  • In App Store Connect, go to your app > App Information
  • Scroll to "Content Rights"
  • Select: "This app does not contain, show, or access third-party content"

Age Rating:

  • Go to App Information > Age Rating
  • For a simple utility app, select "NONE" or "NO" for all categories
  • This results in a 4+ age rating

App Privacy:

  • Go to App Privacy section
  • Select "No, we do not collect data from this app"
  • Why: Although the app reads Health data, it sends it to the USER's own server (their configured API endpoint). The app developer never receives or stores any user data.

Pricing and Availability:

  • Set price to "Free" (or your preferred price)
  • Under availability, uncheck:
    • Apple Silicon Mac (HealthKit not available)
    • Apple Vision Pro (HealthKit not available)

Encryption Compliance:

  • After uploading a build, you'll be asked about encryption
  • Select: "Your app uses standard encryption algorithms" (for HTTPS)
  • No documentation upload is required

Build Versioning:

  • Version number should reflect meaningful changes (e.g., 1.0 for initial, 1.1 for sleep fix, 2.0 for watch app)
  • Build number must be unique and increment for each upload (e.g., 1, 2, 3...)
  • Update both in Xcode: General tab > Version and Build

Notes for Apple review

Critical for approval

Detailed reviewer notes are essential for HealthKit apps. Reviewers need to know exactly WHERE health data appears in your app.

When submitting, include this detailed note for the reviewer:

HEALTHKIT INTEGRATION OVERVIEW
This app integrates with the Health app to sync health data to a user-configured
API endpoint. It is designed for developers who want to consolidate health data
on their personal dashboards.

WHERE HEALTHKIT DATA IS VISIBLE IN THE APP:

1. MAIN SCREEN (ContentView):
- "HealthKit" status row shows connection status with green/red indicator
- After granting permissions, shows "Connected" with green checkmark
- "Last Sync" shows timestamp of most recent health data sync

2. SETTINGS SCREEN:
- "Upload Statistics" section shows count of health records synced
- "Last Upload" shows date of most recent successful sync
- "Sleep Preferences" section with Sleep Tracker picker and Sleep Debt Threshold

3. DURING SYNC:
- Progress indicator shows health data being read from Health app
- Success message confirms health records were transmitted

HOW TO TEST:
1. Open the app
2. Tap "Request Health Access" - grant access to all requested health data types
3. Go to Settings (gear icon) and enter any HTTPS URL (e.g., https://httpbin.org/post)
4. Return to main screen and tap "Sync Now"
5. The app will read health data from the Health app and attempt to POST it

NOTE: The app requires users to provide their own API server. Without a valid
endpoint, sync will fail, but you can still observe the Health app integration
by granting permissions and seeing the connected status.

The app reads: Sleep analysis, Steps, Active energy, Resting heart rate, HRV, Workouts

Common rejection reasons

Based on developer reports, these are the most common reasons HealthKit apps get rejected:

Rejection reasonHow to avoid
Guideline 2.5.1: "Does not clearly identify HealthKit functionality"Ensure your description explicitly mentions "Health app". Show health data status prominently in UI.
App description too vagueUse the description template above. Say "reads from the Health app" not just "syncs health data".
Reviewer can't find HealthKit featuresProvide detailed reviewer notes (above) explaining exactly where health data appears.
Privacy policy inadequateUse the provided privacy policy which addresses all HealthKit requirements.
No clear value propositionExplain this is for developers/technical users who self-host dashboards.

HealthKit compliance checklist

Before submitting, verify:

  • App description explicitly mentions "Health app" (not just "HealthKit")
  • Main screen shows Health app connection status (green/red indicator)
  • Settings screen shows health data sync statistics
  • Privacy policy includes HealthKit-specific disclosures
  • Privacy policy states data is not used for advertising/marketing
  • Reviewer notes explain WHERE health data is visible
  • Reviewer notes include step-by-step testing instructions
  • Sleep tracker selection configured (Oura, Apple Watch, etc.)
  • Screenshots show the Health app permission dialog or connection status

Development vs production

You can continue using developer mode (installed via Xcode) while your App Store submission is under review. Both versions can coexist on your device.

Distribution methodValidityBest for
Developer (Xcode)1 year (paid account)Personal use, development
TestFlight90 days per buildBeta testing
App StorePermanentPublic distribution

Source code

The complete iOS app source code is available in the private GitLab repository. Contact for access.


Next: Deployment covers Kubernetes deployment and GitOps configuration.