MSI 또는 EXE 앱용 Microsoft Store 제출 API
MSI 또는 EXE 앱용 Microsoft Store 제출 API를 사용하여 사용자 또는 조직의 파트너 센터 계정에 대한 MSI 또는 EXE 앱에 대한 제출을 프로그래밍 방식으로 쿼리하고 만듭니다. 이 API는 계정에서 많은 앱을 관리하고 이러한 자산에 대한 제출 프로세스를 자동화하고 최적화하려는 경우 유용합니다. 이 API는 Azure AD(Azure Active Directory)를 사용하여 앱 또는 서비스의 호출을 인증합니다.
다음 단계에서는 Microsoft Store 제출 API를 사용하는 종단 간 프로세스를 설명합니다.
- 모든 필수 조건을 충족하였는지 확인합니다.
- Microsoft Store 제출 API에서 메서드를 호출하기 전에 Azure AD 액세스 토큰을 가져옵니다. 토큰을 가져온 후 만료되기 전에 이 토큰을 Microsoft Store 제출 API에 대한 호출에 사용할 수 있는 시간은 60분입니다. 토큰이 만료된 후 새 토큰을 생성할 수 있습니다.
- MSI 또는 EXE 앱용 Microsoft Store 제출 API를 호출합니다.
1단계: Microsoft Store 제출 API를 사용하기 위한 필수 조건 완료하기
MSI 또는 EXE 앱용 Microsoft Store 제출 API를 호출하기 위한 코드 작성을 시작하기 전에 다음 전제 조건을 완료했는지 확인합니다.
- 사용자(또는 조직)에 Azure AD 디렉터리가 있어야 하며 디렉터리에 대한 전역 관리자 권한이 있어야 합니다. Microsoft 365 또는 Microsoft의 기타 비즈니스 서비스를 사용하고 있다면 Azure AD 디렉터리를 이미 보유하고 있습니다. 그렇지 않은 경우 추가 비용 없이 파트너 센터에서 새 Azure AD 만들기를 할 수 있습니다.
- Azure AD 애플리케이션을 파트너 센터 계정에 연결하고 테넌트 ID, 클라이언트 ID 및 키를 가져와야 합니다. 이러한 값은 Microsoft Store 제출 API 호출에 사용할 Azure AD 액세스 토큰을 가져오는 데 필요합니다.
- Microsoft 스토어 제출 API에서 사용하도록 앱을 준비합니다.
- 앱이 아직 파트너 센터에 없는 경우에는 파트너 센터에 이름을 예약하여 앱 만들기를 해야 합니다. Microsoft Store 제출 API를 사용하여 파트너 센터 앱을 만들 수 없습니다. 파트너 센터에서 앱을 만든 다음, API를 사용하여 앱에 액세스하고 프로그래밍 방식으로 제출을 만들 수 있습니다.
- 이 API를 사용하여 지정된 앱에 대한 제출을 만들기 전에 먼저 연령별 등급 질문에 대한 대답을 포함하여 파트너 센터에서 앱에 대한 한 개의 제출 만들기를 완료해야 합니다. 이렇게 하면 API를 사용하여 프로그래밍 방식으로 이 앱에 대한 새 제출을 만들 수 있습니다.
- 앱 제출을 작성하거나 업데이트할 때 새로운 패키지를 포함해야 하는 경우에서 패키지 세부 정보를 준비합니다.
- 앱 제출을 만들거나 업데이트하는 경우 Store 목록에 대한 스크린샷 또는 이미지를 포함해야 하는 경우 앱 스크린샷과 이미지를 준비합니다.
파트너 센터 계정에 Azure AD 애플리케이션을 연결하는 방법
Microsoft Store 제출 API for MSI 또는 EXE 앱을 사용하려면 먼저 Azure AD 애플리케이션을 Partner Center 계정과 연결하고 애플리케이션의 테넌트 ID와 클라이언트 ID를 검색한 후 키를 생성해야 합니다. Azure AD 애플리케이션은 호출하고자 하는 Microsoft Store 제출 API를 나타내는 앱 또는 서비스입니다. API에 전달하는 Azure AD 액세스 토큰을 가져오려면 테넌트 ID, 클라이언트 ID 및 키가 필요합니다.
참고 항목
이 작업은 한 번만 수행하면 됩니다. 테넌트 ID, 클라이언트 ID 및 키가 있으면 새 Azure AD 액세스 토큰을 만들어야 할 때마다 다시 사용할 수 있습니다.
- 파트너 센터에서 조직의 Azure AD 디렉터리에 조직의 파트너 센터 계정을 연결합니다.
- 다음으로 Partner Center의 Account Settings 섹션에 있는 Users 페이지에서 Partner Center 계정에 대한 제출물에 액세스하는 데 사용할 앱 또는 서비스를 나타내는 Azure AD 애플리케이션을 추가합니다. 이 응용 프로그램에 관리자 역할을 할당해야 합니다. 애플리케이션이 Azure AD 디렉터리에 아직 없는 경우에는 파트너 센터에서 새 Azure AD 애플리케이션 만들기로 진행할 수 있습니다.
- 사용자 페이지로 돌아가서 Azure AD 응용 프로그램의 이름을 클릭하여 응용 프로그램 설정으로 이동하고 테넌트 ID 및 클라이언트 ID 값을 복사합니다.
- 새 키 또는 클라이언트 암호를 추가하려면 다음 지침을 참조하거나 Azure Portal을 통해 앱을 등록하는 지침을 참조하세요.
앱을 등록하려면:
Azure Portal에 로그인합니다.
여러 테넌트에 액세스할 수 있는 경우 위쪽 메뉴의 디렉터리 + 구독 필터 를 사용하여 애플리케이션을 등록하려는 테넌트로 전환합니다.
Azure Active Directory를 검색하고 선택합니다.
Manage(관리)에서 App registrations(앱 등록)를 > 선택합니다. 응용 프로그램을 선택합니다.
인증서 & 비밀 > 클라이언트 비밀 > 새 클라이언트 비밀을 선택합니다.
클라이언트 비밀에 대한 설명을 추가합니다.
암호에 대해 만료를 선택하거나 사용자 지정 수명을 지정합니다.
클라이언트 암호 수명은 2년(24개월) 이하로 제한됩니다. 사용자 지정 수명은 24개월 이상으로 지정할 수 없습니다.
참고 항목
12개월 미만의 만료 값을 설정하는 것이 좋습니다.
추가를 선택합니다.
클라이언트 애플리케이션 코드에서 사용할 비밀 값을 기록합니다. 이 비밀 값은 이 페이지에서 나가면 다시 표시되지 않습니다.
2단계: Azure AD 액세스 토큰 가져오기
MSI 또는 EXE 앱용 Microsoft Store 제출 API에서 메서드를 호출하기 전에 API의 각 메서드의 Authorization 헤더에 전달하는 Azure AD 액세스 토큰을 먼저 얻어야 합니다. 액세스 토큰을 가져온 후 만료되기까지 60분이 걸립니다. 토큰이 만료된 후 API에 대한 추가 호출에서 계속 사용할 수 있도록 토큰을 새로 고칠 수 있습니다.
액세스 토큰을 얻으려면 [Service to Service Calls Using Client Credentials]/azure/active-directory/azuread-dev/v1-oauth2-client-creds-grant-flow)의 지시에 따라 HTTP POST를 https://login.microsoftonline.com/<tenant_id>/oauth2/token 끝점으로 보냅니다. 샘플 요청은 다음과 같습니다.
POST https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded; charset=utf-8
grant_type=client_credentials
&client_id=<your_client_id>
&client_secret=<your_client_secret>
&scope=https://api.store.microsoft.com/.default
POST URI의 tenant_id
값과 client_id
및 client_secret
매개 변수의 경우 이전 섹션의 Partner Center에서 검색한 애플리케이션의 테넌트 ID, 클라이언트 ID 및 키를 지정합니다. Scope 파라미터의 경우 https://api.store.microsoft.com/.default
을 지정해야 합니다.
액세스 토큰이 만료되면 여기에 있는 지침에 따라 새로 고칠 수 있습니다.
C# 또는 Node.js를 사용하여 액세스 토큰을 가져오는 방법을 보여 주는 예제는 MSI 또는 EXE 앱용 Microsoft Store 제출 API에 대한 코드 예제 를 참조하세요.
3단계: Microsoft Store 제출 API 사용하기
Azure AD 액세스 토큰을 가진 후 MSI 또는 EXE 앱용 Microsoft Store 제출 API에서 메서드를 호출할 수 있습니다. API에는 앱에 대한 시나리오로 그룹화된 많은 메서드가 포함되어 있습니다. 제출을 만들거나 업데이트하려면 일반적으로 특정 순서로 여러 메서드를 호출합니다. 각 시나리오 및 각 방법의 구문에 대한 자세한 내용은 다음 섹션을 참조하십시오:
참고 항목
액세스 토큰을 얻은 후 토큰이 만료되기 전에 MSI 또는 EXE 앱용 Microsoft Store 제출 API에서 메서드를 호출하는 데 60분이 걸립니다.
Base URL
EXE 또는 MSI 앱용 Microsoft Store 제출 API의 기본 URL은 다음과 같습니다. https://api.store.microsoft.com
API 계약
현재 초안 제출 메타데이터 API 가져오기
현재 초안 제출에서 각 모듈(목록, 속성 또는 가용성)의 메타데이터를 가져옵니다.
경로 [모든 모듈]: /submission/v1/product/{productId}/metadata?languages={languages}&에 언어 목록={true/false}가 포함되어 있습니다
경로 [단일 모듈]: /submission/v1/product/{productId}/metadata/{moduleName}?languages={languages}&includelanguagelist={true/false}
메서드: GET
경로 매개 변수
매개 변수 | 설명 |
---|---|
productId | 제품의 파트너 센터 ID |
moduleName | 파트너 센터 모듈 – 목록, 속성 또는 가용성 |
쿼리 매개 변수
매개 변수 | 설명 |
---|---|
언어 | 선택 사항 목록 언어는 쉼표로 구분된 문자열[최대 200개 언어 제한]로 필터링됩니다. 없는 경우 사용 가능한 처음 200개 목록 언어 메타데이터가 검색됩니다. [ 예: "en-us, en-gb"]. |
includelanguagelist | 선택적 부울 – true이면 추가된 목록 언어 목록과 해당 완전성 상태 반환합니다. |
필수 헤더
헤더 | 값 |
---|---|
Authorization: Bearer <Token> |
파트너 센터 계정에 등록된 Azure AD 앱 ID |
X-Seller-Account-Id |
파트너 센터 계정의 판매자 ID |
응답 헤더
헤더 | 값 |
---|---|
X-Correlation-ID |
각 요청에 대한 GUID 형식 고유 ID입니다. 이는 문제를 분석하기 위해 지원 팀과 공유할 수 있습니다. |
Retry-After |
속도 제한으로 인해 API를 다시 호출하기 전에 클라이언트가 기다려야 하는 시간(초)입니다. |
응답 매개 변수
속성 | 형식 | 설명 |
---|---|---|
accessibilitySupport | Boolean | |
additionalLicenseTerms | 문자열 | |
사용 가능성 | Object | 가용성 모듈 데이터 |
category | 문자열 | 아래 범주 목록을 참조하세요. |
certificationNotes | 문자열 | |
코드 | 문자열 | 메시지의 오류 코드 |
contactInfo | 문자열 | |
저작권 | 문자열 | |
dependsOnDriversOrNT | Boolean | |
description | 문자열 | |
developedBy | 문자열 | |
발견 가능성 | 문자열 | [DISCOVERABLE, DEEPLINK_ONLY] |
enableInFutureMarkets | Boolean | |
오류 | 개체의 배열 | 오류 또는 경고 메시지(있는 경우) 목록 |
freeTrial | 문자열 | [NO_FREE_TRIAL, FREE_TRIAL] |
hardwareItemType | 문자열 | |
isPrivacyPolicyRequired | Boolean | |
isRecommended | Boolean | |
isRequired | Boolean | |
isSuccess | Boolean | |
isSystemFeatureRequired | 개체의 배열 | |
language | 문자열 | 아래 언어 목록을 참조하세요. |
목록 | 개체의 배열 | 각 언어에 대한 모듈 데이터 나열 |
시장 | 문자열 배열 | 아래 시장 목록을 참조하세요. |
message | 문자열 | 오류에 대한 설명입니다 |
minimumHardware | 문자열 | |
minimumRequirement | 문자열 | |
penAndInkSupport | Boolean | |
가격 책정 | 문자열 | [무료, FREEMIUM, 구독, 지불된] |
privacyPolicyUrl | 문자열 | |
productDeclarations | Object | |
productFeatures | 문자열 배열 | |
속성 | Object | 속성 모듈 데이터 |
recommendedHardware | 문자열 | |
recommendedRequirement | 문자열 | |
responseData | Object | 요청에 대한 실제 응답 페이로드를 포함합니다. |
요구 사항 | 개체의 배열 | |
searchTerms | 문자열 배열 | |
shortDescription | 문자열 | |
하위 범주 | 문자열 | 아래 하위 범주 목록 참조 |
supportContactInfo | 문자열 | |
systemRequirementDetails | 개체의 배열 | |
target | 문자열 | 오류가 발생한 엔터티입니다. |
웹 사이트 | 문자열 | |
whatsNew | 문자열 |
샘플 응답
{
"isSuccess": true,
"errors": [{
"code": "badrequest",
"message": "Error Message 1",
"target": "listings"
}, {
"code": "warning",
"message": "Warning Message 1",
"target": "properties"
}],
"responseData": {
"availability":{
"markets": ["US"],
"discoverability": "DISCOVERABLE",
"enableInFutureMarkets": true,
"pricing": "PAID",
"freeTrial": "NO_FREE_TRIAL"
},
"properties":{
"isPrivacyPolicyRequired": true,
"privacyPolicyUrl": "http://contoso.com",
"website": "http://contoso.com",
"supportContactInfo": "http://contoso.com",
"certificationNotes": "Certification Notes",
"category": "DeveloperTools",
"subcategory": "Database",
"productDeclarations": {
"dependsOnDriversOrNT": false,
"accessibilitySupport": false,
"penAndInkSupport": false
},
"isSystemFeatureRequired": [
{
"isRequired": true,
"isRecommended": false,
"hardwareItemType": "Touch"
},
{
"isRequired": true,
"isRecommended": false,
"hardwareItemType": "Keyboard"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "Mouse"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "Camera"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "NFC_HCE"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "NFC_Proximity"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "Bluetooth_LE"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "Telephony"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "Microphone"
}
],
"systemRequirementDetails": [
{
"minimumRequirement": "1GB",
"recommendedRequirement": "4GB",
"hardwareItemType": "Memory"
},
{
"minimumRequirement": "",
"recommendedRequirement": "",
"hardwareItemType": "DirectX"
},
{
"minimumRequirement": "",
"recommendedRequirement": "",
"hardwareItemType": "Video_Memory"
},
{
"minimumRequirement": "",
"recommendedRequirement": "",
"hardwareItemType": "Processor"
},
{
"minimumRequirement": "",
"recommendedRequirement": "",
"hardwareItemType": "Graphics"
}
]
},
"listings":[{
"language": "en-us",
"description": "Description",
"whatsNew": "What's New",
"productFeatures": ["Feature 1"],
"shortDescription": "Short Description",
"searchTerms": ["Search Ter 1"],
"additionalLicenseTerms": "License Terms",
"copyright": "Copyright Information",
"developedBy": "Developer Details",
"sortTitle": "Product 101",
"requirements": [
{
"minimumHardware": "Pentium4",
"recommendedHardware": "Corei9"
}
],
"contactInfo": "contactus@contoso.com"
}],
"listingLanguages": [{"language":"en-us", "isComplete": true}]
}
}
현재 초안 제출 메타데이터 API 업데이트
초안 제출에서 각 모듈의 메타데이터를 업데이트. API 검사
- 활성 제출의 경우 있는 경우 오류 메시지와 함께 실패합니다.
- 모든 모듈이 준비되면 초안 저장 작업을 허용하도록 상태.
- 제출의 각 필드는 Microsoft Store의 요구 사항에 따라 유효성이 검사됩니다.
- 시스템 요구 사항 세부 정보 유효성 검사 규칙:
- hardwareItemType의 허용되는 값 = 메모리: 300MB, 750MB, 1GB, 2GB, 4GB, 6GB, 8GB, 12GB, 16GB, 20GB
- Allowed Values in hardwareItemType = DirectX: DX9, DX10, DX11, DX12-FEATURELEVEL11, DX12-FEATURELEVEL12
- hardwareItemType에서 허용되는 값 = Video_Memory: 1GB, 2GB, 4GB, 6GB
경로 [전체 모듈 업데이트]: /submission/v1/product/{productId}/metadata
메서드: PUT
경로 [모듈 패치 업데이트]: /제출/v1/제품/{productId}/메타데이터
방법: PATCH
API 동작
전체 모듈 업데이트 API의 경우 모든 필드의 전체 업데이트 요청에 전체 모듈 데이터가 있어야 합니다. 요청에 없는 모든 필드의 기본값은 특정 모듈의 현재 값을 덮어쓰는 데 사용됩니다.
패치 모듈 업데이트 API의 경우 업데이트할 필드만 요청에 있어야 합니다. 요청의 이러한 필드 값은 기존 값을 덮어쓰고 요청에 없는 다른 모든 필드를 특정 모듈의 현재 필드와 동일하게 유지합니다.
경로 매개 변수
매개 변수 | 설명 |
---|---|
productId | 제품의 파트너 센터 ID |
필수 헤더
헤더 | 값 |
---|---|
Authorization: Bearer <Token> |
파트너 센터 계정에 등록된 Azure AD 앱 ID |
X-Seller-Account-Id |
파트너 센터 계정의 판매자 ID |
요청 매개 변수
속성 | 형식 | 설명 |
---|---|---|
사용 가능성 | Object | 가용성 모듈 메타데이터를 보유할 개체 |
시장 | 문자열 배열 | 필수 : 아래 시장 목록을 참조하세요. |
발견 가능성 | 문자열 | 필수 [DISCOVERABLE, DEEPLINK_ONLY] |
enableInFutureMarkets | Boolean | Required |
가격 책정 | 문자열 | 필수 [무료, FREEMIUM, 구독, 유료] |
freeTrial | 문자열 | 가격 책정이 유료이거나 구독 인 경우 필수 [NO_FREE_TRIAL, FREE_TRIAL] |
속성 | Object | 속성 모듈 메타데이터를 저장할 개체 |
isPrivacyPolicyRequired | Boolean | Required |
privacyPolicyUrl | 문자열 | isPrivacyPolicyRequired = true 인 경우 필수 URL이어야 합니다. |
웹 사이트 | 문자열 | 반드시 유효한 URI여야 합니다. |
supportContactInfo | 문자열 | 반드시 유효한 URL 또는 이메일 주소여야 합니다 |
certificationNotes | 문자열 | 권장 문자 제한 = 2000 |
category | 문자열 | 필수 항목은 아래 범주 목록을 참조하세요. |
하위 범주 | 문자열 | 필수 아래 하위 카테고리 목록 참조 |
productDeclarations | Object | Required |
isSystemFeatureRequired | 개체의 배열 | [터치, 키보드, 마우스, 카메라, NFC_HCE, NFC_Proximity, Bluetooth_LE, 전화 통신, 마이크] |
isRequired | Boolean | Required |
isRecommended | Boolean | Required |
hardwareItemType | 문자열 | Required |
systemRequirementDetails | 개체의 배열 | [프로세서, 그래픽, 메모리, DirectX, Video_Memory] |
minimumRequirement | 문자열 | systemRequirementsText에 필요 , MaxLength = 200 하드웨어에서 허용되는 값 ItemType = 메모리: [300MB, 750MB, 1GB, 2GB, 4GB, 6GB, 8GB, 12GB, 16GB, 20GB] Allowed Values in hardwareItemType = DirectX: [DX9, DX10, DX11, DX12-FEATURELEVEL11, DX12-FEATURELEVEL12] Allowed Values in hardwareItemType = Video_Memory: [1GB, 2GB, 4GB, 6GB] |
recommendedRequirement | 문자열 | systemRequirementsText에 필요 , MaxLength = 200 하드웨어에서 허용되는 값 ItemType = 메모리: [300MB, 750MB, 1GB, 2GB, 4GB, 6GB, 8GB, 12GB, 16GB, 20GB] Allowed Values in hardwareItemType = DirectX: [DX9, DX10, DX11, DX12-FEATURELEVEL11, DX12-FEATURELEVEL12] Allowed Values in hardwareItemType = Video_Memory: [1GB, 2GB, 4GB, 6GB] |
dependsOnDriversOrNT | Boolean | Required |
accessibilitySupport | Boolean | Required |
penAndInkSupport | Boolean | Required |
목록 | Object | 단일 언어에 대한 모듈 데이터를 나열하는 개체 |
language | 문자열 | 필수 아래 언어 목록 참조 |
description | 문자열 | 필수 문자 제한 = 10000 |
whatsNew | 문자열 | 문자 제한 = 1500 |
productFeatures | 문자열의 배열 | 기능당 200자; 최대 20개의 기능 |
shortDescription | 문자열 | 문자 제한 = 1000 |
searchTerms | 문자열의 배열 | 검색어당 30자; 최대 7개의 검색어 모든 검색어에서 총 21개의 고유한 단어 합계 |
additionalLicenseTerms | 문자열 | 필수 문자 제한 = 10000 |
저작권 | 문자열 | 문자 제한 = 200 |
developedBy | 문자열 | 문자 제한 = 255 |
요구 사항 | 개체의 배열 | 항목당 200자; 최소 항목과 권장 항목 사이의 최대 11개 항목 합계] |
minimumHardware | 문자열 | 문자 제한 = 200 |
recommendedHardware | 문자열 | 문자 제한 = 200 |
contactInfo | 문자열 | 문자 제한 = 200 |
listingsToAdd | 문자열 배열 | 아래 언어 목록을 참조하세요. |
listingsToRemove | 문자열 배열 | 아래 언어 목록을 참조하세요. |
시장
시장 | 약어 |
---|---|
아프가니스탄 | AF |
알바니아 | AL |
알제리 | DZ |
아메리칸 사모아 | AS |
안도라 | AD |
앙골라 | AO |
앵귈라 | AI |
남극 대륙 | AQ |
앤티가 바부다 | AG |
아르헨티나 | AR |
아르메니아 | AM |
아루바 | AW |
오스트레일리아 | AU |
오스트리아 | AT |
아제르바이잔 | AZ |
바하마 | BS |
바레인 | BH |
방글라데시 | BD |
바베이도스 | BB |
벨로루시 | BY |
벨기에 | BE |
벨리즈 | BZ |
베냉 | BJ |
버뮤다 | BM |
부탄 | BT |
베네수엘라 볼리바르 공화국 | VE |
볼리비아 | BO |
보네르 | Bq |
보스니아 헤르체고비나 | BA |
보츠와나 | BW |
부베섬 | BV |
브라질 | BR |
영국령 인도양 식민지 | IO |
영국령 버진아일랜드 | VG |
브루나이 | BN |
불가리아 | BG |
부르키나파소 | BF |
부룬디 | BI |
캄보디아 | KH |
카메룬 | CM |
캐나다 | CA |
카보베르데 | CV |
케이맨 제도 | KY |
중앙 아프리카 공화국 | CF |
차드 | TD |
칠레 | CL |
중국 | CN |
크리스마스섬 | CX |
코코스 제도 | CC |
콜롬비아 | CO |
코모로 | KM |
콩고 공화국 | CG |
콩고민주공화국 | CD |
쿡 제도 | CK |
코스타리카 | CR |
크로아티아 | HR |
퀴라소 | CW |
키프로스 | CY |
체코 | CZ |
코트디부아르 | CI |
덴마크 | DK |
지부티 | DJ |
도미니카 | DM |
도미니카 공화국 | DO |
에콰도르 | EC |
이집트 | EG |
엘살바도르 | SV |
적도 기니 | GQ |
에리트리아 | ER |
에스토니아 | EE |
에티오피아 | ET |
포클랜드 제도 | FK |
페로 제도 | FO |
피지 | FJ |
핀란드 | FI |
프랑스 | FR |
프랑스령 기아나 | GF |
프랑스령 폴리네시아 | PF |
프랑스령 남부 및 남극 지역 | TF |
가봉 | GA |
감비아 | GM |
조지아 | GE |
독일 | DE |
가나 | GH |
지브롤터 | GI |
그리스 | GR |
그린란드 | GL |
그레나다 | GD |
과들루프 | GP |
괌 | GU |
과테말라 | GT |
건지 | GG |
기니 | GN |
기니비사우 | GW |
가이아나 | GY |
아이티 | HT |
허드 섬 및 맥도널드 제도 | HM |
바티칸 시국 | VA |
온두라스 | HN |
홍콩 특별행정구 | HK |
헝가리 | HU |
아이슬란드 | IS |
인도 | IN |
인도네시아 | ID |
이라크 | IQ |
아일랜드 | IE |
이스라엘 | IL |
이탈리아 | IT |
자메이카 | JM |
일본 | JP |
저지 | JE |
요르단 | JO |
카자흐스탄 | KZ |
케냐 | KE |
키리바시 | KI |
대한민국 | KR |
쿠웨이트 | KW |
키르기스스탄 | KG |
라오스 | LA |
라트비아 | LV |
레바논 | LB |
레소토 | LS |
라이베리아 | LR |
리비아 | LY |
리히텐슈타인 | LI |
리투아니아 | LT |
룩셈부르크 | LU |
마카오 특별행정구 | MO |
북마케도니아 | MK |
마다가스카르 | MG |
말라위 | MW |
말레이시아 | MY |
몰디브 | MV |
말리 | ML |
몰타 | MT |
맨 섬 | IM |
마셜 제도 | MH |
마르티니크 | MQ |
모리타니 | MR |
모리셔스 | MU |
마요트 | YT |
멕시코 | MX |
미크로네시아 | FM |
몰도바 | MD |
모나코 | MC |
몽골 | MN |
몬테네그르 - ME | |
몬트세라트 | MS |
모로코 | MA |
모잠비크 | MZ |
미얀마 | MM |
나미비아 | 해당 없음 |
나우루 | NR |
네팔 | NP |
네덜란드 | NL |
뉴칼레도니아 | NC |
뉴질랜드 | NZ |
니카라과 | NI |
니제르 | NE |
나이지리아 | NG |
니우에 | NU |
노퍽섬 | NF |
북마리아나제도 | MP |
노르웨이 | 아니요 |
오만 | OM |
파키스탄 | PK |
팔라우 | PW |
팔레스타인 자치 정부 | PS |
파나마 | PA |
파푸아뉴기니 | PG |
파라과이 | PY |
페루 | PE |
필리핀 | PH |
핏케언 제도 | PN |
폴란드 | PL |
포르투갈 | PT |
카타르 | QA |
리유니언 | RE |
루마니아 | RO |
러시아 | RU |
르완다 | RW |
세인트 바르텔레미 | BL |
세인트 헬레나, 어센션 및 트리스탄 다 쿠나 | SH |
세인트키츠 네비스 | KN |
세인트루시아 | LC |
세인트 마틴 (프랑스 파트) | Mf |
생피에르앤드미클롱 | PM |
세인트빈센트그레나딘 | VC |
사모아 | WS |
산마리노 | SM |
사우디아라비아 | SA |
세네갈 | SN |
세르비아 | RS |
세이셸 | SC |
시에라리온 | SL |
싱가포르 | SG |
신트마르턴(네덜란드 영역) | SX |
슬로바키아 | SK |
슬로베니아 | SI |
솔로몬 제도 | SB |
소말리아 | SO |
남아프리카 공화국 | ZA |
사우스조지아 사우스샌드위치 제도 | GS |
스페인 | ES |
스리랑카 | LK |
수리남 | SR |
스발바르제도-얀마웬섬 | SJ |
스와질랜드 | SZ |
스웨덴 | SE |
스위스 | CH |
상투메 프린시페 | ST |
대만 | TW |
타지키스탄 | TJ |
탄자니아 | TZ |
태국 | TH |
동티모르(Timor-Leste) | Tl |
Tog - TG | |
토켈라우 | TK |
통가 | TO |
트리니다드 토백 - TT | |
튀니지 | TN |
튀르키예 | TR |
투르크메니스탄 | TM |
터크스 케이커스 제도 | TC |
투발루 | TV |
미국령 외딴섬 | UM |
미국 버진 아일랜드 | VI |
우간다 | UG |
우크라이나 | UA |
아랍에미리트 | AE |
영국 | GB |
미국 | US |
우루과이 | UY |
우즈베키스탄 | UZ |
바누아투 | VU |
베트남 | VN |
왈리스-푸투나 제도 | WF |
예멘 | YE |
잠비아 | ZM |
짐바브웨 | ZW |
올란드 제도 | Dynamics AX |
범주 및 하위 범주
범주 | 하위 범주 |
---|---|
BooksAndReference | EReader, 소설, 논픽션, 참조 |
비즈니스 | AccountingAndfinance, Collaboration, CRM, DataAndAnalytics, FileManagement, InventoryAndlogistics, LegalAndHR, ProjectManagement, RemoteDesktop, SalesAndMarketing, TimeAndExpenses |
개발 도구 | Database, DesignTools, DevelopmentKits, 네트워킹, ReferenceAndTraining, 서버, 유틸리티, WebHosting |
교육 | EducationBooksAndReference, EarlyLearning, InstructionalTools, Language, StudyAids |
Entertainment | (없음) |
FoodAndDining | (없음) |
GovernmentAndPolitics | (없음) |
HealthAndFitness | (없음) |
KidsAndFamily | KidsAndFamilyBooksAndReference, KidsAndFamilyEntertainment, HobbiesAndToys, SportsAndActivities, KidsAndFamilyTravel |
라이프스타일 | 자동차, DYI, HomeAndGarden, 관계, SpecialInterest, StyleAndFashion |
의료 | (없음) |
MultimediaDesign | IllustrationAndGraphicDesign, 음악Production, PhotoAndVideoProduction |
음악 | (없음) |
NavigationAndMaps | (없음) |
NewsAndWeather | 뉴스, 날씨 |
PersonalFinance | BankingAndInvestments, BudgetingAndTaxes |
개인 설정 | 벨소리AndSounds, 테마, WallpaperAndLockScreens |
PhotoAndVideo | (없음) |
생산성 | (없음) |
보안 | PCProtection, PersonalSecurity |
쇼핑 | (없음) |
소셜 | (없음) |
스포츠 | (없음) |
여행 | 시티기데스, 호텔 |
UtilitiesAndTools | BackupAndManage, FileManager |
언어
language_name | 지원되는 언어 코드 |
---|---|
아프리칸스어 | af, af-za |
알바니아어 | sq, sq-al |
암하라어 | am, am-et |
아르메니아 | hy, hy-am |
아삼어 | as, as-in |
아제르바이잔어 | az-arab, az-arab-az, az-cyrl, az-cyrl-az, az-latn, az-latn-az |
바스크어(바스크) | eu, eu-es |
벨로루시어 | be, be-by |
벵골어 | bn, bn-bd, bn-in |
보스니아 헤르체고비나어 | bs, bs-cyrl, bs-cyrl-ba, bs-latn, bs-latn-ba |
불가리아어 | bg, bg-bg |
카탈로니아어 | ca, ca-es, ca-es-valencia |
체로키어 | chr-cher, chr-cher-us, chr-latn |
중국어(간체) | zh-Hans, zh-cn, zh-hans-cn, zh-sg, zh-hans-sg |
중국어(번체) | zh-Hant, zh-hk, zh-mo, zh-tw, zh-hant-hk, zh-hant-mo, zh-hant-tw, zh-mo, zh-tw, zh-hant-hk, zh-hant-mo, zh-hant-tw |
크로아티아어 | hr, hr-hr, hr-ba |
체코어 | cs, cs-cz |
덴마크어 | da, da-dk |
다리어 | prs, prs-af, prs-arab |
네덜란드어 | nl, nl-nl, nl-be |
영어 | en, en-au, en-ca, en-gb, en-ie, en-in, en-nz, en-sg, en-us, en-za, en-bz, en-hk, en-id, en-jm, en-kz, en-mt, en-my, en-ph, en-pk, en-tt, en-vn, en-zw |
에스토니아어 | et, et-ee |
Filipin - fil, fil-latn, fil-ph | |
핀란드어 | fi, fi-fi |
프랑스어 | fr, fr-be , fr-ca , fr-ch , fr-fr , fr-lu, fr-cd, fr-ci, fr-cm, fr-ht, fr-ma, fr-mc, fr-ml, fr-re, frc-latn, frp-latn |
갈리시아어 | gl, gl-es |
그루지야 문자 | ka, ka-ge |
독일어 | de, de-at, de-ch, de-de, de-lu, de-li |
그리스어 | el, el-gr |
구자라트어 | gu, gu-in |
하우사어 | ha, ha-latn, ha-latn-ng |
히브리어 | 그, 허일 |
힌디어 | hi, hi-in |
헝가리어 | hu, hu-hu |
아이슬란드어 | is, is-is |
Igb - ig-latn, ig-ng | |
인도네시아어 | id, id-id |
이누크티투트어 (라틴어) | iu-cans, iu-latn, iu-latn-ca |
아일랜드어 | ga, ga-ie |
코사어 | xh, xh-za |
줄루어 | zu, zu-za |
이탈리아어 | it, it-it, it-ch |
일본어 | ja , ja-jp |
칸나다어 | kn, kn-in |
카자흐어 | kk, kk-kz |
크메르어 | km, km-kh |
키체어 | quc-latn, qut-gt, qut-latn |
키냐르완다어 | rw, rw-rw |
KiSwahili | sw, sw-ke |
코카니어 | kok, kok-in |
한국어 | ko, ko-kr |
쿠르드어 | ku-arab, ku-arab-iq |
키르기스어 | ky-kg, ky-cyrl |
라오스어 | lo, lo-la |
라트비아어 | lv, lv-lv |
리투아니아어 | lt, lt-lt |
룩셈부르크어 | lb, lb-lu |
마케도니아어 | mk, mk-mk |
말레이어 | ms, ms-bn, ms-my |
말라얄람어 | ml, ml-in |
몰타어 | mt, mt-mt |
마오리어 | mi, mi-latn, mi-nz |
마라티어 | mr, mr-in |
몽골어(키릴 자모) | mn-cyrl, mn-mong, mn-mn, mn-phag |
네팔어 | ne, ne-np |
노르웨이어 | nb, nb-no, nn, nn-no, no, no-no |
오디아어 | 또는, 또는-in |
페르시아어 | fa, fa-ir |
폴란드어 | pl, pl-pl |
포르투갈어(브라질) | pt-br |
포르투갈어(포르투갈) | pt, pt-pt |
펀잡어 | pa, pa-arab, pa-arab-pk, pa-deva, pa-in |
케추아어 | quz, quz-bo, quz-ec, quz-pe |
루마니아어 | ro, ro-ro |
러시아어 | ru , ru-ru |
스코틀랜드 게일어 | gd-gb, gd-latn |
세르비아어(라틴 문자) | sr-Latn, sr-latn-cs, sr, sr-latn-ba, sr-latn-me, sr-latn-rs |
세르비아어(키릴 자모) | sr-cyrl, sr-cyrl-ba, sr-cyrl-cs, sr-cyrl-me, sr-cyrl-rs |
Sesotho sa Leboa | nso, nso-za |
세츠와나어 | tn, tn-bw, tn-za |
신디어 | sd-arab, sd-arab-pk, sd-deva |
싱할라어 | si, si-lk |
슬로바키아어 | sk, sk-sk |
슬로베니아어 | sl, sl-si |
스페인어 | es, es-cl, es-co, es-es, es-mx, es-ar, es-bo, es-cr, es-do, es-ec, es-gt, es-hn, es-ni, es-pa, es-pe, es-pr, es-py, es-sv, es-us, es-uy, es-ve |
스웨덴어 | sv, sv-se, sv-fi |
타지크어(키릴 자모) | tg-arab, tg-cyrl, tg-cyrl-tj, tg-latn |
타밀어 | ta, ta-in |
타타르어 | tt-arab, tt-cyrl, tt-latn, tt-ru |
텔루구어 | te, te-in |
태국어 | th, th-th |
티그리냐어 | ti, ti-et |
터키어 | tr, tr-tr |
투르크멘어 | tk-cyrl, tk-latn, tk-tm, tk-latn-tr, tk-cyrl-tr |
우크라이나어 | uk, uk-ua |
우르두어 | your, your-pk |
위구르어 | ug-arab, ug-cn, ug-cyrl, ug-latn |
우즈베크어(라틴 문자) | uz, uz-cyrl, uz-latn, uz-latn-uz |
베트남어 | vi, vi-vn |
웨일스어 | cy, cy-gb |
월라프어 | wo, wo-sn |
요루바어 | yo-latn, yo-ng |
샘플 요청
{
"availability":{
"markets": ["US"],
"discoverability": "DISCOVERABLE",
"enableInFutureMarkets": true,
"pricing": "PAID",
"freeTrial": "NO_FREE_TRIAL"
},
"properties":{
"isPrivacyPolicyRequired": true,
"privacyPolicyUrl": "http://contoso.com",
"website": "http://contoso.com",
"supportContactInfo": "http://contoso.com",
"certificationNotes": "Certification Notes",
"category": "DeveloperTools",
"subcategory": "Database",
"productDeclarations": {
"dependsOnDriversOrNT": false,
"accessibilitySupport": false,
"penAndInkSupport": false
},
"isSystemFeatureRequired": [
{
"isRequired": true,
"isRecommended": false,
"hardwareItemType": "Touch"
},
{
"isRequired": true,
"isRecommended": false,
"hardwareItemType": "Keyboard"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "Mouse"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "Camera"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "NFC_HCE"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "NFC_Proximity"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "Bluetooth_LE"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "Telephony"
},
{
"isRequired": false,
"isRecommended": false,
"hardwareItemType": "Microphone"
}
],
"systemRequirementDetails": [
{
"minimumRequirement": "1GB",
"recommendedRequirement": "4GB",
"hardwareItemType": "Memory"
},
{
"minimumRequirement": "",
"recommendedRequirement": "",
"hardwareItemType": "DirectX"
},
{
"minimumRequirement": "",
"recommendedRequirement": "",
"hardwareItemType": "Video_Memory"
},
{
"minimumRequirement": "",
"recommendedRequirement": "",
"hardwareItemType": "Processor"
},
{
"minimumRequirement": "",
"recommendedRequirement": "",
"hardwareItemType": "Graphics"
}
]
},
"listings":{
"language": "en-us",
"description": "Description",
"whatsNew": "What's New",
"productFeatures": ["Feature 1"],
"shortDescription": "Short Description",
"searchTerms": ["Search Ter 1"],
"additionalLicenseTerms": "License Terms",
"copyright": "Copyright Information",
"developedBy": "Developer Details",
"sortTitle": "Product 101",
"requirements": [
{
"minimumHardware": "Pentium4",
"recommendedHardware": "Corei9"
}
],
"contactInfo": "contactus@contoso.com"
},
"listingsToAdd": ["en-au"],
"listingsToRemove": ["en-gb"]
}
응답 헤더
헤더 | 값 |
---|---|
X-Correlation-ID |
각 요청에 대한 GUID 형식 고유 ID입니다. 이는 문제를 분석하기 위해 지원 팀과 공유할 수 있습니다. |
Retry-After |
속도 제한으로 인해 클라이언트가 API를 다시 호출하기 전에 대기해야 하는 시간(초) |
응답 매개 변수
속성 | 형식 | 설명 |
---|---|---|
isSuccess | Boolean | |
오류 | 개체의 배열 | 오류 또는 경고 메시지(있는 경우) 목록 |
코드 | 문자열 | 메시지의 오류 코드 |
message | 문자열 | 오류에 대한 설명입니다 |
target | 문자열 | 오류가 발생한 엔터티입니다. |
responseData | Object | 요청에 대한 실제 응답 페이로드를 포함합니다. |
pollingUrl | 문자열 | 진행 중인 제출의 상태 가져오기 위한 폴링 URL |
ongoingSubmissionId | 문자열 | 이미 진행 중인 제출의 제출 ID |
샘플 응답
{
"isSuccess": true,
"errors": [{
"code": "badrequest",
"message": "Error Message 1",
"target": "listings"
}, {
"code": "warning",
"message": "Warning Message 1",
"target": "properties"
}],
"responseData": {
"pollingUrl": "/submission/v1/product/{productId}/submission/{submissionId}/status",
"ongoingSubmissionId": ""
}
}
현재 초안 패키지 가져오기 API
현재 초안 제출에서 패키지 세부 정보를 가져옵니다.
경로 [모든 패키지]: /submission/v1/product/{productId}/packages
메서드: GET
경로 [단일 패키지]: /submission/v1/product/{productId}/packages/{packageId}
메서드: GET
경로 매개 변수
속성 | 설명 |
---|---|
productId | 제품의 파트너 센터 ID |
packageId | 가져올 패키지의 고유 ID |
필수 헤더
헤더 | 값 |
---|---|
Authorization: Bearer <Token> |
Partner Center 계정에 등록된 Azure AD 앱 ID 사용 |
X-Seller-Account-Id |
파트너 센터 계정의 판매자 ID |
응답 헤더
헤더 | 값 |
---|---|
X-Correlation-ID |
각 요청에 대한 GUID 형식 고유 ID입니다. 이는 문제를 분석하기 위해 지원 팀과 공유할 수 있습니다. |
Retry-After |
속도 제한으로 인해 API를 다시 호출하기 전에 클라이언트가 기다려야 하는 시간(초)입니다. |
응답 매개 변수
속성 | 형식 | 설명 |
---|---|---|
isSuccess | Boolean | |
오류 | 개체의 배열 | 오류 또는 경고 메시지(있는 경우) 목록 |
코드 | 문자열 | 메시지의 오류 코드 |
message | 문자열 | 오류에 대한 설명입니다 |
target | 문자열 | 오류가 발생한 엔터티입니다. |
responseData | Object | |
패키지 | 개체의 배열 | 패키지 모듈 데이터를 저장할 개체 |
packageId | 문자열 | |
packageUrl | 문자열 | |
언어 | 문자열 배열 | |
아키텍처 | 문자열 배열 | [Neutral, X86, X64, Arm, Arm64] |
isSilentInstall | Boolean | 스위치 또는 false를 요구하지 않고 설치 관리자가 자동 모드로 실행되는 경우 true로 표시되어야 합니다. |
installerParameters | 문자열 | |
genericDocUrl | 문자열 | |
errorDetails | 개체의 배열 | |
errorScenario | 문자열 | |
errorScenarioDetails | 개체의 배열 | |
errorValue | 문자열 | |
errorUrl | 문자열 | |
packageType | 문자열 |
샘플 응답
{
"isSuccess": true,
"errors": [{
"code": "badrequest",
"message": "Error Message 1",
"target": "listings"
}, {
"code": "warning",
"message": "Warning Message 1",
"target": "properties"
}],
"responseData":{
"packages":[{
"packageId": "pack0832",
"packageUrl": "https://www.contoso.com/downloads/1.1/setup.exe",
"languages": ["en-us"],
"architectures": ["X86"],
"isSilentInstall": true,
"installerParameters": "/s",
"genericDocUrl": "https://docs.contoso.com/doclink",
"errorDetails": [{
"errorScenario": "rebootRequired",
"errorScenarioDetails": [{
"errorValue": "ERR001001",
"errorUrl": "https://errors.contoso.com/errors/ERR001001"
}]
}],
"packageType": "exe",
}]
}
}
현재 초안 패키지 API 업데이트
현재 초안 제출에 따라 패키지 세부 정보를 업데이트합니다.
경로 [전체 모듈 업데이트]: /submission/v1/product/{productId}/packages
메서드: PUT
경로 [단일 패키지 패치 업데이트]: /제출/v1/제품/{productId}/패키지/{packageId}
방법: PATCH
API 동작
전체 모듈 업데이트 API의 경우 모든 필드의 전체 업데이트를 요청하려면 전체 패키지 데이터가 있어야 합니다. 요청에 없는 필드는 기본값을 사용하여 해당 특정 모듈의 현재 값을 덮어씁니다. 그러면 요청의 새 패키지 집합으로 모든 기존 패키지를 덮어씁니다. 그러면 패키지 ID가 다시 생성되고 사용자는 최신 패키지 ID에 대해 GET Packages API를 호출해야 합니다.
단일 패키지 패치 업데이트 API의 경우 지정된 패키지에 대해 업데이트해야 하는 필드만 요청에 있어야 합니다. 요청의 이러한 필드 값은 기존 값을 덮어쓰며 요청에 없는 다른 모든 필드는 해당 특정 패키지의 현재 값과 동일하게 유지됩니다. 집합의 다른 패키지는 그대로 다시 기본.
경로 매개 변수
속성 | 설명 |
---|---|
productId | 제품의 파트너 센터 ID |
packageId | 패키지의 고유 ID |
필수 헤더
헤더 | 값 |
---|---|
Authorization: Bearer <Token> |
Partner Center 계정에 등록된 Azure AD 앱 ID 사용 |
X-Seller-Account-Id |
파트너 센터 계정의 판매자 ID |
요청 매개 변수
속성 | 형식 | 설명 |
---|---|---|
패키지 | 객체 배열 | 패키지 모듈 데이터를 저장할 개체 [전체 모듈 업데이트에만 필요] |
packageUrl | 문자열 | Required |
언어 | 문자열 배열 | Required |
아키텍처 | 문자열 배열 | 필수 는 단일 아키텍처를 포함해야 합니다. 중립, X86, X64, Arm, Arm64 |
isSilentInstall | Boolean | 필수 설치 프로그램이 스위치를 필요로 하지 않고 무음 모드로 실행되는 경우 true로 표시하거나 그렇지 않으면 false로 표시해야 합니다 |
installerParameters | 문자열 | isSilentInstall이 false이면 필수입니다. |
genericDocUrl | 문자열 | packageType이 EXE 형식 설치 관리자에 대한 사용자 지정 오류 코드의 세부 정보를 포함하는 문서에 대한 exe 링크인 경우 필수입니다. |
errorDetails | 객체 배열 | EXE 유형 설치 관리자에 대한 사용자 지정 오류 코드 및 세부 정보를 보관하는 메타데이터입니다. |
errorScenario | 문자열 | 특정 오류 시나리오를 식별합니다. [installationCancelledByUser, applicationAlreadyExists, installationAlreadyInProgress, diskSpaceIsFull, rebootRequired, networkFailure, packageRejectedDuringInstallation, installationSuccessful, 기타] |
errorScenarioDetails | 객체 배열 | |
errorValue | 문자열 | 설치 중에 표시될 수 있는 오류 코드 |
errorUrl | 문자열 | 오류에 대한 세부 정보가 있는 URL |
packageType | 문자열 | 필수 [exe, msi] |
샘플 요청 [전체 모듈 업데이트]
{
"packages":[{
"packageUrl": "https://www.contoso.com/downloads/1.1/setup.exe",
"languages": ["en-us"],
"architectures": ["X86"],
"isSilentInstall": true,
"installerParameters": "/s",
"genericDocUrl": "https://docs.contoso.com/doclink",
"errorDetails": [{
"errorScenario": "rebootRequired",
"errorScenarioDetails": [{
"errorValue": "ERR001001",
"errorUrl": "https://errors.contoso.com/errors/ERR001001"
}]
}],
"packageType": "exe",
}]
}
샘플 요청 [단일 패키지 패치 업데이트]
{
"packageUrl": "https://www.contoso.com/downloads/1.1/setup.exe",
"languages": ["en-us"],
"architectures": ["X86"],
"isSilentInstall": true,
"installerParameters": "/s",
"genericDocUrl": "https://docs.contoso.com/doclink",
"errorDetails": [{
"errorScenario": "rebootRequired",
"errorScenarioDetails": [{
"errorValue": "ERR001001",
"errorUrl": "https://errors.contoso.com/errors/ERR001001"
}]
}],
"packageType": "exe",
}
응답 헤더
헤더 | 값 |
---|---|
X-Correlation-ID |
각 요청에 대한 GUID 형식 고유 ID입니다. 이는 문제를 분석하기 위해 지원 팀과 공유할 수 있습니다. |
Retry-After |
속도 제한으로 인해 API를 다시 호출하기 전에 클라이언트가 기다려야 하는 시간(초)입니다. |
응답 매개 변수
속성 | 형식 | 설명 |
---|---|---|
isSuccess | Boolean | |
오류 | 개체의 배열 | [오류 또는 경고 메시지가 있는 경우 목록] |
코드 | 문자열 | 메시지의 오류 코드 |
message | 문자열 | 오류에 대한 설명입니다 |
target | 문자열 | 오류가 발생한 엔터티입니다. |
responseData | Object | |
pollingUrl | 문자열 | [이미 진행 중인 제출의 경우 제출 상태를 가져오기 위한 폴링 URL] |
ongoingSubmissionId | 문자열 | [이미 진행중인 제출물의 제출 ID] |
샘플 응답
{
"isSuccess": true,
"errors": [{
"code": "badrequest",
"message": "Error Message 1",
"target": "listings"
}, {
"code": "warning",
"message": "Warning Message 1",
"target": "properties"
}],
"responseData": {
"pollingUrl": "/submission/v1/product/{productId}/submission/{submissionId}/status",
"ongoingSubmissionId": ""
}
}
패키지 커밋 API
현재 초안 제출에서 패키지 업데이트 API를 사용하여 업데이트된 새 패키지 집합을 커밋합니다. 이 API는 패키지 업로드를 추적하기 위해 폴링 URL을 반환합니다.
경로: /submission/v1/product/{productId}/packages/commit
메서드: POST
경로 매개 변수
속성 | 설명 |
---|---|
productId | 제품의 파트너 센터 ID |
필수 헤더
헤더 | 값 |
---|---|
Authorization: Bearer <Token> |
Partner Center 계정에 등록된 Azure AD App ID 사용 |
X-Seller-Account-Id |
파트너 센터 계정의 판매자 ID |
응답 헤더
헤더 | 값 |
---|---|
X-Correlation-ID |
각 요청에 대한 GUID 형식 고유 ID입니다. 이는 문제를 분석하기 위해 지원 팀과 공유할 수 있습니다. |
Retry-After |
속도 제한으로 인해 API를 다시 호출하기 전에 클라이언트가 기다려야 하는 시간(초)입니다. |
응답 매개 변수
속성 | 형식 | 설명 |
---|---|---|
isSuccess | Boolean | |
오류 | 개체의 배열 | [오류 또는 경고 메시지가 있는 경우 목록] |
코드 | 문자열 | 메시지의 오류 코드 |
message | 문자열 | 오류에 대한 설명입니다 |
target | 문자열 | 오류가 발생한 엔터티입니다. |
responseData | Object | |
pollingUrl | 문자열 | [이미 진행 중인 제출의 경우 패키지 업로드 또는 제출 상태 상태 가져오기 위한 폴링 URL] |
ongoingSubmissionId | 문자열 | [이미 진행중인 제출물의 제출 ID] |
샘플 응답
{
"isSuccess": true,
"errors": [{
"code": "badrequest",
"message": "Error Message 1",
"target": "listings"
}, {
"code": "warning",
"message": "Warning Message 1",
"target": "properties"
}],
"responseData": {
"pollingUrl": "/submission/v1/product/{productId}/status",
"ongoingSubmissionId": ""
}
}
현재 초안 목록 자산 API 가져오기
현재 초안 제출에서 목록 자산 세부 정보를 가져옵니다.
경로: /submission/v1/product/{productId}/listings/assets?languages={languages}
메서드: GET
경로 매개 변수
속성 | 설명 |
---|---|
productId | 제품의 파트너 센터 ID |
쿼리 매개 변수
속성 | 설명 |
---|---|
언어 | [선택사항] 목록 언어는 쉼표로 구분된 문자열로 필터링됩니다 [최대 200개 언어 제한]. 없는 경우 사용 가능한 처음 200개 목록 언어의 자산 데이터가 검색됩니다. (e.g., “en-us, en-gb") |
필수 헤더
헤더 | 값 |
---|---|
Authorization: Bearer <Token> |
Partner Center 계정에 등록된 Azure AD App ID 사용 |
X-Seller-Account-Id |
파트너 센터 계정의 판매자 ID |
응답 헤더
헤더 | 값 |
---|---|
X-Correlation-ID |
각 요청에 대한 GUID 형식 고유 ID입니다. 이는 문제를 분석하기 위해 지원 팀과 공유할 수 있습니다. |
Retry-After |
속도 제한으로 인해 API를 다시 호출하기 전에 클라이언트가 기다려야 하는 시간(초)입니다. |
응답 매개 변수
속성 | 형식 | 설명 |
---|---|---|
isSuccess | Boolean | |
오류 | 개체의 배열 | 오류 또는 경고 메시지(있는 경우) 목록 |
코드 | 문자열 | 메시지의 오류 코드 |
message | 문자열 | 오류에 대한 설명입니다 |
target | 문자열 | 오류가 발생한 엔터티입니다. |
responseData | Object | |
listingAssets | 개체의 배열 | 각 언어에 대한 자산 세부 정보 나열 |
language | 문자열 | |
storeLogos | 개체의 배열 | |
스크린샷 | 개체의 배열 | |
id | 문자열 | |
assetUrl | 문자열 | 반드시 유효한 URI여야 합니다. |
imageSize | Object | |
width | 정수 | |
높이 | 정수 |
샘플 응답
{
"isSuccess": true,
"errors": [{
"code": "badrequest",
"message": "Error Message 1",
"target": "listings"
}, {
"code": "warning",
"message": "Warning Message 1",
"target": "properties"
}],
"responseData":{
"listingAssets": [{
"language": "en-us",
"storeLogos": [
{
"id": "1234567890abcdefgh",
"assetUrl": "https://contoso.com/blob=1234567890abcdefgh",
"imageSize": {
"width": 2160,
"height": 2160
}
}
],
"screenshots": [
{
"id": "1234567891abcdefgh",
"assetUrl": "https://contoso.com/blob=1234567891abcdefgh",
"imageSize": {
"width": 2160,
"height": 2160
}
}
]
}]
}
}
목록 자산 API 만들기
현재 초안 제출에서 새 목록 자산 업로드를 만듭니다.
자산 나열 업데이트
EXE 또는 MSI 앱용 Microsoft Store 제출 API는 업로드가 성공한 후 커밋 API 호출과 함께 각 개별 이미지 자산 업로드에 대해 Blob Store에 런타임 생성 SAS URL을 사용합니다. 목록 자산을 업데이트하고 목록 모듈에서 로캘을 추가/제거할 수 있도록 하기 위해 다음 방법을 사용할 수 있습니다.
- 목록 자산 만들기 API를 사용하여 자산의 언어, 유형 및 수와 함께 자산 업로드에 대한 요청을 보냅니다.
- 요청된 자산 수에 따라 자산 ID는 요청 시 생성되며 단기 SAS URL을 만들고 자산 유형에 따라 응답 본문으로 다시 보냅니다. 이 URL을 사용하여 HTTP 클라이언트 [Put Blob(REST API) - Azure Storage |를 사용하여 특정 유형의 이미지 자산을 업로드할 수 있습니다. Microsoft Docs].
- 업로드한 후 커밋 목록 자산 API를 사용하여 이전 API 호출에서 이전에 받은 새 자산 ID 정보도 보낼 수 있습니다. 단일 API는 유효성 검사 후 목록 자산 데이터를 내부적으로 커밋합니다.
- 이 방법은 요청에서 전송되는 특정 언어로 자산 형식의 이전 이미지의 전체 집합을 효과적으로 덮어씁 수 있습니다. 따라서 이전에 업로드한 자산이 제거됩니다.
경로: /submission/v1/product/{productId}/listings/assets/create
메서드: POST
경로 매개 변수
속성 | 설명 |
---|---|
productId | 제품의 파트너 센터 ID |
필수 헤더
헤더 | 설명 |
---|---|
Authorization: Bearer <Token> |
Partner Center 계정에 등록된 Azure AD App ID 사용 |
X-Seller-Account-Id |
파트너 센터 계정의 판매자 ID |
요청 매개 변수
속성 | 형식 | 설명 |
---|---|---|
언어 | 문자열 | Required |
createAssetRequest | Object | Required |
스크린샷 | 정수 | ISV가 스크린샷을 업데이트하거나 새 목록 언어 를 추가해야 하는 경우 필요 [1 - 10] |
로고 | 정수 | ISV가 로고를 업데이트하거나 새로운 목록 언어를 추가해야 하는 경우 필요합니다 [1 또는 2] |
응답 헤더
헤더 | 설명 |
---|---|
X-Correlation-ID |
각 요청에 대한 GUID 형식 고유 ID입니다. 이는 문제를 분석하기 위해 지원 팀과 공유할 수 있습니다. |
Retry-After |
속도 제한으로 인해 API를 다시 호출하기 전에 클라이언트가 기다려야 하는 시간(초)입니다. |
응답 매개 변수
속성 | 형식 | 설명 |
---|---|---|
isSuccess | Boolean | |
오류 | 개체의 배열 | 오류 또는 경고 메시지(있는 경우) 목록 |
코드 | 문자열 | 메시지의 오류 코드 |
message | 문자열 | 오류에 대한 설명입니다 |
target | 문자열 | 오류가 발생한 엔터티입니다. |
responseData | Object | |
listingAssets | Object | 업로드할 StoreLogos 및 스크린샷의 세부 정보가 포함된 개체 |
language | 문자열 | |
storeLogos | 개체의 배열 | |
스크린샷 | 개체의 배열 | |
id | 문자열 | |
primaryAssetUploadUrl | 문자열 | Azure Blob REST API를 사용하여 목록 자산을 업로드하는 기본 URL |
secondaryAssetUploadUrl | 문자열 | Azure Blob REST API를 사용하여 상장 자산을 업로드하는 보조 URL |
httpMethod | HTTP 메서드 | 자산 업로드 URL (기본 또는 보조)을 통해 자산을 업로드하는 데 필요한 HTTP 메서드 |
httpHeaders | Object | 자산 업로드 URL에 대한 업로드 API 호출에 필요한 헤더로 키가 있는 개체입니다. 값이 비어있지 않은 경우 헤더에 특정 값이 있어야 합니다. 그렇지 않으면 API 호출 중에 값이 계산됩니다. |
샘플 응답
{
"isSuccess": true,
"errors": [{
"code": "badrequest",
"message": "Error Message 1",
"target": "listings"
}, {
"code": "warning",
"message": "Warning Message 1",
"target": "properties"
}],
"responseData": {
"listingAssets": {
"language": "en-us",
"storeLogos":[{
"id": "1234567890abcdefgh",
"primaryAssetUploadUrl": "https://contoso.com/upload?blob=1234567890abcdefgh&sig=12345",
"secondaryAssetUploadUrl": "https://contoso.com/upload?blob=0987654321abcdfger&sig=54326",
"httpMethod": "PUT",
"httpHeaders": {"Required Header Name": "Header Value"}
}],
"screenshots":[{
"id": "0987654321abcdfger",
"primaryAssetUploadUrl": "https://contoso.com/upload?blob=0987654321abcdfger&sig=54321",
"secondaryAssetUploadUrl": "https://contoso.com/upload?blob=0987654321abcdfger&sig=54322",
"httpMethod": "PUT",
"httpHeaders": {"Required Header Name": "Header Value"}
}]
}
}
}
목록 자산 API 커밋
현재 초안 제출에서 자산 만들기 API의 세부 정보를 사용하여 업로드된 새 목록 자산을 커밋합니다.
경로: /submission/v1/product/{productId}/listings/assets/commit
메서드: PUT
경로 매개 변수
속성 | 설명 |
---|---|
productId | 제품의 파트너 센터 ID |
필수 헤더
헤더 | 설명 |
---|---|
Authorization: Bearer <Token> |
Partner Center 계정에 등록된 Azure AD App ID 사용 |
X-Seller-Account-Id |
파트너 센터 계정의 판매자 ID |
요청 매개 변수
속성 | 형식 | 설명 |
---|---|---|
listingAssets | Object | |
language | 문자열 | |
storeLogos | 개체 배열 | |
스크린샷 | 개체 배열 | |
id | 문자열 | 현재 목록 자산 가져오기 API에서 유지하려는 기존 ID 또는 목록 자산 만들기 API에서 새 자산이 업로드된 새 ID여야 합니다. |
assetUrl | 문자열 | 사용자가 현재 목록 자산 가져오기 API에서 유지하려는 기존 자산의 URL이거나 새 자산이 목록 자산 만들기 API에서 업로드된 것을 사용하는 업로드 URL(기본 또는 보조)이어야 합니다. 반드시 유효한 URI여야 합니다. |
샘플 요청
{
"listingAssets": {
"language": "en-us",
"storeLogos": [
{
"id": "1234567890abcdefgh",
"assetUrl": "https://contoso.com/blob=1234567890abcdefgh",
}
],
"screenshots": [
{
"id": "1234567891abcdefgh",
"assetUrl": "https://contoso.com/blob=1234567891abcdefgh",
}
]
}
}
응답 헤더
헤더 | 설명 |
---|---|
X-Correlation-ID |
각 요청에 대한 GUID 형식 고유 ID입니다. 이는 문제를 분석하기 위해 지원 팀과 공유할 수 있습니다. |
Retry-After |
속도 제한으로 인해 API를 다시 호출하기 전에 클라이언트가 기다려야 하는 시간(초)입니다. |
응답 매개 변수
속성 | 형식 | 설명 |
---|---|---|
isSuccess | Boolean | |
오류 | 개체의 배열 | 오류 또는 경고 메시지(있는 경우) 목록 |
코드 | 문자열 | 메시지의 오류 코드 |
message | 문자열 | 오류에 대한 설명입니다 |
target | 문자열 | 오류가 발생한 엔터티입니다. |
responseData | Object | |
pollingUrl | 문자열 | 진행 중인 제출의 상태를 가져오는 폴링 URL |
ongoingSubmissionId | 문자열 | 이미 진행중인 제출물의 제출 ID |
샘플 응답
{
"isSuccess": true,
"errors": [{
"code": "badrequest",
"message": "Error Message 1",
"target": "listings"
}, {
"code": "warning",
"message": "Warning Message 1",
"target": "properties"
}],
"responseData": {
"pollingUrl": "/submission/v1/product/{productId}/submission/{submissionId}/status",
"ongoingSubmissionId": ""
}
}
모듈 상태 폴링 API
제출을 만들기 전에 모듈 준비 상태를 검사 API입니다. 또한 패키지 업로드 상태 유효성을 검사합니다.
경로: /submission/v1/product/{productId}/상태
메서드: GET
경로 매개 변수
속성 | 설명 |
---|---|
productId | 제품의 파트너 센터 ID |
필수 헤더
헤더 | 설명 |
---|---|
Authorization: Bearer <Token> |
Partner Center 계정에 등록된 Azure AD App ID 사용 |
X-Seller-Account-Id |
파트너 센터 계정의 판매자 ID |
응답 헤더
헤더 | 설명 |
---|---|
X-Correlation-ID |
각 요청에 대한 GUID 형식 고유 ID입니다. 이는 문제를 분석하기 위해 지원 팀과 공유할 수 있습니다. |
Retry-After |
속도 제한으로 인해 API를 다시 호출하기 전에 클라이언트가 기다려야 하는 시간(초)입니다. |
응답 매개 변수
속성 | 형식 | 설명 |
---|---|---|
isSuccess | Boolean | |
오류 | 개체의 배열 | 오류 또는 경고 메시지(있는 경우) 목록 |
코드 | 문자열 | 메시지의 오류 코드 |
message | 문자열 | 오류에 대한 설명입니다 |
target | 문자열 | 오류가 발생한 엔터티입니다. |
responseData | Object | |
isReady | Boolean | 패키지 업로드를 포함하여 모든 모듈이 준비 상태인지를 나타냅니다. |
ongoingSubmissionId | 문자열 | 이미 진행중인 제출물의 제출 ID |
샘플 응답
{
"isSuccess": true,
"errors": [{
"code": "badrequest",
"message": "Error Message 1",
"target": "listings"
}, {
"code": "warning",
"message": "Warning Message 1",
"target": "properties"
}],
"responseData": {
"isReady": true,
"ongoingSubmissionId": ""
}
}
제출 API 만들기
MSI 또는 EXE 앱에 대한 현재 초안에서 제출을 만듭니다. API는 다음을 확인합니다:
- 활성 제출의 경우 활성 제출이 있는 경우 오류 메시지와 함께 실패합니다.
- 모든 모듈이 제출을 만들 준비가 되었는지 상태.
- 제출물의 각 필드는 스토어의 요구 사항에 따라 검증됩니다
경로:/submission/v1/product/{productId}/제출
메서드: POST
경로 매개 변수
속성 | 설명 |
---|---|
productId | 제품의 파트너 센터 ID |
필수 헤더
헤더 | 설명 |
---|---|
Authorization: Bearer <Token> |
Partner Center 계정에 등록된 Azure AD App ID 사용 |
X-Seller-Account-Id |
파트너 센터 계정의 판매자 ID |
응답 헤더
헤더 | 설명 |
---|---|
X-Correlation-ID |
각 요청에 대한 GUID 형식 고유 ID입니다. 이는 문제를 분석하기 위해 지원 팀과 공유할 수 있습니다. |
Retry-After |
속도 제한으로 인해 API를 다시 호출하기 전에 클라이언트가 기다려야 하는 시간(초)입니다. |
응답 매개 변수
속성 | 형식 | 설명 |
---|---|---|
isSuccess | Boolean | |
오류 | 개체의 배열 | 오류 또는 경고 메시지(있는 경우) 목록 |
코드 | 문자열 | 메시지의 오류 코드 |
message | 문자열 | 오류에 대한 설명입니다 |
target | 문자열 | 오류가 발생한 엔터티입니다. |
responseData | Object | |
pollingUrl | 문자열 | 제출용 패키지 업로드를 포함하여 모듈 준비 상태 가져오기 위한 폴링 URL |
submissionId | 문자열 | 새로 만든 제출의 ID |
ongoingSubmissionId | 문자열 | 이미 진행중인 제출물의 제출 ID |
샘플 응답
{
"isSuccess": true,
"errors": [{
"code": "badrequest",
"message": "Error Message 1",
"target": "listings"
}, {
"code": "warning",
"message": "Warning Message 1",
"target": "properties"
}],
"responseData": {
"submissionId": "1234567890",
"pollingUrl": "/submission/v1/product/{productId}/submission/{submissionId}/status",
"ongoingSubmissionId": ""
}
}
제출 상태 폴링 API
제출 상태를 검사 API입니다.
경로: /submission/v1/product/{productId}/submission/{submissionId}/상태
메서드: GET
경로 매개 변수
속성 | 설명 |
---|---|
productId | 제품의 파트너 센터 ID |
필수 헤더
헤더 | 설명 |
---|---|
Authorization: Bearer <Token> |
Partner Center 계정에 등록된 Azure AD App ID 사용 |
X-Seller-Account-Id |
파트너 센터 계정의 판매자 ID |
응답 헤더
헤더 | 설명 |
---|---|
X-Correlation-ID |
각 요청에 대한 GUID 형식 고유 ID입니다. 이는 문제를 분석하기 위해 지원 팀과 공유할 수 있습니다. |
Retry-After |
속도 제한으로 인해 API를 다시 호출하기 전에 클라이언트가 기다려야 하는 시간(초)입니다. |
응답 매개 변수
속성 | 형식 | 설명 |
---|---|---|
isSuccess | Boolean | |
오류 | 개체의 배열 | 오류 또는 경고 메시지(있는 경우) 목록 |
코드 | 문자열 | 메시지의 오류 코드 |
message | 문자열 | 오류에 대한 설명입니다 |
target | 문자열 | 오류가 발생한 엔터티입니다. |
responseData | Object | |
publishingStatus | 문자열 | 제출 게시 상태 - [INPROGRESS, PUBLISHED, FAILED, UNKNOWN] |
hasFailed | Boolean | 게시가 실패했으며 다시 시도되지 않음을 나타냅니다. |
샘플 응답
{
"isSuccess": true,
"errors": [{
"code": "badrequest",
"message": "Error Message 1",
"target": "listings"
}, {
"code": "warning",
"message": "Warning Message 1",
"target": "properties"
}],
"responseData": {
"publishingStatus": "INPROGRESS",
"hasFailed": false
}
}
코드 예제
다음 문서에서는 다양한 프로그래밍 언어에서 Microsoft Store 제출 API를 사용하는 방법을 보여주는 자세한 코드 예제를 제공합니다:
C# 샘플: MSI 또는 EXE 앱용 Microsoft Store Submission API
이 문서에서는 MSI 또는 EXE 앱에 Microsoft Store 제출 API를 사용하는 방법을 설명하는 C# 코드 예제를 제공합니다. 각 예시를 검토하여 예시에서 보여 주는 작업에 대해 자세히 알아보거나 이 문서의 모든 코드 예시를 콘솔 애플리케이션으로 빌드할 수 있습니다.
필수 구성 요소 다음 예제에서는 다음 라이브러리를 사용합니다.
- Newtonsoft의 Newtonsoft.Json NuGet 패키지.
메인 프로그램 다음 예제는 Microsoft Store 제출 API를 사용하는 다양한 방법을 설명하기 위해 이 문서의 다른 예제 방법을 호출하는 명령줄 프로그램을 구현합니다. 다음을 따라 이 프로그램을 자신의 용도에 맞게 조정합니다.
- 파트너 센터 계정의 판매자 ID에 SellerId 속성을 할당합니다.
- 관리하려는 앱의 ID에 ApplicationId 속성을 할당합니다.
- ClientId 및 ClientSecret 속성을 앱의 클라이언트 ID 및 키에 할당하고 TokenEndpoint URL의 tenantid 문자열을 앱의 tenantID로 바꿉니다. 자세한 정보는 Azure AD 애플리케이션을 파트너 센터 계정과 연결하는 방법을 참조하세요.
using System;
using System.Threading.Tasks;
namespace Win32SubmissionApiCSharpSample
{
public class Program
{
static async Task Main(string[] args)
{
var config = new ClientConfiguration()
{
ApplicationId = "...",
ClientId = "...",
ClientSecret = "...",
Scope = "https://api.store.microsoft.com/.default",
ServiceUrl = "https://api.store.microsoft.com",
TokenEndpoint = "...",
SellerId = 0
};
await new AppSubmissionUpdateSample(config).RunAppSubmissionUpdateSample();
}
}
}
C#을 사용하는 ClientConfiguration 도우미 클래스
샘플 앱은 ClientConfiguration 도우미 클래스를 사용하여 Azure Active Directory 데이터와 앱 데이터를 Microsoft Store 제출 API를 사용하는 각 예제 메서드에 전달합니다.
using System;
using System.Collections.Generic;
using System.Text;
namespace Win32SubmissionApiCSharpSample
{
public class ClientConfiguration
{
/// <summary>
/// Client Id of your Azure Active Directory app.
/// Example" 00001111-aaaa-2222-bbbb-3333cccc4444
/// </summary>
public string ClientId { get; set; }
/// <summary>
/// Client secret of your Azure Active Directory app
/// </summary>
public string ClientSecret { get; set; }
/// <summary>
/// Service root endpoint.
/// Example: "https://api.store.microsoft.com"
/// </summary>
public string ServiceUrl { get; set; }
/// <summary>
/// Token endpoint to which the request is to be made. Specific to your Azure Active Directory app
/// Example: https://login.microsoftonline.com/d454d300-128e-2d81-334a-27d9b2baf002/oauth2/v2.0/token
/// </summary>
public string TokenEndpoint { get; set; }
/// <summary>
/// Resource scope. If not provided (set to null), default one is used for the production API
/// endpoint ("https://api.store.microsoft.com/.default")
/// </summary>
public string Scope { get; set; }
/// <summary>
/// Partner Center Application ID.
/// Example: 3e31a9f9-84e8-4d2d-9eba-487878d02ebf
/// </summary>
public string ApplicationId { get; set; }
/// <summary>
/// The Partner Center Seller Id
/// Example: 123456892
/// </summary>
public int SellerId { get; set; }
}
}
C를 사용하여 앱 제출 만들기Create an app submission using C#
다음 예제는 Microsoft Store 제출 API에서 여러 메서드를 사용하여 앱 제출을 업데이트하는 클래스를 구현합니다.
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace Win32SubmissionApiCSharpSample
{
public class AppSubmissionUpdateSample
{
private ClientConfiguration ClientConfig;
/// <summary>
/// Constructor
/// </summary>
/// <param name="configuration">An instance of ClientConfiguration that contains all parameters populated</param>
public AppSubmissionUpdateSample(ClientConfiguration configuration)
{
this.ClientConfig = configuration;
}
/// <summary>
/// Main method to Run the Sample Application
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public async Task RunAppSubmissionUpdateSample()
{
// **********************
// SETTINGS
// **********************
var appId = this.ClientConfig.ApplicationId;
var clientId = this.ClientConfig.ClientId;
var clientSecret = this.ClientConfig.ClientSecret;
var serviceEndpoint = this.ClientConfig.ServiceUrl;
var tokenEndpoint = this.ClientConfig.TokenEndpoint;
var scope = this.ClientConfig.Scope;
// Get authorization token.
Console.WriteLine("Getting authorization token");
var accessToken = await SubmissionClient.GetClientCredentialAccessToken(
tokenEndpoint,
clientId,
clientSecret,
scope);
var client = new SubmissionClient(accessToken, serviceEndpoint);
client.DefaultHeaders = new Dictionary<string, string>()
{
{"X-Seller-Account-Id", this.ClientConfig.SellerId.ToString() }
};
Console.WriteLine("Getting Current Application Draft Status");
dynamic AppDraftStatus = await client.Invoke<dynamic>(HttpMethod.Get, string.Format(SubmissionClient.ProductDraftStatusPollingUrlTemplate,
SubmissionClient.Version, appId), null);
Console.WriteLine(AppDraftStatus.ToString());
Console.WriteLine("Getting Application Packages ");
dynamic PackagesResponse = await client.Invoke<dynamic>(HttpMethod.Get, string.Format(SubmissionClient.PackagesUrlTemplate,
SubmissionClient.Version, appId), null);
Console.WriteLine(PackagesResponse.ToString());
Console.WriteLine("Getting Single Package");
dynamic SinglePackageResponse = await client.Invoke<dynamic>(HttpMethod.Get, string.Format(SubmissionClient.PackageByIdUrlTemplate,
SubmissionClient.Version, appId, (string)PackagesResponse.responseData.packages[0].packageId), null);
Console.WriteLine(SinglePackageResponse.ToString());
Console.WriteLine("Updating Entire Package Set");
// Update data in Packages list to have final set of updated Packages
// Example - Updating Installer Parameters
PackagesResponse.responseData.packages[0].installerParameters = "/s /r new-args";
dynamic PackagesUpdateRequest = new
{
packages = PackagesResponse.responseData.packages
};
dynamic PackagesUpdateResponse = await client.Invoke<dynamic>(HttpMethod.Put, string.Format(SubmissionClient.PackagesUrlTemplate,
SubmissionClient.Version, appId), PackagesUpdateRequest);
Console.WriteLine(PackagesUpdateResponse.ToString());
Console.WriteLine("Updating Single Package's Download Url");
// Update data in the SinglePackage object
var SinglePackageUpdateRequest = SinglePackageResponse.responseData.packages[0];
// Example - Updating Installer Parameters
SinglePackageUpdateRequest.installerParameters = "/s /r /t new-args";
dynamic PackageUpdateResponse = await client.Invoke<dynamic>(HttpMethod.Patch, string.Format(SubmissionClient.PackageByIdUrlTemplate,
SubmissionClient.Version, appId, SinglePackageUpdateRequest.packageId), SinglePackageUpdateRequest);
Console.WriteLine("Committing Packages");
dynamic PackageCommitResponse = await client.Invoke<dynamic>(HttpMethod.Post, string.Format(SubmissionClient.PackagesCommitUrlTemplate,
SubmissionClient.Version, appId), null);
Console.WriteLine(PackageCommitResponse.ToString());
Console.WriteLine("Polling Package Upload Status");
AppDraftStatus = await client.Invoke<dynamic>(HttpMethod.Get, string.Format(SubmissionClient.ProductDraftStatusPollingUrlTemplate,
SubmissionClient.Version, appId), null);
while (!((bool)AppDraftStatus.responseData.isReady))
{
AppDraftStatus = await client.Invoke<dynamic>(HttpMethod.Get, string.Format(SubmissionClient.ProductDraftStatusPollingUrlTemplate,
SubmissionClient.Version, appId), null);
Console.WriteLine("Waiting for Upload to finish");
await Task.Delay(TimeSpan.FromSeconds(2));
if(AppDraftStatus.errors != null && AppDraftStatus.errors.Count > 0)
{
for(var index = 0; index < AppDraftStatus.errors.Count; index++)
{
if(AppDraftStatus.errors[index].code == "packageuploaderror")
{
throw new InvalidOperationException("Package Upload Failed. Please try committing packages again.");
}
}
}
}
Console.WriteLine("Getting Application Metadata - All Modules");
dynamic AppMetadata = await client.Invoke<dynamic>(HttpMethod.Get, string.Format(SubmissionClient.AppMetadataUrlTemplate,
SubmissionClient.Version, appId), null);
Console.WriteLine(AppMetadata.ToString());
Console.WriteLine("Getting Application Metadata - Listings");
dynamic AppListingsMetadata = await client.Invoke<dynamic>(HttpMethod.Get, string.Format(SubmissionClient.AppListingsFetchMetadataUrlTemplate,
SubmissionClient.Version, appId), null);
Console.WriteLine(AppListingsMetadata.ToString());
Console.WriteLine("Updating Listings Metadata - Description");
// Update Required Fields in Listings Metadata Object - Per Language. For eg. AppListingsMetadata.responseData.listings[0]
// Example - Updating Description
AppListingsMetadata.responseData.listings[0].description = "New Description Updated By C# Sample Code";
dynamic ListingsUpdateRequest = new
{
listings = AppListingsMetadata.responseData.listings[0]
};
dynamic UpdateListingsMetadataResponse = await client.Invoke<dynamic>(HttpMethod.Put, string.Format(SubmissionClient.AppMetadataUrlTemplate,
SubmissionClient.Version, appId), ListingsUpdateRequest);
Console.WriteLine(UpdateListingsMetadataResponse.ToString());
Console.WriteLine("Getting All Listings Assets");
dynamic ListingAssets = await client.Invoke<dynamic>(HttpMethod.Get, string.Format(SubmissionClient.ListingAssetsUrlTemplate,
SubmissionClient.Version, appId), null);
Console.WriteLine(ListingAssets.ToString());
Console.WriteLine("Creating Listing Assets for 1 Screenshot");
dynamic AssetCreateRequest = new
{
language = ListingAssets.responseData.listingAssets[0].language,
createAssetRequest = new Dictionary<string, int>()
{
{"Screenshot", 1 },
{"Logo", 0 }
}
};
dynamic AssetCreateResponse = await client.Invoke<dynamic>(HttpMethod.Post, string.Format(SubmissionClient.ListingAssetsCreateUrlTemplate,
SubmissionClient.Version, appId), AssetCreateRequest);
Console.WriteLine(AssetCreateResponse.ToString());
Console.WriteLine("Uploading Listing Assets");
// Path to PNG File to be Uploaded as Screenshot / Logo
var PathToFile = "./Image.png";
var AssetToUpload = File.OpenRead(PathToFile);
await client.UploadAsset(AssetCreateResponse.responseData.listingAssets.screenshots[0].primaryAssetUploadUrl.Value as string, AssetToUpload);
Console.WriteLine("Committing Listing Assets");
dynamic AssetCommitRequest = new
{
listingAssets = new
{
language = ListingAssets.responseData.listingAssets[0].language,
storeLogos = ListingAssets.responseData.listingAssets[0].storeLogos,
screenshots = JToken.FromObject(new List<dynamic>() { new
{
id = AssetCreateResponse.responseData.listingAssets.screenshots[0].id.Value as string,
assetUrl = AssetCreateResponse.responseData.listingAssets.screenshots[0].primaryAssetUploadUrl.Value as string
}
}.ToArray())
}
};
dynamic AssetCommitResponse = await client.Invoke<dynamic>(HttpMethod.Put, string.Format(SubmissionClient.ListingAssetsCommitUrlTemplate,
SubmissionClient.Version, appId), AssetCommitRequest);
Console.WriteLine(AssetCommitResponse.ToString());
Console.WriteLine("Getting Current Application Draft Status before Submission");
AppDraftStatus = await client.Invoke<dynamic>(HttpMethod.Get, string.Format(SubmissionClient.ProductDraftStatusPollingUrlTemplate,
SubmissionClient.Version, appId), null);
Console.WriteLine(AppDraftStatus.ToString());
if (AppDraftStatus == null || !((bool)AppDraftStatus.responseData.isReady))
{
throw new InvalidOperationException("Application Current Status is not in Ready Status for All Modules");
}
Console.WriteLine("Creating Submission");
dynamic SubmissionCreationResponse = await client.Invoke<dynamic>(HttpMethod.Post, string.Format(SubmissionClient.CreateSubmissionUrlTemplate,
SubmissionClient.Version, appId), null);
Console.WriteLine(SubmissionCreationResponse.ToString());
Console.WriteLine("Current Submission Status");
dynamic SubmissionStatus = await client.Invoke<dynamic>(HttpMethod.Get, string.Format(SubmissionClient.SubmissionStatusPollingUrlTemplate,
SubmissionClient.Version, appId, SubmissionCreationResponse.responseData.submissionId.Value as string), null);
Console.Write(SubmissionStatus.ToString());
// User can Poll on this API to know if Submission Status is INPROGRESS, PUBLISHED or FAILED.
// This Process involves File Scanning, App Certification and Publishing and can take more than a day.
}
}
}
C를 사용하는 IngestionClient 도우미 클래스#
Ingestion Client 클래스는 샘플 앱의 다른 방법에서 다음 작업을 수행하는 데 사용되는 도우미 방법을 제공합니다:
- Microsoft Store 제출 API에서 메서드를 호출하는 데 사용할 수 있는 Azure AD 액세스 토큰을 가져옵니다. 토큰을 가져온 후 만료되기 전에 이 토큰을 Microsoft Store 제출 API에 대한 호출에 사용할 수 있는 시간은 60분입니다. 토큰이 만료된 후 새 토큰을 생성할 수 있습니다.
- Microsoft Store 제출 API에 대한 HTTP 요청을 처리합니다.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace Win32SubmissionApiCSharpSample
{
/// <summary>
/// This class is a proxy that abstracts the functionality of the API service
/// </summary>
public class SubmissionClient : IDisposable
{
public static readonly string Version = "1";
private HttpClient httpClient;
private HttpClient imageUploadClient;
private readonly string accessToken;
public static readonly string PackagesUrlTemplate = "/submission/v{0}/product/{1}/packages";
public static readonly string PackageByIdUrlTemplate = "/submission/v{0}/product/{1}/packages/{2}";
public static readonly string PackagesCommitUrlTemplate = "/submission/v{0}/product/{1}/packages/commit";
public static readonly string AppMetadataUrlTemplate = "/submission/v{0}/product/{1}/metadata";
public static readonly string AppListingsFetchMetadataUrlTemplate = "/submission/v{0}/product/{1}/metadata/listings";
public static readonly string ListingAssetsUrlTemplate = "/submission/v{0}/product/{1}/listings/assets";
public static readonly string ListingAssetsCreateUrlTemplate = "/submission/v{0}/product/{1}/listings/assets/create";
public static readonly string ListingAssetsCommitUrlTemplate = "/submission/v{0}/product/{1}/listings/assets/commit";
public static readonly string ProductDraftStatusPollingUrlTemplate = "/submission/v{0}/product/{1}/status";
public static readonly string CreateSubmissionUrlTemplate = "/submission/v{0}/product/{1}/submit";
public static readonly string SubmissionStatusPollingUrlTemplate = "/submission/v{0}/product/{1}/submission/{2}/status";
public const string JsonContentType = "application/json";
public const string PngContentType = "image/png";
public const string BinaryStreamContentType = "application/octet-stream";
/// <summary>
/// Initializes a new instance of the <see cref="SubmissionClient" /> class.
/// </summary>
/// <param name="accessToken">
/// The access token. This is JWT a token obtained from Azure Active Directory allowing the caller to invoke the API
/// on behalf of a user
/// </param>
/// <param name="serviceUrl">The service URL.</param>
public SubmissionClient(string accessToken, string serviceUrl)
{
if (string.IsNullOrEmpty(accessToken))
{
throw new ArgumentNullException("accessToken");
}
if (string.IsNullOrEmpty(serviceUrl))
{
throw new ArgumentNullException("serviceUrl");
}
this.accessToken = accessToken;
this.httpClient = new HttpClient
{
BaseAddress = new Uri(serviceUrl)
};
this.imageUploadClient = new HttpClient();
this.DefaultHeaders = new Dictionary<string, string>();
}
/// <summary>
/// Gets or Sets the default headers.
/// </summary>
public Dictionary<string, string> DefaultHeaders { get; set; }
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting
/// unmanaged resources.
/// </summary>
public void Dispose()
{
if (this.httpClient != null)
{
this.httpClient.Dispose();
this.httpClient = null;
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Gets the authorization token for the provided client id, client secret, and the scope.
/// This token is usually valid for 1 hour, so if your submission takes longer than that to complete,
/// make sure to get a new one periodically.
/// </summary>
/// <param name="tokenEndpoint">Token endpoint to which the request is to be made. Specific to your
/// Azure Active Directory app. Example: https://login.microsoftonline.com/d454d300-128e-2d81-334a-27d9b2baf002/oauth2/v2.0/token </param>
/// <param name="clientId">Client Id of your Azure Active Directory app. Example" 00001111-aaaa-2222-bbbb-3333cccc4444</param>
/// <param name="clientSecret">Client secret of your Azure Active Directory app</param>
/// <param name="scope">Scope. If not provided, default one is used for the production API endpoint.</param>
/// <returns>Autorization token. Prepend it with "Bearer: " and pass it in the request header as the
/// value for "Authorization: " header.</returns>
public static async Task<string> GetClientCredentialAccessToken(
string tokenEndpoint,
string clientId,
string clientSecret,
string scope = null)
{
if (scope == null)
{
scope = "https://api.store.microsoft.com/.default";
}
dynamic result;
using (HttpClient client = new HttpClient())
{
string tokenUrl = tokenEndpoint;
using (
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post,
tokenUrl))
{
string strContent =
string.Format(
"grant_type=client_credentials&client_id={0}&client_secret={1}&scope={2}",
clientId,
clientSecret,
scope);
request.Content = new StringContent(strContent, Encoding.UTF8,
"application/x-www-form-urlencoded");
using (HttpResponseMessage response = await client.SendAsync(request))
{
string responseContent = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject(responseContent);
}
}
}
return result.access_token;
}
/// <summary>
/// Invokes the specified HTTP method.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="httpMethod">The HTTP method.</param>
/// <param name="relativeUrl">The relative URL.</param>
/// <param name="requestContent">Content of the request.</param>
/// <returns>instance of the type T</returns>
/// <exception cref="ServiceException"></exception>
public async Task<T> Invoke<T>(HttpMethod httpMethod,
string relativeUrl,
object requestContent)
{
using (var request = new HttpRequestMessage(httpMethod, relativeUrl))
{
this.SetRequest(request, requestContent);
using (HttpResponseMessage response = await this.httpClient.SendAsync(request))
{
T result;
if (this.TryHandleResponse(response, out result))
{
return result;
}
if (response.IsSuccessStatusCode)
{
var resource = JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync());
return resource;
}
throw new Exception(await response.Content.ReadAsStringAsync());
}
}
}
/// <summary>
/// Uploads a given Image Asset file to Asset Storage
/// </summary>
/// <param name="assetUploadUrl">Asset Storage Url</param>
/// <param name="fileStream">The Stream instance of file to be uploaded</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task UploadAsset(string assetUploadUrl, Stream fileStream)
{
using (var request = new HttpRequestMessage(HttpMethod.Put, assetUploadUrl))
{
request.Headers.Add("x-ms-blob-type", "BlockBlob");
request.Content = new StreamContent(fileStream);
request.Content.Headers.ContentType = new MediaTypeHeaderValue(PngContentType);
using (HttpResponseMessage response = await this.imageUploadClient.SendAsync(request))
{
if (response.IsSuccessStatusCode)
{
return;
}
throw new Exception(await response.Content.ReadAsStringAsync());
}
}
}
/// <summary>
/// Sets the request.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="requestContent">Content of the request.</param>
protected virtual void SetRequest(HttpRequestMessage request, object requestContent)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", this.accessToken);
foreach (var header in this.DefaultHeaders)
{
request.Headers.Add(header.Key, header.Value);
}
if (requestContent != null)
{
request.Content = new StringContent(JsonConvert.SerializeObject(requestContent),
Encoding.UTF8,
JsonContentType);
}
}
/// <summary>
/// Tries the handle response.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="response">The response.</param>
/// <param name="result">The result.</param>
/// <returns>true if the response was handled</returns>
protected virtual bool TryHandleResponse<T>(HttpResponseMessage response, out T result)
{
result = default(T);
return false;
}
}
}
Node.js 샘플: MSI 또는 EXE 앱용 Microsoft Store Submission API
이 문서에서는 MSI 또는 EXE 앱에 Microsoft Store 제출 API를 사용하는 방법을 보여주는 Node.js 코드 예제를 제공합니다. 각 예시를 검토하여 예시에서 보여 주는 작업에 대해 자세히 알아보거나 이 문서의 모든 코드 예시를 콘솔 애플리케이션으로 빌드할 수 있습니다.
필수 구성 요소 다음 예제에서는 다음 라이브러리를 사용합니다.
- node-fetch v2 [npm install node-fetch@2]
node.js 사용하여 앱 제출 만들기
다음 예제에서는 이 문서의 다른 예제 메서드를 호출하여 Microsoft Store 제출 API를 사용하는 다양한 방법을 보여 줍니다. 다음을 따라 이 프로그램을 자신의 용도에 맞게 조정합니다.
- 파트너 센터 계정의 판매자 ID에 SellerId 속성을 할당합니다.
- 관리하려는 앱의 ID에 ApplicationId 속성을 할당합니다.
- ClientId 및 ClientSecret 속성을 앱의 클라이언트 ID 및 키에 할당하고 TokenEndpoint URL의 tenantid 문자열을 앱의 tenantID로 바꿉니다. 자세한 정보는 Azure AD 애플리케이션을 파트너 센터 계정과 연결하는 방법을 참조하세요.
다음 예제는 Microsoft Store 제출 API에서 여러 메서드를 사용하여 앱 제출을 업데이트하는 클래스를 구현합니다.
const config = require('./Configuration');
const submissionClient = require('./SubmissionClient');
const fs = require('fs');
var client = new submissionClient(config);
/**
* Main entry method to Run the Store Submission API Node.js Sample
*/
async function RunNodeJsSample(){
print('Getting Access Token');
await client.getAccessToken();
print('Getting Current Application Draft Status');
var currentDraftStatus = await client.callStoreAPI(client.productDraftStatusPollingUrlTemplate, 'get');
print(currentDraftStatus);
print('Getting Application Packages');
var currentPackages = await client.callStoreAPI(client.packagesUrlTemplate, 'get');
print(currentPackages);
print('Getting Single Package');
var packageId = currentPackages.responseData.packages[0].packageId;
var packageIdUrl = `${client.packageByIdUrlTemplate}`.replace('{packageId}', packageId);
var singlePackage = await client.callStoreAPI(packageIdUrl, 'get');
print(singlePackage);
print('Updating Entire Package Set');
// Update data in Packages list to have final set of updated Packages
currentPackages.responseData.packages[0].installerParameters = "/s /r new-args";
var packagesUpdateRequest = {
'packages': currentPackages.responseData.packages
};
print(packagesUpdateRequest);
var packagesUpdateResponse = await client.callStoreAPI(client.packagesUrlTemplate, 'put', packagesUpdateRequest);
print(packagesUpdateResponse);
print('Updating Single Package\'s Download Url');
// Update data in the SinglePackage object
singlePackage.responseData.packages[0].installerParameters = "/s /r /t new-args";
var singlePackageUpdateResponse = await client.callStoreAPI(packageIdUrl, 'patch', singlePackage.responseData.packages[0]);
print(singlePackageUpdateResponse);
print('Committing Packages');
var commitPackagesResponse = await client.callStoreAPI(client.packagesCommitUrlTemplate, 'post');
print(commitPackagesResponse);
await poll(async ()=>{
print('Waiting for Upload to finish');
return await client.callStoreAPI(client.productDraftStatusPollingUrlTemplate, 'get');
}, 2);
print('Getting Application Metadata - All Modules');
var appMetadata = await client.callStoreAPI(client.appMetadataUrlTemplate, 'get');
print(appMetadata);
print('Getting Application Metadata - Listings');
var appListingMetadata = await client.callStoreAPI(client.appListingsFetchMetadataUrlTemplate, 'get');
print(appListingMetadata);
print('Updating Listings Metadata - Description');
// Update Required Fields in Listings Metadata Object - Per Language. For eg. AppListingsMetadata.responseData.listings[0]
// Example - Updating Description
appListingMetadata.responseData.listings[0].description = 'New Description Updated By Node.js Sample Code';
var listingsUpdateRequest = {
'listings': appListingMetadata.responseData.listings[0]
};
var listingsMetadataUpdateResponse = await client.callStoreAPI(client.appMetadataUrlTemplate, 'put', listingsUpdateRequest);
print(listingsMetadataUpdateResponse);
print('Getting All Listings Assets');
var listingAssets = await client.callStoreAPI(client.listingAssetsUrlTemplate, 'get');
print(listingAssets);
print('Creating Listing Assets for 1 Screenshot');
var listingAssetCreateRequest = {
'language': listingAssets.responseData.listingAssets[0].language,
'createAssetRequest': {
'Screenshot': 1,
'Logo': 0
}
};
var listingAssetCreateResponse = await client.callStoreAPI(client.listingAssetsCreateUrlTemplate, 'post', listingAssetCreateRequest);
print(listingAssetCreateResponse);
print('Uploading Listing Assets');
const pathToFile = './Image.png';
const stats = fs.statSync(pathToFile);
const fileSize = stats.size;
const fileStream = fs.createReadStream(pathToFile);
await client.uploadAssets(listingAssetCreateResponse.responseData.listingAssets.screenshots[0].primaryAssetUploadUrl, fileStream, fileSize);
print('Committing Listing Assets');
var assetCommitRequest = {
'listingAssets': {
'language': listingAssets.responseData.listingAssets[0].language,
'storeLogos': listingAssets.responseData.listingAssets[0].storeLogos,
'screenshots': [{
'id': listingAssetCreateResponse.responseData.listingAssets.screenshots[0].id,
'assetUrl': listingAssetCreateResponse.responseData.listingAssets.screenshots[0].primaryAssetUploadUrl
}]
}
};
var assetCommitResponse = await client.callStoreAPI(client.listingAssetsCommitUrlTemplate, 'put', assetCommitRequest);
print(assetCommitResponse);
print('Getting Current Application Draft Status before Submission');
currentDraftStatus = await client.callStoreAPI(client.productDraftStatusPollingUrlTemplate, 'get');
print(currentDraftStatus);
if(!currentDraftStatus.responseData.isReady){
throw new Error('Application Current Status is not in Ready Status for All Modules');
}
print('Creating Submission');
var submissionCreationResponse = await client.callStoreAPI(client.createSubmissionUrlTemplate, 'post');
print(submissionCreationResponse);
print('Current Submission Status');
var submissionStatusUrl = `${client.submissionStatusPollingUrlTemplate}`.replace('{submissionId}', submissionCreationResponse.responseData.submissionId);
var submissionStatusResponse = await client.callStoreAPI(submissionStatusUrl, 'get');
print(submissionStatusResponse);
// User can Poll on this API to know if Submission Status is INPROGRESS, PUBLISHED or FAILED.
// This Process involves File Scanning, App Certification and Publishing and can take more than a day.
}
/**
* Utility Method to Poll using a given function and time interval in seconds
* @param {*} func
* @param {*} intervalInSeconds
* @returns
*/
async function poll(func, intervalInSeconds){
var result = await func();
if(result.responseData.isReady){
Promise.resolve(true);
}
else if(result.errors && result.errors.length > 0 && result.errors.find(element => element.code == 'packageuploaderror') != undefined){
throw new Error('Package Upload Failed');
}
else{
await new Promise(resolve => setTimeout(resolve, intervalInSeconds*1000));
return await poll(func, intervalInSeconds);
}
}
/**
* Utility function to Print a Json or normal string
* @param {*} json
*/
function print(json){
if(typeof(json) == 'string'){
console.log(json);
}
else{
console.log(JSON.stringify(json));
}
console.log("\n");
}
/** Run the Node.js Sample Application */
RunNodeJsSample();
ClientConfiguration 도우미
샘플 앱은 ClientConfiguration 도우미 클래스를 사용하여 Azure Active Directory 데이터와 앱 데이터를 Microsoft Store 제출 API를 사용하는 각 예제 메서드에 전달합니다.
/** Configuration Object for Store Submission API */
var config = {
version : "1",
applicationId : "...",
clientId : "...",
clientSecret : "...",
serviceEndpoint : "https://api.store.microsoft.com",
tokenEndpoint : "...",
scope : "https://api.store.microsoft.com/.default",
sellerId : "...",
jsonContentType : "application/json",
pngContentType : "image/png",
binaryStreamContentType : "application/octet-stream"
};
module.exports = config;
node.js 사용하여 IngestionClient 도우미
Ingestion Client 클래스는 샘플 앱의 다른 방법에서 다음 작업을 수행하는 데 사용되는 도우미 방법을 제공합니다:
- Microsoft Store 제출 API에서 메서드를 호출하는 데 사용할 수 있는 Azure AD 액세스 토큰을 가져옵니다. 토큰을 가져온 후 만료되기 전에 이 토큰을 Microsoft Store 제출 API에 대한 호출에 사용할 수 있는 시간은 60분입니다. 토큰이 만료된 후 새 토큰을 생성할 수 있습니다.
- Microsoft Store 제출 API에 대한 HTTP 요청을 처리합니다.
const fetch = require('node-fetch');
/**
* Submission Client to invoke all available Store Submission API and Asset Upload to Blob Store
*/
class SubmissionClient{
constructor(config){
this.configuration = config;
this.accessToken = "";
this.packagesUrlTemplate = `/submission/v${this.configuration.version}/product/${this.configuration.applicationId}/packages`;
this.packageByIdUrlTemplate = `/submission/v${this.configuration.version}/product/${this.configuration.applicationId}/packages/{packageId}`;
this.packagesCommitUrlTemplate = `/submission/v${this.configuration.version}/product/${this.configuration.applicationId}/packages/commit`;
this.appMetadataUrlTemplate = `/submission/v${this.configuration.version}/product/${this.configuration.applicationId}/metadata`;
this.appListingsFetchMetadataUrlTemplate = `/submission/v${this.configuration.version}/product/${this.configuration.applicationId}/metadata/listings`;
this.listingAssetsUrlTemplate = `/submission/v${this.configuration.version}/product/${this.configuration.applicationId}/listings/assets`;
this.listingAssetsCreateUrlTemplate = `/submission/v${this.configuration.version}/product/${this.configuration.applicationId}/listings/assets/create`;
this.listingAssetsCommitUrlTemplate = `/submission/v${this.configuration.version}/product/${this.configuration.applicationId}/listings/assets/commit`;
this.productDraftStatusPollingUrlTemplate = `/submission/v${this.configuration.version}/product/${this.configuration.applicationId}/status`;
this.createSubmissionUrlTemplate = `/submission/v${this.configuration.version}/product/${this.configuration.applicationId}/submit`;
this.submissionStatusPollingUrlTemplate = `/submission/v${this.configuration.version}/product/${this.configuration.applicationId}/submission/{submissionId}/status`;
}
async getAccessToken(){
var params = new URLSearchParams();
params.append('grant_type','client_credentials');
params.append('client_id',this.configuration.clientId);
params.append('client_secret',this.configuration.clientSecret);
params.append('scope',this.configuration.scope);
var response = await fetch(this.configuration.tokenEndpoint,{
method: "POST",
body: params
});
var data = await response.json();
this.accessToken = data.access_token;
}
async callStoreAPI(url, method, data){
var request = {
method: method,
headers:{
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': this.configuration.jsonContentType,
'X-Seller-Account-Id': this.configuration.sellerId
},
};
if(data){
request.body = JSON.stringify(data);
}
var response = await fetch(`${this.configuration.serviceEndpoint}${url}`,request);
var jsonResponse = await response.json();
return jsonResponse;
}
async uploadAssets(url, stream, size){
var request = {
method: 'put',
headers:{
'Content-Type': this.configuration.pngContentType,
'x-ms-blob-type': 'BlockBlob',
"Content-length": size
},
body: stream
};
var response = await fetch(`${url}`,request);
if(response.ok){
return response;
}
else{
throw new Error('Uploading of assets failed');
}
}
}
module.exports = SubmissionClient;
추가 도움말
Microsoft Store 제출 API에 대한 질문이 있거나 이 API의 제출을 관리하는 데 도움이 필요한 경우 다음의 리소스를 사용하세요.
Windows developer