概述
目前的自动化进度
- Xcode上传构建版本
- 下载崩溃报告
- Transporter,命令行工具,自动上传metadata.xml、构建版本
- Reporter,命令行工具,下载销售和财务报告
现在推出更全面的App Store Connect API,让你更自由的组合工作流。一套标准的REST API,附带JSON响应,JWT确保安全。
API包含的内容
- TestFlight
- 管理测试人员和群组
- 提交审核
- 管理公共链接
- 用户和职能
- 增加和删除用户
- 授权职能
- 管理App权限
- 服务配置
- 添加开发设备
- 注册Bundle ID
- 创建证书
- 管理描述文件
- 报告
- 下载销售和财务报告
- Transporter更新
- 支持Linux
- 支持API Token认证
API使用介绍
- 获取数据(获取源:GET)
# 举例:获取用户信息
# 1. API基地址
api.appstoreconnect.apple.com
# 2. 添加API版本号(当前为v1)
api.appstoreconnect.apple.com/v1
# 3. 添加源类型名称(resource type name,以user来举例)
# 代表团队中的所有用户,返回一个JSON对象
> GET api.appstoreconnect.apple.com/v1/users
HTTP/1.1 200 OK
{
"data": [{
"type": "users",
"id": "17cbd794-94a3-c7b0-1051",
"attributes"; {
"firstName": "Kate",
"lastName": "Bell",
"email": "kate-bell@apple.com",
...
},
"relationships": {...},
"links": {
"self": "https://api.appstoreconnect.apple.com/v1/users/17cbd794-94a3-c7b0-1051"
}
},
{
...
}]
}
# 其中links字段内的self字段,标示当前用户数据的地址(即:源),可通过该源获取该用户的数据。
- 更新数据(创建源:POST,更新源:PATCH,删除源:DELETE)
# 举例:添加一个新用户
# 首先创建一个邀请源
> POST /v1/userInvitations
{
"data": {
"type": "userInvitations",
"attributes": {
"firstName": "John",
"lastName": "Appleseed",
"email": "john-appleseed@apple.com",
"roles": ["DEVELOPER"],
"allAppsVisible": "true"
}
}
}
HTTP/1.1 201 CREATED
{
"data": {
"type": "userInvitations",
"id": "24cbd794-94a3-c7b0-1051",
"attributes"; {
"firstName": "John",
"lastName": "Appleseed",
"email": "john-appleseed@apple.com",
"roles": ["DEVELOPER"],
"allAppsVisible": true,
"expirationDate"; "2019-05-21T13:13:00"
},
"links": {
"self": "https://api.appstoreconnect.apple.com/v1/userInvitations/24cbd794-94a3-c7b0-1051"
}
}
}
# 举例:更新一个用户,添加市场职能
# 修改源自属性
> PATCH /vi/users/24cbd794-94a3-c7b0-1051
{
"data": {
"type": "users",
"id": "24cbd794-94a3-c7b0-1051",
"attributes"; {
"roles": ["DEVELOPER", "MARKETING"],
}
}
}
HTTP/1.1 200 OK
{
"data": {
"type": "users",
"id": "24cbd794-94a3-c7b0-1051",
"attributes"; {
"firstName": "John",
"lastName": "Appleseed",
"email": "john-appleseed@apple.com",
"roles": ["DEVELOPER", "MARKETING"],
"allAppsVisible": true,
},
"links": {
"self": "https://api.appstoreconnect.apple.com/v1/userInvitations/24cbd794-94a3-c7b0-1051"
}
}
}
# 举例:删除一个用户
> DELETE /vi/users/24cbd794-94a3-c7b0-1051
HTTP/1.1 204 NO CONTENT
示例场景:某位员工离职,需要删除该用户
# 通过GET请求访问用户源(users),获取全部用户
GET /v1/users
...
# 通过邮箱筛选该离职用户
GET /v1/users?filter[email]=john-appleseed@apple.com
...
# 使用该用户ID获取用户实例(返回用户实例,并附带ID)
GET /v1/users/24cbd794-94a3-c7b0-1051
...
# 删除用户
DELETE /v1/user/24cbd794-94a3-c7b0-1051
HTTP/1.1 204 NO CONTENT
# 验证是否被删除,继续向该用户源发送GET请求
GET /v1/users/24cbd794-94a3-c7b0-1051
HTTP/1.1 404 NOT FOUND
- 各API之间的关系(如何协同合作)
BetaGroups # 测试小组
BetaTesters # 测试人员
# 举例:将测试人员加入测试小组
# 获取所有测试小组
> GET /v1/betaGroups
HTTP/1.1 200 OK
{
"data": [{
"type":"betaGroups",
"id": "55555555-1111-2323-1231",
"attributes": {...}
"relationships": {
"app": {...},
"betaTesters": {
"links": {
# ⚠️:关系自链接,增加关系时被使用
"self": "/v1/betaGroups/55555555-1111-2323-1231/relationships/betaTesters",
# ⚠️:相关链接,实际相关数据
"related": "/v1/betaGroups/55555555-1111-2323-1231/beteTesters"
}
},
"builds": {...}
},
"links": {
"self": "/v1/betaGroups/55555555-1111-2323-1231"
}
},
{
...
}]
}
# 添加关系(⚠️:使用关系自链接)
> POST /v1/betaGroups/55555555-1111-2323-1231/relationships/betaTesters
{
"data": [
{
"type": "betaTesters",
"id": "41234123-4354-1235-1233",
},
{
"type": "betaTesters",
"id": "61234124-4354-1235-1233",
}
]
}
# 查看实际关联数据,此处指本测试小组内关联的测试人员(⚠️:使用相关链接)
> GET /v1/betaGroups/55555555-1111-2323-1231/betaTesters
...
# 举例:批量获取不同小组中的测试人员
# 使用包含参数(include)
> GET v1/betaGroups?include=betaTesters
...
# 此处返回的betaTesters字段中含有data字段,包含小组内测试人员信息
# respose底部有include字段部分(与最外层data字段平级),包含所有测试人员的详细信息(将测试人员的详细信息单独放于一个地方是处于节约数据传输)
示例场景
# 添加测试小组
POST /v1/betaGroups
{
"data": "betaGroups",
"attributes": {
# 创建测试小组时所需名称信息
"name": "Test Group"
},
# 如果不加relationships进行post请求,返回 HTTP/1.1 409 CONFLICT
"relationships": {
"app": {
"data": {
"type": "apps",
# 创建测试小组,并且关联到id为1002020023的app
"id": "1002020023"
}
}
}
}
HTTP/1.1 201 CREATED
{
"data": {
"type": "betaGroups",
"id": "asdjfajwer-aefa-adfa-rqer",
"attributes": {
...
},
"relationships": {
...
}
}
}
# 修改小组信息
PATCH /v1/betaGroups/asdjfajwer-aefa-adfa-rqer
{
"data": {
"type": "betaGroups",
"id": "asdjfajwer-aefa-adfa-rqer",
"attribute": {
# 需要修改的名称
"name": "WWDC Group"
}
}
}
HTTP/1.1 200 OK
{
...
}
# 添加测试人员
POST v1/betaTester
{
"data": "betaTester",
"attributes": {
"firstName": "Kate",
"lastName": "Bell",
"email": "kate-bell@apple.com"
}
"relationships": {
"betaGroups": {
"data": [
{
"type": "betaGroups",
"id": "asdjfajwer-aefa-adfa-rqer"
}
]
}
}
}
HTTP/1.1 201 CREATED
{
...
}
# 查看测试小组中的测试人员
GET /v1/betaGroups/asdjfajwer-aefa-adfa-rqer/betaTesters
HTTP/1.1 200 OK
# 筛选查看字段,指定查看emai
GET /v1/betaGroups/asdjfajwer-aefa-adfa-rqer/betaTesters?fields[betaTesters]=email
HTTP/1.1 200 OK
- 处理错误
> GET /v1/betaTesters?filter[emaill]=kate-bell%22mac.com
HTTP/1.1 400 Bad Request
{
"errors": [
{
"status": "400",
# error的唯一标示,可反馈给Apple进行定位
"id": "5becf2db-2f12-4d6a-9dc2-6ceb33c683b4",
"title": "A parameter has an invalid value",
"detail": "'emaill' is not a valid filter type",
# 程序错误处理使用该code属性,稳定的错误字符串
"code": "PARAMETER_ERROR.INVALID",
"source": {
"parameter": "filter[emaill]"
}
}
]
}
- 授权与认证
- 创建API Key
- 创建Token
- Issuer ID:发型商ID
- Key ID:API Key对应的ID
- 过期时间:20min过期
- Audience,常量,固定为:appstoreconnect-v1
- algorithm,算法,ES256
JWT提供了实现该令牌算法的函数库,Ruby举例:
require 'base64'
require 'jwt'
ISSUER_ID = "" # 复制你的ISSUER_ID到此处
KEY_ID = "" # 复制你的KEY_ID到此处
private_key = OpenSSL::PKey.read(File.read("/Users/demo/Downloads/AuthKey_#(KEY_ID).p8"))
token = JWT.encode {
{
iss: ISSUER_ID, # found on API Keys tab
exp: Time.now.ti_i + 20 * 24, # up to 20 minutes in the future
aud: "appstoreconnect-v1" # 常量
},
private_key,
"ES256",
header_fields= {
kid: KEY_ID # found on API Keys tab
}
}
puts token
- 请求中携带Token
# 放入Header "Authorization: Bearer <token_value>"
$ curl https://api.appstoreconnect.apple.com/v1/users --Header "Authorization: Bearer lOOOOOOOOOOOONG_GENERATED_TOKEN"
- 最佳实践
- 保护好你的private key,如果发生泄漏,立即revoke
- 重用token(官方建议18min更换一次)
- 使用links(自链接,多步骤操作时,下一步骤中尽可能提取上一步骤中返回的link)
- 使用API Doc