Practical recipes over the App Store Connect API via Fastlane
Read more about the why in this blog post .
Applelink is a small, self-contained, rack-based service using Hanami::API , that wraps over Spaceship and exposes some nice common recipes as RESTful endpoints in an entirely stateless fashion.
These are based on the needs of the framework that Tramline implements over App Store. The API pulls its weight so Tramline has to do as little as possible. Currently, it exposes 13 API endpoints .
In Applelink, a complex recipe, such as release/prepare , will perform the following tasks all bunched up:
Ensure that there is an App Store version that we can use for the release, or create a new one
Update the release metadata for that release version
Enable phased releases, if necessary
Similarly a simple fetch endpoint like release/live will give you the current status of the latest release.
Applelink is a separate service that is not reliant on Tramline’s internal state. It can be used in a standalone way, for e.g. from a CI workflow, or a Slack bot that spits out app release information.
bundle install
just start
just lint # run lint
All APIs (except ping) are secured by JWT auth. Please use standard authorization header:
Authorization: Bearer <AUTH_TOKEN>
The AUTH_TOKEN
can be generated using HS256
algo and the secret for generating and verifying the token is shared between Tramline/any other client and applelink.
These can be configured using the following env variables:
AUTH_ISSUER=tramline.dev
AUTH_SECRET=password
AUTH_AUD=applelink
These values can be set to whatever you want, as long as they are same between the caller and Applelink.
Example code for generating the token can be taken from this file in the Tramline repo.
In addition to the auth token, you also need the App Store Connect JWT token which is documented here .
For the development environment, you can generate the above tokens using the following helper API:
curl -i -X GET http://127.0.0.1:4000/internal/keys? key_id=KEY_ID& issuer_id=ISSUER_ID
{
" store_token" : " eyJraWQiOiJLRVlfSUQiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJJU1NVRVJfSUQiLCJpYXQiOjE2ODIwNjA1MzYsImV4cCI6MTY4MjA2MTAzNiwiYXVkIjoiYXBwc3RvcmVjb25uZWN0LXYxIn0.-pFtamhBjsNKLr5Z2Ft2tW9H2NojBF1d8RqQBr7nNZF43KUNGMQIPQyp9BCSrFXJop1k7hk7jJstXRJ-WMH_8Q" ,
" auth_token" : " eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODIwNjA1MzYsImV4cCI6MTY4MjA2MTUzNiwiYXVkIjoiYXBwbGVsaW5rIiwiaXNzIjoidHJhbWxpbmUuZGV2In0.HDJJw6o6YK-Jmzpl0Xu4SmlTcGtNeEFI0VIg6fqitdw"
}
This expects the correct env variables to be set for AUTH_TOKEN
and the App Store Connect key.p8
file to be present in the Applelink directory along with the relevant KEY_ID
and ISSUER_iD
being passed to the API.
One can also use requests in restclient-mode to interactively play around with the entire API including fetching and refreshing tokens.
Headers
Name
Description
Authorization
Bearer token signed by tramline
Content-Type
Most endpoints expect application/json
X-AppStoreConnect-Key-Id
App Store Connect key id acquired from the portal
X-AppStoreConnect-Issuer-Id
App Store Connect issuer id acquired from the portal
X-AppStoreConnect-Token
App Store Connect expirable JWT signed using the key-id and issuer-id
Fetch metadata for an App
GET
/apple/connect/v1/apps/:bundle-id
name
type
data type
description
bundle-id
required
string
app's unique identifier
curl -X GET \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app
{
"id" : " 1658845856" ,
"name" : " Ueno" ,
"bundle_id" : " com.tramline.ueno" ,
"sku" : " com.tramline.ueno"
}
Fetch live info for an app
GET
/apple/connect/v1/apps/:bundle-id/current_status
name
type
data type
description
bundle-id
required
string
app's unique identifier
curl -X GET \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
http://localhost:4000/apple/connect/v1/apps/:bundle-id/current_status
[
{
"name" : " Big External Group" ,
"builds" : [
{
"id" : " da720570-cb6e-4b25-b82f-790045a6038e" ,
"build_number" : " 10001" ,
"status" : " BETA_APPROVED" ,
"version_string" : " 1.46.0" ,
"release_date" : " 2023-04-17T07:03:01-07:00"
},
{
"id" : " 1c4d0eb3-5cec-47f2-a843-949b12a69784" ,
"build_number" : " 9103" ,
"status" : " BETA_APPROVED" ,
"version_string" : " 1.45.0" ,
"release_date" : " 2023-04-13T00:09:38-07:00"
}
]
},
{
"name" : " Small External Group" ,
"builds" : [
{
"id" : " e1aa4795-0df2-4d76-b899-8ee95fb8589e" ,
"build_number" : " 10002" ,
"status" : " BETA_APPROVED" ,
"version_string" : " 1.47.0" ,
"release_date" : " 2023-04-17T10:00:19-07:00"
},
{
"id" : " da720570-cb6e-4b25-b82f-790045a6038e" ,
"build_number" : " 10001" ,
"status" : " BETA_APPROVED" ,
"version_string" : " 1.46.0" ,
"release_date" : " 2023-04-17T07:03:01-07:00"
}
]
},
{
"name" : " production" ,
"builds" : [
{
"id" : " bf11d7a3-fe1c-4c71-acae-a9dc8af57907" ,
"version_string" : " 1.44.1" ,
"status" : " READY_FOR_SALE" ,
"release_date" : " 2023-04-11T22:45:25-07:00" ,
"build_number" : " 9086"
}
]
}
]
Fetch all beta groups for an app
GET
/apple/connect/v1/apps/:bundle-id/groups
name
type
data type
description
bundle-id
required
string
app's unique identifier
curl -X GET \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/groups
[{
"name" : " The Pledge" ,
"id" : " fcacfdf7-db62-44af-a0cb-0676e17c251b" ,
"internal" : true
},
{
"name" : " The Prestige" ,
"id" : " 2cd6be09-d959-4ed3-a4e7-db8cabbe44d0" ,
"internal" : true
},
{
"name" : " The Trick" ,
"id" : " dab66de0-7af2-48ae-97af-cc8dfdbde51d" ,
"internal" : true
},
{
"name" : " Big External Group" ,
"id" : " 3bc1ca3e-1d4f-4478-8f38-2dcae4dcbb69" ,
"internal" : false
},
{
"name" : " Small External Group" ,
"id" : " dc64b810-1157-4228-825b-eb9e95cc8fba" ,
"internal" : false
}]
Fetch a single build for an app
GET
/apple/connect/v1/apps/:bundle-id/builds/:build-number
name
type
data type
description
bundle-id
required
string
app's unique identifier
build-number
required
integer
build number
curl -X GET \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/builds/9018
{
"id" : " bc90d402-ed0c-4d05-887f-d300abc104e9" ,
"build_number" : " 9018" ,
"beta_internal_state" : " IN_BETA_TESTING" ,
"beta_external_state" : " BETA_APPROVED" ,
"uploaded_date" : " 2023-02-22T22:27:48-08:00" ,
"expired" : false ,
"processing_state" : " VALID" ,
"version_string" : " 1.5.0"
}
Update the test notes for a build in TestFlight
PATCH
/apple/connect/v1/apps/:bundle-id/builds/:build-number
name
type
data type
description
bundle-id
required
string
app's unique identifier
name
type
data type
description
notes
required
string
"test the feature to add release notes to builds"
curl -X PATCH \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
-d ' {"notes": "test the feature to add release notes to builds"}' \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/builds/latest
Fetch the latest build for an app
GET
/apple/connect/v1/apps/:bundle-id/builds/latest
name
type
data type
description
bundle-id
required
string
app's unique identifier
curl -X GET \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/builds/latest
{
"id" : " bc90d402-ed0c-4d05-887f-d300abc104e9" ,
"build_number" : " 9018" ,
"beta_internal_state" : " IN_BETA_TESTING" ,
"beta_external_state" : " BETA_APPROVED" ,
"uploaded_date" : " 2023-02-22T22:27:48-08:00" ,
"expired" : false ,
"processing_state" : " VALID" ,
"version_string" : " 1.5.0"
}
Assign a build to a beta group
PATCH
/apple/connect/v1/apps/:bundle_id/groups/:group-id/add_build
name
type
data type
description
bundle-id
required
string
app's unique identifier
build-number
required
integer
build number
group-id
required
string
beta group id (uuid)
curl -X PATCH \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/groups/3bc1ca3e-1d4f-4478-8f38-2dcae4dcbb69/add_build
Prepare a release for the app
POST
/apple/connect/v1/apps/:bundle_id/release/prepare
name
type
data type
description
bundle-id
required
string
app's unique identifier
name
type
data type
description
build-number
required
integer
build number
version
required
string
version name
is_phased_release
optional
boolean
flag to enable or disable phased release, defaults to false
metadata
required
hash
{ "promotional_text": "this is the app store version promo text", "whats_new": "release notes"}
curl -X POST \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
-d ' {"build_number": 9018, "version": "1.6.2", "is_phased_release": true, "metadata": {"promotional_text": "app store version promo text", "whats_new": "release notes"} }' \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/release/prepare
Submit a release for review
PATCH
/apple/connect/v1/apps/:bundle_id/release/submit
name
type
data type
description
bundle-id
required
string
app's unique identifier
name
type
data type
description
build-number
required
integer
build number
curl -X PATCH \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
-d ' {"build_number": 9018}' \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/release/submit
Fetch the status of current inflight release
GET
/apple/connect/v1/apps/:bundle-id/release?build_number=:build-number
name
type
data type
description
bundle-id
required
string
app's unique identifier
name
type
data type
description
build-number
required
integer
build number
curl -X GET \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/release? build_number=500
{
"id" : " bd31faa6-6a9a-4958-82de-d271ddc639a8" ,
"version_name" : " 1.8.0" ,
"app_store_state" : " PENDING_DEVELOPER_RELEASE" ,
"release_type" : " MANUAL" ,
"earliest_release_date" : null ,
"downloadable" : true ,
"created_date" : " 2023-02-25T03:02:46-08:00" ,
"build_number" : " 33417" ,
"build_id" : " 31aafef2-d5fb-45d4-9b02-f0ab5911c1b2" ,
"phased_release" : {
"id" : " bd31faa6-6a9a-4958-82de-d271ddc639a8" ,
"phased_release_state" : " INACTIVE" ,
"start_date" : " 2023-02-28T06:38:39Z" ,
"total_pause_duration" : 0 ,
"current_day_number" : 0
},
"details" : {
"id" : " ef59d099-6154-4ccb-826b-3ffe6005ed59" ,
"description" : " The true Yamanote line aural aesthetic." ,
"locale" : " en-US" ,
"keywords" : " japanese, aural, subway" ,
"marketing_url" : null ,
"promotional_text" : null ,
"support_url" : " http://tramline.app" ,
"whats_new" : " We now have the total distance covered by each station across the line!"
}
}
Start a release after review is approved
PATCH
/apple/connect/v1/apps/:bundle_id/release/start
name
type
data type
description
bundle-id
required
string
app's unique identifier
name
type
data type
description
build-number
required
integer
build number
curl -X PATCH \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
-d ' {"build_number": 9018}' \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/release/start
Fetch the status of current live release
GET
/apple/connect/v1/apps/:bundle-id/release/live
name
type
data type
description
bundle-id
required
string
app's unique identifier
curl -X GET \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/release/live
{
"id" : " bd31faa6-6a9a-4958-82de-d271ddc639a8" ,
"version_name" : " 1.8.0" ,
"app_store_state" : " READY_FOR_SALE" ,
"release_type" : " MANUAL" ,
"earliest_release_date" : null ,
"downloadable" : true ,
"created_date" : " 2023-02-25T03:02:46-08:00" ,
"build_number" : " 33417" ,
"build_id" : " 31aafef2-d5fb-45d4-9b02-f0ab5911c1b2" ,
"phased_release" : {
"id" : " bd31faa6-6a9a-4958-82de-d271ddc639a8" ,
"phased_release_state" : " COMPLETE" ,
"start_date" : " 2023-02-28T06:38:39Z" ,
"total_pause_duration" : 0 ,
"current_day_number" : 4
},
"details" : {
"id" : " ef59d099-6154-4ccb-826b-3ffe6005ed59" ,
"description" : " The true Yamanote line aural aesthetic." ,
"locale" : " en-US" ,
"keywords" : " japanese, aural, subway" ,
"marketing_url" : null ,
"promotional_text" : null ,
"support_url" : " http://tramline.app" ,
"whats_new" : " We now have the total distance covered by each station across the line!"
}
}
Pause the rollout of current live release
PATCH
/apple/connect/v1/apps/:bundle_id/release/live/rollout/pause
name
type
data type
description
bundle-id
required
string
app's unique identifier
curl -X PATCH \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/release/live/rollout/pause
Resume the rollout of current live release
PATCH
/apple/connect/v1/apps/:bundle_id/release/live/rollout/resume
name
type
data type
description
bundle-id
required
string
app's unique identifier
curl -X PATCH \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/release/live/rollout/resume
Fully release the current live release to all users
PATCH
/apple/connect/v1/apps/:bundle_id/release/live/rollout/complete
name
type
data type
description
bundle-id
required
string
app's unique identifier
curl -X PATCH \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/release/live/rollout/complete
Halt the current live release from distribution
PATCH
/apple/connect/v1/apps/:bundle_id/release/live/rollout/halt
name
type
data type
description
bundle-id
required
string
app's unique identifier
curl -X PATCH \
-H " Authorization: Bearer token" \
-H " X-AppStoreConnect-Key-Id: key-id" \
-H " X-AppStoreConnect-Issuer-Id: iss-id" \
-H " X-AppStoreConnect-Token: token" \
-H " Content-Type: application/json" \
http://localhost:4000/apple/connect/v1/apps/com.tramline.app/release/live/rollout/halt