App Store Connect API 키: Team Key로 할 수 있는 것과 없는 것
새 앱을 App Store Connect에 등록하면서, 화면을 계속 왔다 갔다 하기 귀찮아서 API로 자동화할 수 있는 부분은 자동화해보려고 했습니다. 결론부터 적으면 절반만 성공했습니다. 조회·수정은 되는데, 앱 생성 자체는 API로 안 됩니다.
인증부터
App Store Connect API는 OAuth가 아니라 자체 서명한 JWT로 인증합니다. 필요한 건 셋입니다.
- Key ID: 키를 만들면 발급되는 10자리 식별자 (예:
AuthKey_XXXXXXXXXX.p8파일명에 들어있음) - Issuer ID: 계정 전체에 하나, UUID 형태. App Store Connect → 사용자 및 접근 → 통합 탭 상단에 있습니다
- .p8 개인키 파일: 생성 시점에 딱 한 번만 다운로드할 수 있습니다. 잃어버리면 키를 새로 만들어야 합니다
Key ID·Issuer ID는 식별자일 뿐 비밀이 아닙니다(OAuth의 Client ID 같은 개념). 보호해야 하는 건 .p8 파일 하나뿐입니다.
JWT는 직접 서명하면 됩니다. Node 기준으로 외부 라이브러리 없이 crypto.sign만으로 됩니다. ES256 알고리즘이라 dsaEncoding: 'ieee-p1363' 옵션을 꼭 줘야 합니다(안 주면 Node 기본값인 DER 인코딩으로 서명되는데, Apple은 raw R||S 형식을 기대합니다).
const signature = crypto.sign('sha256', Buffer.from(signingInput), {
key: privateKeyPem,
dsaEncoding: 'ieee-p1363',
});팀 키와 개별 키
App Store Connect API 키는 두 종류입니다.
- 팀 키(Team Key): 조직 계정 자체에 속합니다. 역할을 직접 지정하고, 만든 사람이 팀에서 빠져도 유지됩니다. CI·자동화용입니다. 계정당 최대 50개까지 활성 상태로 둘 수 있습니다.
- 개별 키(Individual Key): 특정 사람 계정에 묶입니다. 그 사람이 팀에서 가진 권한을 그대로 상속받고, 1인당 1개만 만들 수 있습니다.
자동화라면 당연히 팀 키입니다.
역할을 올려도 안 되는 것
여기서 한참 헤맸습니다. 처음 쓰던 키가 "앱 관리(App Manager)" 역할이었는데, 새 앱을 만들려고 POST /v1/apps를 호출하니 이런 에러가 났습니다.
{
"code": "FORBIDDEN_ERROR",
"detail": "The resource 'apps' does not allow 'CREATE'. Allowed operations are: GET_COLLECTION, GET_INSTANCE, UPDATE"
}권한이 부족한가 싶어서 "관리자(Admin)" 역할로 새 키를 만들어 다시 시도했습니다. 똑같은 에러였습니다. 혹시나 해서 세 번째 키까지 만들어봤는데 결과는 같았습니다.
세 번 다 같은 응답이 나오고 나서야 이게 권한 문제가 아니라는 걸 알았습니다. App Store Connect의 공개 API는 앱 생성을 아예 지원하지 않습니다. 역할을 아무리 올려도 apps 리소스의 CREATE는 원천적으로 막혀 있고, 허용되는 건 조회(GET_COLLECTION, GET_INSTANCE)와 수정(UPDATE)뿐입니다. Fastlane의 produce나 다른 CI 도구들이 앱 생성을 자동화할 때 공개 API 대신 Apple ID 로그인 세션(2단계 인증 포함)을 흉내 내는 비공식 방식을 쓰는 이유가 이거였습니다.
반대로 Bundle ID(App ID) 등록은 공개 API로 됩니다. POST /v1/bundleIds는 정상적으로 201을 반환합니다. 그래서 "번들 ID는 미리 등록해두고, 실제 앱 레코드 생성만 웹 화면에서 하는" 순서로 정리했습니다.
// 되는 것
POST /v1/bundleIds // 201
// 안 되는 것 (역할 무관)
POST /v1/apps // 403 FORBIDDEN_ERROR이름 필드에서 한 번 더 걸렸습니다
Bundle ID를 등록할 때 name에 한글을 넣었더니 409 ENTITY_ERROR.ATTRIBUTE.INVALID가 났습니다. 이 필드는 Apple 내부에서 쓰는 식별용 라벨이라 영문/숫자만 허용하고, 스토어에 노출되는 실제 앱 이름과는 다른 값입니다. 앱 레코드 자체의 name(App Store에 노출되는 이름)은 한글이 됩니다. 같은 "이름"이라는 단어를 쓰는 필드 두 개가 서로 다른 규칙을 가진 셈이라 처음엔 헷갈렸습니다.
앱 단위 접근 범위도 따로 있었습니다
앱을 웹 화면에서 만들 때 "사용자 액세스 권한"을 묻는 단계가 있습니다. 제한된 액세스를 고르면 그 자리에서 지정한 사람·역할만 이 앱을 볼 수 있고, 이미 "전체 앱" 범위로 설정된 키라도 이 목록에 없으면 안 보입니다. 전체 액세스를 고르면 "전체 앱" 범위를 가진 역할·키가 자동으로 이 앱에 접근하게 됩니다.
키 쪽의 "앱 관리" 역할 + "전체 앱" 범위 설정과, 앱 쪽의 "전체 액세스" 설정이 둘 다 맞아야 자동화 키가 새로 만든 앱을 조회할 수 있습니다. 하나라도 어긋나면 목록 조회에서 그 앱만 조용히 빠집니다. 에러가 나는 게 아니라 그냥 안 보이니, 처음엔 "아직 전파가 안 됐나" 하고 몇 번 더 조회해봤습니다.
정리
| 하려는 것 | 공개 API | 필요 조건 |
|---|---|---|
| 앱 목록/상세 조회 | 가능 | 팀 키, 최소 조회 권한 |
| Bundle ID 등록 | 가능 | POST /v1/bundleIds |
| 앱 레코드 생성 | 불가 | 역할 무관, 웹 UI(또는 비공식 세션 인증)만 가능 |
| 새로 만든 앱 조회 | 조건부 | 키 범위 "전체 앱" + 앱 자체 "전체 액세스" 둘 다 필요 |
키를 세 개나 만들어보고서야 확인한 사실이라, 이 글이 같은 삽질을 한 번 줄여주면 좋겠습니다.
함께 읽기
- RN 앱 두 번째 배포: 재사용되는 인증서, 새로 해야 하는 등록몇 주 전에 만든 고객 포털 앱을 반쯤 복사해서 새 RN 앱을 하나 더 만들었습니다. 성경 속 여정을 지도에 그리는 화면 하나를 웹에서 옮겨온 것뿐인데, 옮기고 나니 "인증서까지 그대로 가져다 쓸 수 있나"가 궁금해졌습니다. 결론부터 적으면 절반만 맞았습니다.
- React Native Debug·Release 구성 차이: No script URL provided 가 뜨는 이유앱 두 개를 아이폰에 올려놓고 하나를 열었더니 빨간 화면에 이 문구가 떴습니다.
- Liquid Glass 아이콘 직접 만들기: .icon 파일 구조와 librsvg 함정앱 아이콘 하나 만드는 데 하루를 썼습니다. 열 번 넘게 갈아엎었고 빌드를 네 번 중간에 끊었습니다. 그 과정에서 알아낸 것 중에 검색해도 잘 안 나오는 게 두 개 있어서 적어둡니다.
- Expo 앱을 통째로 복사해 새 프로젝트 만들기: 그대로 가는 파일과 고쳐야 하는 파일성경 지도라는 앱을 만들면서 npx create-expo-app을 치지 않았습니다. 이미 두 스토어에 배포되고 있는 다른 앱의 폴더를 통째로 복사해서 시작했습니다.
- 스토어 이미지 규격 정리: 스크린샷·아이콘·그래픽에 실제로 필요한 것앱 하나를 App Store와 Google Play에 동시에 올리면서 이미지를 몇 개나 만들었는지 세어봤습니다. 스크린샷 12장, 스토어 전용 그래픽 2개, 앱 번들에 들어가는 아이콘 자산 7개. 합쳐서 21개입니다. 코드를 다 짜놓고 시작한 마무리 작업인데 결국 여기에 시간을 더 썼습니다.