Skip to content
Back to Blog

How to Install IPA Files Over the Air (OTA)

BetaDrop Team
Updated
9 min read
install IPA over the airOTA IPA installmanifest.plistitms-services
How to Install IPA Files Over the Air (OTA)

Reviewed by Kaushal Rola

Founder, Immersive Mobile Designs — we build the OTA tooling this post describes.

Share:

Over-the-air installation is three moving parts: a signed IPA, a manifest that describes it, and an itms-services link that points iOS at the manifest. Get all three right and a tester installs your build by tapping a link in Safari. Get one wrong and iOS reports a generic failure that names none of them.

This page is the reference for all three. It covers every manifest key, the exact grammar of the itms-services URL, what Apple requires of the server, the Xcode 26 export settings, and a table that maps each install error to its cause.

It is written for the developer who built the IPA and holds its signing identity. If you did not build the app, none of this will make it installable: the provisioning profile inside the build decides which devices accept it, and no manifest can change that.

What happens when a tester taps the link

Nothing about the flow is magic, and knowing the order tells you which step failed:

  1. The tester opens a URL beginning itms-services://. iOS, not the browser, handles it.
  2. iOS reads the url= parameter and fetches that manifest over HTTPS.
  3. iOS parses the manifest and shows the confirmation prompt, using the title and, if present, the icon assets.
  4. On confirmation, iOS downloads the IPA from the software-package URL in the manifest.
  5. iOS verifies the signature and the embedded provisioning profile against the device.
  6. The app appears on the Home Screen.

Steps 2 and 4 are separate network requests to whatever URLs you supplied. Both must be HTTPS, and both must work without a login, a redirect to a consent page, or a user-agent check.

What the server has to provide

HTTPS, on both URLs

Apple is explicit about the manifest: in the Device Management documentation, the app manifest URL needs to begin with https: (Apple Device Management, InstallApplicationCommand.Command, ManifestURL, checked on 2026-09-03). Apple documents the asset URL the same way: it needs to start with https:// (apple/device-management, other/manifesturl.yaml, checked on 2026-09-03). A certificate the device does not already trust fails the same way a plain http URL does, and the tester sees a generic download error rather than anything about TLS, so verify both URLs in Safari on a real device before you blame the manifest.

Direct, unauthenticated file responses

iOS is not a browser session. It will not fill in a login form, click through an interstitial, or carry a cookie you set earlier. If your host serves a download page instead of the file, or redirects to a sign-in, the install fails. Test with curl -IL and confirm the final response is the file itself.

Content types

Serve the manifest as XML and the IPA as application/octet-stream. Apple's deployment guide gives these as application/octet-stream for the .ipa and text/xml for the plist (Apple Platform Deployment, Distribute proprietary in-house apps to Apple devices, checked on 2026-09-03). A host that returns text/html for an unknown extension is a common cause of a manifest that iOS silently refuses to parse, so setting these removes the question.

Manifest key reference

The manifest is a property list with one items array. Each item has an assets array and a metadata dictionary. Assets are identified by their kind.

Assets

kindRequiredWhat it is
software-packageYesThe HTTPS URL of the .ipa. This is the only asset iOS actually downloads and installs.
display-imageNoA PNG shown as the placeholder icon on the Home Screen while the app downloads. Without it the tester watches a grey square install.
full-size-imageNoA larger PNG used for the artwork iOS shows alongside the install. Xcode writes both icon URLs when you ask it to generate a manifest.
asset-pack-manifestOnly with on-demand resourcesPoints at the asset pack manifest. Xcode calls the matching export key assetPackManifestURL and requires it when the app uses on-demand resources.

Metadata

KeyRequiredRule
bundle-identifierYesMust match CFBundleIdentifier inside the IPA character for character. A mismatch installs a second, broken copy or fails outright.
bundle-versionNoThe version string shown in the install prompt; teams normally use CFBundleShortVersionString, falling back to CFBundleVersion. Shipping a new build under a version iOS already has is the usual reason an update appears not to apply.
kindYesAlways the literal string software for an app.
titleYesThe name shown in the install prompt. This is the only thing the tester reads before agreeing, so make it the app name and not the file name.
subtitleNoOptional. Apple defines it as the name of the app or package developer.

Not sure what is actually inside your build? The IPA inspector reads the bundle identifier and version out of the file in your browser, so you can copy them into the manifest instead of typing them from memory.

Copy-paste manifest template

Replace the four URLs and the three metadata values. Everything else is fixed:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>items</key>
  <array>
    <dict>
      <key>assets</key>
      <array>
        <!-- Required: the IPA itself -->
        <dict>
          <key>kind</key>
          <string>software-package</string>
          <key>url</key>
          <string>https://example.com/builds/myapp.ipa</string>
        </dict>

        <!-- Optional: placeholder icon during install -->
        <dict>
          <key>kind</key>
          <string>display-image</string>
          <key>url</key>
          <string>https://example.com/icons/display.png</string>
        </dict>

        <!-- Optional: larger artwork -->
        <dict>
          <key>kind</key>
          <string>full-size-image</string>
          <key>url</key>
          <string>https://example.com/icons/full-size.png</string>
        </dict>
      </array>

      <key>metadata</key>
      <dict>
        <key>bundle-identifier</key>
        <string>com.yourcompany.appname</string>
        <key>bundle-version</key>
        <string>1.0.0</string>
        <key>kind</key>
        <string>software</string>
        <key>title</key>
        <string>My App Name</string>
      </dict>
    </dict>
  </array>
</dict>
</plist>

The DOCTYPE line is the one http URL that belongs in the file. It is an XML document type identifier, not something iOS fetches, and rewriting it to https changes nothing.

If you would rather not hand-edit XML for every build, the manifest.plist generator fills the identifier, version and title from an IPA you drop into the page and hands back the finished file plus the matching install link. The parsing runs in your browser, so the build never leaves your machine.

Anatomy of the itms-services link

The link has exactly one shape:

itms-services://?action=download-manifest&url=https://example.com/manifest.plist
  • itms-services:// is the scheme iOS registers. There is no host, which is why the URL has an empty authority and goes straight to ?.
  • action=download-manifest is the only action that matters here and is not optional.
  • url= takes the absolute HTTPS URL of the manifest.

Encoding rules

The manifest URL is a parameter value inside another URL, so anything with meaning in a query string has to be percent-encoded before it goes in:

  • & becomes %26. An unencoded ampersand ends the url parameter early, and iOS fetches a truncated address.
  • ? becomes %3F, = becomes %3D, # becomes %23, and a space becomes %20.
  • The :// in the manifest URL can be left as is and usually is. Encoding it to %3A%2F%2F also works.
  • When the link is written into HTML, the ampersand before url= is itself markup, so it has to be &amp; in the source. Copying a link out of a rendered page and pasting it into code is a common way to end up with a literal &amp; in the URL.

Keep the manifest URL free of query strings if you can. A signed URL with an expiry and a signature is legal but multiplies the encoding mistakes, and it also means the link stops working when the signature expires.

Where the link works and where it does not

The link only resolves on iOS and iPadOS, in a real browser. Safari hands it to the system; the embedded browsers inside Slack, Gmail, Teams and Notion swallow it silently, which produces the most common support message in beta testing: nothing happens when I tap it. Tell testers to open it in Safari, or hand them a QR code from the free QR code generator so the phone camera opens it in Safari for them.

Exporting the IPA on Xcode 26

In Xcode: Product, then Archive, then Distribute App, then Release Testing, which signs automatically against the devices your team has registered in App Store Connect. Choose Custom if you need to name the profile yourself. Release Testing is the preconfigured option that replaced the command line name ad-hoc; Xcode 15.4 began warning on ad-hoc and Xcode 26 refuses it, though Ad Hoc still appears under Custom.

In an ExportOptions.plist, the value is release-testing. Xcode 26.6 lists the old spelling as deprecated in favour of it (xcodebuild -help, exportOptionsPlist reference, checked on 2026-09-03). This is the single most common breakage in a CI lane that has not been touched for a year, and fastlane is not exempt: gym copies export_method straight into that same plist key, so a lane carrying the old value fails exactly as a hand-written plist does. Set it to release-testing.

Xcode can also write the manifest for you. Supplying a manifest dictionary in the export options, with the sub-keys appURL, displayImageURL and fullSizeImageURL, makes the export produce a distribution manifest alongside the IPA (same source, checked on 2026-09-03). The catch is that you have to know the final hosting URLs at export time, which is why most teams generate the manifest after upload instead.

Enterprise builds export the same way with the enterprise method. They install on any device rather than a registered list, and the licence covers internal employee use only, so they are not a shortcut around the device cap for outside testers.

Publish a build over the air by hand

The full manual path, in order:

  1. Export the archive for release testing.

    In Xcode, choose Product then Archive, then Distribute App, then Release Testing, which signs automatically against the devices your team has registered in App Store Connect. Choose Custom if you need to name the profile yourself. In an ExportOptions.plist the method value is release-testing.

  2. Host the IPA on HTTPS.

    Upload the .ipa to a server reachable over HTTPS with a certificate the device already trusts. Confirm the file downloads from that URL in a browser before going further.

  3. Write the manifest.

    Create a manifest property list containing the software-package asset URL, the bundle-identifier, the bundle-version and the title. The bundle identifier and version must match the values inside the IPA exactly.

  4. Host the manifest on HTTPS.

    Upload the manifest next to the IPA. Apple requires the manifest URL to begin with https, so an http URL fails before the download starts.

  5. Build the itms-services link.

    Assemble itms-services://?action=download-manifest&url= followed by the percent-encoded HTTPS URL of the manifest.

  6. Open the link on the device.

    Send the link to the tester and have them open it in Safari on the iPhone or iPad itself. An in-app browser inside a chat or mail client will not hand the link to iOS.

Why the install fails

iOS reports very little. This table maps what the tester sees to what is actually wrong:

What the tester seesUsual causeFix
Nothing happens on tap, or Safari shows the raw linkThe link was opened in an in-app browser, the manifest URL is not HTTPS, or the manifest is unreachable or unparsableFixing an itms-services link that does not work
Unable to Verify App after install, app will not openThe device cannot reach Apple to validate the certificate, or the profile does not cover this deviceFixing "Unable to Verify App" on iOS
Untrusted Enterprise DeveloperAn enterprise-signed build whose certificate the tester has not trusted yetFixing the untrusted enterprise developer error
App Integrity Could Not Be VerifiedThe signature no longer matches the payload, usually a re-signed or edited IPA, or an expired certificateFixing "app integrity could not be verified"
The update installs but the old build is still thereThe manifest bundle-version does not match the IPA, or the new build is signed with a different identity than the copy already installedFixing an incompatible update install

Two causes sit behind more than half of these and neither is visible from the manifest. The first is a device UDID that was never added to the provisioning profile before signing: collect them with the UDID checker and rebuild, because adding a device to the portal does not retroactively change a build that is already signed. The second is a profile or certificate that has expired since the build was made. Both are readable from the file itself with the provisioning profile decoder.

Skipping the manual path

Everything above is worth understanding once and worth automating after that. Hosting, HTTPS, the manifest, the icon assets and the encoded link are all mechanical, and they are the same for every build you will ever ship.

BetaDrop does that part: upload a signed .ipa and it serves the IPA and the manifest over HTTPS, writes the manifest from the metadata inside your build, extracts the icon, and returns the install link and a QR code. There is no server to configure and no XML to edit. If you are moving from another over-the-air service, the InstallOnAir alternative and Diawi alternative pages cover what changes.

Upload a signed IPA file and the manifest, the hosting and the encoded itms-services URL are handled for you.

Related reading

Frequently asked questions

What is OTA installation for iOS?

OTA (Over-The-Air) installation lets an iPhone or iPad download and install an app straight from a web server, with no Mac and no cable. The device opens an itms-services link, reads a manifest property list from the URL in that link, downloads the IPA the manifest points at, and installs it.

What is a manifest.plist file?

It is the XML property list iOS reads before it downloads anything. It carries the HTTPS URL of the IPA under the software-package asset, plus the bundle identifier, the bundle version and the display title that appear in the install prompt. Optional display-image and full-size-image assets replace the grey placeholder icon with your own artwork.

Why does OTA installation require HTTPS?

Apple states it directly: in the Device Management documentation the app manifest URL "needs to begin with https:" (checked on 2026-09-03). A plain http URL, a self-signed certificate, or a chain the device does not already trust all fail before the install prompt appears, and the error the tester sees does not mention TLS.

Can I install any IPA over the air?

No. The build has to be signed with a profile that covers the device. An ad-hoc profile installs only on devices whose UDID was added before signing, and an enterprise profile installs anywhere but is licensed for internal employee use only. An unsigned IPA, or one signed for a device that is not in the profile, fails at install no matter how the manifest is written.

What is the correct Xcode 26 export method for an over-the-air build?

release-testing. Xcode 26.6 lists ad-hoc as deprecated in favour of release-testing in the exportOptionsPlist reference (xcodebuild -help, checked on 2026-09-03), and fastlane is not exempt: gym copies export_method straight into the same plist key, so a lane still using the old value hits the same failure as a hand-written plist.

How long do OTA install links last?

As long as the files stay on the server and the signing certificate is valid. On BetaDrop a link starts at 3 days and a free account can extend it to 7 days; anonymous guest links last 24 hours. There are no servers or certificates for you to manage.

Ready to Distribute Your App?

Upload your IPA or APK file and get a shareable install link in seconds. Your first upload needs no account. Completely free.