appstoreconnect-swift-sdk: typed App Store Connect endpoints without hand-rolling JWTs
The Swift SDK to work with the App Store Connect API from Apple.
At a glance
- What is it?
- This Swift package wraps Apple's App Store Connect API in generated endpoint enums, handles the JWT signing, and gives you a paged async sequence over results. What it costs you is a non-standard licence and a hard organisational gate on API access.
- Who is it for?
- Adopt this SDK if your Swift codebase needs App Store Connect automation and your team is enrolled as an organization with a Team Agent who can mint an API key, because it removes the JWT plumbing and the OpenAPI request typing in one move. Do not adopt it if you are an individual enrollee, if you cannot accept a non-standard licence, or if you need endpoints the package has not generated yet.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The access gate is an organisation, not an account
Before any code runs there is a question of eligibility that trips up a lot of first-time users. The README states it directly: to request access you go to the API Keys section in Users and Access in App Store Connect, and you must be the Team Agent, the Legal role, of a development team enrolled as an organization. Access for developers enrolled as an individual is described as coming soon.
Read that as a hard constraint rather than a queue position. If your team is an individual enrolment, this SDK cannot help you yet, because the blocker is on Apple's side and no client library changes it. If you are an organization but not the Team Agent, your next step is a request to whoever holds that legal role, not a code change.
The keys themselves come from a separate page, appstoreconnect.apple.com/access/integrations/api, which is where you create a key and where you find both the issuer ID and the private key ID.
Reading the .p8 file is manual by design
The private key downloads as a .p8 file. The README walks through what you do with it, and the manual step is worth knowing about before you automate a setup script around it: you open the file in a text editor, and it contains PEM markers around the key material.
-----BEGIN PRIVATE KEY-----
FDFDGgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwQgPaXyFvZfNydDEjxgjUCUxyGjXcQxiulEdGxoVbasV3GgCgYIKomokDj0DAQehRANCAAASffd/DU3TUWAoLmqE6hZL9A7i0DWpXtmIDCDiITRznC6K4/WjdIcuMcixy+m6O0IrffxJOablIX2VM8sHRpoiuy
-----END PRIVATE KEY-----The documented procedure is to copy the contents and remove the white lines along with the BEGIN and END markers. That is the string you hand to the SDK as the private key. If you are building this into a deployment pipeline, this manual extraction step is the piece you will want to script carefully, since the SDK expects key material with the markers stripped.
APIConfiguration takes either key material or a file path
There are two construction paths and they exist for different deployment situations. Passing the key string directly keeps the secret in memory and lets it come from wherever your configuration lives.
let configuration = APIConfiguration(issuerID: "<YOUR ISSUER ID>", privateKeyID: "<YOUR PRIVATE KEY ID>", privateKey: "<YOUR PRIVATE KEY>")Passing a URL points the SDK at the file on disk instead, which suits a machine where the .p8 is provisioned as a file and you would rather not read it into your configuration.
let configuration = APIConfiguration(issuerID: "<YOUR ISSUER ID>", privateKeyID: "<YOUR PRIVATE KEY ID>", privateKeyURL: URL(fileURLWithPath: "~/AuthKey_<YOUR PRIVATE KEY ID>.p8"))Both accept an optional `expirationDuration`, defaulting to 20 minutes, which the README identifies as the maximum the API allows. Shorter values are suggested for clock skew, and the shorter form is spelled out as a seconds value. One convenience detail: `privateKeyID` can be omitted entirely when you pass a path, because the SDK infers it from the .p8 filename.
A typed endpoint chain instead of a URL string
Once you have a provider, requests are built by navigating a generated enum rather than assembling paths and query strings. The APIProvider holds the configuration and exposes a request method.
let request = APIEndpoint
.v1
.apps
.get(parameters: .init(
sort: [.bundleID],
fieldsApps: [.appInfos, .name, .bundleID],
limit: 5
))
let apps = try await self.provider.request(request).data
print("Did fetch \(apps.count) apps")Three things fall out of that shape. The sort fields, the requested fields and the limit are all typed, so a typo in `fieldsApps` is a compile error rather than a silently ignored query parameter. The response is decoded into a model, so `.data` is typed as the entity collection. And the request is `await`, so the call fits structured concurrency directly.
The README describes support for all Apple platforms, and the feature list credits this coverage to OpenAPI generated requests and entities.
Pagination comes in two shapes, and the async one is the better deal
Any list endpoint can return multiple pages, and the package offers both a sequence and manual stepping. The sequence form collects results across pages.
var allApps: [App] = []
for try await pagedResult in provider.paged(request) {
allApps.append(contentsOf: pagedResult.data)
}
print("There are \(allApps.count) apps in total")The manual form is there when you need to stop early or handle each page as it lands.
let firstPageResult = try await provider.request(request)
let firstPageApps = firstPageResult.data
if provider.request(request, isPagedResponse: firstPageResult) {
if let nextPage = try await provider.request(request, pageAfter: firstPageResult) {
let secondPageApps = nextPage.data
}
}Note the shape of the check: you first ask the provider whether the previous result is a paged response, then ask for the next page, and the result is optional. Any pipeline that iterates a large collection, your own choice between the two is about whether you need per-page control.
Errors carry Apple's status code, title and detail
Failure handling is a typed catch on the provider error, which destructures into a status code and an error response.
do {
print(try await self.provider.request(requestWithError).data)
} catch APIProvider.Error.requestFailure(let statusCode, let errorResponse, _) {
print("Request failed with statuscode: \(statusCode) and the following errors:")
errorResponse?.errors?.forEach({ error in
print("Error code: \(error.code)")
print("Error title: \(error.title)")
print("Error detail: \(error.detail)")
})
} catch {
print("Something went wrong fetching the apps: \(error.localizedDescription)")
}The error object exposes code, title and detail per failure, and the README points at Apple's own guidance on parsing the error response code for the semantics of those fields. The third catch-all binding is discarded with an underscore, which suggests the underlying error carries at least one more associated value the README does not walk through.
For a CI job that surfaces failures, printing title and detail is the difference between an actionable log line and a status code.
Swift Package Manager setup, and a licence you have to read
Installation goes through Swift Package Manager, which the README notes is integrated into the swift compiler. Adding the SDK means adding it to the dependencies array of your Package.swift. The repository has both Package.swift and [email protected], so there is a manifest variant for older Swift toolchains, and there is an Example directory with a macOS target and a Shared directory that shows how the pieces fit together in an actual project.
The licence is the item to look at before anything else. The repository carries a LICENSE file, and the repository metadata classifies the licence as Other, a custom licence that GitHub cannot map onto a standard identifier. That is materially different from the permissiveness most Swift package dependencies carry, and the README does not summarise the terms. If your organisation runs a licence allowlist, this package needs a human read of LICENSE before it enters a product, and that is a point in favour of reading it now rather than at audit time.
The project is not archived and the last push was on 2026-10-09, the same day as release 4.5.1, with 4.5 on 2026-09-26 and 4.4.3 on 2026-08-22. The cadence suggests an active release line rather than a dormant dependency, and the 4.x version number implies breaking changes are possible across the major.
Not every endpoint is there yet, and that is the real limitation
The README carries an explicit caveat above the usage section: not all endpoints are available yet, and work is ongoing to implement them, with the Sources/Endpoints directory given as the place to check. Everything else in this article assumes the endpoint you need is in that generated set.
That caveat interacts with the version scheme in an uncomfortable way. Because requests and entities are OpenAPI generated, coverage should grow over releases, but a pipeline that depends on a specific endpoint cannot assume it exists at any given tag. The concrete next step for anyone evaluating this is to open Sources/Endpoints and confirm the paths your workflow calls exist in the version you plan to pin, then pin that version explicitly rather than tracking master.
The alternative to this SDK is Apple's own documentation-driven approach: build requests by hand against the App Store Connect API endpoints, sign the JWT yourself, and decode the JSON yourself. That costs you the typed parameter lists and the pagination helpers, and it keeps you free of a custom licence, which for some teams is the better trade.
Editorial conclusion
Adopt this SDK if your Swift codebase needs App Store Connect automation and your team is enrolled as an organization with a Team Agent who can mint an API key, because it removes the JWT plumbing and the OpenAPI request typing in one move. Do not adopt it if you are an individual enrollee, if you cannot accept a non-standard licence, or if you need endpoints the package has not generated yet. Before you start, open the Sources/Endpoints directory the README points at and confirm the specific endpoints your pipeline depends on exist, since the README still carries a note that not all endpoints are available yet.
Frequently asked questions
Who can get App Store Connect API access for appstoreconnect-swift-sdk?
You must be the Team Agent, the Legal role, of a development team enrolled as an organization. The README states that access for developers enrolled as an individual is coming soon.
How long does the JWT from appstoreconnect-swift-sdk last?
The expirationDuration parameter defaults to 20 minutes, which is the maximum the API allows. The README suggests specifying a shorter value in seconds to account for possible clock skews.
Does appstoreconnect-swift-sdk support paging?
Yes. You can iterate all pages through an AsyncSequence with provider.paged(request), or request the next page manually with provider.request(request, isPagedResponse:) and provider.request(request, pageAfter:).
Are all App Store Connect endpoints implemented in this SDK?
No. The README notes that not all endpoints are available yet and points to the Sources/Endpoints directory for what currently exists.
How do I install the SDK with Swift Package Manager?
You add the SDK to the dependencies value of your Package.swift file. The repository ships both Package.swift and [email protected], and an Example directory shows a working setup.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/avdlee-appstoreconnect-swift-sdk)Was this useful?