What the rejection typically looks like
Guideline 2.5.1 - Performance - Software Requirements
Your app uses or references the following non-public or deprecated APIs:
Frameworks/SomeSDK.framework/SomeSDK
Symbols: _IOPSCopyPowerSourcesInfo, _IOPSGetPowerSourceDescription
The use of non-public or deprecated APIs is not permitted on the App Store, as they can lead to a poor user experience should these APIs change. Continuing to use or conceal non-public or deprecated APIs in future submissions may result in the termination of your Apple Developer account.
Next Steps
If you use third-party libraries, update to the most recent version of those libraries. The strings, otool and nm tools can help you find where the code resides.
Paraphrased example – the exact wording in your message may differ.
What Guideline 2.5.1 actually means
Guideline 2.5.1 opens with one sentence that covers most rejections: "Apps may only use public APIs and must run on the currently shipping OS" (App Store Review Guidelines). The rest of the guideline adds three expectations:
- Public APIs only. No private frameworks, undocumented C functions or selectors, private system notifications or private URL schemes.
- Current OS, no deprecated tech. The app works on the OS customers run today and phases out "deprecated features, frameworks or technologies" Apple is retiring.
- Intended purpose and disclosure. APIs are used for what they were built for, and the app description mentions the integration. Apple's own examples: HomeKit should provide home automation, and HealthKit should serve health and fitness and integrate with the Health app.
Problems surface at two stages:
| Where | What you see | What it means |
|---|---|---|
| Upload / processing | Email with ITMS-90338: Non-public API usage or ITMS-90809: Deprecated API Usage | An automated scan of the uploaded binary. The email asks you to correct the issues and upload again. There is nobody to reply to, so the only fix is a new build. |
| App Review | Rejection citing "Guideline 2.5.1 - Performance - Software Requirements" | A reviewer flagged a symbol, a framework used without a visible purpose, or behavior on the current OS. You can reply in App Store Connect. |
Common triggers for a 2.5.1 rejection
- A third-party SDK with non-public symbols. The most frequent case. Old analytics, ad or device-info libraries call undocumented functions. One example is the IOKit power-source functions (
IOPSCopyPowerSourcesInfo), which Apple documents only for macOS and Mac Catalyst, not iOS. Static libraries are the hardest to spot because their code ends up inside your main executable. - Your method names collide with private selectors. The scan matches names, not intent. The ITMS-90338 email itself says renaming methods that match private APIs helps prevent future flags.
- Private Settings URL schemes and system notifications. Opening
prefs:root=orApp-Prefs:root=to jump to Wi-Fi or Bluetooth settings, or listening to private Darwin notifications such ascom.apple.springboard.lockcomplete, are reported as 2.5.1 non-public API findings. - UIWebView. Deprecated since iOS 12 in favor of
WKWebView. New apps that reference it are rejected at upload with ITMS-90809. The reference often hides in an old Cordova or Xamarin version, an ad SDK, or a forgotten storyboard or nib. - HealthKit or HomeKit without a visible purpose. A typical message reads that the app "uses the HealthKit or CareKit APIs but does not clearly identify the HealthKit and CareKit functionality in the app's user interface". Leftover entitlements from a template or plugin cause the same problem.
- Misused system services. VoIP pushes used to wake a chat app, or background modes without a matching feature (often cited under 2.5.4).
- Not running on the current OS. Crashes or broken layouts on the newest iOS, an outdated toolchain, or a deployment target set to an unreleased iOS version.
Rejected or stuck? Talk to an App Specialist – for free.
Book a free consultation call: we look at your rejection or setup, explain the fastest way forward and tell you honestly whether you need us. Prefer to hand it off? Book our AI-powered + human-powered service and we take care of it.
How to find the offending symbol or SDK
The message names a binary and a list of symbols or selectors. A path like Frameworks/Foo.framework/Foo points to an embedded dynamic framework. The bare app name (or Runner for Flutter) means the main executable, which includes every statically linked library.
- Get the exact build you uploaded. Export the IPA from Xcode's Organizer or take it from your CI artifacts, then run
unzip -q MyApp.ipa -d /tmp/ipa. Searching the.xcarchiveitself also works and includes dSYMs. - Search every file in the bundle.
grep -rl "UIWebView" /tmp/ipa/Payload/MyApp.applists every executable, framework and compiled nib that contains the string. Repeat with each name from the email. - Inspect a suspicious framework. Run
nm -u Frameworks/Foo.framework/Foo | grep -i webviewto see imported symbols such as_OBJC_CLASS_$_UIWebView. Then runotool -L Frameworks/Foo.framework/Foo: any path containing/PrivateFrameworks/is a red flag.otool -ovprints the Objective-C classes and methods a binary defines. - Check selectors. Objective-C selector names are stored as plain strings, so
strings -a MyApp | grep -Fx "flaggedSelector:"confirms whether a flagged selector is really in the binary. Copy the name exactly as listed, including colons. - Trace main-executable hits to a library. Set Build Settings → Write Link Map File (
LD_GENERATE_MAP_FILE = YES) and rebuild. Search the map file for the symbol: it shows which.ofile or.aarchive the symbol came from. - Search the dependency sources. Run
grep -rl "symbolName" Pods/ node_modules/ ios/(plus the Flutter pub cache or Swift package checkouts) and map the hit to a package withnpm ls <package>orflutter pub deps. - Check your own code last. If the selector is defined in your sources, for example in a category or extension, rename it with a project prefix.
How to fix a 2.5.1 rejection step by step
- Update or replace the dependency. Move to the SDK's latest version and check its changelog for private API or UIWebView fixes. Replace abandoned libraries and delete unused pods and plugins. If a static library you can't update contains the symbol, it has to go.
- Swap private calls for public APIs. For Settings links, use
UIApplication.openSettingsURLString(your app's settings page),openNotificationSettingsURLString(iOS 16+) oropenDefaultApplicationsSettingsURLString(iOS 18.3+). There is no public way to open the Wi-Fi or Bluetooth pages, so tell users where to go instead. For battery information, useUIDevicebattery monitoring instead of IOKit. - Migrate UIWebView to WKWebView. Use Xcode's Find in Project for "UIWebView" and check storyboards and xibs too: Interface Builder files can still contain the old class.
- Fix framework purpose and disclosure. Make the integration visible in the UI and the description, or remove the capability completely in Signing & Capabilities (see the next section).
- Build with the required toolchain. Since April 28, 2026, uploads must be built with Xcode 26 or later using the iOS 26 SDK or later. Since September 9, 2026, iOS and iPadOS apps must also target iOS 13 or later. Apple has announced that iOS and iPadOS apps must be built with the iOS 27 SDK or later from April 2027. Check Apple's upcoming requirements for the current rule, and use a release or Release Candidate version of Xcode for App Store builds, not a beta.
- Test on the shipping OS. Run the release build on a device with the current major iOS version (iOS 27 as of October 2026) and on the oldest version you support. A crash on launch is usually rejected under Guideline 2.1 App Completeness.
- Verify and upload a new build. Increment
CFBundleVersion, repeat thegrep/nmchecks on the new IPA, run Validate App in the Organizer, upload, and select the new build on the version page before you resubmit.
Intended purpose: HealthKit, HomeKit, CallKit and background modes
Here App Review checks behavior, not symbols: does what the app visibly does match the entitlements it ships with?
| Technology | What App Review expects | Typical miss |
|---|---|---|
| HealthKit / CareKit | The Health functionality is clearly identified in the UI, and the description mentions the Health app integration | Data appears without attribution, or the entitlement is enabled but no feature uses it |
| HomeKit | The app actually provides home automation | Using HomeKit data for unrelated features |
| PushKit VoIP + CallKit | Every VoIP push reports an incoming call to CallKit | Using VoIP pushes to wake a messaging app |
Background modes (UIBackgroundModes) | Each mode matches a feature the reviewer can find, such as audio playback or navigation | Silent audio or location updates used only to keep the app alive |
For HealthKit, developers in Apple's forums report getting approved after adding a short explanation screen before the permission prompt, a permanent label such as "Health data sourced from Apple Health" next to the values, and a description sentence naming the Health app integration. Keep the purpose strings specific too (see the 5.1.1 guide).
For PushKit, Apple's documentation is explicit: on iOS 13 and later, the system terminates your app if it fails to report a call to CallKit. For anything that isn't a call, use standard notifications.
Flutter, React Native, Capacitor and game engines
In cross-platform apps the culprit is rarely in your Dart, JavaScript or C# code. It's usually in a native plugin or SDK.
- React Native / Expo: native modules come in through CocoaPods from
node_modules. Upgrade the package yourgrepfound. With Expo, regenerate the native project (npx expo prebuild --clean) so stale pods don't linger. - Flutter: plugin code is compiled into
Runneror shipped as frameworks.flutter pub outdatedshows upgrade paths. Replace unmaintained plugins. - Capacitor / Cordova: check every plugin's iOS sources, not just the platform version. Old plugins are a frequent source of
UIWebViewandprefs:rootreferences. - Unity / Unreal: upload a release (shipping) build and keep the engine and ad SDKs current. Development builds and outdated engine or ad SDK versions are the first things to rule out.
Plugin updates often fix other upload blockers at the same time, such as missing SDK privacy manifests. If your email also lists ITMS-91053, handle both in one pass.
How to respond to App Review (and when not to)
For ITMS upload emails there is nobody to reply to. Fix the binary, increment the build number and upload again.
For an App Review rejection, open Apps in App Store Connect, select the app, click the unresolved issues link at the top, click Resolve next to the submission and then Reply to App Review (Apple's help page). Name each flagged symbol or framework, what you changed and the new build number. For intended-purpose issues, point to the screen showing the integration and attach a screenshot.
If you believe the flag is a false positive, say so with evidence, such as nm output showing the symbol isn't in the binary. If App Review misunderstood how your app works, you can submit one appeal per submission to the App Review Board.
Never hide a private API with string obfuscation, dlsym or NSClassFromString. The standard 2.5.1 rejection text warns that continuing to use or conceal non-public APIs may lead to termination of your developer account.
For bug fix updates to a live app, the guidelines say fixes won't be delayed over guideline violations unless legal or safety issues are involved. You can ask App Review in App Store Connect to use this process and fix the issue in your next submission (App Review Guidelines). It doesn't help with ITMS upload errors, because those builds never reach review.
How to prevent 2.5.1 rejections
- Scan every release build in CI. Unzip the IPA and fail the pipeline if
grep -rlfindsUIWebView,prefs:root,App-Prefsor/PrivateFrameworks/. It takes seconds. Expect the occasional false positive and treat a hit as a prompt to investigate. - Diff entitlements per release.
codesign -d --entitlements - MyApp.appshows what you actually ship. A new HealthKit or HomeKit entitlement you didn't add on purpose is a warning sign. - Update dependencies on a schedule, not the week your release is due.
- Pin and plan the toolchain. Pin Xcode on your CI runners, plan the iOS 27 SDK move before April 2027, and run TestFlight builds on the latest iOS release and its betas.
- Keep descriptions honest. Mention every framework integration you rely on. The 2.3 metadata guide covers the rest of your listing.
AI-assisted checks speed up the tedious parts, like matching flagged symbols to dependency changelogs, while a human judges whether a framework's use matches its purpose. At appsubmitter.io we combine both. You can book a free consultation call to go through your rejection, or book the iOS service for pipeline setup, guidance on the fix and review communication. Code changes aren't included and are discussed and quoted upfront.
Template: how to reply to App Review
Adapt this template to your situation. Keep it factual, short and specific – and only claim what you have actually changed.
Checklist before you resubmit
- Every symbol, selector and binary path from the email or rejection is traced to a specific library or source file.
- grep -rl on the new IPA finds none of the flagged names, UIWebView, prefs:root, App-Prefs or /PrivateFrameworks/.
- nm -u and otool -L on each embedded framework show no private symbols or private framework paths.
- The SDKs and plugins involved are updated to their latest versions, and unused ones are removed.
- Your own methods that matched private selector names are renamed with a project prefix.
- Every entitlement and UIBackgroundModes value matches a feature a reviewer can find, and HealthKit or HomeKit use is labeled in the UI and named in the description.
- The build uses a release or RC version of Xcode 26 or later with the iOS 26 SDK or later, and the deployment target is iOS 13 or later.
- The release build launches and works on a device running the current major iOS version.
- CFBundleVersion is incremented, Validate App passes, and the new build is selected on the version page.
Frequently asked questions
What does ITMS-90338 Non-public API usage mean if I never used a private API?
grep, nm or a link map, then update or remove the library, or rename the method.Why do I get ITMS-90809 for UIWebView when my code doesn't use it?
grep -rl "UIWebView" on the unzipped IPA or the archive to see which file contains it, then update or remove that dependency.Are app updates with UIWebView still accepted?
WKWebView as soon as an ITMS-90809 email arrives.Can I hide a private API call with obfuscation or dlsym?
Can my app open the Wi-Fi or Bluetooth settings page directly?
App-Prefs:root=WIFI are non-public and have led to 2.5.1 rejections. Use UIApplication.openSettingsURLString to open your app's own settings page and tell users where to find the rest.Which Xcode version do I need to upload to the App Store in 2026?
How do I fix a 2.5.1 HealthKit rejection?
Official source: App Store Review Guidelines – 2.5.1 Software Requirements. Store policies change regularly – always check the current version. This guide is independent advice and not affiliated with Apple or Google.