共用方式為


MSI 或 EXE 應用程式的 Microsoft Store 提交 API

使用適用於 MSI 或 EXE 應用程式的 Microsoft Store 提交 API 以程式設計方式查詢並為你或你組織的合作夥伴中心帳戶建立 MSI 或 EXE 應用程式提交。 如果您的帳戶管理許多應用程式,而且您想要將這些資產的提交程序自動化並最佳化,則此 API 很有用。 此 API 使用 Azure Active Directory (Azure AD) 來驗證來自您的應用程式或服務的呼叫。

下列步驟說明使用 Microsoft Store 提交 API 的端對端程序:

  1. 請確定您已完成所有必要條件。
  2. 在 Microsoft Store 提交 API 中呼叫方法之前,請先取得 Azure AD 存取權杖。 取得權杖之後,您有 60 分鐘的時間使用此權杖來呼叫 Microsoft Store 提交 API,之後權杖才會到期。 權杖過期後,您可以生成新的權杖。
  3. 呼叫 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 使用:

如何將 Azure AD 應用程式與合作夥伴中心帳戶相關聯

您必須先將 Azure AD 應用程式與合作夥伴中心帳戶建立關聯,擷取應用程式的租用戶識別碼和用戶端識別碼,並產生金鑰後,才能使用 MSI 或 EXE 應用程式的 Microsoft Store 提交 API。 Azure AD 應用程式代表您想要從中呼叫 Microsoft Store 提交 API 的應用程式或服務。 您需要租用戶識別碼、用戶端識別碼和金鑰,才能取得傳遞至 API 的 Azure AD 存取全權杖。

注意

您只需要執行此工作一次。 擁有租用戶識別碼、用戶端識別碼和金鑰之後,您可以隨時重複使用,以建立新的 Azure AD 存取權限。

  1. 在合作夥伴中心中,將組織的合作夥伴中心帳戶與組織的 Azure AD 目錄建立關聯
  2. 接下來,從合作夥伴中心的帳戶設定區段中的使用者頁面,新增 Azure AD 應用程式,代表您要用來存取合作夥伴中心帳戶提交的應用程式或服務。 請確定為此應用程式指派管理員角色。 如果應用程式尚不存在於 Azure AD 目錄中,您可以在合作夥伴中心建立新的 Azure AD 應用程式
  3. 返回 [使用者] 頁面,按一下 Azure AD 應用程式的名稱以移至應用程式設定,然後複製 [租用戶識別碼] 和 [用戶端識別碼] 值。
  4. 若要新增金鑰或客戶端密碼,請參閱下列指示,或參閱透過 Azure 入口網站註冊應用程式的指示:

若要註冊您的應用程式:

  1. 登入 Azure 入口網站

  2. 如果您有多個租用戶的存取權,請使用頂端功能表中的「目錄 + 訂閱」篩選條件來切換要在其中註冊應用程式的租用戶。

  3. 搜尋並選取 [Azure Active Directory]。

  4. 在管理底下,選取應用程式註冊>選取您的應用程式。

  5. 選擇證書&密碼>用戶端密碼>新用戶端密碼。

  6. 新增用戶端密碼的描述。

  7. 選取祕密的到期日,或指定自訂存留期。

  8. 用戶端祕密存留期限制為兩年 (24 個月) 或更少。 您無法指定超過 24 個月的自訂存留期。

    注意

    Microsoft 建議您將到期值設定為少於 12 個月。

  9. 選取 [新增]。

  10. 記錄祕密的值,以在用戶端應用程式程式碼中使用。 離開此頁面後,就「不會再次顯示」此祕密值。

步驟 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_idclient_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 呼叫。 為了能夠更新清單資源,並能夠在清單模組中新增/刪除區域設置,可以使用以下方法:

  1. 使用建立清單資產 API 來傳送有關資產上傳的要求,以及資產的語言、類型和計數。
  2. 根據請求的資產數量,按需建立資產 ID,並建立短期 SAS URL 並將其發送回資產類型下的回應正文中。 您可以使用此 URL 透過 HTTP 用戶端上傳特定類型的映像資產 [Put Blob (REST API) - Azure 儲存體 | Microsoft Docs]。
  3. 上傳後,您還可以使用提交清單資產 API 發送先前從先前的 API 呼叫中收到的新資產識別碼資訊。 在驗證之後,單一 API 會在內部認可列出資產資料。
  4. 此方法將有效地覆寫在要求中傳送的特定語言下資產類型上一組先前的影像。 因此,將會移除先前上傳的資產。

路徑:/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 管理提交方面的協助,請使用下列資源:

  • 我們的論壇上提問。
  • 請瀏覽我們的支援頁面,並要求合作夥伴中心的其中一個協助支援選項。 如果系統提示您選擇問題類型和類別,請分別選擇 [應用程式提交和認證] 和 [提交應用程式]。