MSI 或 EXE 應用程式的 Microsoft Store 提交 API
使用適用於 MSI 或 EXE 應用程式的 Microsoft Store 提交 API 以程式設計方式查詢並為你或你組織的合作夥伴中心帳戶建立 MSI 或 EXE 應用程式提交。 如果您的帳戶管理許多應用程式,而且您想要將這些資產的提交程序自動化並最佳化,則此 API 很有用。 此 API 使用 Azure Active Directory (Azure AD) 來驗證來自您的應用程式或服務的呼叫。
下列步驟說明使用 Microsoft Store 提交 API 的端對端程序:
- 請確定您已完成所有必要條件。
- 在 Microsoft Store 提交 API 中呼叫方法之前,請先取得 Azure AD 存取權杖。 取得權杖之後,您有 60 分鐘的時間使用此權杖來呼叫 Microsoft Store 提交 API,之後權杖才會到期。 權杖過期後,您可以生成新的權杖。
- 呼叫 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 應用程式與您的合作夥伴中心帳戶建立關聯,並取得您的租用戶識別碼、用戶端識別碼和金鑰。 您需要這些值來取得 Azure AD 存取權杖,用於呼叫 Microsoft Store 提交 API。
- 準備您的應用程式以搭配 Microsoft Store 提交 API 使用:
- 如果您的應用程式尚不存在於合作夥伴中心,您必須在合作夥伴中心透過保留應用程式名稱來建立您的應用程式。 您無法使用 Microsoft Store 提交 API 在合作夥伴中心建立應用程式;您必須在合作夥伴中心工作才能建立它,在這之後,您就可以使用 API 來存取應用程式,並以程式設計方式建立應用程式的提交。
- 您必須先在合作夥伴中心為應用程式建立一個提交,包括回答年齡分級問卷,才能使用此 API 為指定的應用程式建立提交。 執行此動作之後,您將能夠使用 API 以程式設計方式建立此應用程式的新提交。
- 如果您要建立或更新應用程式提交,而且需要包含新套件,請準備套件細節。
- 如果您要建立或更新應用程式提交,而且您需要包含市集清單的螢幕擷取畫面或影像,請準備應用程式螢幕擷取畫面和影像。
如何將 Azure AD 應用程式與合作夥伴中心帳戶相關聯
您必須先將 Azure AD 應用程式與合作夥伴中心帳戶建立關聯,擷取應用程式的租用戶識別碼和用戶端識別碼,並產生金鑰後,才能使用 MSI 或 EXE 應用程式的 Microsoft Store 提交 API。 Azure AD 應用程式代表您想要從中呼叫 Microsoft Store 提交 API 的應用程式或服務。 您需要租用戶識別碼、用戶端識別碼和金鑰,才能取得傳遞至 API 的 Azure AD 存取全權杖。
注意
您只需要執行此工作一次。 擁有租用戶識別碼、用戶端識別碼和金鑰之後,您可以隨時重複使用,以建立新的 Azure AD 存取權限。
- 在合作夥伴中心中,將組織的合作夥伴中心帳戶與組織的 Azure AD 目錄建立關聯。
- 接下來,從合作夥伴中心的帳戶設定區段中的使用者頁面,新增 Azure AD 應用程式,代表您要用來存取合作夥伴中心帳戶提交的應用程式或服務。 請確定為此應用程式指派管理員角色。 如果應用程式尚不存在於 Azure AD 目錄中,您可以在合作夥伴中心建立新的 Azure AD 應用程式。
- 返回 [使用者] 頁面,按一下 Azure AD 應用程式的名稱以移至應用程式設定,然後複製 [租用戶識別碼] 和 [用戶端識別碼] 值。
- 若要新增金鑰或客戶端密碼,請參閱下列指示,或參閱透過 Azure 入口網站註冊應用程式的指示:
若要註冊您的應用程式:
登入 Azure 入口網站。
如果您有多個租用戶的存取權,請使用頂端功能表中的「目錄 + 訂閱」篩選條件來切換要在其中註冊應用程式的租用戶。
搜尋並選取 [Azure Active Directory]。
在管理底下,選取應用程式註冊>選取您的應用程式。
選擇證書&密碼>用戶端密碼>新用戶端密碼。
新增用戶端密碼的描述。
選取祕密的到期日,或指定自訂存留期。
用戶端祕密存留期限制為兩年 (24 個月) 或更少。 您無法指定超過 24 個月的自訂存留期。
注意
Microsoft 建議您將到期值設定為少於 12 個月。
選取 [新增]。
記錄祕密的值,以在用戶端應用程式程式碼中使用。 離開此頁面後,就「不會再次顯示」此祕密值。
步驟 2:取得 Azure AD 存取權杖
在呼叫 MSI 或 EXE 應用程式的 Microsoft Store 提交 API 中的任何方法之前,您必須先取得傳遞至 API 中每個方法的授權標頭的 Azure AD 存取權杖。 取得存取權杖之後,您在其到期之前有 60 分鐘的時間可以使用。 權杖到期之後,您可以重新整理權杖,以便繼續用於對 API 的進一步呼叫。
若要取得存取權杖,請遵循 [服務對服務呼叫使用客戶端認證]/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
針對tenant_id
POST URI 和 client_id
和 client_secret
參數中的值,指定您在上一節中從合作夥伴中心擷取之應用程式的租使用者識別碼、用戶端識別碼和密鑰。 對於範圍參數,您必須指定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 包含許多方法,這些方法被分組為應用程式的場景。 若要建立或更新提交,您通常會按特定順序呼叫多個方法。 有關每個場景和每個方法的語法的資訊,請參閱以下部分:
注意
取得存取權杖後,您有 60 分鐘的時間呼叫 MSI 或 EXE 應用程式的 Microsoft Store 提交 API 中的方法,之後權杖便會過期。
基底 URL
EXE 或 MSI 應用程式的 Microsoft Store 提交 API 的基本 URL 是:https://api.store.microsoft.com
API 合約
取得目前草稿提交中繼資料 API
取得目前草稿提交下每個模組中的中繼資料 (清單、屬性或可用性)。
路徑 [所有模組]:/submission/v1/product/{productId}/metadata?languages={languages}&includelanguagelist={true/false}
路徑 [單一模組]:/submission/v1/product/{productId}/metadata/{moduleName}?languages={languages}&includelanguagelist={true/false}
方法:GET
路徑參數
參數 | 描述 |
---|---|
productId | 產品的合作夥伴中心識別碼 |
moduleName | 合作夥伴中心模組 – 清單、屬性或可用性 |
查詢參數
參數 | 描述 |
---|---|
語言 | 選擇性清單語言會篩選為逗號分隔字串 [限制最多 200 種語言]。 如果不存在,則檢索前 200 種可用的清單語言中繼資料。 [例如「en-us、en-gb」]. |
includelanguagelist | 選擇性 布林值 - 如果為 true,則傳回新增的清單語言的清單及其完整性狀態。 |
必要標頭
頁首 | 值 |
---|---|
Authorization: Bearer <Token> |
使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼 |
X-Seller-Account-Id |
合作夥伴中心帳戶的賣家識別碼 |
回應標頭
頁首 | 值 |
---|---|
X-Correlation-ID |
每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。 |
Retry-After |
由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。 |
回應參數
名稱 | 類型 | 描述 |
---|---|---|
accessibilitySupport | 布林值 | |
additionalLicenseTerms | String | |
可用性 | Object | 可用性模組資料 |
category | String | 請參閱下列類別清單 |
certificationNotes | String | |
code | String | 訊息的錯誤代碼 |
contactInfo | String | |
著作權 | String | |
dependsOnDriversOrNT | 布林值 | |
description | String | |
developedBy | String | |
可搜尋性 | String | [DISCOVERABLE, DEEPLINK_ONLY] |
enableInFutureMarkets | 布林值 | |
錯誤 | 物件陣列 | 錯誤或警告訊息清單 (如有) |
freeTrial | String | [NO_FREE_TRIAL, FREE_TRIAL] |
hardwareItemType | String | |
isPrivacyPolicyRequired | 布林值 | |
isRecommended | 布林值 | |
isRequired | 布林值 | |
isSuccess | 布林值 | |
isSystemFeatureRequired | 物件陣列 | |
language | String | 請參閱下列語言清單 |
清單 | 物件陣列 | 列出每個語言的模組資料 |
市場 | 字串陣列 | 請參閱下面的市場清單 |
message | String | 錯誤的描述 |
minimumHardware | String | |
minimumRequirement | String | |
penAndInkSupport | 布林值 | |
價格 | String | [FREE, FREEMIUM, SUBSCRIPTION, PAID] |
privacyPolicyUrl | String | |
productDeclarations | Object | |
productFeatures | 字串陣列 | |
內容 | Object | 屬性模組資料 |
recommendedHardware | String | |
recommendedRequirement | String | |
responseData | Object | 包含請求的實際回應負載 |
requirements | 物件陣列 | |
searchTerms | 字串陣列 | |
shortDescription | String | |
子類別 | String | 請參閱下面的子類別清單 |
supportContactInfo | String | |
systemRequirementDetails | 物件陣列 | |
目標 | String | 錯誤來源的實體 |
website | String | |
whatsNew | String |
範例回應
{
"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 檢查
- 針對主動提交。 如果存在,則失敗並出現錯誤訊息。
- 如果所有模組都處於就緒狀態,則可以進行儲存草稿操作。
- 提交中的每個欄位均根據商店的要求進行驗證
- 系統需求詳細資料驗證規則:
- hardwareItemType 中允許的值 = 記憶體:300MB、750MB、1GB、2GB、4GB、6GB、8GB、12GB、16GB、20GB
- hardwareItemType = DirectX 中的允許值:DX9、DX10、DX11、DX12-FEATURELEVEL11、DX12-FEATURELEVEL12
- hardwareItemType 中允許的值 = Video_Memory:1GB、2GB、4GB、6GB
路徑 [完整模組更新]:/submission/v1/product/{productId}/metadata
方法:PUT
路徑 [模組修補檔更新]:/submission/v1/product/{productId}/metadata
方法:PATCH
API 行為:
在完整模組更新 API 的情況下,整個模組資料需要出現在請求中才能完整更新每個欄位。 請求中不存在的任何欄位,其預設值用於覆蓋該特定模組的當前值。
在修補程式模組更新 API 的情況下, 只有需要更新的欄位才會出現在要求中。 請求中的這些欄位值將覆蓋其現有值,保留請求中不存在的所有其他欄位,與該特定模組的當前值相同。
路徑參數
參數 | 描述 |
---|---|
productId | 產品的合作夥伴中心識別碼 |
必要標頭
頁首 | 值 |
---|---|
Authorization: Bearer <Token> |
使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼 |
X-Seller-Account-Id |
合作夥伴中心帳戶的賣家識別碼 |
要求參數
名稱 | 類型 | 描述 |
---|---|---|
可用性 | Object | 要保存可用性模組中繼資料的物件 |
市場 | 字串陣列 | 必要請參閱下面的市場清單 |
可搜尋性 | String | 必要 [DISCOVERABLE, DEEPLINK_ONLY] |
enableInFutureMarkets | 布林值 | 必要 |
價格 | String | 必要 [FREE, FREEMIUM, SUBSCRIPTION, PAID] |
freeTrial | String | 如果定價是付費或訂用帳戶 [NO_FREE_TRIAL, FREE_TRIAL] |
內容 | Object | 要保存屬性模組中繼資料的物件 |
isPrivacyPolicyRequired | 布林值 | 必要 |
privacyPolicyUrl | String | 如果 isPrivacyPolicyRequired = true ,則必須是有效的 URL |
website | String | 必須為有效的 URL |
supportContactInfo | String | 必須是有效的 URL 或電子郵件地址 |
certificationNotes | String | 建議字元限制 = 2000 |
category | String | 必要請參閱下列類別清單 |
子類別 | String | 必要請參閱下列子類別清單 |
productDeclarations | Object | 必要 |
isSystemFeatureRequired | 物件陣列 | [Touch, Keyboard, Mouse, Camera, NFC_HCE, NFC_Proximity, Bluetooth_LE, Telephony, Microphone] |
isRequired | 布林值 | 必要 |
isRecommended | 布林值 | 必要 |
hardwareItemType | String | 必要 |
systemRequirementDetails | 物件陣列 | [Processor, Graphics, Memory, DirectX, Video_Memory] |
minimumRequirement | String | 必要 For systemRequirementsText, MaxLength = 200 hardwareItemType 中允許的值 = 記憶體:[300MB、750MB、1GB、2GB、4GB、6GB、8GB、12GB、16GB、20GB] hardwareItemType = DirectX 中的允許值:[DX9、DX10、DX11、DX12-FEATURELEVEL11、DX12-FEATURELEVEL12] hardwareItemType 中允許的值 = Video_Memory:[1GB、2GB、4GB、6GB] |
recommendedRequirement | String | 必要 For systemRequirementsText, MaxLength = 200 hardwareItemType 中允許的值 = 記憶體:[300MB、750MB、1GB、2GB、4GB、6GB、8GB、12GB、16GB、20GB] hardwareItemType = DirectX 中的允許值:[DX9、DX10、DX11、DX12-FEATURELEVEL11、DX12-FEATURELEVEL12] hardwareItemType 中允許的值 = Video_Memory:[1GB、2GB、4GB、6GB] |
dependsOnDriversOrNT | 布林值 | 必要 |
accessibilitySupport | 布林值 | 必要 |
penAndInkSupport | 布林值 | 必要 |
清單 | Object | 反對列出單一語言的模組資料 |
language | String | 必要請參閱下列語言清單 |
description | String | 必要字元限制 = 10000 |
whatsNew | String | 字元限制 = 1500 |
productFeatures | 字串陣列 | 每個功能 200 個字元;最多 20 個功能 |
shortDescription | String | 字元限制 = 1000 |
searchTerms | 字串陣列 | 每個搜尋字詞 30 個字元;最多 7 個搜尋字詞 所有搜尋字詞中共有 21 個唯一單字 |
additionalLicenseTerms | String | 必要字元限制 = 10000 |
著作權 | String | 字元限制 = 200 |
developedBy | String | 字元限制 = 255 |
requirements | 物件陣列 | 每項 200 個字元; 最少和推薦之間總計最多 11 項] |
minimumHardware | String | 字元限制 = 200 |
recommendedHardware | String | 字元限制 = 200 |
contactInfo | String | 字元限制 = 200 |
listingsToAdd | 字串陣列 | 請參閱下列語言清單 |
listingsToRemove | 字串陣列 | 請參閱下列語言清單 |
市場
Market | 縮寫 |
---|---|
阿富汗 | AF |
阿爾巴尼亞 | AL |
阿爾及利亞 | 阿爾及利亞 |
美屬薩摩亞 | AS |
安道爾 | AD |
安哥拉 | AO |
安奎拉 | AI |
南極洲 | AQ |
安地卡及巴布達 | AG |
阿根廷 | AR |
亞美尼亞 | 上午 |
荷屬阿魯巴 | AW |
澳洲 | AU |
奧地利 | AT |
亞塞拜然 | AZ |
巴哈馬 | BS |
巴林 | BH |
孟加拉 | 孟加拉 |
巴貝多 | BB |
白俄羅斯 | BY |
比利時 | BE |
貝里斯 | BZ |
貝南 | BJ |
百慕達 | BM |
不丹 | BT |
委內瑞拉玻利瓦爾共和國 | 佛蒙特州 |
玻利維亞 | BO |
波奈 | BQ |
波士尼亞與赫塞哥維納 | 波士尼亞與赫塞哥維納 |
波札那 | BW |
布威島 | BV |
巴西 | 巴西 |
英屬印度洋領地 | IO |
英屬維京群島 | VG |
汶萊 | BN |
保加利亞 | BG |
布吉納法索 | BF |
蒲隆地 | BI |
柬埔寨 | 柬埔寨 |
喀麥隆 | 喀麥隆 |
加拿大 | CA |
維德角 | CV |
開曼群島 | KY |
中非共和國 | CF |
查德 | TD |
智利 | CL |
中國 | CN |
聖誕島 | CX |
科克斯 (基靈) 群島 | CC |
哥倫比亞 | CO |
葛摩 | KM |
剛果 | CG |
剛果民主共和國 | CD |
庫克群島 | CK |
哥斯大黎加 | 哥斯大黎加 |
克羅埃西亞 | HR |
庫拉索 | CW |
賽普勒斯 | CY |
捷克共和國 | 捷克 |
象牙海岸 (科特迪瓦) | CI |
丹麥 | DK |
吉布地 | DJ |
多米尼克 | DM |
多明尼加共和國 | D 0 |
厄瓜多 | EC |
埃及 | 埃及 |
薩爾瓦多 | 薩爾瓦多 |
赤道幾內亞 | GQ |
厄利垂亞 | ER |
愛沙尼亞 | EE |
衣索比亞 | ET |
福克蘭群島 | FK |
法羅群島 | F0 |
斐濟 | 斐濟 |
芬蘭 | FI |
法國 | 法國 |
法屬圭亞那 | GF |
法屬玻里尼西亞 | PF |
法屬南部和南極土地 | TF |
加彭 | GA |
甘比亞 | GM |
喬治亞 | GE |
德國 | DE |
迦納 | 迦納 |
直布羅陀 | GI |
希臘 | GR |
格陵蘭 | GL |
格瑞那達 | GD |
瓜地洛普 | GP |
關島 | GU |
瓜地馬拉 | GT |
根息 | GG |
幾內亞 | GN |
幾內亞比索 | GW |
蓋亞那 | GY |
海地 | HT |
赫德島及麥當勞群島 | HM |
梵蒂岡 | VA |
宏都拉斯 | 宏都拉斯 |
香港特別行政區 | 香港 |
匈牙利 | 匈牙利 |
冰島 | IS |
印度 | IN |
印尼 | 識別碼 |
Iraq | 伊拉克 |
愛爾蘭 | IE |
以色列 | 伊利諾州 |
義大利 | IT |
牙買加 | 牙買加 |
日本 | 日本 |
澤西島 | JE |
約旦 | 約旦 |
哈薩克 | 哈薩克 |
肯亞 | KB |
吉里巴斯 | KI |
南韓 | 韓國 |
科威特 | KW |
吉爾吉斯 | 吉爾吉斯 |
寮國 | 洛杉磯 |
拉脫維亞 | LV |
黎巴嫩 | LB |
賴索托 | LS |
賴比瑞亞 | LR |
利比亞 | LY |
列支敦斯登 | LI |
立陶宛 | LT |
盧森堡 | LU |
澳門特別行政區 | MO |
北馬其頓 | 北馬其頓 |
馬達加斯加 | MG |
馬拉威 | MW |
馬來西亞 | MY |
馬爾地夫 | MV |
馬利 | ML |
馬爾他 | MT |
曼島 | IM |
馬紹爾群島 | MH |
馬丁尼克 | MQ |
茅利塔尼亞 | MR |
模里西斯 | 模里西斯 |
馬約特島 | YT |
墨西哥 | MX |
密克羅尼西亞 | FM |
摩爾多瓦 | MD |
摩納哥 | MC |
蒙古 | MN |
Montenegr - ME | |
蒙哲臘 | 毫秒 |
摩洛哥 | MA |
莫三比克 | MZ |
緬甸 | MM |
納米比亞 | NA |
諾魯 | NR |
尼泊爾 | NP |
荷蘭 | 荷蘭 |
新喀里多尼亞 | NC |
紐西蘭 | 紐西蘭 |
尼加拉瓜 | NI |
尼日 | NE |
奈及利亞 | 奈及利亞 |
紐埃島 | NU |
諾福克島 | NF |
北馬利安納群島 | MP |
挪威 | 否 |
阿曼 | OM |
巴基斯坦 | PK |
帛琉 | PW |
巴勒斯坦民族權力機構 | PS |
巴拿馬 | PA |
巴布亞紐幾內亞 | PG |
巴拉圭 | 巴拉圭 |
秘魯 | PE |
菲律賓 | PH |
皮特肯群島 | PN |
波蘭 | 波蘭 |
葡萄牙 | PT |
卡達 | 問與答 |
留尼旺 | RE |
羅馬尼亞 | RO |
俄羅斯 | 俄羅斯 |
盧安達 | RW |
聖巴瑟米 | BL |
聖赫勒拿、阿森松和特里斯坦達庫尼亞群島 | SH |
聖克里斯多福及尼維斯 | KN |
聖露西亞 | LC |
聖馬丁 (法國部分) | MF |
聖皮埃與密克隆群島 | PM |
聖文森及格瑞那丁 | VC |
薩摩亞 | WS |
聖馬利諾 | SM |
沙烏地阿拉伯 | SA |
塞內加爾 | SN |
塞爾維亞 | RS |
塞席爾 | SC |
獅子山 | SL |
新加坡 | SG |
聖馬滕 (荷蘭部分) | SX |
斯洛伐克 | SK |
斯洛維尼亞 | SI |
索羅門群島 | SB |
索馬利亞 | SO |
南非 | 南非 |
南喬治亞及南三明治群島 | GS |
西班牙 | ES |
斯里蘭卡 | 斯里蘭卡 |
蘇利南 | SR |
挪威屬斯瓦巴及尖棉 | SJ |
史瓦濟蘭 | SZ |
瑞典 | SE |
瑞士 | CH |
聖多美普林西比 | ST |
台灣 | 台灣 |
塔吉克 | TJ |
坦尚尼亞 | 坦尚尼亞 |
泰國 | TH |
東帝汶 | TL |
Tog - TG | |
托克勞群島 | TK |
東加 | 收件人 |
千里達及托巴哥 - TT | |
突尼西亞 | TN |
土耳其 | 土耳其 |
土庫曼 | TM |
土克斯及開科斯群島 | TC |
吐瓦魯 | TV |
美國外島 | UM |
美屬維爾京群島 | VI |
烏干達 | 烏干達 |
烏克蘭 | 烏克蘭 |
阿拉伯聯合大公國 | 阿拉伯聯合大公國 |
英國 | GB |
美國 | 美國 |
烏拉圭 | 烏拉圭 |
烏茲別克 | 烏茲別克 |
萬那杜 | VU |
越南 | VN |
瓦利斯群島和富圖那群島 | WF |
葉門 | YE |
尚比亞 | 尚比亞 |
辛巴威 | 辛巴威 |
奧蘭群島 | AX |
類別和子類別
類別 | 子類別 |
---|---|
BooksAndReference | EReader, Fiction, Nonfiction, Reference |
業務 | AccountingAndfinance, Collaboration, CRM, DataAndAnalytics, FileManagement, InventoryAndlogistics, LegalAndHR, ProjectManagement, RemoteDesktop, SalesAndMarketing, TimeAndExpenses |
DeveloperTools | Database, DesignTools, DevelopmentKits, Networking, ReferenceAndTraining, Servers, Utilities, WebHosting |
教育程度 | EducationBooksAndReference, EarlyLearning, InstructionalTools, Language, StudyAids |
娛樂 | (無) |
FoodAndDining | (無) |
GovernmentAndPolitics | (無) |
HealthAndFitness | (無) |
KidsAndFamily | KidsAndFamilyBooksAndReference, KidsAndFamilyEntertainment, HobbiesAndToys, SportsAndActivities, KidsAndFamilyTravel |
生活方式 | Automotive, DYI, HomeAndGarden, Relationships, SpecialInterest, StyleAndFashion |
醫療 | (無) |
MultimediaDesign | IllustrationAndGraphicDesign, MusicProduction, PhotoAndVideoProduction |
音樂 | (無) |
NavigationAndMaps | (無) |
NewsAndWeather | 新聞、天氣 |
PersonalFinance | BankingAndInvestments, BudgetingAndTaxes |
個人化 | RingtonesAndSounds, themes, WallpaperAndLockScreens |
PhotoAndVideo | (無) |
生產力 | (無) |
安全性 | PCProtection, PersonalSecurity |
購物 | (無) |
社交網路 | (無) |
運動 | (無) |
旅遊 | CityGuides, Hotels |
UtilitiesAndTools | BackupAndManage, FileManager |
語言
語言名稱 | 支援的語言代碼: |
---|---|
南非荷蘭文 | 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 |
希伯來文 | he、he-il |
印度文 | hi、hi-in |
匈牙利文 | hu、hu-hu |
冰島文 | is, is-is |
Igb - ig-latn, ig-ng | |
印尼文 | id、id-id |
Inuktitut (Latin) | 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 |
K'iche' | 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 |
歐迪亞文 | or、or-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 |
北索托文 | 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 |
烏都文 | ur、ur-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 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。 |
Retry-After |
由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位) |
回應參數
名稱 | 類型 | 描述 |
---|---|---|
isSuccess | 布林值 | |
錯誤 | 物件陣列 | 錯誤或警告訊息清單 (如有) |
code | String | 訊息的錯誤代碼 |
message | String | 錯誤的描述 |
目標 | String | 錯誤來源的實體 |
responseData | Object | 包含請求的實際回應負載 |
pollingUrl | String | 輪詢 URL 以取得任何進行中提交的狀態 |
ongoingSubmissionId | String | 任何進行中提交的提交標識碼 |
範例回應
{
"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 | 產品的合作夥伴中心識別碼 |
packageId | 要取得的封裝唯一識別碼 (ID)。 |
必要標頭
頁首 | 值 |
---|---|
Authorization: Bearer <Token> |
使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼 |
X-Seller-Account-Id |
合作夥伴中心帳戶的賣家識別碼 |
回應標頭
頁首 | 值 |
---|---|
X-Correlation-ID |
每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。 |
Retry-After |
由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。 |
回應參數
名稱 | 類型 | 描述 |
---|---|---|
isSuccess | 布林值 | |
錯誤 | 物件陣列 | 錯誤或警告訊息清單 (如有) |
code | String | 訊息的錯誤代碼 |
message | String | 錯誤的描述 |
目標 | String | 錯誤來源的實體 |
responseData | Object | |
packages | 物件陣列 | 要保存封裝模組資料的物件 |
packageId | String | |
packageUrl | String | |
語言 | 字串陣列 | |
架構 | 字串陣列 | [Neutral, X86, X64, Arm, Arm64] |
isSilentInstall | 布林值 | 如果您的安裝程式以無訊息模式執行,而不需要切換或其他 false,則這應該標示為 true |
installerParameters | String | |
genericDocUrl | String | |
errorDetails | 物件陣列 | |
errorScenario | String | |
errorScenarioDetails | 物件陣列 | |
errorValue | String | |
errorUrl | String | |
PackageType | String |
範例回應
{
"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
路徑 [單一套件修補檔更新]:/submission/v1/product/{productId}/packages/{packageId}
方法:PATCH
API 行為:
在完整模組更新 API 的情況下,整個套件資料需要出現在請求中才能完整更新每個欄位。 請求中不存在的任何欄位,其預設值用於覆蓋該特定模組的當前值。 這會導致從要求使用一組新套件覆寫所有現有的套件。 這會導致套件標識碼的重新產生,而用戶應該呼叫 GET Packages API 以取得最新的套件識別碼。
在單一套件修補程式更新 API 的案例中,只有要針對指定套件更新的欄位必須存在於要求中。 請求中的這些欄位值將覆蓋其現有值,保留請求中不存在的所有其他字段,與該特定套件的當前值相同。 集合中的其他套件會維持原樣。
路徑參數
名稱 | 描述 |
---|---|
productId | 產品的合作夥伴中心識別碼 |
packageId | 封裝的唯一識別碼 |
必要標頭
頁首 | 值 |
---|---|
Authorization: Bearer <Token> |
使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼 |
X-Seller-Account-Id |
合作夥伴中心帳戶的賣家識別碼 |
要求參數
名稱 | 類型 | 描述 |
---|---|---|
packages | 物件陣列 | 保留封裝模組資料的物件 [僅適用於完整模組更新的必要專案] |
packageUrl | String | 必要 |
語言 | 字串陣列 | 必要 |
架構 | 字串陣列 | 必要 應包含單一架構 - Neutral、X86、X64、Arm、Arm64 |
isSilentInstall | 布林值 | 必要 如果您的安裝程式以無訊息模式執行,而不需要切換或其他 false,則這應該標示為 true |
installerParameters | String | 如果isSilentInstall 為 false,則為必要項 |
genericDocUrl | String | 如果 packageType 是 exe 包含 EXE 類型安裝程式的自訂錯誤代碼詳細資訊的文件連結 |
errorDetails | 物件陣列 | 用於保存 EXE 類型安裝程式的自訂錯誤代碼和詳細資訊的中繼資料。 |
errorScenario | String | 識別特定的錯誤案例。 [installationCancelledByUser, applicationAlreadyExists, installationAlreadyInProgress, diskSpaceIsFull, rebootRequired, networkFailure, packageRejectedDuringInstallation, installationSuccessful, miscellaneous] |
errorScenarioDetails | 物件陣列 | |
errorValue | String | 安裝期間可能出現的錯誤碼 |
errorUrl | String | 取得錯誤詳細資料的 URL |
PackageType | String | 必要 [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 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。 |
Retry-After |
由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。 |
回應參數
名稱 | 類型 | 描述 |
---|---|---|
isSuccess | 布林值 | |
錯誤 | 物件陣列 | [錯誤或警告訊息清單 (如有)] |
code | String | 訊息的錯誤代碼 |
message | String | 錯誤的描述 |
目標 | String | 錯誤來源的實體 |
responseData | Object | |
pollingUrl | String | [輪詢 URL 以取得任何已進行中的提交狀態] |
ongoingSubmissionId | String | [任何進行中提交的提交標識碼] |
範例回應
{
"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 | 產品的合作夥伴中心識別碼 |
必要標頭
頁首 | 值 |
---|---|
Authorization: Bearer <Token> |
使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼 |
X-Seller-Account-Id |
合作夥伴中心帳戶的賣家識別碼 |
回應標頭
頁首 | 值 |
---|---|
X-Correlation-ID |
每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。 |
Retry-After |
由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。 |
回應參數
名稱 | 類型 | 描述 |
---|---|---|
isSuccess | 布林值 | |
錯誤 | 物件陣列 | [錯誤或警告訊息清單 (如有)] |
code | String | 訊息的錯誤代碼 |
message | String | 錯誤的描述 |
目標 | String | 錯誤來源的實體 |
responseData | Object | |
pollingUrl | String | [輪詢 URL 以取得套件上傳或提交狀態,以防任何已進行中提交] |
ongoingSubmissionId | String | [任何進行中提交的提交標識碼] |
範例回應
{
"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 | 產品的合作夥伴中心識別碼 |
查詢參數
名稱 | 描述 |
---|---|
語言 | [選擇性] 清單語言會篩選為逗號分隔字串 [限制最多 200 種語言]。 如果不存在,則會擷取前 200 個可用清單語言的資產資料。 (例如「en-us、en-gb」) |
必要標頭
頁首 | 值 |
---|---|
Authorization: Bearer <Token> |
使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼 |
X-Seller-Account-Id |
合作夥伴中心帳戶的賣家識別碼 |
回應標頭
頁首 | 值 |
---|---|
X-Correlation-ID |
每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。 |
Retry-After |
由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。 |
回應參數
名稱 | 類型 | 描述 |
---|---|---|
isSuccess | 布林值 | |
錯誤 | 物件陣列 | 錯誤或警告訊息清單 (如有) |
code | String | 訊息的錯誤代碼 |
message | String | 錯誤的描述 |
目標 | String | 錯誤來源的實體 |
responseData | Object | |
listingAssets | 物件陣列 | 列出每個語言的資產詳細資料 |
language | String | |
storeLogos | 物件陣列 | |
螢幕擷取畫面 | 物件陣列 | |
id | String | |
assetUrl | String | 必須為有效的 URL |
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 會針對每個個別映像資產上傳使用執行時間產生的 SAS URL,以及上傳成功之後的認可 API 呼叫。 為了能夠更新清單資源,並能夠在清單模組中新增/刪除區域設置,可以使用以下方法:
- 使用建立清單資產 API 來傳送有關資產上傳的要求,以及資產的語言、類型和計數。
- 根據請求的資產數量,按需建立資產 ID,並建立短期 SAS URL 並將其發送回資產類型下的回應正文中。 您可以使用此 URL 透過 HTTP 用戶端上傳特定類型的映像資產 [Put Blob (REST API) - Azure 儲存體 | Microsoft Docs]。
- 上傳後,您還可以使用提交清單資產 API 發送先前從先前的 API 呼叫中收到的新資產識別碼資訊。 在驗證之後,單一 API 會在內部認可列出資產資料。
- 此方法將有效地覆寫在要求中傳送的特定語言下資產類型上一組先前的影像。 因此,將會移除先前上傳的資產。
路徑:/submission/v1/product/{productId}/listings/assets/create
方法:POST
路徑參數
名稱 | 描述 |
---|---|
productId | 產品的合作夥伴中心識別碼 |
必要標頭
標頭 | 描述 |
---|---|
Authorization: Bearer <Token> |
使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼 |
X-Seller-Account-Id |
合作夥伴中心帳戶的賣家識別碼 |
要求參數
名稱 | 類型 | 描述 |
---|---|---|
語言 | String | 必要 |
createAssetRequest | Object | 必要 |
Screenshot | 整數 | 如果ISV需要更新螢幕快照或新增清單語言 [1 - 10],則為必要專案 |
標誌 | 整數 | 如果ISV需要更新螢幕快照或新增清單語言 [1 或 2],則為必要專案 |
回應標頭
標頭 | 描述 |
---|---|
X-Correlation-ID |
每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。 |
Retry-After |
由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。 |
回應參數
名稱 | 類型 | 描述 |
---|---|---|
isSuccess | 布林值 | |
錯誤 | 物件陣列 | 錯誤或警告訊息清單 (如有) |
code | String | 訊息的錯誤代碼 |
message | String | 錯誤的描述 |
目標 | String | 錯誤來源的實體 |
responseData | Object | |
listingAssets | Object | 包含要上傳的 StoreLogos 和螢幕擷取畫面詳細資訊的物件 |
language | String | |
storeLogos | 物件陣列 | |
螢幕擷取畫面 | 物件陣列 | |
id | String | |
primaryAssetUploadUrl | String | 使用 Azure Blob REST API 上傳清單資產的主要 URL |
secondaryAssetUploadUrl | String | 使用 Azure Blob REST API 上傳清單資產的次要 URL |
httpMethod | HTTP 方法 | HTTP 方法必須用來透過資產上傳 URL 上傳資產 – 主要或次要 |
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 | 產品的合作夥伴中心識別碼 |
必要標頭
標頭 | 描述 |
---|---|
Authorization: Bearer <Token> |
使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼 |
X-Seller-Account-Id |
合作夥伴中心帳戶的賣家識別碼 |
要求參數
名稱 | 類型 | 描述 |
---|---|---|
listingAssets | Object | |
language | String | |
storeLogos | 物件陣列 | |
螢幕擷取畫面 | 物件陣列 | |
id | String | 應該是使用者想要從取得目前列表資產 API 保存的現有識別碼,或是建立清單資產 API 中上傳新資產的新標識碼。 |
assetUrl | String | 應該是使用者希望從取得目前清單資產 API 中保留的現有資產的 URL,或是上傳 URL (主要或次要),使用該 URL 在建立清單資產 API 中上傳新資產。 必須為有效的 URL |
範例要求
{
"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 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。 |
Retry-After |
由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。 |
回應參數
名稱 | 類型 | 描述 |
---|---|---|
isSuccess | 布林值 | |
錯誤 | 物件陣列 | 錯誤或警告訊息清單 (如有) |
code | String | 訊息的錯誤代碼 |
message | String | 錯誤的描述 |
目標 | String | 錯誤來源的實體 |
responseData | Object | |
pollingUrl | String | 輪詢 URL 以取得任何進行中提交的狀態 |
ongoingSubmissionId | String | 任何進行中提交的提交標識碼 |
範例回應
{
"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}/status
方法:GET
路徑參數
名稱 | 描述 |
---|---|
productId | 產品的合作夥伴中心識別碼 |
必要標頭
標頭 | 描述 |
---|---|
Authorization: Bearer <Token> |
使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼 |
X-Seller-Account-Id |
合作夥伴中心帳戶的賣家識別碼 |
回應標頭
標頭 | 描述 |
---|---|
X-Correlation-ID |
每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。 |
Retry-After |
由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。 |
回應參數
名稱 | 類型 | 描述 |
---|---|---|
isSuccess | 布林值 | |
錯誤 | 物件陣列 | 錯誤或警告訊息清單 (如有) |
code | String | 訊息的錯誤代碼 |
message | String | 錯誤的描述 |
目標 | String | 錯誤來源的實體 |
responseData | Object | |
isReady | 布林值 | 指出所有模組是否處於就緒狀態,包括套件上傳 |
ongoingSubmissionId | String | 任何進行中提交的提交標識碼 |
範例回應
{
"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}/submit
方法:POST
路徑參數
名稱 | 描述 |
---|---|
productId | 產品的合作夥伴中心識別碼 |
必要標頭
標頭 | 描述 |
---|---|
Authorization: Bearer <Token> |
使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼 |
X-Seller-Account-Id |
合作夥伴中心帳戶的賣家識別碼 |
回應標頭
標頭 | 描述 |
---|---|
X-Correlation-ID |
每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。 |
Retry-After |
由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。 |
回應參數
名稱 | 類型 | 描述 |
---|---|---|
isSuccess | 布林值 | |
錯誤 | 物件陣列 | 錯誤或警告訊息清單 (如有) |
code | String | 訊息的錯誤代碼 |
message | String | 錯誤的描述 |
目標 | String | 錯誤來源的實體 |
responseData | Object | |
pollingUrl | String | 輪詢 URL 以取得模組整備狀態,包括提交套件上傳 |
submissionId | String | 新建立提交的標識碼 |
ongoingSubmissionId | String | 任何進行中提交的提交標識碼 |
範例回應
{
"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}/status
方法:GET
路徑參數
名稱 | 描述 |
---|---|
productId | 產品的合作夥伴中心識別碼 |
必要標頭
標頭 | 描述 |
---|---|
Authorization: Bearer <Token> |
使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼 |
X-Seller-Account-Id |
合作夥伴中心帳戶的賣家識別碼 |
回應標頭
標頭 | 描述 |
---|---|
X-Correlation-ID |
每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。 |
Retry-After |
由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。 |
回應參數
名稱 | 類型 | 描述 |
---|---|---|
isSuccess | 布林值 | |
錯誤 | 物件陣列 | 錯誤或警告訊息清單 (如有) |
code | String | 訊息的錯誤代碼 |
message | String | 錯誤的描述 |
目標 | String | 錯誤來源的實體 |
responseData | Object | |
publishingStatus | String | 提交狀態 - [INPROGRESS, PUBLISHED, FAILED, UNKNOWN] |
hasFailed | 布林值 | 指出發佈是否失敗且不會重試 |
範例回應
{
"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 提交 API
本文提供 C# 程式碼範例,示範如何使用 MSI 或 EXE 應用程式的 Microsoft Store 提交 API。 您可以檢閱每個範例,以深入了解其中示範的工作,也可以將本文中的所有程式碼範例建置到主控台應用程式。
必要條件 這些範例會使用下列連結庫:
- 來自 Newtonsoft 的 Newtonsoft.Json NuGet 套件。
主要程式 下列範例會實作命令列程式,呼叫本文中的其他範例方法,以示範使用 Microsoft Store 提交 API 的不同方式。 若要改寫此程式以供您自己使用:
- 將 SellerId 屬性指派給合作夥伴中心帳戶的賣方標識碼。
- 將 ApplicationId 屬性指派給您想要管理之應用程式的識別碼。
- 將 ClientId 和 ClientSecret 屬性指派給應用程式的用戶端識別碼和金鑰,並以您應用程式的租用戶識別碼取代 TokenEndpoint URL 中的 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# 建立應用程式提交
下面的範例實作的類別會使用 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 幫助程式類
IngestionClient 類別會提供協助程式方法,供範例應用程式中的其他方法用來執行下列工作:
- 取得 Azure AD 存取權仗,可用來呼叫 Microsoft Store 提交 API 中的方法 。 取得權杖之後,您有 60 分鐘的時間使用此權杖來呼叫 Microsoft Store 提交 API,之後權杖才會到期。 權杖過期後,您可以生成新的權杖。
- 處理 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 提交 API
本文提供 Node.js 程式碼範例,示範如何使用 MSI 或 EXE 應用程式的 Microsoft Store 提交 API。 您可以檢閱每個範例,以深入了解其中示範的工作,也可以將本文中的所有程式碼範例建置到主控台應用程式。
必要條件 這些範例會使用下列連結庫:
- node-fetch v2 [npm install node-fetch@2]
使用 node.js 建立應用程式提交
下列範例會呼叫本文中的其他範例方法,以示範使用 Microsoft Store 提交 API 的不同方式。 若要改寫此程式以供您自己使用:
- 將 SellerId 屬性指派給合作夥伴中心帳戶的賣方標識碼。
- 將 ApplicationId 屬性指派給您想要管理之應用程式的識別碼。
- 將 ClientId 和 ClientSecret 屬性指派給應用程式的用戶端識別碼和金鑰,並以您應用程式的租用戶識別碼取代 TokenEndpoint URL 中的 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 協助程式
IngestionClient 類別會提供協助程式方法,供範例應用程式中的其他方法用來執行下列工作:
- 取得 Azure AD 存取權仗,可用來呼叫 Microsoft Store 提交 API 中的方法 。 取得權杖之後,您有 60 分鐘的時間使用此權杖來呼叫 Microsoft Store 提交 API,之後權杖才會到期。 權杖過期後,您可以生成新的權杖。
- 處理 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 管理提交方面的協助,請使用下列資源: