共用方式為


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

路徑參數

參數 描述
產品ID 產品的合作夥伴中心識別碼
模組名稱 合作夥伴中心模組 – 清單、屬性或可用性

查詢參數

參數 描述
語言 選擇性清單語言會篩選為逗號分隔字串 [限制最多 200 種語言]。

如果不存在,則檢索前 200 種可用的清單語言中繼資料。 [例如「en-us、en-gb」].
包含語言列表 選擇性 布林值 - 如果為 true,則傳回新增的清單語言的清單及其完整性狀態。

必要標頭

頁首
Authorization: Bearer <Token> 使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼
X-Seller-Account-Id 合作夥伴中心帳戶的賣家識別碼

回應標頭

頁首
X-Correlation-ID 每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。
Retry-After 由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。

回應參數

名稱 類型​​ 描述
無障礙支持 布林值
額外授權條款 繩子
可用性 物體 可用性模組資料
類別 繩子 請參閱下列類別清單
認證備註 繩子
程式碼 繩子 訊息的錯誤代碼
聯絡資訊 繩子
著作權 繩子
取決於驅動程式或NT 布林值
說明 繩子
由...開發 繩子
可搜尋性 繩子 [DISCOVERABLE, DEEPLINK_ONLY]
enableInFutureMarkets 布林值
錯誤 物件陣列 錯誤或警告訊息清單 (如有)
freeTrial 繩子 [NO_FREE_TRIAL,FREE_TRIAL]
硬體項目類型 繩子
隱私政策是否必需 布林值
是否推薦 布林值
必需的 布林值
是否成功 布林值
系統功能是否必需 物件陣列
語言 繩子 請參閱下列語言清單
清單 物件陣列 列出每個語言的模組資料
市場 字串陣列 請參閱下面的市場清單
訊息 繩子 錯誤的描述
最低硬體要求 繩子
最低要求 繩子
觸控筆和墨水支持 布林值
價格 繩子 [免費, 免費增值, 訂閱, 付費]
隱私政策網址 繩子
產品聲明 物體
產品功能 字串陣列
內容 物體 屬性模組資料
建議硬體配置 繩子
建議的需求 繩子
回應資料 物體 包含請求的實際回應負載
要求 物件陣列
搜尋詞 字串陣列
簡短描述 繩子
子類別 繩子 請參閱下面的子類別清單
支援聯繫資訊 繩子
系統需求詳情 物件陣列
目標 繩子 錯誤來源的實體
網站 繩子
最新動態 繩子

範例回應

{
    "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 的情況下, 只有需要更新的欄位才會出現在要求中。 請求中的這些欄位值將覆蓋其現有值,保留請求中不存在的所有其他欄位,與該特定模組的當前值相同。

路徑參數

參數 描述
產品ID 產品的合作夥伴中心識別碼

必要標頭

頁首
Authorization: Bearer <Token> 使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼
X-Seller-Account-Id 合作夥伴中心帳戶的賣家識別碼

要求參數

名稱 類型​​ 描述
可用性 物體 要保存可用性模組中繼資料的物件
市場 字串陣列 必要請參閱下面的市場清單
可搜尋性 繩子 必要 [DISCOVERABLE, DEEPLINK_ONLY]
enableInFutureMarkets 布林值 必要
價格 繩子 必要 [FREE, FREEMIUM, SUBSCRIPTION, PAID]
freeTrial 繩子 如果定價是付費或訂用帳戶 [NO_FREE_TRIAL, FREE_TRIAL]
內容 物體 要保存屬性模組中繼資料的物件
隱私政策是否必需 布林值 必要
隱私政策網址 繩子 如果 isPrivacyPolicyRequired = true ,則必須是有效的 URL
網站 繩子 必須為有效的 URL
支援聯繫資訊 繩子 必須是有效的 URL 或電子郵件地址
認證備註 繩子 建議字元限制 = 2000
類別 繩子 必要請參閱下列類別清單
子類別 繩子 必要請參閱下列子類別清單
產品聲明 物體 必要
系統功能是否需要 物件陣列 [觸控,鍵盤,滑鼠,相機,NFC_HCE,NFC_Proximity,Bluetooth_LE,電話,麥克風]
必需的 布林值 必要
是否推薦 布林值 必要
硬體項目類型 繩子 必要
系統需求詳情 物件陣列 [處理器,圖形,記憶體,DirectX,顯示記憶體]
最低要求 繩子 必要 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]
建議的需求 繩子 必要 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]
取決於驅動程式或NT 布林值 必要
無障礙支持 布林值 必要
觸控筆和墨水支持 布林值 必要
清單 物體 反對列出單一語言的模組資料
語言 繩子 必要請參閱下列語言清單
說明 繩子 必要字元限制 = 10000
最新動態 繩子 字元限制 = 1500
產品功能 字串陣列 每個功能 200 個字元;最多 20 個功能
簡短描述 繩子 字元限制 = 1000
搜尋詞 字串陣列 每個搜尋字詞 30 個字元;最多 7 個搜尋字詞

所有搜尋字詞中共有 21 個唯一單字
額外授權條款 繩子 必要字元限制 = 10000
著作權 繩子 字元限制 = 200
由...開發 繩子 字元限制 = 255
要求 物件陣列 每項 200 個字元; 最少和推薦之間總計最多 11 項]
最低硬體要求 繩子 字元限制 = 200
建議硬體配置 繩子 字元限制 = 200
聯絡資訊 繩子 字元限制 = 200
要添加的列表 字串陣列 請參閱下列語言清單
要移除的列表 字串陣列 請參閱下列語言清單

市場

市場 縮寫
阿富汗 自動對焦
阿爾巴尼亞
阿爾及利亞 阿爾及利亞
美屬薩摩亞
安道爾 廣告
安哥拉
安奎拉 人工智慧
南極洲 AQ
安地卡及巴布達
阿根廷 擴增實境 (AR)
亞美尼亞 上午
荷屬阿魯巴 AW
澳洲 AU
奧地利
亞塞拜然 AZ
巴哈馬 理學士
巴林 BH
孟加拉 孟加拉
巴貝多 BB
白俄羅斯
比利時
貝里斯 BZ
貝南 BJ
百慕達 BM
不丹 BT
委內瑞拉玻利瓦爾共和國 佛蒙特州
玻利維亞
波奈 BQ
波士尼亞與赫塞哥維納 波士尼亞與赫塞哥維納
波札那 BW
布威島 BV (必維)
巴西 巴西
英屬印度洋領地 IO
英屬維京群島 VG
汶萊
保加利亞 BG
布吉納法索 男友 / 好朋友
蒲隆地 商業智慧 (BI)
柬埔寨 柬埔寨
喀麥隆 喀麥隆
加拿大 CA
維德角 簡歷
開曼群島 肯塔基州
中非共和國 CF
查德 TD
智利 CL
中國 中國
聖誕島 客戶體驗
科克斯 (基靈) 群島 抄送
哥倫比亞 一氧化碳
葛摩 公里
剛果 電腦圖形 (CG)
剛果民主共和國 CD(光碟)
庫克群島 肌酸激酶
哥斯大黎加 哥斯大黎加
克羅埃西亞 人力資源
庫拉索 連續波
賽普勒斯 CY
捷克共和國 捷克
象牙海岸 (科特迪瓦) 持續整合
丹麥 DK
吉布地 DJ(音樂主持人)
多米尼克 分米
多明尼加共和國 D 0
厄瓜多 電子商務 (EC)
埃及 埃及
薩爾瓦多 薩爾瓦多
赤道幾內亞 GQ
厄利垂亞 急診室
愛沙尼亞 EE
衣索比亞 ET
福克蘭群島 FK
法羅群島 F0
斐濟 斐濟
芬蘭 FI
法國 法國
法屬圭亞那 女朋友
法屬玻里尼西亞 PF (功率因數)
法屬南部和南極土地 TF 系列
加彭 加語
甘比亞 通用汽車
喬治亞 通用電氣
德國
迦納 迦納
直布羅陀 胃腸
希臘 希臘
格陵蘭 GL
格瑞那達 GD
瓜地洛普 全科醫生
關島
瓜地馬拉 燃氣輪機
根息 GG
幾內亞 GN
幾內亞比索 GW
蓋亞那 GY
海地 HT
赫德島及麥當勞群島 HM
梵蒂岡 VA (弗吉尼亞州)
宏都拉斯 宏都拉斯
香港特別行政區 香港
匈牙利 匈牙利
冰島 伊斯蘭國
印度 在內
印尼 識別碼
伊拉克 伊拉克
愛爾蘭 網路瀏覽器 (Internet Explorer)
以色列 伊利諾州
義大利 資訊技術
牙買加 牙買加
日本 日本
澤西島 日本腦炎
約旦 約旦
哈薩克 哈薩克
肯亞 KB
吉里巴斯 KI
南韓 韓國
科威特 千瓦
吉爾吉斯 吉爾吉斯
寮國 洛杉磯
拉脫維亞 低壓
黎巴嫩
賴索托 LS
賴比瑞亞 LR
利比亞
列支敦斯登
立陶宛 LT
盧森堡
澳門特別行政區 密蘇里
北馬其頓 北馬其頓
馬達加斯加 毫克
馬拉威 兆瓦
馬來西亞
馬爾地夫 音樂錄影帶
馬利 機器學習
馬爾他 MT
曼島 即時通信
馬紹爾群島 MH
馬丁尼克 MQ
茅利塔尼亞 先生
模里西斯 模里西斯
馬約特島 YouTube
墨西哥 墨西哥
密克羅尼西亞 調頻
摩爾多瓦 醫學博士
摩納哥 司儀
蒙古 明尼蘇達州
黑山 - ME
蒙哲臘 毫秒
摩洛哥 麻州
莫三比克 MZ
緬甸 毫米
納米比亞
諾魯 未評分
尼泊爾 NP
荷蘭 荷蘭
新喀里多尼亞 數控
紐西蘭 紐西蘭
尼加拉瓜
尼日 NE
奈及利亞 奈及利亞
紐埃島
諾福克島 NF 系列
北馬利安納群島 國會議員
挪威
阿曼 OM
巴基斯坦 PK
帛琉 密碼
巴勒斯坦民族權力機構 附註
巴拿馬 PA
巴布亞紐幾內亞 PG (輔導級)
巴拉圭 巴拉圭
秘魯 體育課
菲律賓 酸鹼度
皮特肯群島 PN
波蘭 波蘭
葡萄牙 物理治療師
卡達 問與答
留尼旺 回覆:
羅馬尼亞 RO
俄羅斯 俄羅斯
盧安達 烏爾曼
聖巴瑟米 BL
聖赫勒拿、阿森松和特里斯坦達庫尼亞群島 上海
聖克里斯多福及尼維斯 KN
聖露西亞 立法會
聖馬丁 (法國部分) MF
聖皮埃與密克隆群島 下午
聖文森及格瑞那丁 風險投資 (VC)
薩摩亞 WS
聖馬利諾 SM
沙烏地阿拉伯 SA
塞內加爾 序列號 (if "SN" stands for serial number)
塞爾維亞 RS
塞席爾 SC
獅子山 SL
新加坡 新加坡
聖馬滕 (荷蘭部分) SX
斯洛伐克 SK
斯洛維尼亞 國際單位制
索羅門群島 某人
索馬利亞 所以
南非 南非
南喬治亞及南三明治群島 GS
西班牙 ES
斯里蘭卡 斯里蘭卡
蘇利南
挪威屬斯瓦巴及尖棉 SJ
史瓦濟蘭 SZ
瑞典
瑞士 CH
聖多美普林西比
台灣 台灣
塔吉克 TJ
坦尚尼亞 坦尚尼亞
泰國 TH
東帝汶 TL
Tog - TG
托克勞群島 TK
東加 收件人
千里達及托巴哥 - TT
突尼西亞 TN
土耳其 土耳其
土庫曼 TM (商標)
土克斯及開科斯群島 TC
吐瓦魯 電視
美國外島
美屬維爾京群島
烏干達 烏干達
烏克蘭 烏克蘭
阿拉伯聯合大公國 阿拉伯聯合大公國
英國 國標
美國 美國
烏拉圭 烏拉圭
烏茲別克 烏茲別克
萬那杜 VU
越南 VN
瓦利斯群島和富圖那群島 WF
葉門 你們
尚比亞 尚比亞
辛巴威 辛巴威
奧蘭群島 斧頭

類別和子類別

類別 子類別
BooksAndReference EReader、小說、非小說、參考書
業務 AccountingAndfinance、Collaboration、CRM、DataAndAnalytics、FileManagement、InventoryAndlogistics、LegalAndHR、ProjectManagement、RemoteDesktop、SalesAndMarketing、TimeAndExpenses
DeveloperTools 資料庫、設計工具、開發套件、網路、參考與訓練、伺服器、公用程式、網頁託管
教育程度 教育書籍與參考資料、早期學習、教學工具、語言、學習輔助
娛樂 (無)
FoodAndDining (無)
政府與政治 (無)
健康與健身 (無)
KidsAndFamily KidsAndFamilyBooksAndReference、KidsAndFamilyEntertainment、HobbiesAndToys、SportsAndActivities、KidsAndFamilyTravel
生活方式 汽車、DIY、居家與園藝、人際關係、特殊興趣、風格與時尚
醫療 (無)
多媒體設計 插畫與平面設計、音樂製作、攝影與影片製作
音樂 (無)
導航與地圖 (無)
新聞與天氣 新聞、天氣
個人理財 銀行與投資、預算與稅務
個人化 鈴聲和聲音,主題,桌布和鎖定畫面
PhotoAndVideo (無)
生產力 (無)
安全性 PCProtection、PersonalSecurity
購物 (無)
社交網路 (無)
運動 (無)
旅遊 CityGuides, Hotels
公用程式和工具 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
菲利平 - 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
Igb - ig-latn,ig-ng
印尼文 id、id-id
伊努克提圖語(拉丁字母) iu-cans、iu-latn、iu-latn-ca
愛爾蘭文 ga、ga-ie
科薩文 xh、xh-za
祖魯文 zu、zu-za
義大利文 it、it-it、it-ch
日文 ja 、ja-jp
坎那達文 kn、kn-in
哈薩克文 kk、kk-kz
高棉文 km、km-kh
K'iche' quc-latn、qut-gt、qut-latn
盧安達文 rw、rw-rw
斯瓦希里語 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 之前需要等待的時間 (以秒為單位)

回應參數

名稱 類型​​ 描述
是否成功 布林值
錯誤 物件陣列 錯誤或警告訊息清單 (如有)
程式碼 繩子 訊息的錯誤代碼
訊息 繩子 錯誤的描述
目標 繩子 錯誤來源的實體
回應資料 物體 包含請求的實際回應負載
輪詢網址 繩子 輪詢 URL 以取得任何進行中提交的狀態
持續提交ID 繩子 任何進行中提交的提交標識碼

範例回應

{
    "isSuccess": true,
    "errors": [{
        "code": "badrequest",
        "message": "Error Message 1",
        "target": "listings"
        }, {
        "code": "warning",
        "message": "Warning Message 1",
        "target": "properties"
    }],
    "responseData": {
        "pollingUrl": "/submission/v1/product/{productId}/submission/{submissionId}/status",
        "ongoingSubmissionId": ""
    } 
}

取得目前的草稿套件 API

擷取目前草稿提交下的套件詳細資料。

路徑 [所有套件]:/submission/v1/product/{productId}/packages
方法:GET

路徑 [單一套件]:/submission/v1/product/{productId}/packages/{packageId}
方法:GET

路徑參數

名稱 描述
產品ID 產品的合作夥伴中心識別碼
軟體包ID 要取得的封裝唯一識別碼 (ID)。

必要標頭

頁首
Authorization: Bearer <Token> 使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼
X-Seller-Account-Id 合作夥伴中心帳戶的賣家識別碼

回應標頭

頁首
X-Correlation-ID 每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。
Retry-After 由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。

回應參數

名稱 類型​​ 描述
是否成功 布林值
錯誤 物件陣列 錯誤或警告訊息清單 (如有)
程式碼 繩子 訊息的錯誤代碼
訊息 繩子 錯誤的描述
目標 繩子 錯誤來源的實體
回應資料 物體
套件 物件陣列 要保存封裝模組資料的物件
軟體包ID 繩子
套件網址 繩子
語言 字串陣列
架構 字串陣列 [Neutral, X86, X64, Arm, Arm64]
isSilentInstall(靜默安裝) 布林值 如果您的安裝程式以無訊息模式執行,而不需要切換或其他 false,則這應該標示為 true
安裝參數 繩子
genericDocUrl 繩子
錯誤詳情 物件陣列
錯誤場景 繩子
錯誤情境詳情 物件陣列
錯誤值 繩子
錯誤網址 繩子
PackageType 繩子

範例回應

{   
    "isSuccess": true,
    "errors": [{
        "code": "badrequest",
        "message": "Error Message 1",
        "target": "listings"
    }, {
        "code": "warning",
        "message": "Warning Message 1",
        "target": "properties"
    }],
    "responseData":{
        "packages":[{
            "packageId": "pack0832",
            "packageUrl": "https://www.contoso.com/downloads/1.1/setup.exe",
            "languages": ["en-us"],
            "architectures": ["X86"],
            "isSilentInstall": true,
            "installerParameters": "/s",
            "genericDocUrl": "https://docs.contoso.com/doclink",
            "errorDetails": [{
                "errorScenario": "rebootRequired",
                "errorScenarioDetails": [{
                    "errorValue": "ERR001001",
                    "errorUrl": "https://errors.contoso.com/errors/ERR001001"
                }]
            }],
            "packageType": "exe",
        }]
    }
}

更新目前的草稿套件 API

更新目前草稿提交下的套件詳細資料。

路徑 [完整模組更新]:/submission/v1/product/{productId}/packages
方法:PUT

路徑 [單一套件修補檔更新]:/submission/v1/product/{productId}/packages/{packageId}
方法:PATCH

API 行為

在完整模組更新 API 的情況下,整個套件資料需要出現在請求中才能完整更新每個欄位。 請求中不存在的任何欄位,其預設值用於覆蓋該特定模組的當前值。 這會導致從要求使用一組新套件覆寫所有現有的套件。 這會導致套件標識碼的重新產生,而用戶應該呼叫 GET Packages API 以取得最新的套件識別碼。

在單一套件修補程式更新 API 的案例中,只有要針對指定套件更新的欄位必須存在於要求中。 請求中的這些欄位值將覆蓋其現有值,保留請求中不存在的所有其他字段,與該特定套件的當前值相同。 集合中的其他套件會維持原樣。

路徑參數

名稱 描述
產品ID 產品的合作夥伴中心識別碼
軟體包ID 封裝的唯一識別碼

必要標頭

頁首
Authorization: Bearer <Token> 使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼
X-Seller-Account-Id 合作夥伴中心帳戶的賣家識別碼

要求參數

名稱 類型​​ 描述
套件 物件陣列 保留封裝模組資料的物件 [僅適用於完整模組更新的必要專案]
套件網址 繩子 必要
語言 字串陣列 必要
架構 字串陣列 必要 應包含單一架構 - Neutral、X86、X64、Arm、Arm64
isSilentInstall(靜默安裝) 布林值 必要 如果您的安裝程式以無訊息模式執行,而不需要切換或其他 false,則這應該標示為 true
安裝參數 繩子 如果isSilentInstall 為 false,則為必要項
genericDocUrl 繩子 如果 packageType 是 exe 包含 EXE 類型安裝程式的自訂錯誤代碼詳細資訊的文件連結
錯誤詳情 物件陣列 用於保存 EXE 類型安裝程式的自訂錯誤代碼和詳細資訊的中繼資料。
錯誤場景 繩子 識別特定的錯誤案例。 [安裝已被使用者取消,應用程式已存在,安裝已在進行中,磁碟空間已滿,需要重新開機,網路故障,安裝過程中封包被拒,安裝成功,其他]
錯誤情境詳情 物件陣列
錯誤值 繩子 安裝期間可能出現的錯誤碼
錯誤網址 繩子 取得錯誤詳細資料的 URL
PackageType 繩子 必要 [exe, msi]

範例要求 [完整模組更新]

{
    "packages":[{
        "packageUrl": "https://www.contoso.com/downloads/1.1/setup.exe",
        "languages": ["en-us"],
        "architectures": ["X86"],
        "isSilentInstall": true,
        "installerParameters": "/s",
        "genericDocUrl": "https://docs.contoso.com/doclink",
        "errorDetails": [{
            "errorScenario": "rebootRequired",
            "errorScenarioDetails": [{
                "errorValue": "ERR001001",
                "errorUrl": "https://errors.contoso.com/errors/ERR001001"
            }]
        }],
        "packageType": "exe",
    }]
}

範例要求 [單一套件修補程式更新]

{
    "packageUrl": "https://www.contoso.com/downloads/1.1/setup.exe",
    "languages": ["en-us"],
    "architectures": ["X86"],
    "isSilentInstall": true,
    "installerParameters": "/s",
    "genericDocUrl": "https://docs.contoso.com/doclink",
    "errorDetails": [{
        "errorScenario": "rebootRequired",
        "errorScenarioDetails": [{
            "errorValue": "ERR001001",
            "errorUrl": "https://errors.contoso.com/errors/ERR001001"
        }]
    }],
    "packageType": "exe",
}

回應標頭

頁首
X-Correlation-ID 每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。
Retry-After 由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。

回應參數

名稱 類型​​ 描述
是否成功 布林值
錯誤 物件陣列 [錯誤或警告訊息清單 (如有)]
程式碼 繩子 訊息的錯誤代碼
訊息 繩子 錯誤的描述
目標 繩子 錯誤來源的實體
回應資料 物體
輪詢網址 繩子 [輪詢 URL 以取得任何已進行中的提交狀態]
持續提交ID 繩子 [任何進行中提交的提交標識碼]

範例回應

{
    "isSuccess": true,
    "errors": [{
        "code": "badrequest",
        "message": "Error Message 1",
        "target": "listings"
        }, {
        "code": "warning",
        "message": "Warning Message 1",
        "target": "properties"
    }],
    "responseData": {
        "pollingUrl": "/submission/v1/product/{productId}/submission/{submissionId}/status",
        "ongoingSubmissionId": ""
    } 
}

認可套件 API

在目前的草稿提交下,認可使用套件更新 API 更新的新套件集。 此 API 會傳迴輪詢 URL 來追蹤套件上傳。

路徑:/submission/v1/product/{productId}/packages/commit
方法:POST

路徑參數

名稱 描述
產品ID 產品的合作夥伴中心識別碼

必要標頭

頁首
Authorization: Bearer <Token> 使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼
X-Seller-Account-Id 合作夥伴中心帳戶的賣家識別碼

回應標頭

頁首
X-Correlation-ID 每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。
Retry-After 由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。

回應參數

名稱 類型​​ 描述
是否成功 布林值
錯誤 物件陣列 [錯誤或警告訊息清單 (如有)]
程式碼 繩子 訊息的錯誤代碼
訊息 繩子 錯誤的描述
目標 繩子 錯誤來源的實體
回應資料 物體
輪詢網址 繩子 [輪詢 URL 以取得套件上傳或提交狀態,以防任何已進行中提交]
持續提交ID 繩子 [任何進行中提交的提交標識碼]

範例回應

{
    "isSuccess": true,
    "errors": [{
        "code": "badrequest",
        "message": "Error Message 1",
        "target": "listings"
        }, {
        "code": "warning",
        "message": "Warning Message 1",
        "target": "properties"
    }],
    "responseData": {
        "pollingUrl": "/submission/v1/product/{productId}/status",
        "ongoingSubmissionId": ""
    } 
}

取得目前的草稿清單資產 API

擷取目前草稿提交下的資產詳細資料。

路徑:/submission/v1/product/{productId}/listings/assets?languages={languages}
方法:GET

路徑參數

名稱 描述
產品ID 產品的合作夥伴中心識別碼

查詢參數

名稱 描述
語言 [選擇性] 清單語言會篩選為逗號分隔字串 [限制最多 200 種語言]。 如果不存在,則會擷取前 200 個可用清單語言的資產資料。 (例如「en-us、en-gb」)

必要標頭

頁首
Authorization: Bearer <Token> 使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼
X-Seller-Account-Id 合作夥伴中心帳戶的賣家識別碼

回應標頭

頁首
X-Correlation-ID 每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。
Retry-After 由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。

回應參數

名稱 類型​​ 描述
是否成功 布林值
錯誤 物件陣列 錯誤或警告訊息清單 (如有)
程式碼 繩子 訊息的錯誤代碼
訊息 繩子 錯誤的描述
目標 繩子 錯誤來源的實體
回應資料 物體
資產列表 物件陣列 列出每個語言的資產詳細資料
語言 繩子
店鋪標誌 物件陣列
螢幕擷取畫面 物件陣列
識別碼 繩子
資產網址 繩子 必須為有效的 URL
影像大小 物體
寬度 整數
高度 整數

範例回應

{   
"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

路徑參數

名稱 描述
產品ID 產品的合作夥伴中心識別碼

必要標頭

頁首 描述
Authorization: Bearer <Token> 使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼
X-Seller-Account-Id 合作夥伴中心帳戶的賣家識別碼

要求參數

名稱 類型​​ 描述
語言 繩子 必要
建立資產請求 物體 必要
螢幕截圖 整數 如果ISV需要更新螢幕快照或新增清單語言 [1 - 10],則為必要專案
標誌 整數 如果ISV需要更新螢幕快照或新增清單語言 [1 或 2],則為必要專案

回應標頭

頁首 描述
X-Correlation-ID 每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。
Retry-After 由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。

回應參數

名稱 類型​​ 描述
是否成功 布林值
錯誤 物件陣列 錯誤或警告訊息清單 (如有)
程式碼 繩子 訊息的錯誤代碼
訊息 繩子 錯誤的描述
目標 繩子 錯誤來源的實體
回應資料 物體
資產列表 物體 包含要上傳的 StoreLogos 和螢幕擷取畫面詳細資訊的物件
語言 繩子
店鋪標誌 物件陣列
螢幕擷取畫面 物件陣列
識別碼 繩子
主要資產上傳網址 繩子 使用 Azure Blob REST API 上傳清單資產的主要 URL
secondaryAssetUploadUrl(次要資產上傳URL) 繩子 使用 Azure Blob REST API 上傳清單資產的次要 URL
httpMethod HTTP 方法 HTTP 方法必須用來透過資產上傳 URL 上傳資產 – 主要或次要
HTTP 標頭 物體 一個物件,其鍵作為所需標頭出現在對資產上傳 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

路徑參數

名稱 描述
產品ID 產品的合作夥伴中心識別碼

必要標頭

頁首 描述
Authorization: Bearer <Token> 使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼
X-Seller-Account-Id 合作夥伴中心帳戶的賣家識別碼

要求參數

名稱 類型​​ 描述
資產列表 物體
語言 繩子
店鋪標誌 物件陣列
螢幕擷取畫面 物件陣列
識別碼 繩子 應該是使用者想要從取得目前列表資產 API 保存的現有識別碼,或是建立清單資產 API 中上傳新資產的新標識碼。
資產網址 繩子 應該是使用者希望從取得目前清單資產 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 之前需要等待的時間 (以秒為單位)。

回應參數

名稱 類型​​ 描述
是否成功 布林值
錯誤 物件陣列 錯誤或警告訊息清單 (如有)
程式碼 繩子 訊息的錯誤代碼
訊息 繩子 錯誤的描述
目標 繩子 錯誤來源的實體
回應資料 物體
輪詢網址 繩子 輪詢 URL 以取得任何進行中提交的狀態
持續提交ID 繩子 任何進行中提交的提交標識碼

範例回應

{
    "isSuccess": true,
    "errors": [{
        "code": "badrequest",
        "message": "Error Message 1",
        "target": "listings"
        }, {
        "code": "warning",
        "message": "Warning Message 1",
        "target": "properties"
    }],
    "responseData": {
        "pollingUrl": "/submission/v1/product/{productId}/submission/{submissionId}/status",
        "ongoingSubmissionId": ""
    } 
}

模組狀態輪詢 API

建立提交之前檢查模組整備程度的 API。 也會驗證套件上傳狀態。

路徑:/submission/v1/product/{productId}/status
方法:GET

路徑參數

名稱 描述
產品ID 產品的合作夥伴中心識別碼

必要標頭

頁首 描述
Authorization: Bearer <Token> 使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼
X-Seller-Account-Id 合作夥伴中心帳戶的賣家識別碼

回應標頭

頁首 描述
X-Correlation-ID 每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。
Retry-After 由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。

回應參數

名稱 類型​​ 描述
是否成功 布林值
錯誤 物件陣列 錯誤或警告訊息清單 (如有)
程式碼 繩子 訊息的錯誤代碼
訊息 繩子 錯誤的描述
目標 繩子 錯誤來源的實體
回應資料 物體
準備好 布林值 指出所有模組是否處於就緒狀態,包括套件上傳
持續提交ID 繩子 任何進行中提交的提交標識碼

範例回應

{
    "isSuccess": true,
    "errors": [{
        "code": "badrequest",
        "message": "Error Message 1",
        "target": "listings"
        }, {
        "code": "warning",
        "message": "Warning Message 1",
        "target": "properties"
    }],
    "responseData": {
        "isReady": true,
        "ongoingSubmissionId": ""
    }
}

建立提交 API

從 MSI 或 EXE 應用程式的目前草稿建立提交。 API 檢查:

  • 針對作用中提交,如果作用中提交存在,則失敗並出現錯誤訊息。
  • 如果所有模組都處於就緒狀態以建立提交,則為 。
  • 提交中的每個欄位均根據商店的要求進行驗證

路徑:/submission/v1/product/{productId}/submit
方法:POST

路徑參數

名稱 描述
產品ID 產品的合作夥伴中心識別碼

必要標頭

頁首 描述
Authorization: Bearer <Token> 使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼
X-Seller-Account-Id 合作夥伴中心帳戶的賣家識別碼

回應標頭

頁首 描述
X-Correlation-ID 每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。
Retry-After 由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。

回應參數

名稱 類型​​ 描述
是否成功 布林值
錯誤 物件陣列 錯誤或警告訊息清單 (如有)
程式碼 繩子 訊息的錯誤代碼
訊息 繩子 錯誤的描述
目標 繩子 錯誤來源的實體
回應資料 物體
輪詢網址 繩子 輪詢 URL 以取得模組整備狀態,包括提交套件上傳
提交編號 (submissionId) 繩子 新建立提交的標識碼
持續提交ID 繩子 任何進行中提交的提交標識碼

範例回應

{
    "isSuccess": true,
    "errors": [{
        "code": "badrequest",
        "message": "Error Message 1",
        "target": "listings"
        }, {
        "code": "warning",
        "message": "Warning Message 1",
        "target": "properties"
    }],
    "responseData": {
        "submissionId": "1234567890", 
        "pollingUrl": "/submission/v1/product/{productId}/submission/{submissionId}/status",
        "ongoingSubmissionId": ""
    }
}

提交狀態輪詢 API

檢查提交狀態的 API。

路徑:/submission/v1/product/{productId}/submission/{submissionId}/status
方法:GET

路徑參數

名稱 描述
產品ID 產品的合作夥伴中心識別碼

必要標頭

頁首 描述
Authorization: Bearer <Token> 使用合作夥伴中心帳戶註冊的 Azure AD 應用程式識別碼
X-Seller-Account-Id 合作夥伴中心帳戶的賣家識別碼

回應標頭

頁首 描述
X-Correlation-ID 每個請求的 GUID 類型唯一識別碼。 可以與支援團隊分享此資訊以分析任何問題。
Retry-After 由於速率限制,客戶端在再次呼叫 API 之前需要等待的時間 (以秒為單位)。

回應參數

名稱 類型​​ 描述
是否成功 布林值
錯誤 物件陣列 錯誤或警告訊息清單 (如有)
程式碼 繩子 訊息的錯誤代碼
訊息 繩子 錯誤的描述
目標 繩子 錯誤來源的實體
回應資料 物體
出版狀態 繩子 提交狀態 - [INPROGRESS, PUBLISHED, FAILED, UNKNOWN]
已失敗 布林值 指出發佈是否失敗且不會重試

範例回應

{
    "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 安裝 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 管理提交方面的協助,請使用下列資源:

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