Agent Skillsblitzdotdev/blitz-mac › asc-team-key-create

asc-team-key-create

GitHub

通过Apple iris API创建App Store Connect团队API密钥,下载私钥并存入本地。用于CLI认证、CI/CD或外部工具集成。需预置Web会话且具备Admin权限。

src/resources/skills/asc-team-key-create/SKILL.md blitzdotdev/blitz-mac

Trigger Scenarios

用户请求创建新的ASC API密钥 需要为asc auth login生成新密钥 需要为CI/CD流水线配置API密钥

Install

npx skills add blitzdotdev/blitz-mac --skill asc-team-key-create -g -y
More Options

Non-standard path

npx skills add https://github.com/blitzdotdev/blitz-mac/tree/master/src/resources/skills/asc-team-key-create -g -y

Use without installing

npx skills use blitzdotdev/blitz-mac@asc-team-key-create

指定 Agent (Claude Code)

npx skills add blitzdotdev/blitz-mac --skill asc-team-key-create -a claude-code -g -y

安装 repo 全部 skill

npx skills add blitzdotdev/blitz-mac --all -g -y

预览 repo 内 skill

npx skills add blitzdotdev/blitz-mac --list

SKILL.md

Frontmatter
{
    "name": "asc-team-key-create",
    "description": "Create a new App Store Connect Team API Key with Admin permissions, download the one-time .p8 private key, and store it in ~\/.blitz. Use when the user needs a new ASC API key for CLI auth, CI\/CD, or external tooling."
}

asc team key create

Use this skill to create a new App Store Connect API Key with Admin permissions via Apple's iris API, download the one-time .p8 private key, and save it to ~/.blitz.

When to use

  • User asks to "create an API key", "generate a team key", "new ASC key"
  • User needs a fresh key for asc auth login, CI/CD pipelines, or external tooling
  • User wants to rotate or replace an existing API key

Preconditions

  • Web session file available at ~/.blitz/asc-agent/web-session.json. If no session exists or it has expired (401), call the asc_web_auth MCP tool first — this opens the Apple ID login window in Blitz and captures the session automatically.
  • The authenticated Apple ID must have Account Holder or Admin role.

Workflow

1. Check for an existing web session

Before anything else, check if a web session file already exists:

test -f ~/.blitz/asc-agent/web-session.json && echo "SESSION_EXISTS" || echo "NO_SESSION"
  • If NO_SESSION: call the asc_web_auth MCP tool first to open the Apple ID login window in Blitz. Wait for it to complete before proceeding.
  • If SESSION_EXISTS: proceed to the next step.

2. Ask the user for a key name

Ask the user what they want to name the key (the nickname field in ASC). This is a required input — do not guess or use a default.

3. Create the key, download the .p8, and save it

Use the following self-contained script. Replace KEY_NAME with the user's chosen name. Do not print or log cookies — they contain sensitive session tokens.

python3 -c "
import json, urllib.request, base64, os, sys, time

KEY_NAME = 'KEY_NAME_HERE'

# Read web session file (silent — never print these)
session_path = os.path.expanduser('~/.blitz/asc-agent/web-session.json')
if not os.path.isfile(session_path):
    print('ERROR: No web session found. Call asc_web_auth MCP tool first.')
    sys.exit(1)
with open(session_path) as f:
    raw = f.read()

store = json.loads(raw)
session = store['sessions'][store['last_key']]
cookie_str = '; '.join(
    f'{c[\"name\"]}={c[\"value\"]}'
    for cl in session['cookies'].values() for c in cl
    if c.get('name') and c.get('value')
)

headers = {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'X-Requested-With': 'XMLHttpRequest',
    'Origin': 'https://appstoreconnect.apple.com',
    'Referer': 'https://appstoreconnect.apple.com/',
    'Cookie': cookie_str
}

# Step 1: Create the API key
create_body = json.dumps({
    'data': {
        'type': 'apiKeys',
        'attributes': {
            'nickname': KEY_NAME,
            'roles': ['ADMIN'],
            'allAppsVisible': True,
            'keyType': 'PUBLIC_API'
        }
    }
}).encode()

req = urllib.request.Request(
    'https://appstoreconnect.apple.com/iris/v1/apiKeys',
    data=create_body, method='POST', headers=headers)
try:
    resp = urllib.request.urlopen(req)
    create_data = json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
    body = e.read().decode()
    if e.code == 401:
        print('ERROR: Session expired. Call asc_web_auth MCP tool to re-authenticate.')
    elif e.code == 409:
        print(f'ERROR: A key with this name may already exist. Details: {body[:300]}')
    else:
        print(f'ERROR creating key: HTTP {e.code} — {body[:300]}')
    sys.exit(1)

key_id = create_data['data']['id']
can_download = create_data['data']['attributes'].get('canDownload', False)
print(f'Created API key \"{KEY_NAME}\" — Key ID: {key_id}')

if not can_download:
    print('ERROR: Key created but canDownload is false. Cannot retrieve private key.')
    sys.exit(1)

# Step 2: Download the one-time private key
time.sleep(0.5)
dl_headers = dict(headers)
dl_headers.pop('Content-Type', None)
req = urllib.request.Request(
    f'https://appstoreconnect.apple.com/iris/v1/apiKeys/{key_id}?fields%5BapiKeys%5D=privateKey',
    method='GET', headers=dl_headers)
try:
    resp = urllib.request.urlopen(req)
    dl_data = json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
    print(f'ERROR downloading key: HTTP {e.code} — {e.read().decode()[:300]}')
    sys.exit(1)

pk_b64 = dl_data['data']['attributes'].get('privateKey')
if not pk_b64:
    print('ERROR: No privateKey in response. The key may have already been downloaded.')
    sys.exit(1)

private_key_pem = base64.b64decode(pk_b64).decode()

# Step 3: Get the issuer ID from the provider relationship
time.sleep(0.35)
req = urllib.request.Request(
    f'https://appstoreconnect.apple.com/iris/v1/apiKeys/{key_id}?include=provider',
    method='GET', headers=dl_headers)
try:
    resp = urllib.request.urlopen(req)
    provider_data = json.loads(resp.read().decode())
    issuer_id = None
    for inc in provider_data.get('included', []):
        if inc['type'] == 'contentProviders':
            issuer_id = inc['id']
            break
    if not issuer_id:
        issuer_id = provider_data['data']['relationships']['provider']['data']['id']
except Exception:
    issuer_id = 'UNKNOWN'

# Step 4: Save .p8 file to ~/.blitz
blitz_dir = os.path.expanduser('~/.blitz')
os.makedirs(blitz_dir, exist_ok=True)
p8_path = os.path.join(blitz_dir, f'AuthKey_{key_id}.p8')
with open(p8_path, 'w') as f:
    f.write(private_key_pem)
os.chmod(p8_path, 0o600)

print(f'Private key saved to: {p8_path}')
print(f'Issuer ID: {issuer_id}')
print(f'Key ID: {key_id}')
print()
print('To use with asc CLI:')
print(f'  asc auth login --key-id {key_id} --issuer-id {issuer_id} --private-key-path {p8_path}')
print()
print('WARNING: This .p8 file can only be downloaded ONCE. Keep it safe.')
"

4. Fill the credential form via MCP

After the script succeeds, call the asc_set_credentials MCP tool to pre-fill the Blitz credential form:

asc_set_credentials(issuerId: "<issuer_id>", privateKeyPath: "~/.blitz/AuthKey_<key_id>.p8")

keyId is optional here because Blitz can derive it from Apple's default AuthKey_<KEYID>.p8 filename. This lets the user visually verify the values and confirm in Blitz.

5. Report results to the user

After the script runs, report:

  • Key name and Key ID
  • Issuer ID
  • File path of the saved .p8
  • That the credential form has been pre-filled — they should verify and confirm in Blitz

Common Errors

401 Not Authorized

The web session has expired or doesn't exist. Call the asc_web_auth MCP tool — this opens the Apple ID login window in Blitz and refreshes ~/.blitz/asc-agent/web-session.json automatically. Then retry the key creation script.

409 Conflict

A key with the same name may already exist, or another conflict occurred. Try a different name.

"No privateKey in response"

The key's one-time download window has passed (canDownload flipped to false). This happens if the key was already downloaded. The key must be revoked and a new one created.

Agent Behavior

  • Always ask the user for the key name before creating. Do not use defaults.
  • NEVER print, log, or echo session cookies. The python script handles cookies internally.
  • Use the self-contained python script above — do NOT extract cookies separately or pass them as shell variables.
  • After creation, confirm the .p8 file exists and report the path.
  • If iris API returns 401, tell the user to re-authenticate via Blitz or asc web auth login.
  • The iris API is rate-limited; the script includes 350ms+ delays between requests.
  • The .p8 private key file is saved with 0600 permissions (owner read/write only).

Notes

  • The private key can only be downloaded once from Apple. After the first download, canDownload flips to false permanently. The saved .p8 file is the only copy.
  • The issuer ID is the same for all keys in the team — it's the content provider UUID.
  • Keys are created with allAppsVisible: true (access to all apps in the team).
  • To revoke a key later, use PATCH /iris/v1/apiKeys/{keyId} with {"data": {"type": "apiKeys", "id": "KEY_ID", "attributes": {"isActive": false}}}.

Version History

  • f039dd6 Current 2026-07-25 09:25

Same Skill Collection

.claude/skills/asc-iap-attach/SKILL.md
.claude/skills/asc-privacy-nutrition-labels/SKILL.md
.claude/skills/asc-team-key-create/SKILL.md
src/resources/skills/asc-app-create-ui/SKILL.md
src/resources/skills/asc-iap-attach/SKILL.md
src/resources/skills/asc-privacy-nutrition-labels/SKILL.md

Metadata

Files
0
Version
f039dd6
Hash
de5a1e33
Indexed
2026-07-25 09:25

Главная - Вики-сайт
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-30 15:03
浙ICP备14020137号-1 $Гость$