app_store_dev_api 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,417 @@
1
+ # Bundle IDs - App Store Connect API
2
+
3
+ > 官方文档: https://developer.apple.com/documentation/appstoreconnectapi/bundle-ids
4
+
5
+ ## 概述
6
+
7
+ Bundle IDs 是用于标识 iOS、macOS 或通用平台应用的唯一标识符。通过 App Store Connect API,你可以注册、查询、更新和删除 Bundle ID,并管理其关联的 App、Capabilities 和 Profiles。
8
+
9
+ ## 数据模型
10
+
11
+ ### BundleId 对象
12
+
13
+ | 字段 | 类型 | 说明 |
14
+ |------|------|------|
15
+ | `type` | string | 固定值 `"bundleIds"` |
16
+ | `id` | string | 资源唯一标识 |
17
+ | `attributes.name` | string | Bundle ID 显示名称 |
18
+ | `attributes.platform` | BundleIdPlatform | 平台类型 |
19
+ | `attributes.identifier` | string | Bundle ID 标识符(如 `com.example.app`) |
20
+ | `attributes.seedId` | string | Team Seed ID |
21
+
22
+ ### BundleIdPlatform 枚举
23
+
24
+ | 值 | 说明 |
25
+ |----|------|
26
+ | `IOS` | iOS 平台 |
27
+ | `MAC_OS` | macOS 平台 |
28
+ | `UNIVERSAL` | 通用平台(iOS + macOS) |
29
+
30
+ ### 关联关系 (Relationships)
31
+
32
+ | 关系 | 类型 | 说明 |
33
+ |------|------|------|
34
+ | `profiles` | Profiles[] | 关联的配置文件 |
35
+ | `bundleIdCapabilities` | BundleIdCapability[] | 关联的能力配置 |
36
+ | `app` | App | 关联的应用 |
37
+
38
+ ---
39
+
40
+ ## API 端点
41
+
42
+ ### 1. 列出所有 Bundle IDs
43
+
44
+ ```
45
+ GET /v1/bundleIds
46
+ ```
47
+
48
+ 获取所有已注册的 Bundle ID 列表。
49
+
50
+ #### 查询参数
51
+
52
+ **过滤 (Filter)**
53
+
54
+ | 参数 | 类型 | 说明 |
55
+ |------|------|------|
56
+ | `filter[name]` | string | 按名称过滤 |
57
+ | `filter[platform]` | string | 按平台过滤,可选值: `IOS`, `MAC_OS`, `UNIVERSAL` |
58
+ | `filter[identifier]` | string | 按标识符过滤 |
59
+ | `filter[seedId]` | string | 按 Seed ID 过滤 |
60
+ | `filter[id]` | string | 按资源 ID 过滤 |
61
+
62
+ **排序 (Sort)**
63
+
64
+ | 参数 | 说明 |
65
+ |------|------|
66
+ | `sort` | 排序字段,可选: `name`, `-name`, `platform`, `-platform`, `identifier`, `-identifier`, `seedId`, `-seedId`, `id`, `-id` |
67
+
68
+ **字段选择 (Fields)**
69
+
70
+ | 参数 | 可选值 |
71
+ |------|--------|
72
+ | `fields[bundleIds]` | `name`, `platform`, `identifier`, `seedId`, `profiles`, `bundleIdCapabilities`, `app` |
73
+ | `fields[profiles]` | `name`, `platform`, `profileType`, `profileState`, `profileContent`, `uuid`, `createdDate`, `expirationDate`, `bundleId`, `devices`, `certificates` |
74
+ | `fields[bundleIdCapabilities]` | `capabilityType`, `settings` |
75
+ | `fields[apps]` | `name`, `bundleId`, `sku`, `primaryLocale` 等 |
76
+
77
+ **分页与包含**
78
+
79
+ | 参数 | 说明 |
80
+ |------|------|
81
+ | `limit` | 每页数量限制 |
82
+ | `limit[bundleIdCapabilities]` | Capabilities 关系数量限制 |
83
+ | `limit[profiles]` | Profiles 关系数量限制 |
84
+ | `include` | 包含关联资源,可选: `profiles`, `bundleIdCapabilities`, `app` |
85
+
86
+ #### 响应
87
+
88
+ | 状态码 | 说明 | 响应体 |
89
+ |--------|------|--------|
90
+ | 200 | 成功 | `BundleIdsResponse` |
91
+ | 400 | 请求参数错误 | `ErrorResponse` |
92
+ | 401 | 未认证 | `ErrorResponse` |
93
+ | 403 | 无权限 | `ErrorResponse` |
94
+ | 429 | 请求过于频繁 | `ErrorResponse` |
95
+
96
+ ---
97
+
98
+ ### 2. 注册新的 Bundle ID
99
+
100
+ ```
101
+ POST /v1/bundleIds
102
+ ```
103
+
104
+ 注册一个新的 Bundle ID。
105
+
106
+ #### 请求体 (BundleIdCreateRequest)
107
+
108
+ ```json
109
+ {
110
+ "data": {
111
+ "type": "bundleIds",
112
+ "attributes": {
113
+ "name": "My App Bundle ID",
114
+ "platform": "IOS",
115
+ "identifier": "com.example.myapp",
116
+ "seedId": "XXXXXXXXXX"
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ **必填字段:**
123
+
124
+ | 字段 | 类型 | 说明 |
125
+ |------|------|------|
126
+ | `attributes.name` | string | **必填** - Bundle ID 显示名称 |
127
+ | `attributes.platform` | BundleIdPlatform | **必填** - 平台类型 |
128
+ | `attributes.identifier` | string | **必填** - Bundle ID 标识符 |
129
+
130
+ **可选字段:**
131
+
132
+ | 字段 | 类型 | 说明 |
133
+ |------|------|------|
134
+ | `attributes.seedId` | string | 可选 - Team Seed ID(nullable) |
135
+
136
+ #### 响应
137
+
138
+ | 状态码 | 说明 | 响应体 |
139
+ |--------|------|--------|
140
+ | 201 | 创建成功 | `BundleIdResponse` |
141
+ | 400 | 请求参数错误 | `ErrorResponse` |
142
+ | 401 | 未认证 | `ErrorResponse` |
143
+ | 403 | 无权限 | `ErrorResponse` |
144
+ | 409 | 资源冲突(已存在) | `ErrorResponse` |
145
+ | 422 | 请求体无法处理 | `ErrorResponse` |
146
+ | 429 | 请求过于频繁 | `ErrorResponse` |
147
+
148
+ ---
149
+
150
+ ### 3. 获取单个 Bundle ID
151
+
152
+ ```
153
+ GET /v1/bundleIds/{id}
154
+ ```
155
+
156
+ 根据 ID 获取指定的 Bundle ID 信息。
157
+
158
+ #### 路径参数
159
+
160
+ | 参数 | 类型 | 说明 |
161
+ |------|------|------|
162
+ | `id` | string | **必填** - Bundle ID 资源的唯一标识 |
163
+
164
+ #### 查询参数
165
+
166
+ | 参数 | 说明 |
167
+ |------|------|
168
+ | `fields[bundleIds]` | 选择返回的 Bundle ID 字段 |
169
+ | `fields[profiles]` | 选择返回的 Profile 字段 |
170
+ | `fields[bundleIdCapabilities]` | 选择返回的 Capability 字段 |
171
+ | `fields[apps]` | 选择返回的 App 字段 |
172
+ | `include` | 包含关联资源: `profiles`, `bundleIdCapabilities`, `app` |
173
+ | `limit[bundleIdCapabilities]` | Capabilities 数量限制 |
174
+ | `limit[profiles]` | Profiles 数量限制 |
175
+
176
+ #### 响应
177
+
178
+ | 状态码 | 说明 | 响应体 |
179
+ |--------|------|--------|
180
+ | 200 | 成功 | `BundleIdResponse` |
181
+ | 400 | 请求参数错误 | `ErrorResponse` |
182
+ | 401 | 未认证 | `ErrorResponse` |
183
+ | 403 | 无权限 | `ErrorResponse` |
184
+ | 404 | 资源不存在 | `ErrorResponse` |
185
+ | 429 | 请求过于频繁 | `ErrorResponse` |
186
+
187
+ ---
188
+
189
+ ### 4. 更新 Bundle ID
190
+
191
+ ```
192
+ PATCH /v1/bundleIds/{id}
193
+ ```
194
+
195
+ 更新已有 Bundle ID 的名称。
196
+
197
+ #### 路径参数
198
+
199
+ | 参数 | 类型 | 说明 |
200
+ |------|------|------|
201
+ | `id` | string | **必填** - Bundle ID 资源的唯一标识 |
202
+
203
+ #### 请求体 (BundleIdUpdateRequest)
204
+
205
+ ```json
206
+ {
207
+ "data": {
208
+ "type": "bundleIds",
209
+ "id": "BUNDLE_ID_RESOURCE_ID",
210
+ "attributes": {
211
+ "name": "Updated App Name"
212
+ }
213
+ }
214
+ }
215
+ ```
216
+
217
+ **可更新字段:**
218
+
219
+ | 字段 | 类型 | 说明 |
220
+ |------|------|------|
221
+ | `attributes.name` | string | 可选 - 新的显示名称(nullable) |
222
+
223
+ > 注意: `platform` 和 `identifier` 字段在创建后不可修改。
224
+
225
+ #### 响应
226
+
227
+ | 状态码 | 说明 | 响应体 |
228
+ |--------|------|--------|
229
+ | 200 | 更新成功 | `BundleIdResponse` |
230
+ | 400 | 请求参数错误 | `ErrorResponse` |
231
+ | 401 | 未认证 | `ErrorResponse` |
232
+ | 403 | 无权限 | `ErrorResponse` |
233
+ | 404 | 资源不存在 | `ErrorResponse` |
234
+ | 409 | 资源冲突 | `ErrorResponse` |
235
+ | 422 | 请求体无法处理 | `ErrorResponse` |
236
+ | 429 | 请求过于频繁 | `ErrorResponse` |
237
+
238
+ ---
239
+
240
+ ### 5. 删除 Bundle ID
241
+
242
+ ```
243
+ DELETE /v1/bundleIds/{id}
244
+ ```
245
+
246
+ 删除指定的 Bundle ID。
247
+
248
+ #### 路径参数
249
+
250
+ | 参数 | 类型 | 说明 |
251
+ |------|------|------|
252
+ | `id` | string | **必填** - Bundle ID 资源的唯一标识 |
253
+
254
+ #### 响应
255
+
256
+ | 状态码 | 说明 |
257
+ |--------|------|
258
+ | 204 | 删除成功(无响应体) |
259
+ | 400 | 请求参数错误 |
260
+ | 401 | 未认证 |
261
+ | 403 | 无权限 |
262
+ | 404 | 资源不存在 |
263
+ | 429 | 请求过于频繁 |
264
+
265
+ ---
266
+
267
+ ## 关联端点
268
+
269
+ ### 6. 获取 Bundle ID 关联的 App(关系)
270
+
271
+ ```
272
+ GET /v1/bundleIds/{id}/relationships/app
273
+ ```
274
+
275
+ 获取 Bundle ID 与 App 的关联关系 ID。
276
+
277
+ #### 响应
278
+
279
+ | 状态码 | 说明 | 响应体 |
280
+ |--------|------|--------|
281
+ | 200 | 成功 | `BundleIdAppLinkageResponse` |
282
+ | 404 | 资源不存在 | `ErrorResponse` |
283
+
284
+ ---
285
+
286
+ ### 7. 获取 Bundle ID 关联的 App(详情)
287
+
288
+ ```
289
+ GET /v1/bundleIds/{id}/app
290
+ ```
291
+
292
+ 获取与 Bundle ID 关联的 App 完整信息。
293
+
294
+ #### 查询参数
295
+
296
+ | 参数 | 说明 |
297
+ |------|------|
298
+ | `fields[apps]` | 选择返回的 App 字段 |
299
+
300
+ #### 响应
301
+
302
+ | 状态码 | 说明 | 响应体 |
303
+ |--------|------|--------|
304
+ | 200 | 成功 | `AppWithoutIncludesResponse` |
305
+ | 404 | 资源不存在 | `ErrorResponse` |
306
+
307
+ ---
308
+
309
+ ### 8. 获取 Bundle ID 关联的 Capabilities(关系)
310
+
311
+ ```
312
+ GET /v1/bundleIds/{id}/relationships/bundleIdCapabilities
313
+ ```
314
+
315
+ 获取 Bundle ID 与 Capabilities 的关联关系 ID 列表。
316
+
317
+ #### 查询参数
318
+
319
+ | 参数 | 说明 |
320
+ |------|------|
321
+ | `limit` | 数量限制 |
322
+
323
+ #### 响应
324
+
325
+ | 状态码 | 说明 | 响应体 |
326
+ |--------|------|--------|
327
+ | 200 | 成功 | `BundleIdBundleIdCapabilitiesLinkagesResponse` |
328
+ | 404 | 资源不存在 | `ErrorResponse` |
329
+
330
+ ---
331
+
332
+ ### 9. 获取 Bundle ID 关联的 Capabilities(详情)
333
+
334
+ ```
335
+ GET /v1/bundleIds/{id}/bundleIdCapabilities
336
+ ```
337
+
338
+ 获取与 Bundle ID 关联的所有 Capability 完整信息。
339
+
340
+ #### 查询参数
341
+
342
+ | 参数 | 说明 |
343
+ |------|------|
344
+ | `fields[bundleIdCapabilities]` | 选择返回的字段: `capabilityType`, `settings` |
345
+ | `limit` | 数量限制 |
346
+
347
+ #### 响应
348
+
349
+ | 状态码 | 说明 | 响应体 |
350
+ |--------|------|--------|
351
+ | 200 | 成功 | `BundleIdCapabilitiesWithoutIncludesResponse` |
352
+ | 404 | 资源不存在 | `ErrorResponse` |
353
+
354
+ ---
355
+
356
+ ### 10. 获取 Bundle ID 关联的 Profiles(关系)
357
+
358
+ ```
359
+ GET /v1/bundleIds/{id}/relationships/profiles
360
+ ```
361
+
362
+ 获取 Bundle ID 与 Profiles 的关联关系 ID 列表。
363
+
364
+ #### 查询参数
365
+
366
+ | 参数 | 说明 |
367
+ |------|------|
368
+ | `limit` | 数量限制 |
369
+
370
+ #### 响应
371
+
372
+ | 状态码 | 说明 | 响应体 |
373
+ |--------|------|--------|
374
+ | 200 | 成功 | `BundleIdProfilesLinkagesResponse` |
375
+ | 404 | 资源不存在 | `ErrorResponse` |
376
+
377
+ ---
378
+
379
+ ### 11. 获取 Bundle ID 关联的 Profiles(详情)
380
+
381
+ ```
382
+ GET /v1/bundleIds/{id}/profiles
383
+ ```
384
+
385
+ 获取与 Bundle ID 关联的所有 Profile 完整信息。
386
+
387
+ #### 查询参数
388
+
389
+ | 参数 | 说明 |
390
+ |------|------|
391
+ | `fields[profiles]` | 选择返回的字段 |
392
+ | `limit` | 数量限制 |
393
+
394
+ #### 响应
395
+
396
+ | 状态码 | 说明 | 响应体 |
397
+ |--------|------|--------|
398
+ | 200 | 成功 | `ProfilesWithoutIncludesResponse` |
399
+ | 404 | 资源不存在 | `ErrorResponse` |
400
+
401
+ ---
402
+
403
+ ## 端点汇总
404
+
405
+ | 方法 | 路径 | 说明 |
406
+ |------|------|------|
407
+ | `GET` | `/v1/bundleIds` | 列出所有 Bundle IDs |
408
+ | `POST` | `/v1/bundleIds` | 注册新 Bundle ID |
409
+ | `GET` | `/v1/bundleIds/{id}` | 获取单个 Bundle ID |
410
+ | `PATCH` | `/v1/bundleIds/{id}` | 更新 Bundle ID |
411
+ | `DELETE` | `/v1/bundleIds/{id}` | 删除 Bundle ID |
412
+ | `GET` | `/v1/bundleIds/{id}/relationships/app` | 获取 App 关系 |
413
+ | `GET` | `/v1/bundleIds/{id}/app` | 获取关联 App 详情 |
414
+ | `GET` | `/v1/bundleIds/{id}/relationships/bundleIdCapabilities` | 获取 Capabilities 关系 |
415
+ | `GET` | `/v1/bundleIds/{id}/bundleIdCapabilities` | 获取关联 Capabilities 详情 |
416
+ | `GET` | `/v1/bundleIds/{id}/relationships/profiles` | 获取 Profiles 关系 |
417
+ | `GET` | `/v1/bundleIds/{id}/profiles` | 获取关联 Profiles 详情 |
@@ -0,0 +1,137 @@
1
+ # 创建 App Store Connect API 密钥
2
+
3
+ > 官方文档: https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api
4
+
5
+ ## 概述
6
+
7
+ 要使用 App Store Connect API,你需要先创建 API 密钥来进行身份认证。Apple 提供了两种类型的 API 密钥:**团队密钥 (Team Keys)** 和 **个人密钥 (Individual Keys)**。
8
+
9
+ ## 密钥类型
10
+
11
+ ### 团队密钥 (Team Keys)
12
+
13
+ - 由 **Admin(管理员)** 角色创建和管理
14
+ - 作用于整个开发者团队
15
+ - 可分配不同的角色权限
16
+ - 适用于 CI/CD 自动化、后端服务等场景
17
+
18
+ ### 个人密钥 (Individual Keys)
19
+
20
+ - 由任何团队成员为自己创建
21
+ - 权限范围与创建者的角色一致
22
+ - 适用于个人开发工具和脚本
23
+
24
+ ## 创建步骤
25
+
26
+ ### 前置条件
27
+
28
+ - 拥有 Apple Developer Program 会员资格
29
+ - 拥有 App Store Connect 的 Admin 角色(创建团队密钥时需要)
30
+ - 访问 [App Store Connect](https://appstoreconnect.apple.com/)
31
+
32
+ ### 创建团队 API 密钥
33
+
34
+ 1. 登录 [App Store Connect](https://appstoreconnect.apple.com/)
35
+ 2. 导航到 **用户和访问 (Users and Access)**
36
+ 3. 选择 **集成 (Integrations)** 选项卡
37
+ 4. 在左侧导航中选择 **App Store Connect API**
38
+ 5. 选择 **团队密钥 (Team Keys)** 标签页
39
+ 6. 点击 **生成 API 密钥 (Generate API Key)** 或 **+** 按钮
40
+ 7. 输入密钥名称(用于标识用途)
41
+ 8. 选择密钥的访问权限(角色)
42
+ 9. 点击 **生成 (Generate)**
43
+
44
+ ### 密钥角色权限
45
+
46
+ 创建团队密钥时,可分配以下角色:
47
+
48
+ | 角色 | 说明 |
49
+ |------|------|
50
+ | Admin | 完全管理权限,可执行所有 API 操作 |
51
+ | Finance | 财务数据访问权限 |
52
+ | App Manager | 应用管理权限 |
53
+ | Developer | 开发相关权限 |
54
+ | Marketing | 营销相关权限 |
55
+ | Sales | 销售数据访问权限 |
56
+ | Customer Support | 客户支持权限 |
57
+ | Read Only | 只读访问权限 |
58
+
59
+ ## 密钥属性
60
+
61
+ 创建 API 密钥后,你将获得以下三个关键信息:
62
+
63
+ ### 1. Issuer ID(发行者 ID)
64
+
65
+ - 格式:UUID(如 `57246542-96fe-1a63-e053-0824d011072a`)
66
+ - 对整个团队唯一,所有 API 密钥共享同一个 Issuer ID
67
+ - 在 App Store Connect 的 **密钥 (Keys)** 页面顶部可以找到
68
+ - 用于 JWT Token 的 `iss` 声明
69
+
70
+ ### 2. Key ID(密钥 ID)
71
+
72
+ - 格式:10 位字母数字字符串(如 `2X9R4HXF34`)
73
+ - 每个 API 密钥唯一
74
+ - 在密钥列表中显示
75
+ - 用于 JWT Token Header 的 `kid` 字段
76
+
77
+ ### 3. Private Key(私钥)
78
+
79
+ - 格式:ECDSA P-256 私钥(`.p8` 文件)
80
+ - 使用 ES256 算法
81
+ - **仅在创建时可下载一次**
82
+
83
+ ## 下载私钥
84
+
85
+ > ⚠️ **重要提示**: 私钥文件 (.p8) 只能在创建后下载一次。如果丢失,需要撤销当前密钥并创建新的。
86
+
87
+ 1. 创建密钥后,点击 **下载 API 密钥 (Download API Key)**
88
+ 2. 保存 `.p8` 文件到安全位置
89
+ 3. 文件名格式为 `AuthKey_{Key ID}.p8`(如 `AuthKey_2X9R4HXF34.p8`)
90
+
91
+ ### 私钥文件格式
92
+
93
+ ```
94
+ -----BEGIN PRIVATE KEY-----
95
+ MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg...
96
+ (Base64 编码的 ECDSA P-256 私钥数据)
97
+ ...
98
+ -----END PRIVATE KEY-----
99
+ ```
100
+
101
+ ## 安全建议
102
+
103
+ 1. **妥善保管私钥** - 将私钥存储在安全的密钥管理系统中(如 Vault、AWS Secrets Manager)
104
+ 2. **不要提交到代码仓库** - 将 `.p8` 文件添加到 `.gitignore`
105
+ 3. **使用环境变量** - 通过环境变量传递私钥内容,而非硬编码文件路径
106
+ 4. **最小权限原则** - 为 API 密钥分配完成任务所需的最小权限角色
107
+ 5. **定期轮换** - 定期撤销旧密钥并创建新密钥
108
+ 6. **限制密钥数量** - 每个团队最多可创建的活跃密钥数量有限
109
+
110
+ ## 在本项目中使用
111
+
112
+ 使用 `app_store_dev_api` gem 时,需要提供以上三个凭证信息:
113
+
114
+ ```ruby
115
+ require 'app_store_dev_api'
116
+
117
+ # 方式一:直接传入私钥内容
118
+ client = AppStoreDevApi::Client.new(
119
+ issuer_id: '57246542-96fe-1a63-e053-0824d011072a',
120
+ key_id: '2X9R4HXF34',
121
+ private_key: File.read('/path/to/AuthKey_2X9R4HXF34.p8')
122
+ )
123
+
124
+ # 方式二:通过环境变量
125
+ client = AppStoreDevApi::Client.new(
126
+ issuer_id: ENV['APP_STORE_CONNECT_ISSUER_ID'],
127
+ key_id: ENV['APP_STORE_CONNECT_KEY_ID'],
128
+ private_key: ENV['APP_STORE_CONNECT_PRIVATE_KEY']
129
+ )
130
+ ```
131
+
132
+ 初始化后,gem 会自动处理 JWT Token 的生成和 API 请求认证。
133
+
134
+ ## 相关文档
135
+
136
+ - [生成 API 请求 Token](generating_tokens.md)
137
+ - [撤销 API 密钥](revoking_api_keys.md)