# itms-services Install Links and manifest.plist, Explained

> How an itms-services link installs an iOS app over the air: the manifest.plist format, HTTPS and MIME type rules, and why the build must still be signed.

Last updated: 2026-11-10
Canonical: https://buildferry.app/itms-services-manifest-plist

Every over-the-air install of a test build, whatever tool hosts it, comes down to two files on a web server and one special link.

## The link

```
<a href="itms-services://?action=download-manifest&url=https://example.com/manifest.plist">Install App</a>
```

When someone taps it in Safari on an iPhone, iOS downloads the manifest, asks to install the app, then downloads and installs the .ipa the manifest points to. Don't link to the .ipa directly. iOS won't install it that way.

## The manifest

The manifest is a small XML property list. This is the shape of Apple's own example:

```
<?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>
        <dict>
          <key>kind</key>
          <string>software-package</string>
          <key>url</key>
          <string>https://example.com/MyApp.ipa</string>
        </dict>
        <dict>
          <key>kind</key>
          <string>display-image</string>
          <key>url</key>
          <string>https://example.com/icon.png</string>
        </dict>
      </array>
      <key>metadata</key>
      <dict>
        <key>bundle-identifier</key>
        <string>com.example.myapp</string>
        <key>bundle-version</key>
        <string>1.2</string>
        <key>kind</key>
        <string>software</string>
        <key>title</key>
        <string>My App</string>
      </dict>
    </dict>
  </array>
</dict>
</plist>
```

- **software-package** is the .ipa's URL.
- **display-image** is the icon shown while the app installs.
- **bundle-identifier** must be the app's real bundle ID. Don't use a wildcard.
- **bundle-version** is the app's version.
- **title** is the name shown in the install prompt.

## The server rules

- **HTTPS everywhere.** The manifest URL and the .ipa must both use HTTPS, with a certificate the iPhone trusts. A self-signed certificate makes the install fail.
- **MIME types.** Apple suggests serving `.ipa` as `application/octet-stream` and `.plist` as `text/xml`.
- **Open it in Safari.** In-app browsers, such as the ones in Gmail or Slack, don't hand the link to iOS, so nothing happens.

## The link doesn't sign anything

The manifest only tells iOS where the build is. The .ipa still has to be signed for the iPhone: an ad hoc or development build that lists its UDID, or an enterprise build. Otherwise you'll see "Unable to Install" or ["integrity could not be verified"](https://buildferry.app/app-integrity-could-not-be-verified). [Check which devices a build includes](https://buildferry.app/check-udid-in-provisioning-profile).

## Hosting it yourself vs a tool

Hosting it yourself works well if you already run an HTTPS server and your testers' iPhones are already in the profile. You write the manifest for each build and keep the files up to date.

[Quick share](https://buildferry.app/share-ipa) does the hosting for you: drop a signed .ipa and get the link and a QR code, free, with no account for 24 hours.

When new testers keep joining, the slow part isn't the link, it's re-signing for each new iPhone. [BuildFerry](https://buildferry.app/) registers each iPhone you approve with your Apple account and re-signs the build, behind one link that never changes. [Start free](https://buildferry.app/signup).
