🔀 How to untangle and manage build distribution — Webinar, May 16th — Register
🔀 How to untangle and manage build distribution — Webinar, May 16th — Register

A hitchhiker's guide to the App Store Connect API

While the App Store Connect API is a powerful service for performing all sorts of tasks in App Store Connect, it's sometimes difficult to work with (shocking!).

To help make it easier to work with, we've written this post to explain what the App Store Connect API is, provide some use cases that show where it excels, and give you some tips on how to get started using the API.

What is the App Store Connect API?

Performing actions manually in Apple’s App Store Connect website is the most common way for developers to manage their membership settings as well as manage and monitor the state of their apps in the App Store.

Performing certain tasks manually through the website can be cumbersome, slow, and error-prone as you have to jump in and out of the site during the release process to get things done. On top of this, App Store Connect can sometimes be unreliable and is not necessarily the best at giving actionable feedback when an internal error occurs in the platform.

To streamline your release process and avoid having to perform manual tasks in App Store Connect, you can build tools and apps that use the App Store Connect API and can even run without any manual input or authentication.

Common use cases for the App Store Connect API

What can you do with the App Store Connect API? Here are some resources and code examples that can help you get started: 

Libraries

There are numerous Open-Source libraries that make interacting with the App Store Connect API a breeze.

Resources

We've written several articles showcasing how to build your own automations using the App Store Connect API. Behold: 

5 tips on how to interact with the App Store Connect API

#1 Leverage the power of OpenAPI

The data structure for the App Store Connect API is fairly complex and trying to write all the data structures to decode the API’s responses can be a time-consuming process.

Thankfully, the App Store Connect API has a set of OpenAPI definitions that specify the available endpoints and their responses and that you can use to generate code for your app automatically. There are numerous OpenAPI generators available for a wide variety of languages including Swift.

For instance, as Jared explained a while back on our blog, you can use Apple’s own Swift Open API generator to create all necessary Swift structs to decode data from the App Store Connect API’s responses.

Alternatively, you can use a library like Antoine Van Der Lee’s app-store-connect-swift-sdk, which contains all generated Swift types and classes to perform requests with.

#2 Do not handle authentication yourself

Authentication is another common friction point that tends to put people off. All App Store Connect API requests need to be authenticated with a JWT token signed with a private App Store Connect key with the right permissions.

Despite Apple having great resources for both creating App Store Connect keys and signing JWT tokens with them, the process is not trivial and can be time consuming if you don’t have experience with encryption and tokens.

My suggestion, and the approach I use every time I have to work on an automation that makes requests to the App Store Connect API, is to use a library that can handle the heavy lifting for you.

This is an example of some Swift code I wrote a while back to generate JWT tokens from an API key using a third-party library called SwiftJWT:

import SwiftJWT
import Foundation
struct JWTClaims: Claims {
    let iss: String
    let iat: Date?
    let exp: Date?
    let aud: String
}
func generateToken(keyId: String, issuerId: String, privateKey: String) throws -> String {
    let header = Header(typ: "JWT", kid: keyId)
    let claims = JWTClaims(
        iss: issuerId, 
        iat: Date(),
        exp: Date() + 60 * 60 * 12, 
        aud: "appstoreconnect-v1"
    )
    var jwt = JWT(header: header, claims: claims)
    let keyData = Data(base64Encoded: privateKey)
    return try jwt.sign(using: .es256(privateKey: privateKey.data(using: .utf8)!))
}

If you’d like to use a similar library in a different language, jwt.io has a comprehensive list of libraries in a wide range of languages for you to choose from.

An even better approach would be to use an App Store Connect API SDK that you can give an App Store Connect key to and hand off the duties of creating JWT tokens when needed, keeping them up to date and also adding them to the headers of all your requests, like the app-store-connect-swift-sdk library does:

import AppStoreConnect_Swift_SDK

let configuration = try! APIConfiguration(
    issuerID: "",
    privateKeyID: "",
    privateKey: ""
)
let provider = APIProvider(configuration: configuration)
let reviewSubmissions = APIEndpoint
  .v1
  .apps
  .id("6446048195")
  .appStoreVersions
  .get(parameters: .init(filterPlatform: [.macOs], fieldsAppStoreVersionSubmissions: [.appStoreVersion], include: [.appStoreVersionSubmission]))
_ = try await provider.request(reviewSubmissions)

#3 Request only what you need and paginate

Requests to the App Store Connect API can be fairly slow if you are requesting a lot of data. Thankfully, the API’s endpoints are built with performance optimization in mind.

There are a number of query parameters that allow you to reduce the amount of data that the endpoint needs to load and make the response from the server as quick as possible:

  • fields[*]=: For each entity that the App Store Connect API endpoint is returning, you can tell it to only return the properties you are interested in using a comma-separated list with their names.
  • filter[*]=: Most App Store Connect API endpoints have a set of filters you can use to limit the amount of data that they return.
  • limit=: Use this parameter to only return a fixed number of items in the response. I would recommend setting this parameter fairly low and then using the PagedDocumentLinks entity in the response to request more items if and when needed.
  • include=: By default and to make the response time as quick as possible, the App Store Connect API will not return any related objects unless you explicitly tell the endpoint to do so via the include query parameter. It might be tempting to include a lot of entities in this field to avoid multiple network requests but you must know that this can slow down your requests significantly.

Let’s take the apps list endpoint as an example. Let’s say we are only interested in getting the names of 5 iOS apps and their bundle IDs. Instead of requesting all available data, we can reduce the burden on the API and speed the request up using query parameters:

https://api.appstoreconnect.apple.com/v1/apps?fields[apps]=name,bundleId&limit=5

Don’t have a CI/CD pipeline for your mobile app yet? Struggling with a flaky one?
Try Runway Quickstart CI/CD to quickly autogenerate an end-to-end workflow for major CI/CD providers.
Try our free tool ->
Sign up for the Flight Deck — our monthly newsletter.
We'll share our perspectives on the mobile landscape, peeks into how other mobile teams and developers get things done, technical guides to optimizing your app for performance, and more. (See a recent issue here)
The App Store Connect API is very powerful, but it can quickly become a time sink.
Runway offers a lot of the functionality you might be looking for — and more — out‑of‑the‑box and maintenance‑free.
Learn more

#4 Create a layer on top of the networking code

Earlier in the article, I mentioned that using an OpenAPI generator will help you save some time generating the models to decode the App Store Connect API’s responses. While doing this will streamline your development process, these models will mirror the exact data structure as App Store Connect’s API, which is not trivial to work with.

For this reason, I would recommend creating an intermediate layer that simplifies your data models and makes it easier for you to request information or perform actions.

For example, you could create a small extension that automatically extracts the information you need from the includes section of your response:

extension AppStoreVersionResponse {
    var phasedReleases: [AppStoreVersionPhasedRelease] {
        included?
            .compactMap { item in
                if case let .appStoreVersionPhasedRelease(phasedRelease) = item {
                    return phasedRelease
                }
                return nil
            } ?? []
    }
    
    var localizations: [AppStoreVersionLocalization] {
        included?
            .compactMap { item in
                if case let .appStoreVersionLocalization(localization) = item {
                    return localization
                }
                return nil
            } ?? []
    }
}

Or you could even make a small utility method that simplifies complex processes and tasks such as submitting an app for review:

  func submit(platform: Platform, version: AppVersion) async throws {
        guard let reviewSubmission = try await getOrCreateAReviewSubmission(forVersion: version, withPlatform: platform) else { return }
        if reviewSubmission.attributes?.state == .readyForReview && (reviewSubmission.relationships?.items == nil || reviewSubmission.relationships?.items?.data == nil || reviewSubmission.relationships?.items?.data?.isEmpty == true)  {
            let reviewSubmissionRepresentableData = ReviewSubmissionItemCreateRequest.Data.Relationships.ReviewSubmission.Data(type: .reviewSubmissions, id: reviewSubmission.id)
            let reviewSubmissionRepresentable = ReviewSubmissionItemCreateRequest.Data.Relationships.ReviewSubmission(data: reviewSubmissionRepresentableData)
            let appStoreVersionRepresentableData = ReviewSubmissionItemCreateRequest.Data.Relationships.AppStoreVersion.Data(type: .appStoreVersions, id: version.id)
            let appStoreVersionRepresentable = ReviewSubmissionItemCreateRequest.Data.Relationships.AppStoreVersion(data: appStoreVersionRepresentableData)
            let submissionItemRelationships = ReviewSubmissionItemCreateRequest.Data.Relationships(
                reviewSubmission: reviewSubmissionRepresentable,
                appStoreVersion: appStoreVersionRepresentable
            )
            let submissionItemPayload = ReviewSubmissionItemCreateRequest.Data(type: .reviewSubmissionItems, relationships: submissionItemRelationships)
            let submissionItemBody = ReviewSubmissionItemCreateRequest(data: submissionItemPayload)
            let reviewSubmissionItemsRequest = APIEndpoint
                .v1
                .reviewSubmissionItems
                .post(submissionItemBody)
            
            _ = try await provider.request(reviewSubmissionItemsRequest)
        }
        
        if version.state == .readyForReview {
            // Patch the submission item to submit it for review
            let reviewSubmissionAttributes = ReviewSubmissionUpdateRequest.Data.Attributes(isSubmitted: true)
            let reviewSubmissionUpdateRequestBodyData = ReviewSubmissionUpdateRequest.Data(type: .reviewSubmissions, id: reviewSubmission.id, attributes: reviewSubmissionAttributes)
            let reviewSubmissionPatchRequestBody = ReviewSubmissionUpdateRequest(data: reviewSubmissionUpdateRequestBodyData)
            let reviewSubmissionsPatchRequest = APIEndpoint
                .v1
                .reviewSubmissions
                .id(reviewSubmission.id)
                .patch(reviewSubmissionPatchRequestBody)
    
            _ = try await provider.request(reviewSubmissionsPatchRequest)
        }
    }

#5 Can’t find the right endpoint? Check what App Store Connect is doing

While the App Store Connect API is very powerful and extensive, it can also be overwhelming. I have found myself many times digging through the documentation trying to figure out which endpoint to use for a specific action.

The naming of the data models and endpoints can be confusing if you’re not familiar with the API, but there’s a trick that can help you identify which requests Apple is doing in App Store Connect.

Open up App Store Connect on your favourite browser, perform the action you would like to find endpoints for and inspect the network traffic using your browser’s developer tools.

I used this trick recently to find which endpoints to use to submit a beta build for review:

As you can see in the video above, Apple uses their internal version of the API and, while most endpoints map one-to-one with the public version, you will find some situations where Apple uses functionality that is not publicly available.

One example is the history page in App Store Connect. If you inspect the traffic on this page, you will see that Apple is making requests to an endpoint called <code>https://appstoreconnect.apple.com/iris/v1/appStoreVersions/:id/appStoreVersionStateChanges<code> which does not have a counterpart in the App Store Connect API:

Looking to save some time?

As you have seen in this article, there are numerous resources available for you to seamlessly use the App Store Connect API.

But you likely know that, similarly to what you might have encountered on App Store Connect itself, the API is sometimes unreliable with errors that must be handled carefully. Retry mechanisms must be put in place to ensure a good user experience for the tools you are building.

Aside from the occasional unreliability, building your own tooling can sometimes be time-consuming and complex. If you’d like an out-of-the-box no-code solution, Runway offers seamless integration with App Store Connect using its API to automate recurring tasks and pull data into a unified dashboard, alongside the rest of your release stack.

We've built numerous automations that rely on the App Store Connect API and we've noticed that sometimes incidents in the API are not reported on the official App Store Connect status page. For this reason, we've created our own unofficial status page for App Store Connect, providing a clear answer to whether it's currently fully functional or not.

‍

App Development

Release better with Runway.

Runway integrates with all the tools you’re already using to level-up your release coordination and automation, from kickoff to release to rollout. No more cat-herding, spreadsheets, or steady drip of manual busywork.

Release better with Runway.

Runway integrates with all the tools you’re already using to level-up your release coordination and automation, from kickoff to release to rollout. No more cat-herding, spreadsheets, or steady drip of manual busywork.

Don’t have a CI/CD pipeline for your mobile app yet? Struggling with a flaky one?

Try Runway Quickstart CI/CD to quickly autogenerate an end-to-end workflow for major CI/CD providers.

Looking for a better way to distribute all your different flavors of builds, from one-offs to nightlies to RCs?

Give Build Distro a try! Sign up for Runway and see it in action for yourself.

Release better with Runway.

What if you could get the functionality you're looking for, without needing to use the ASC API at all? Runway offers you this — and more — right out-of-the-box, with no maintenance required.