阅读 2791

App Store Connect API

背景

WWDC 2018 苹果把名字iTunes Connect修改为App Store Connect,同时推出了App Store Connect API,用于自动化一些App Store Connect后台操作:

  • 管理证书、管理配置文件、管理设备和安装包ID
  • 管理用户、设置用户角色以及app的访问权限
  • 管理TestFlight、公测用户以及公开链接、查看app的build二进制包信息
  • 下载财务和销售报告

最近后台同事问我,能否通过接口从苹果后台获取销售记录(下载量),方便分析和统计。由于网络上关于App Store Connect API的资料甚少,借此机会我自行研究了一下,并用写下这篇博客,算是给“App Store Connect API”关键字贡献一份力量吧(捂脸)。

本文主要介绍App Store Connect API,关于如何自动获取苹果后台的销售报告,见下一篇文章《如何自动化获取AppStore的销售和趋势报告》

Spaceship VS App Store Connect API

在介绍App Store Connect API之前我想说一下Fastlane,感觉Fastlane比App Store Connect API出名多了,关注的人也更多。
Fastlane是一套用Ruby开发的,用于解决iOS和Android自动化编译打包、上传、发布等的解决方案。
Spaceship是Fastlane的一个子工具,提供了一套Apple Developer CenterApp Store Connect 的API。

Spaceship非常强大,所有你在浏览器上对Apple Developer Center 和 App Store Connect的操作几乎都可以通过Spaceship命令解决,所有App Store Connect API能实现的功能,Spaceship几乎都能实现,有些App Store Connect API没有的功能,Spaceship也能实现。
Spaceship的原理:通过模拟网页操作,分析和抓取网页的接口和数据来实现相应功能。可以参见Technical Details

Spaceship使用到的苹果API

Spaceship这么强大,直接用Spaceship不就行了么。Spaceship当然也有它的缺点。

Spaceship

优点

  • 功能强大。几乎所有你在浏览器上对Apple Developer Center 和 App Store Connect的操作都可以通过Spaceship命令解决。
  • cookie有效期比较长。上面讲了Spaceship的原理是模拟网页请求,所以得先登录(账号、密码),登录后cookie保留在本地,有效期一个月,cookie失效前请求API不需要在登录。

缺点

  • 需要登录两次。Apple Developer Center 和 App Store Connect 需要分别登录,一方生成的cookie对另一方可能不管用。
  • 双重认证。如果你的开发者账号开了双重认证,那登录的时候还需要验证码。验证码这个东西使整体可用性大大降低。
  • 对Window系统支持不友好。Spaceship是Fastlane的子工具,是Ruby开发的,需要Unix(Mac)环境。有人说Window上也可以跑Ruby。大家可以自行尝试
  • 非官方API。因为是非官方API,所以后续如果苹果有改动,可能就用不了了,得Spaceship作者及时更新才行。

App Store Connect API

优点

  • 不需要登录。App Store Connect API是利用私钥生成token来访问API的。不需要提供开发者账号密码,相对也更安全。
  • REST API。App Store Connect API是REST API,这决定了其调用更方便,不受操作系统等限制。
  • 官方API。维护有保障,即使后续苹果调整了网页内容,其相应API也不会受影响。

缺点

  • 功能受限。App Store Connect官方文档列出来的API不多,其实还有很多操作苹果都没有提供相应的API,这使得可用性降低,只能期盼后期苹果能多开放一些API。
  • token有效期较短。token有效期只有20分钟,过期后得重新生成token。

生成 App Store Connect API token

App Store Connect API每个请求都需要在请求头中携带token,要想使用App Store Connect API,首先得知道如何生成token。
token是通过App Store Connect后台生成的密钥签名JWT(下文介绍)产生的。

生成密钥(手动操作,只需做一次)

1.请求App Store Connect API访问权限。
App Store Connect后台,“用户和访问” - “密钥”,点击“请求访问权限”。只有agent才有权限

2.生成密钥。
申请访问权限后,才会看到“生成密钥”的按钮,点击“生成密钥”,根据提示起个名字,完成后会产生一个“Issuer ID”和“密钥ID”,这两个参数后面生成token需要用到。下载密钥,密钥是一个.p8的文件,注意:私钥只能下载一次,永远不会过期,保管好,如果丢失了,去App Store Connect后台撤销密钥,否则别人拿到也可以用。

生成token(用代码生成,有效期20分钟,失效后需要再次生成)

App Store Connect API使用的token采用的是JSON Web Token(JWT)标准,苹果文档介绍了创建JWT并用密钥签名生成token的过程。
不过这里大家可以直接去JWT.io下载各种语言的开源库来生成token。

下面是WWDC上苹果贴的Ruby代码,我稍作了修改。执行下方Ruby代码即可生成一条token(自行替换 ISSUER_ID、KEY_ID、P8_PATH这三个参数,下面代码中贴的是苹果文档上的值)

#生成 App Store Connect API用的token

require "base64"
require "jwt"

ISSUER_ID = "57246542-96fe-1a63-e053-0824d011072a"    #ISSUER_ID(App Store Connect后台获取)
KEY_ID = "2X9R4HXF34"                                 #密钥ID(App Store Connect后台获取)
P8_PATH= "/Users/Documents/test_AuthKey_22xxxxxxxx.p8"    #密钥文件路径(本地PC文件路径)

private_key = OpenSSL::PKey.read(File.read(P8_PATH))

token = JWT.encode(
   {

    iss: ISSUER_ID,
    exp: Time.now.to_i + 20 * 60,    #最多20分钟,设置多了生成的token是无效的
    aud: "appstoreconnect-v1"
   },
   private_key,
   "ES256",
   header_fields={
     kid: KEY_ID }
 )
puts token
复制代码

使用token

调用App Store Connect API时添加下列请求头
'Authorization: Bearer [signed token]'
"Bearer"是固定写法,把[signed token]换成你的token,注意,Bearer和token中间有空格
下面是个例子
curl -v -H 'Authorization: Bearer [signed token]' "https://api.appstoreconnect.apple.com/v1/apps"
token有效期是20分钟,token过期后API会报错,说授权无效。需要重新生成一个新的token使用。
我尝试在JWT配置中让token有效期超过20分钟,但是失败了,这样虽然可以生成token,但用token发起请求会报错,说token无效,看来token最多只能20分钟。

App Store Connect API 部分API介绍

下面介绍部分API功能,大家可自行查看App Store Connect API官方文档查看其它功能。(这里想吐槽一下苹果的文档,真的简洁的不能在简洁了,数据结构层次也不够直观,请求参数、返回响应一个示例都没有给,各个参数代表什么意思也不说明一下,完全靠猜靠试,用户体验很不好)。下面我列出了注意事项,是我对照官方文档反复尝试后的总结,相信对你阅读苹果文档会有帮助。

注意事项:

  • 创建接口,请求参数中的id字段,并不是平时我们用到的uuid,bundle identifier等字段,而是查询和创建接口返回的“隐式”id字段,注意区分。
  • 查询接口,可选参数fields,表示查询结果展示哪些字段,类似sql语句的查询字段,默认是全部。
  • 查询接口,可选参数filter,表示查询某个特定结果,类似sql语句的where,注意,这是模糊查询,效果是contain,而不是match,所以可能查到多条。(怎么精确查找,我也不知道,如果你知道请告知我)
  • 查询接口,可选参数include,表示是否关联某个字段详情到响应体中,默认不包含。比如查询profile列表,并不会返回bundle ID,certificates,devices的信息,(连id都不会返回,只会返回links)。请求参数加上 include=bundleId,certificates,devices 后就会返回这些数据了,加上后会新增included字段返回关联字段的详细信息。
  • 下载文件,部分查询接口会返回文件内容,比如certificates、profile等,是以base64字符串形式返回的。如有需要可自行解码成文件。参考命令echo “MIIFnDCC..BISgAwIBAgII” | base64 -D > a.cer

假设下面介绍的请求默认都带了token请求头的。

Device

获取设备信息

可选参数:
fields[devices]:筛选显示的数据,addedDate, deviceClass, model, name, platform, status, udid
filter[id]
filter[name]
filter[platform]:IOS, MAC_OS
filter[status]:ENABLED, DISABLED
filter[udid]:
limit:integer,最大200
sort:id, -id, name, -name, platform, -platform, status, -status, udid, -udid

获取设备列表
GET https://api.appstoreconnect.apple.com/v1/devices

获取某个设备信息(列表API追加id路径)
GET https://api.appstoreconnect.apple.com/v1/devices/539US4VFVK

获取设备列表 返回数据
{
  "data": [{
    "type": "devices",
    "id": "539US4XXXX",
    "attributes": {
      "addedDate": "2019-07-02T13:11:10.000+0000",
      "name": "random1",
      "deviceClass": "IPHONE",
      "model": "iPhone 6s",
      "udid": "e05a144e8ef31c50f12xxxxxxxxxxx",
      "platform": "IOS",
      "status": "ENABLED"
    },
    "links": {
      "self": "https://api.appstoreconnect.apple.com/v1/devices/539US4XXXX"
    }
  }, {
    "type": "devices",
    "id": "W9W337XXXX",
    "attributes": {
      "addedDate": "2019-11-08T10:46:34.000+0000",
      "name": "test_se",
      "deviceClass": "IPHONE",
      "model": null,
      "udid": "f51f5b7c677565cfa629xxxxxxxxxxxxx",
      "platform": "IOS",
      "status": "ENABLED"
    },
    "links": {
      "self": "https://api.appstoreconnect.apple.com/v1/devices/W9W337XXXX"
    }
  }],
  "links": {
    "self": "https://api.appstoreconnect.apple.com/v1/devices"
  },
  "meta": {
    "paging": {
      "total": 2,
      "limit": 20
    }
  }
}
复制代码

注册一个设备

添加请求头 'Content-Type: application/json'
POST https://api.appstoreconnect.apple.com/v1/devices
必传参数:(其它没列出来的参数表示写法固定,参考请求示例)
platform:IOS,MAC_OS
name:取个名字
udid:设备标识
Status Code: 201 Created表示成功。

请求示例
{
    "data": {
        "attributes": {
            "name": "test_se",
            "udid": "f51f5b7c677565cfa629xxxxxxxxx",
            "platform": "IOS"
        },
        "type": "devices"
    }
}

返回数据
{
  "data": {
    "type": "devices",
    "id": "W9W337XXXX",
    "attributes": {
      "addedDate": "2019-11-08T10:46:34.781+0000",
      "name": "test_se",
      "deviceClass": "IPHONE",
      "model": null,
      "udid": "f51f5b7c677565cfa629xxxxxxxxx",
      "platform": "IOS",
      "status": "ENABLED"
    },
    "links": {
      "self": "https://api.appstoreconnect.apple.com/v1/devices/W9W337XXXX"
    }
  },
  "links": {
    "self": "https://api.appstoreconnect.apple.com/v1/devices"
  }
}
复制代码

删除一个设备

删除设备只能在开发者网站操作,没有提供API。(这句话是苹果文档上写的)

Bundle IDs

创建bundle id

添加请求头 'Content-Type: application/json'
POST https://api.appstoreconnect.apple.com/v1/bundleIds
必传参数:(其它没列出来的参数表示写法固定,参考请求示例)
name:取个名字
identifier:bundle identifier
seedId:Team ID
platform:IOS,MAC_OS
Status Code: 201 Created,表示成功

请求示例
{
    "data": {
        "attributes": {
            "name": "bundlieAPITest",
            "identifier": "cosw.bundlieAPI_test",
            "seedId": "U8FBXXXXXX",
            "platform": "IOS"
        },
        "type": "bundleIds"
    }
}
返回数据
{
    "data": {
        "type": "bundleIds",
        "id": "Q9XMDVCXXX",
        "attributes": {
            "name": "bundlieAPITest",
            "identifier": "cosw.bundlieAPI_test",
            "platform": "IOS",
            "seedId": "U8FBXXXXXX"
        },
        "relationships": {...省略},
        "links": {
            "self": "https://api.appstoreconnect.apple.com/v1/bundleIds/Q9XMDVCXXX"
        }
    },
    "links": {
        "self": "https://api.appstoreconnect.apple.com/v1/bundleIds"
    }
}

复制代码

获取bundle id列表

可选参数:
fields[bundleIds]:attributes字段筛选显示结果,bundleIdCapabilities, identifier, name, platform, profiles, seedId
fields[profiles]:profiles字段筛选显示结果,bundleId, certificates, createdDate, devices, expirationDate, name, platform, profileContent, profileState, profileType, uuid
filter[id]:
filter[identifier]:
filter[name]:
filter[platform]:IOS, MAC_OS
filter[seedId]:
include:bundleIdCapabilities, profiles
limit:integer,最大200
limit[profiles]:integer,最大50
sort:id, -id, name, -name, platform, -platform, seedId, -seedId
fields[bundleIdCapabilities]:bundleId, capabilityType, settings

获取bundle id列表
GET https://api.appstoreconnect.apple.com/v1/bundleIds

获取bundle id列表(精简版,去掉了很多不需要的数据)
GET https://api.appstoreconnect.apple.com/v1/bundleIds?fields[bundleIds]=name,identifier,platform

查找identifier为com.tencent.xin的bundle id(注意这里是模糊查找,比如com.tencent.xin、com.tencent.xin1004都会被列出来)
GET https://api.appstoreconnect.apple.com/v1/bundleIds?fields[bundleIds]=name,identifier,platform&filter[identifier]=com.tencent.xin

获取bundle id列表(精简版) 返回数据
{
    "data": [
        {
            "type": "bundleIds",
            "id": "2795XXXXX",
            "attributes": {
                "name": "XC com.tencent.xin1004",
                "identifier": "com.tencent.xin1004",
                "platform": "IOS"
            },
            "links": {
                "self": "https://api.appstoreconnect.apple.com/v1/bundleIds/2795XXXXX"
            }
        },
        {
            "type": "bundleIds",
            "id": "39VMXXXXX",
            "attributes": {
                "name": "wechat",
                "identifier": "com.tencent.xin",
                "platform": "IOS"
            },
            "links": {
                "self": "https://api.appstoreconnect.apple.com/v1/bundleIds/39VMXXXXX"
            }
        }
    ],
    "links": {
        "self": "https://api.appstoreconnect.apple.com/v1/bundleIds?fields%5BbundleIds%5D=name%2Cidentifier%2Cplatform"
    },
    "meta": {
        "paging": {
            "total": 4,
            "limit": 20
        }
    }
}
复制代码

删除一个 bundle id

DELETE https://api.appstoreconnect.apple.com/v1/bundleIds/{id}
例如
https://api.appstoreconnect.apple.com/v1/bundleIds/2795XXXXX
成功,Status Codes:204 No Content
注意,删除DELETE,而不是POST(后面所有的删除API都符合这一条)

证书

创建证书

添加请求头 Content-Type: application/json
POST https://api.appstoreconnect.apple.com/v1/certificates
必传参数:(其它没列出来的参数表示写法固定,参考请求示例)
csrContent:CSR文件的内容。创建好.csr文件(你可以用 钥匙串-证书助理 来生成CSR文件,或者用代码生成)后,使用命令 cat a.csr,打印出.csr的内容,作为参数赋值。注意:打印出来的内容带有换行符,得把换行符去掉后在赋值,否则会报错。
certificateType:证书类型,IOS_DEVELOPMENT,IOS_DISTRIBUTION,MAC_APP_DISTRIBUTION,MAC_INSTALLER_DISTRIBUTION,MAC_APP_DEVELOPMENT,DEVELOPER_ID_KEXT,DEVELOPER_ID_APPLICATION
成功,Status Code: 201 Created

请求参数
{
    "data": {
        "attributes": {
            "csrContent": "SDADAD12...3dSD123",
            "certificateType": "IOS_DEVELOPMENT"
        },
        "type": "certificates"
    }
}

复制代码

下图是用cat命令打印 .csr 文件后的效果,取出BEGIN和END中间那一段,再去掉每行末尾的换行符就是csrContent参数值。在线过滤换行符

打印.csr文件

我写了段代码,传入.csr文件路径,可以输出处理后的csrContent值,大家可以参考。

#从.csr文件取csrContent值(取第二行到倒数第二行的内容,再去掉换行符)
csrFile='/Users/Cocoakier/Movies/CertificateSigningRequest.certSigningRequest'
line=$[`cat $csrFile | wc -l`-1]
cat $csrFile | sed -n "2,$line""p" | awk 'BEGIN{ORS=""}{print $0}'
复制代码

获取证书信息(下载证书)

可选参数:
fields[certificates]:certificateContent(证书文件), certificateType, csrContent, displayName, expirationDate, name, platform, serialNumber
filter[id]:
filter[serialNumber]:
limit,最大200
sort:certificateType, -certificateType, displayName, -displayName, id, -id, serialNumber, -serialNumber
filter[certificateType]:IOS_DEVELOPMENT, IOS_DISTRIBUTION, MAC_APP_DISTRIBUTION, MAC_INSTALLER_DISTRIBUTION, MAC_APP_DEVELOPMENT, DEVELOPER_ID_KEXT, DEVELOPER_ID_APPLICATION
filter[displayName]:

获取证书列表
GET https://api.appstoreconnect.apple.com/v1/certificates

返回数据
{
  "data": [{
    "type": "certificates",
    "id": "5DW7PXXXX",
    "attributes": {
      "serialNumber": "1D0160EEXXXXXX",
      "certificateContent": "MIIFnDCCBISgAwIBAgIIHQFg7.....4Y2CP+fCYEpTEjXlQtJbOGjaXgwjAIdPhu",
      "displayName": "xiao ming",
      "name": "iOS Development: xiao ming",
      "csrContent": null,
      "platform": "IOS",
      "expirationDate": "2020-03-28T11:43:19.000+0000",
      "certificateType": "IOS_DEVELOPMENT"
    },
    "links": {
      "self": "https://api.appstoreconnect.apple.com/v1/certificates/5DW7PXXXX"
    }
  }, {
    "type": "certificates",
    "id": "D89QXXXXX",
    "attributes": {
      "serialNumber": "39E252BXXXXXX",
      "certificateContent": "MIIFnzCCBIegAwIBAgIIOeJ.....o5zB1YPJtvfX49H2n",
      "displayName": "xiao ming",
      "name": "iOS Distribution: xiao ming",
      "csrContent": null,
      "platform": "IOS",
      "expirationDate": "2020-04-22T11:56:35.000+0000",
      "certificateType": "IOS_DISTRIBUTION"
    },
    "links": {
      "self": "https://api.appstoreconnect.apple.com/v1/certificates/D89QXXXXX"
    }
  }],
  "links": {
    "self": "https://api.appstoreconnect.apple.com/v1/certificates"
  },
  "meta": {
    "paging": {
      "total": 2,
      "limit": 20
    }
  }
}
复制代码

下载证书
上面接口返回的字段中,certificateContent就是证书文件,苹果是以base64的方式通过接口返回的。我们base64解码一下就可以还原成.cer的文件了。

certificateContent="MIIFnDCCBISgAwIB...uG4Y2CP+fCYEpTEjXlQtJbOGjaXgwjAIdPhu"
echo $certificateContent | base64 -D > '/Users/Cocoakier/Movies/a.cer'
复制代码

删除证书

DELETE https://api.appstoreconnect.apple.com/v1/certificates/{id}
例如
DELETE https://api.appstoreconnect.apple.com/v1/certificates/5DW7PXXXX
成功,Status Codes:204 No Content

Provisioning

获取Provisioning信息(下载)

可选参数:
fields[profiles]:bundleId, certificates, createdDate, devices, expirationDate, name, platform, profileContent(provisioning文件), profileState, profileType, uuid
fields[certificates]:certificateContent, certificateType, csrContent, displayName, expirationDate, name, platform, serialNumber
fields[devices]:addedDate, deviceClass, model, name, platform, status, udid
fields[bundleIds]:bundleIdCapabilities, identifier, name, platform, profiles, seedId
filter[id]:
filter[name]:
filter[profileState]:ACTIVE, INVALID
filter[profileType]:IOS_APP_DEVELOPMENT, IOS_APP_STORE, IOS_APP_ADHOC, IOS_APP_INHOUSE, MAC_APP_DEVELOPMENT, MAC_APP_STORE, MAC_APP_DIRECT, TVOS_APP_DEVELOPMENT, TVOS_APP_STORE, TVOS_APP_ADHOC, TVOS_APP_INHOUSE
include:是否返回具体信息(默认不返回),bundleId, certificates, devices
limit:最大200
limit[certificates]:最大50
limit[devices]:最大50
sort:id, -id, name, -name, profileState, -profileState, profileType, -profileType

获取Provisioning列表 普通版(不包含bundleId、certificates、devices信息)
GET https://api.appstoreconnect.apple.com/v1/profiles

获取Provisioning列表 加强版(包含bundleId、certificates、devices信息)
GET https://api.appstoreconnect.apple.com/v1/profiles?include=bundleId,certificates,devices

获取某个Provisioning devices详情(列表API+profiles id+devices)
GET https://api.appstoreconnect.apple.com/v1/profiles/295XXXXX/devices

获取某个Provisioning certificates详情
GET https://api.appstoreconnect.apple.com/v1/profiles/295XXXXX/certificates

获取某个Provisioning bundleId详情
GET https://api.appstoreconnect.apple.com/v1/profiles/295XXXXX/bundleId

获取Provisioning列表 普通版(不包含bundleId、certificates、devices信息) 返回数据
{
    "data": [
        {
            "type": "profiles",
            "id": "295XXXXX",
            "attributes": {
                "profileState": "ACTIVE",
                "createdDate": "2019-03-29T11:54:58.000+0000",
                "profileType": "IOS_APP_DEVELOPMENT",
                "name": "test_dev",
                "profileContent": "MIId9AYJKoZIhvcNAQcC..省略N多字...Syr5TPmPe1DiY4w==",
                "uuid": "1d2xxxxxx-f764-4c71-xxxx-1d77xxxxxxxx",
                "platform": "IOS",
                "expirationDate": "2020-03-28T11:54:58.000+0000"
            },
            "relationships": {...省略没用的信息}
        }
    ]
}

获取Provisioning列表 加强版(包含bundleId、certificates、devices信息) 返回数据(数据太多,我删除了一些无用字段)
{
    "data": [
        {
            "type": "profiles",
            "id": "295XXXXX",
            "attributes": {
                "profileState": "ACTIVE",
                "createdDate": "2019-03-29T11:54:58.000+0000",
                "profileType": "IOS_APP_DEVELOPMENT",
                "name": "test_dev",
                "profileContent": "MIId9AYJKoZIhvcNAQcC..省略N多字...Syr5TPmPe1DiY4w==",
                "uuid": "1d2xxxxxx-f764-4c71-xxxx-1d77xxxxxxxx",
                "platform": "IOS",
                "expirationDate": "2020-03-28T11:54:58.000+0000"
            }
        }
    ],
    "included": [
        {
            "type": "certificates",
            "id": "5DWXXXXXX",
            "attributes": {
                "serialNumber": "1D0160XXXXXXX",
                "certificateContent": "MIIFnDCC...省略N多字...BIXgwjAIdPhu",
                "displayName": "xiao ming",
                "name": "iOS Development: xiao ming",
                "csrContent": null,
                "platform": "IOS",
                "expirationDate": "2020-03-28T11:43:19.000+0000",
                "certificateType": "IOS_DEVELOPMENT"
            }
        },
        {
            "type": "devices",
            "id": "KD88XXXXXX",
            "attributes": {
                "addedDate": "2019-03-29T11:48:47.000+0000",
                "name": "xiaoming_5s",
                "deviceClass": "IPHONE",
                "model": "iPhone 5s (Model A1457, A1518, A1528, A1530)",
                "udid": "61262034b932dxxxxxxxxxxxxxxx",
                "platform": "IOS",
                "status": "ENABLED"
            }
        },
        {
            "type": "devices",
            "id": "5CXUXXXXXX",
            "attributes": {
                "addedDate": "2019-03-29T11:49:04.000+0000",
                "name": "xiaohong_iPhoneX",
                "deviceClass": "IPHONE",
                "model": null,
                "udid": "f51f5b7c677565cxxxxxxxxxxxxxx",
                "platform": "IOS",
                "status": "ENABLED"
            }
        },
        {
            "type": "bundleIds",
            "id": "7RZ4XXXXX",
            "attributes": {
                "name": "wechat",
                "identifier": "com.tencent.xin",
                "platform": "IOS",
                "seedId": "9RDXXXXXX"
            }
        }
    ]
}
复制代码

下载provisioning文件
上面接口返回的字段中,profileContent字段的值就是provisioning文件,和证书一样,苹果是以base64的方式通过接口返回的。我们base64解码一下就可以还原成.mobileprovision的文件了。

profileContent="MIId9AYJKoZIhvcNAQcC..省略..Syr5TPmPe1DiY4w=="
echo $profileContent | base64 -D > '/Users/Cocoakier/Movies/test.mobileprovision'
复制代码

注意:如果profile文件无效(profileState=INVALID),接口返回数据中profileContent的值可能为null。

创建一个Provisioning

添加请求头 'Content-Type: application/json’
POST https://api.appstoreconnect.apple.com/v1/profiles
必传参数:(其它没列出来的参数表示写法固定,参考请求示例)
name:起个名字
profileType:IOS_APP_DEVELOPMENT, IOS_APP_STORE, IOS_APP_ADHOC, IOS_APP_INHOUSE, MAC_APP_DEVELOPMENT, MAC_APP_STORE, MAC_APP_DIRECT, TVOS_APP_DEVELOPMENT, TVOS_APP_STORE, TVOS_APP_ADHOC, TVOS_APP_INHOUSE
(bundleId)id:bundle id,是创建或查询接口返回的那个id不是bundle identider,注意区分!
(certificates)id:证书id,可以传多个,外层是数组
(devices)id:设备id(profileType=IOS_APP_STORE时不传),可以传多个,外层是数组。注意,这里的设备id不是平时用的udid,是创建或查询接口返回的那个id!
成功,Status Code: 201 Created

请求示例
{
    "data": {
        "attributes": {
            "name": "test_se",
            "profileType": "IOS_APP_DEVELOPMENT"
        },
        "type": "profiles",
        "relationships": {
            "bundleId": {
                "data": {
                    "id": "TH9XXXX",
                    "type": "bundleIds"
                }
            },
            "certificates": {
                "data": [
                    {
                        "id": "5DWXXXXX",
                        "type": "certificates"
                    }
                ]
            },
            "devices": {
                "data": [
                    {
                        "id": "7BUHXXXXX",
                        "type": "devices"
                    }
                ]
            }
        }
    }
}

返回数据(去除了一些无用数据)
{
  "data": {
    "type": "profiles",
    "id": "YFV93T5866",
    "attributes": {
      "profileState": "ACTIVE",
      "createdDate": "2019-11-12T13:00:11.605+0000",
      "profileType": "IOS_APP_DEVELOPMENT",
      "name": "test_se",
      "profileContent": “MIId9AYJKoZIhvcNAQcCoII..省略...e1DiY4w==",
      "uuid": "7bbxxxxx-8c21-4xxx-9cc9-3485xxxxx",
      "platform": "IOS",
      "expirationDate": "2020-11-11T13:00:11.605+0000"
    },
    "relationships": {
      "bundleId": {
        "data": {
          "type": "bundleIds",
          "id": "TH9XXXX"
        },
      },
      "certificates": {
        "data": [{
          "type": "certificates",
          "id": "5DWXXXXX"
        }],
      },
      "devices": {
        "data": [{
          "type": "devices",
          "id": "7BUHXXXXX"
        }],
      }
    },
  },
}
复制代码

删除provisioning**

DELETE https://api.appstoreconnect.apple.com/v1/profiles/{id}
例如
DELETE https://api.appstoreconnect.apple.com/v1/profiles/YFV93T5866
成功,Status Codes:204 No Content

User

获取开发者团队成员列表

GET https://api.appstoreconnect.apple.com/v1/users
可选参数:
fields[apps]:betaAppLocalizations, betaAppReviewDetail, betaGroups, betaLicenseAgreement, betaTesters, builds, bundleId, name, preReleaseVersions, primaryLocale, sku
fields[users]:allAppsVisible, firstName, lastName, provisioningAllowed, roles, username, visibleApps
include:visibleApps
limit:最大200
sort:lastName, -lastName, username, -username
filter[roles]:ADMIN, FINANCE, TECHNICAL, ACCOUNT_HOLDER, READ_ONLY, SALES, MARKETING, APP_MANAGER, DEVELOPER, ACCESS_TO_REPORTS, CUSTOMER_SUPPORT
filter[visibleApps]:
filter[username]:
limit[visibleApps]:最大50

返回数据
{
  "data": [{
    "type": "users",
    "id": "73xxx-09ef-4f4a-af23-abb3xxxxxxx",
    "attributes": {
      "username": "xiaoming@qq.com",
      "firstName": "xiao",
      "lastName": "ming",
      "roles": ["ADMIN", "ACCOUNT_HOLDER"],
      "allAppsVisible": true,
      "provisioningAllowed": true
    },
    "relationships": {
      "visibleApps": {
        "links": {
          "self": "https://api.appstoreconnect.apple.com/v1/users/73xxx-09ef-4f4a-af23-abb3xxxxxxx/relationships/visibleApps",
          "related": "https://api.appstoreconnect.apple.com/v1/users/73xxx-09ef-4f4a-af23-abb3xxxxxxx/visibleApps"
        }
      }
    },
    "links": {
      "self": "https://api.appstoreconnect.apple.com/v1/users/73xxx-09ef-4f4a-af23-abb3xxxxxxx"
    }
  }],
  "links": {
    "self": "https://api.appstoreconnect.apple.com/v1/users"
  },
  "meta": {
    "paging": {
      "total": 1,
      "limit": 50
    }
  }
}
复制代码

TestFlight

获取APP列表

GET https://api.appstoreconnect.apple.com/v1/apps
可选参数:
fields[apps]:返回的app信息有限,就是后面这些,builds, bundleId, name, preReleaseVersions, primaryLocale, sku, betaAppLocalizations, betaAppReviewDetail, betaGroups, betaLicenseAgreement, betaTesters
include:betaAppLocalizations, betaAppReviewDetail, betaGroups, betaLicenseAgreement, builds, preReleaseVersions
其它可选参数,请自行查看文档

获取APP列表 返回数据(去除了一些无用字段)
{
  "data": [{
    "type": "apps",
    "id": "1456000000",
    "attributes": {
      "name": "微信",
      "bundleId": "com.tencent.xin",
      "sku": "com.tencent.xin",
      "primaryLocale": "zh-Hans"
    }
}
复制代码

获取某个app的preReleaseVersions信息(对应App Store Connect后台的version)

GET https://api.appstoreconnect.apple.com/v1/apps/1456000000/preReleaseVersions

获取某个app preReleaseVersions信息 返回数据(去除了一些无用数据)
{
  "data": [{
    "type": "preReleaseVersions",
    "id": "316bxxx-5394-46d7-8e49-4axxxxx",
    "attributes": {
      "version": "1.0.2",
      "platform": "IOS"
    }
  }, {
    "type": "preReleaseVersions",
    "id": "8a9xxxx-3620-4bbd-a540-9axxxxx",
    "attributes": {
      "version": "1.0.0",
      "platform": "IOS"
    },
  }, {
    "type": "preReleaseVersions",
    "id": "c649xxxx-3229-4e20-8349-2efaxxxxx",
    "attributes": {
      "version": "1.0.1",
      "platform": "IOS"
    }
  }],
  "meta": {
    "paging": {
      "total": 3,
      "limit": 50
    }
  }
}
复制代码

获取某个app的builds信息(对应App Store Connect后台的二进制包)

GET https://api.appstoreconnect.apple.com/v1/apps/1456000000/builds

获取某个app build信息 返回数据(我去掉了一些无用数据)
{
    "data": [
        {
            "type": "builds",
            "id": "977xxx-c0d1-47a5-b439-7485xxxx",
            "attributes": {
                "version": "5",
                "uploadedDate": "2019-07-05T01:49:16-07:00",
                "expirationDate": "2019-10-03T01:49:16-07:00",
                "expired": true,
                "minOsVersion": "9.0",
                "iconAssetToken": {
                    "templateUrl": "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/41/24/46/41xxx-8fa3-142b-b3da-e1xxxxx/Icon-60@2x.png.png/{w}x{h}bb.{f}",
                    "width": 120,
                    "height": 120
                },
                "processingState": "VALID",
                "usesNonExemptEncryption": false
            },
        },
        {
            "type": "builds",
            "id": "de3d9xxx-d87b-449c-b025-a510xxxxx",
            "attributes": {
                "version": "6",
                "uploadedDate": "2019-11-04T00:13:03-08:00",
                "expirationDate": "2020-02-02T00:13:03-08:00",
                "expired": false,
                "minOsVersion": "9.0",
                "iconAssetToken": {
                    "templateUrl": "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/4b/f2/41/4bxxx-f059-fe0a-c220-39cxxx/Icon-60@2x.png.png/{w}x{h}bb.{f}",
                    "width": 120,
                    "height": 120
                },
                "processingState": "VALID",
                "usesNonExemptEncryption": false
            }
        }
        ...
    ],
    "meta": {
        "paging": {
            "total": 6,
            "limit": 50
        }
    }
}
复制代码

下载销售和趋势报告

《如何自动化获取AppStore的销售和趋势报告》

总结

上述只列出了部分App Store Connnect API的功能,还有些功能,比如TestFlight、User、Bundle ID Capabilities等,没有列出来。通过这篇文章想必大家对苹果文档的“风格”也熟悉了,其它功能可以自行查阅官方文档。给大家推荐一个JSON在线编辑工具,对照苹果文档构造请求体很好用。

总的来说,App Store Connect API目前能做的事情很有限,主要还是用于自动化管理证书、设备、bundle identifier这些,一些重要的接口苹果仍然没有开放,比如创建App,编辑元数据,提交审核,查看审核结果等,我想苹果还是考虑到安全问题吧,现在马甲包本来就泛滥成灾,如果开放了这些接口,后果可想而知。对于Mac平台来说,Fastlane - Space 比 App Store Connect API 强大太多,App Store Connect API显得有些鸡肋了,但是对于Window平台来说,App Store Connect API还是不错的选择。

如果觉得这篇文章对你有帮助,请点个赞吧。如果有疑问可以关注我的公众号给我留言。
转载请注明出处,谢谢!

参考链接:
App Store Connect的新特性(WWDC 2018 session 301 & 303)
App Store Connect API苹果官方文档
JWT官网
Spaceship官网
fastlane-windows-cannot-installing
openssl req 生成证书请求和自建CA