Compartir a través de


API de envío de Microsoft Store para aplicaciones MSI o EXE

Utilice la API de envío de Microsoft Store para aplicaciones MSI o EXE para consultar y crear envíos mediante programación para aplicaciones MSI o EXE para la cuenta del Centro de partners de su organización. Esta API es útil si su cuenta administra muchas aplicaciones y desea automatizar y optimizar el proceso de envío de estos recursos. Esta API utiliza Azure Active Directory (Azure AD) para autenticar las llamadas desde la aplicación o el servicio.

En los pasos siguientes se describe el proceso completo para utilizar la API de envío de Microsoft Store:

  1. Asegúrese de haber completado todos los requisitos previos.
  2. Antes de llamar a un método en la API de envío de Microsoft Store, obtenga un token de acceso de Azure AD. Después de obtener un token, tiene 60 minutos para utilizar este token en llamadas a la API de envío de Microsoft Store antes de que el token expire. Después de que el token expire, puede generar uno nuevo.
  3. Llame a la API de envío de Microsoft Store para la aplicación MSI o EXE.

Paso 1: Completar los requisitos previos para utilizar la API de envío de Microsoft Store

Antes de empezar a escribir código para llamar a la API de envío de Microsoft Store para la aplicación MSI o EXE, asegúrese de haber completado los requisitos previos a continuación.

  • Usted (o su organización) tiene que tener un directorio de Azure AD y el permiso de Administrador global para el directorio. Si usa Microsoft 365 u otros servicios empresariales de Microsoft, ya tiene el directorio de Azure AD. De lo contrario, puede crear una nueva instancia de Azure AD en el Centro de partners sin cargo adicional.
  • Tiene que asociar una aplicación Azure AD a su cuenta del Centro de partners y obtener el identificador de inquilino, el identificador de cliente y la clave. Necesitará estos valores para obtener un token de acceso de Azure AD, que usará en las llamadas a la API de envío de Microsoft Store.
  • Prepare la aplicación para utilizarla con la API de envío de Microsoft Store:
    • Si la aplicación aún no existe en el Centro de partners, para crearla, debe reservar el nombre en el Centro de partners. No puede utilizar la API de envío de Microsoft Store para crear una aplicación en el Centro de partners; debe trabajar en el Centro de partners para crearla y, después, puede utilizar la API para acceder a la aplicación y crear envíos mediante programación para ella.
    • Para poder crear un envío para una aplicación determinada mediante esta API, primero debe crear un envío para la aplicación en el Centro de partners, incluida la respuesta al cuestionario de clasificaciones por edades . Después de esto, podrá crear mediante programación nuevos envíos para esta aplicación a través de la API.
    • Si va a crear o actualizar un envío de aplicación y tiene que incluir un nuevo paquete, prepare los detalles del paquete.
    • Si va a crear o actualizar un envío de aplicación y tiene que incluir capturas de pantalla o imágenes para la descripción de Store, prepare las capturas de pantalla e imágenes de la aplicación.

Cómo asociar una aplicación de Azure AD con la cuenta del Centro de partners

Antes de poder utilizar la API de envío de Microsoft Store para aplicaciones MSI o EXE, tiene que asociar una aplicación de Azure AD a su cuenta del Centro de partners, recuperar el identificador de inquilino y el identificador de cliente para la aplicación y generar una clave. La aplicación de Azure AD representa la aplicación o el servicio desde donde desea llamar a la API de envío de Microsoft Store. Necesita el identificador de inquilino, el identificador de cliente y la clave para obtener un token de acceso de Azure AD para pasar a la API.

Nota:

Solo tiene que realizar esta tarea una vez. Una vez que tenga el identificador de inquilino, el identificador de cliente y la clave, puede volver a usarlos cada vez que tenga que crear un nuevo token de acceso de Azure AD.

  1. En el Centro de partners, asocie la cuenta del Centro de partners de la organización con el directorio de Azure AD de la organización.
  2. A continuación, en la página Usuarios en la sección Configuración de la cuenta del Centro de partners, agregue la aplicación de Azure AD que representa la aplicación o el servicio que utilizará para acceder a los envíos de la cuenta del Centro de partners. Asegúrese de asignar a esta aplicación el rol Administrador. Si la aplicación aún no existe en el directorio de Azure AD, puede crear una nueva aplicación de Azure AD en el Centro de partners.
  3. Vuelva a la página Usuarios, haga clic en el nombre de la aplicación de Azure AD para ir a la configuración de la aplicación y, a continuación, copie los valores de Identificador de inquilino e Identificador de cliente.
  4. Para agregar una nueva clave o un secreto de cliente, consulte las instrucciones siguientes o consulte las instrucciones para registrar la aplicación a través de Azure Portal:

Para registrar la aplicación:

  1. Inicie sesión en Azure Portal.

  2. Si tiene acceso a varios inquilinos, use el filtro Directorios y suscripciones del menú superior para ir al inquilino en el que quiere registrar la aplicación.

  3. Busque y seleccione Azure Active Directory.

  4. En Administrar, seleccione Registros de aplicaciones > Seleccionar la aplicación.

  5. Seleccione Certificados y secretos > Secretos de cliente > Nuevo secreto de cliente.

  6. Agregue una descripción para el secreto de cliente.

  7. Seleccione una expiración para el secreto o especifique una duración personalizada.

  8. La duración del secreto de cliente se limita a dos años (24 meses) o menos. No se puede especificar una duración personalizada superior a 24 meses.

    Nota:

    Microsoft recomienda establecer un valor de expiración de menos de 12 meses.

  9. Seleccione Agregar.

  10. Registre el valor del secreto para su uso en el código de la aplicación cliente. Este valor secreto no se volverá a mostrar una vez que abandone esta página.

Paso 2: Obtener un token de acceso de Azure AD

Antes de llamar a cualquiera de los métodos de la API de envío de Microsoft Store para aplicaciones MSI o EXE, primero tiene que obtener un token de acceso de Azure AD que pase al encabezado de autorización de cada método de la API. Una vez que haya obtenido un token de acceso, tiene 60 minutos para usarlo antes de que expire. Una vez que expire el token, puede actualizarlo para poder seguir utilizándolo en llamadas adicionales a la API.

Para obtener el token de acceso, siga las instrucciones de Llamadas de servicio a servicio mediante credenciales de cilente/azure/active-directory/azuread-dev/v1-oauth2-client-creds-grant-flow) para enviar un HTTP POST al punto de conexión https://login.microsoftonline.com/<tenant_id>/oauth2/token. Esta es una solicitud de ejemplo.

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

Para el valor tenant_id en el URI de POST y los parámetros client_id y client_secret, especifique el identificador de inquilino, el identificador de cliente y la clave de la aplicación que recuperó del Centro de partners en la sección anterior. Para el parámetro scope, tiene que especificar https://api.store.microsoft.com/.default.

Una vez que expire el token de acceso, puede seguir las instrucciones que se indican aquí para actualizarlo.

Para obtener ejemplos que muestran cómo obtener un token de acceso mediante C# o Node.js, consulte los ejemplos de código para la API de envío de Microsoft Store para aplicaciones MSI o EXE.

Paso 3: Utilizar la API de envío de Microsoft Store

Una vez que tenga un token de acceso de Azure AD, puede llamar a los métodos en la API de envío de Microsoft Store para aplicaciones MSI o EXE. La API incluye muchos métodos que se agrupan en escenarios para las aplicaciones. Para crear o actualizar envíos, normalmente se llama a varios métodos en un orden específico. Para obtener información acerca de cada escenario y la sintaxis de cada método, consulte las secciones siguientes:

Nota:

Después de obtener un token de acceso, tiene 60 minutos para llamar a los métodos en la API de envío de Microsoft Store para aplicaciones MSI o EXE antes de que el token expire.

URL base

La URL base de la API de envío de Microsoft Store para aplicaciones MSI o EXE es https://api.store.microsoft.com

Contratos de API

API Get Current Draft Submission Metadata

Captura los metadatos de cada módulo (descripciones, propiedades o disponibilidad) en el envío del borrador actual.

Ruta de acceso [todos los módulos]: /submission/v1/product/{productId}/metadata?languages={languages}&includelanguagelist={true/false}
Ruta de acceso [módulo único]: /submission/v1/product/{productId}/metadata/{moduleName}?languages={languages}&includelanguagelist={true/false}
Método: GET

Parámetros de la ruta de acceso

Parámetro Descripción
productId Identificador del producto en el Centro de partners
moduleName Módulo del Centro de partners: descripciones, propiedades o disponibilidad

Parámetros de consulta

Parámetro Descripción
languages Opcional Los idiomas de descripción se filtran como cadena separada por comas [límite de hasta 200 idiomas].

Si no está presente, se recuperan los metadatos de los primeros 200 idiomas de descripción disponibles. [Por ejemplo, "en-us, en-gb"].
includelanguagelist Opcional Booleano: si es verdadero, devuelve la lista de idiomas de descripción agregados y su estado de integridad.

Encabezados obligatorios

Encabezado Valor
Authorization: Bearer <Token> El identificador de la aplicación de Azure AD registrado en la cuenta del Centro de partners
X-Seller-Account-Id Id. de vendedor de la cuenta del Centro de partners

Encabezados de respuesta

Encabezado Valor
X-Correlation-ID Id. exclusivo de tipo GUID para cada solicitud. Se puede compartir con el equipo de soporte técnico para analizar cualquier problema.
Retry-After Tiempo en segundos que el cliente debe esperar antes de llamar a las API de nuevo debido a la limitación de volumen.

Parámetros de respuesta

Nombre Escribir Descripción
accessibilitySupport Booleano
additionalLicenseTerms Cadena
disponibilidad Object Datos del módulo de disponibilidad
category Cadena Consulte la lista de categorías más adelante
certificationNotes Cadena
code Cadena El código de error del mensaje.
contactInfo Cadena
copyright Cadena
dependsOnDriversOrNT Booleano
descripción Cadena
developedBy Cadena
discoverability Cadena [DISCOVERABLE, DEEPLINK_ONLY]
enableInFutureMarkets Booleano
errors Matriz de objetos Lista de mensajes de error o advertencia, si los hay
freeTrial Cadena [NO_FREE_TRIAL, FREE_TRIAL]
hardwareItemType Cadena
isPrivacyPolicyRequired Booleano
isRecommended Booleano
isRequired Booleano
isSuccess Booleano
isSystemFeatureRequired Matriz de objetos
language Cadena Consulte la lista de idiomas más adelante
listings Matriz de objetos Datos del módulo de descripciones para cada idioma
markets Matriz de cadenas Consulte la lista de mercados más adelante
message String Descripción del error
minimumHardware Cadena
minimumRequirement Cadena
penAndInkSupport Booleano
Precios Cadena [FREE, FREEMIUM, SUBSCRIPTION, PAID]
privacyPolicyUrl Cadena
productDeclarations Object
productFeatures Matriz de cadenas
properties Object Datos del módulo de propiedades
recommendedHardware Cadena
recommendedRequirement Cadena
responseData Object Contiene la carga de respuesta real para la solicitud.
requisitos Matriz de objetos
searchTerms Matriz de cadenas
shortDescription Cadena
subcategory Cadena Consulte la lista de subcategorías más adelante
supportContactInfo Cadena
systemRequirementDetails Matriz de objetos
Destino Cadena Entidad desde la que se originó el error
sitio web Cadena
whatsNew Cadena

Respuesta de ejemplo

{
    "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 Update Current Draft Submission Metadata

Actualiza los metadatos en cada módulo bajo el borrador de envío. La API comprueba:

  • Para el envío activo. Si existe, error con el mensaje de error.
  • Si todos los módulos están en estado listo para permitir la operación Guardar borrador.
  • Cada campo del envío se valida según los requisitos de Store.
  • Reglas de validación de detalles de los requisitos del sistema:
    • Valores permitidos en hardwareItemType = Memory: 300 MB, 750 MB, 1 GB, 2 GB, 4 GB, 6 GB, 8 GB, 12 GB, 16 GB, 20 GB
    • Valores permitidos en hardwareItemType = DirectX: DX9, DX10, DX11, DX12-FEATURELEVEL11, DX12-FEATURELEVEL12
    • Valores permitidos en hardwareItemType = Video_Memory: 1 GB, 2 GB, 4 GB, 6 GB

Ruta de acceso [actualización del módulo completo]: /submission/v1/product/{productId}/metadata
Método: PUT

Ruta de acceso [actualización de revisión del módulo]: /submission/v1/product/{productId}/metadata
Método: PATCH

Comportamiento de la API

En el caso de la API de actualización completa del módulo, es necesario que todos los datos del módulo estén presentes en la solicitud para la actualización completa de cada campo. Cuando un campo no esté presente en la solicitud, su valor predeterminado se usará para sobrescribir el valor actual de ese módulo específico.
En el caso de la API de actualización de la revisión del módulo, solo los campos que se van a actualizar deben estar presentes en la solicitud. Estos valores de campos de la solicitud sobrescribirán sus valores existentes y mantendrán todos los demás campos que no están presentes en la solicitud igual que los actuales para ese módulo específico.

Parámetros de la ruta de acceso

Parámetro Descripción
productId Identificador del producto en el Centro de partners

Encabezados obligatorios

Encabezado Valor
Authorization: Bearer <Token> El identificador de la aplicación de Azure AD registrado en la cuenta del Centro de partners
X-Seller-Account-Id Id. de vendedor de la cuenta del Centro de partners

Parámetros de la solicitud

Nombre Escribir Descripción
disponibilidad Object Objeto para contener los metadatos del módulo de disponibilidad
markets Matriz de cadenas Obligatorio Consulte la lista de mercados más adelante
discoverability Cadena Obligatorio [DISCOVERABLE, DEEPLINK_ONLY]
enableInFutureMarkets Booleano Obligatorio
Precios Cadena Obligatorio [FREE, FREEMIUM, SUBSCRIPTION, PAID]
freeTrial Cadena Obligatorio si Pricing es PAID o SUBSCRIPTION [NO_FREE_TRIAL, FREE_TRIAL]
properties Object Objeto para contener los metadatos del módulo de propiedades
isPrivacyPolicyRequired Booleano Obligatorio
privacyPolicyUrl Cadena Obligatorio si isPrivacyPolicyRequired = true Debe ser una dirección URL válida
sitio web Cadena Debe ser una dirección URL válida
supportContactInfo Cadena Debe ser una dirección URL o dirección de correo electrónico válida
certificationNotes Cadena Recomendado Límite de caracteres = 2000
category Cadena Obligatorio Consulte la lista de categorías más adelante
subcategory Cadena Obligatorio Consulte la lista de subcategorías más adelante
productDeclarations Object Obligatorio
isSystemFeatureRequired Matriz de objetos [Touch, Keyboard, Mouse, Camera, NFC_HCE, NFC_Proximity, Bluetooth_LE, Telephony, Microphone]
isRequired Booleano Obligatorio
isRecommended Booleano Obligatorio
hardwareItemType Cadena Obligatorio
systemRequirementDetails Matriz de objetos [Processor, Graphics, Memory, DirectX, Video_Memory]
minimumRequirement Cadena Obligatorio Para systemRequirementsText, MaxLength = 200

Valores permitidos en hardwareItemType = Memory: [300 MB, 750 MB, 1 GB, 2 GB, 4 GB, 6 GB, 8 GB, 12 GB, 16 GB, 20 GB]

Valores permitidos en hardwareItemType = DirectX: [DX9, DX10, DX11, DX12-FEATURELEVEL11, DX12-FEATURELEVEL12]

Valores permitidos en hardwareItemType = Video_Memory: [1 GB, 2 GB, 4 GB, 6 GB]
recommendedRequirement Cadena Obligatorio Para systemRequirementsText, MaxLength = 200

Valores permitidos en hardwareItemType = Memory: [300 MB, 750 MB, 1 GB, 2 GB, 4 GB, 6 GB, 8 GB, 12 GB, 16 GB, 20 GB]

Valores permitidos en hardwareItemType = DirectX: [DX9, DX10, DX11, DX12-FEATURELEVEL11, DX12-FEATURELEVEL12]

Valores permitidos en hardwareItemType = Video_Memory: [1 GB, 2 GB, 4 GB, 6 GB]
dependsOnDriversOrNT Booleano Obligatorio
accessibilitySupport Booleano Obligatorio
penAndInkSupport Booleano Obligatorio
listings Object Objeto para los datos del módulo de descripciones para un único idioma
language Cadena Obligatorio Consulte la lista de idiomas más adelante
descripción Cadena Obligatorio Límite de caracteres = 10000
whatsNew Cadena Límite de caracteres = 1500
productFeatures Matriz de cadena 200 caracteres por característica; hasta 20 características
shortDescription Cadena Límite de caracteres = 1000
searchTerms Matriz de cadena 30 caracteres por término de búsqueda; hasta 7 términos de búsqueda

21 palabras únicas en total entre todos los términos de búsqueda
additionalLicenseTerms Cadena Obligatorio Límite de caracteres = 10000
copyright Cadena Límite de caracteres = 200
developedBy Cadena Límite de caracteres = 255
requisitos Matriz de objetos 200 caracteres por elemento; hasta 11 elementos en total entre el mínimo y el recomendado]
minimumHardware Cadena Límite de caracteres = 200
recommendedHardware Cadena Límite de caracteres = 200
contactInfo Cadena Límite de caracteres = 200
listingsToAdd Matriz de cadenas Consulte la lista de idiomas más adelante
listingsToRemove Matriz de cadenas Consulte la lista de idiomas más adelante

Mercados

Mercado Abreviatura
Afganistán AF
Albania AL
Argelia DZ
Samoa Americana AS
Andorra AD
Angola AO
Anguila INTELIGENCIA ARTIFICIAL
Antártida AQ
Antigua y Barbuda AG
Argentina AR
Armenia AM
Aruba AW
Australia AU
Austria AT
Azerbaiyán AZ
Bahamas BS
Bahréin BH
Bangladés BD
Barbados BB
Bielorrusia BY
Bélgica BE
Belice BZ
Benín BJ
Bermudas BM
Bután BT
República Bolivariana de Venezuela VE
Bolivia BO
Bonaire BQ
Bosnia y Herzegovina BA
Botsuana BW
Isla Bouvet BV
Brasil BR
Territorio Británico del Océano Índico IO
Islas Vírgenes Británicas VG
Brunéi BN
Bulgaria BG
Burkina Faso BF
Burundi BI
Camboya KH
Camerún CM
Canadá CA
Cabo Verde CV
Islas Caimán KY
República Centroafricana CF
Chad TD
Chile CL
China CN
Isla de Navidad CX
Islas Cocos CC
Colombia CO
Comoras KM
Congo CG
Congo (RDC) CD
Islas Cook CK
Costa Rica CR
Croacia HR
Curazao CW
Chipre CY
República Checa CZ
Costa de Marfil CI
Dinamarca DK
Yibuti DJ
Dominica DM
República Dominicana DO
Ecuador EC
Egipto EG
El Salvador SV
Guinea Ecuatorial GQ
Eritrea ER
Estonia EE
Etiopía ET
Islas Malvinas FK
Islas Feroe FO
Islas Fiji FJ
Finlandia FI
Francia VF
Guayana Francesa GF
Polinesia Francesa PF
Territorios Australes Franceses TF
Gabón GA
Gambia GM
Georgia GE
Alemania DE
Ghana GH
Gibraltar GI
Grecia GR
Groenlandia GL
Granada GD
Guadalupe GP
Guam GU
Guatemala GT
Guernsey GG
Guinea GN
Guinea-Bisáu GW
Guyana GY
Haití HT
Islas Heard y McDonald HM
Ciudad del Vaticano VA
Honduras HN
RAE de Hong Kong HK
Hungría HU
Islandia IS
India IN
Indonesia ID
Iraq IQ
Irlanda IE
Israel IL
Italia IT
Jamaica JM
Japón JP
Jersey JE
Jordania JO
Kazajistán KZ
Kenia KE
Kiribati KI
Corea KR
Kuwait KW
Kirguistán KG
Laos Los Ángeles
Letonia LV
Líbano LB
Lesoto LS
Liberia LR
Libia LY
Liechtenstein LI
Lituania LT
Luxemburgo LU
RAE de Macao MO
Macedonia del Norte MK
Madagascar MG
Malaui MW
Malasia MY
Maldivas MV
Mali ML
Malta MT
Isla de Man IM
Islas Marshall MH
Martinica MQ
Mauritania MR
Mauricio MU
Mayotte YT
México MX
Micronesia FM
Moldavia MD
Mónaco MC
Mongolia MN
Montenegro - ME
Montserrat MS
Marruecos MA
Mozambique MZ
Myanmar MM
Namibia N/D
Nauru NR
Nepal NP
Países Bajos NL
Nueva Caledonia NC
Nueva Zelanda NZ
Nicaragua NI
Níger NE
Nigeria NG
Niue NU
Isla Norfolk NF
Islas Marianas del Norte MP
Noruega NO
Omán OM
Pakistán PK
Palaos PW
Autoridad palestina PS
Panamá PA
Papúa Nueva Guinea PG
Paraguay PY
Perú PE
Filipinas PH
Islas Pitcairn PN
Polonia PL
Portugal PT
Qatar QA
Reunión RE
Rumanía RO
Rusia RU
Ruanda RW
San Bartolomé BL
Santa Elena, Ascensión y Tristán da Cunha SH
San Cristóbal y Nieves KN
Santa Lucía LC
San Martín (zona francesa) MF
San Pedro y Miquelón PM
San Vicente y las Granadinas VC
Samoa WS
San Marino SM
Arabia Saudí SA
Senegal SN
Serbia RS
Seychelles SC
Sierra Leona SL
Singapur SG
Sint Maarten (zona neerlandesa) SX
Eslovaquia SK
Eslovenia SI
Islas Salomón SB
Somalia SO
Sudáfrica ZA
Georgia del Sur e Islas Sandwich del Sur GS
España ES
Sri Lanka LK
Surinam SR
Svalbard y Jan Mayen SJ
Suazilandia SZ
Suecia SE
Suiza CH
Santo Tomé y Príncipe ST
Taiwán TW
Tayikistán TJ
Tanzania TZ
Tailandia TH
Timor-Leste TL
Togo - TG
Tokelau TK
Tonga TO
Trinidad y Tobago - TT
Túnez TN
Turquía TR
Turkmenistán TM
Islas Turcas y Caicos TC
Tuvalu TV
EE. UU. Islas Ultramarinas Menores de Estados Unidos UM
EE. UU. Vírgenes de EE. UU. VI
Uganda UG
Ucrania UA
Emiratos Árabes Unidos AE
Reino Unido GB
Estados Unidos US
Uruguay UY
Uzbekistán UZ
Vanuatu VU
Vietnam VN
Wallis y Futuna WF
Yemen YE
Zambia ZM
Zimbabue ZW
Islas Åland AX

Categorías y subcategorías

Category Subcategorías
BooksAndReference EReader, Fiction, Nonfiction, Reference
Negocio AccountingAndfinance, Collaboration, CRM, DataAndAnalytics, FileManagement, InventoryAndlogistics, LegalAndHR, ProjectManagement, RemoteDesktop, SalesAndMarketing, TimeAndExpenses
DeveloperTools Database, DesignTools, DevelopmentKits, Networking, ReferenceAndTraining, Servers, Utilities, WebHosting
Education EducationBooksAndReference, EarlyLearning, InstructionalTools, Language, StudyAids
Entretenimiento (Ninguna)
FoodAndDining (Ninguna)
GovernmentAndPolitics (Ninguna)
HealthAndFitness (Ninguna)
KidsAndFamily KidsAndFamilyBooksAndReference, KidsAndFamilyEntertainment, HobbiesAndToys, SportsAndActivities, KidsAndFamilyTravel
Estilo de vida Automotive, DYI, HomeAndGarden, Relationships, SpecialInterest, StyleAndFashion
Medicina (Ninguna)
MultimediaDesign IllustrationAndGraphicDesign, MusicProduction, PhotoAndVideoProduction
Música (Ninguna)
NavigationAndMaps (Ninguna)
NewsAndWeather News, Weather
PersonalFinance BankingAndInvestments, BudgetingAndTaxes
Personalización RingtonesAndSounds, themes, WallpaperAndLockScreens
PhotoAndVideo (Ninguna)
Productividad (Ninguna)
Seguridad PCProtection, PersonalSecurity
Compras (Ninguna)
Redes sociales (Ninguna)
Deportes (Ninguna)
Viajes CityGuides, Hotels
UtilitiesAndTools BackupAndManage, FileManager

Idiomas

Nombre del idioma Códigos de idioma admitidos
Afrikáans af, af-za
Albanés sq, sq-al
Amárico am, am-et
Armenio hy, hy-am
Asamés as, as-in
Azerbaiyano az-arab, az-arab-az, az-cyrl, az-cyrl-az, az-latn, az-latn-az
Vasco (España) eu, eu-es
Bielorruso be, be-by
Bengalí bn, bn-bd, bn-in
Bosnio bs, bs-cyrl, bs-cyrl-ba, bs-latn, bs-latn-ba
Búlgaro bg, bg-bg
Catalán ca, ca-es, ca-es-valencia
Cheroqui chr-cher, chr-cher-us, chr-latn
Chino (simplificado) zh-Hans, zh-cn, zh-hans-cn, zh-sg, zh-hans-sg
Chino (tradicional) 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
Croata hr, hr-hr, hr-ba
Checo cs, cs-cz
Danés da, da-dk
Dari prs, prs-af, prs-arab
Neerlandés nl, nl-nl, nl-be
English 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
Estonio et, et-ee
Filipino - fil, fil-latn, fil-ph
Finés fi, fi-fi
Francés 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
Gallego gl, gl-es
Georgiano ka, ka-ge
Alemán de, de-at, de-ch, de-de, de-lu, de-li
Griego el, el-gr
Gujarati gu, gu-in
Hausa ha, ha-latn, ha-latn-ng
Hebreo he, he-il
Hindi hi, hi-in
Húngaro hu, hu-hu
Islandés is, is-is
Igbo - ig-latn, ig-ng
Indonesio id, id-id
Inuktitut (latino) iu-cans, iu-latn, iu-latn-ca
Irlandés ga, ga-ie
isiXhosa xh, xh-za
isiZulu zu, zu-za
Italiano it, it-it, it-ch
Japonés ja , ja-jp
Canarés kn, kn-in
Kazajo kk, kk-kz
Jemer km, km-kh
Quiché quc-latn, qut-gt, qut-latn
Kinyarwanda rw, rw-rw
KiSwahili sw, sw-ke
Konkani kok, kok-in
Coreano ko, ko-kr
Kurdo ku-arab, ku-arab-iq
Kirguís ky-kg, ky-cyrl
Lao lo, lo-la
Letón lv, lv-lv
Lituano lt, lt-lt
Luxemburgués lb, lb-lu
Macedonio mk, mk-mk
Malayo ms, ms-bn, ms-my
Malayalam ml, ml-in
Maltés mt, mt-mt
Maori mi, mi-latn, mi-nz
Maratí mr, mr-in
Mongol (cirílico) mn-cyrl, mn-mong, mn-mn, mn-phag
Nepalí ne, ne-np
Noruego nb, nb-no, nn, nn-no, no, no-no
Odia or, or-in
Persa fa, fa-ir
Polaco pl, pl-pl
Portugués (Brasil) pt-br
Portugués (Portugal) pt, pt-pt
Punjabi pa, pa-arab, pa-arab-pk, pa-deva, pa-in
Quechua quz, quz-bo, quz-ec, quz-pe
Rumano ro, ro-ro
Ruso ru , ru-ru
Gaélico escocés gd-gb, gd-latn
Serbio (latino) sr-Latn, sr-latn-cs, sr, sr-latn-ba, sr-latn-me, sr-latn-rs
Serbio (cirílico) sr-cyrl, sr-cyrl-ba, sr-cyrl-cs, sr-cyrl-me, sr-cyrl-rs
Sotho septentrional nso, nso-za
Setsuana tn, tn-bw, tn-za
Sindhi sd-arab, sd-arab-pk, sd-deva
Cingalés si, si-lk
Eslovaco sk, sk-sk
Esloveno sl, sl-si
Español 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
Sueco sv, sv-se, sv-fi
Tayiko (cirílico) tg-arab, tg-cyrl, tg-cyrl-tj, tg-latn
Tamil ta, ta-in
Tatar tt-arab, tt-cyrl, tt-latn, tt-ru
Telugu te, te-in
Tailandés th, th-th
Tigriña ti, ti-et
Turco tr, tr-tr
Turcomano tk-cyrl, tk-latn, tk-tm, tk-latn-tr, tk-cyrl-tr
Ucraniano uk, uk-ua
Urdu ur, ur-pk
Uigur ug-arab, ug-cn, ug-cyrl, ug-latn
Uzbeko (latino) uz, uz-cyrl, uz-latn, uz-latn-uz
Vietnamita vi, vi-vn
Galés cy, cy-gb
Wolof wo, wo-sn
Yoruba yo-latn, yo-ng

Solicitud de ejemplo

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

Encabezados de respuesta

Encabezado Valor
X-Correlation-ID Id. exclusivo de tipo GUID para cada solicitud. Se puede compartir con el equipo de soporte técnico para analizar cualquier problema.
Retry-After Tiempo en segundos que el cliente debe esperar antes de llamar a las API de nuevo debido a la limitación de volumen

Parámetros de respuesta

Nombre Escribir Descripción
isSuccess Booleano
errors Matriz de objetos Lista de mensajes de error o advertencia, si los hay
code Cadena El código de error del mensaje.
message String Descripción del error
Destino Cadena Entidad desde la que se originó el error
responseData Object Contiene la carga de respuesta real para la solicitud.
pollingUrl Cadena Dirección URL de sondeo para obtener el estado de cualquier envío en curso
ongoingSubmissionId Cadena Id. de envío de cualquier envío ya en curso

Respuesta de ejemplo

{
    "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 Get Current Draft Packages

Captura los detalles de los paquetes en el borrador del envío actual.

Ruta de acceso [todos los paquetes]: /submission/v1/product/{productId}/packages
Método: GET

Ruta de acceso [paquete único]: /submission/v1/product/{productId}/packages/{packageId}
Método: GET

Parámetros de la ruta de acceso

Nombre Descripción
productId Identificador del producto en el Centro de partners
packageId Id. exclusivo del paquete que se va a capturar

Encabezados obligatorios

Encabezado Valor
Authorization: Bearer <Token> Con el identificador de la aplicación de Azure AD registrado en la cuenta del Centro de partners
X-Seller-Account-Id Id. de vendedor de la cuenta del Centro de partners

Encabezados de respuesta

Encabezado Valor
X-Correlation-ID Id. exclusivo de tipo GUID para cada solicitud. Se puede compartir con el equipo de soporte técnico para analizar cualquier problema.
Retry-After Tiempo en segundos que el cliente debe esperar antes de llamar a las API de nuevo debido a la limitación de volumen.

Parámetros de respuesta

Nombre Escribir Descripción
isSuccess Booleano
errors Matriz de objetos Lista de mensajes de error o advertencia, si los hay
code Cadena El código de error del mensaje.
message String Descripción del error
Destino Cadena Entidad desde la que se originó el error
responseData Object
packages Matriz de objetos Objeto que contiene los datos del módulo de paquetes
packageId Cadena
packageUrl Cadena
languages Matriz de cadenas
arquitecturas Matriz de cadenas [Neutral, X86, X64, Arm, Arm64]
isSilentInstall Booleano Debe marcarse como true si el instalador se ejecuta en modo silencioso sin necesidad de conmutadores o, de lo contrario, es false
installerParameters Cadena
genericDocUrl Cadena
errorDetails Matriz de objetos
errorScenario Cadena
errorScenarioDetails Matriz de objetos
errorValue Cadena
errorUrl Cadena
packageType Cadena

Respuesta de ejemplo

{   
    "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 Update Current Draft Packages

Actualiza los detalles de los paquetes en el borrador del envío actual.

Ruta de acceso [actualización del módulo completo]: /submission/v1/product/{productId}/packages
Método: PUT

Ruta de acceso [actualización de la revisión del paquete único]: /submission/v1/product/{productId}/packages/{packageId}
Método: PATCH

Comportamiento de la API

En el caso de la API de actualización completa del módulo, es necesario que todos los datos de los paquetes estén presentes en la solicitud para la actualización completa de cada campo. Cuando un campo no esté presente en la solicitud, su valor predeterminado se usará para sobrescribir el valor actual de ese módulo específico. Esto hace que se sobrescriban todos los paquetes existentes con un nuevo conjunto de paquetes de la solicitud. Esto dará lugar a la regeneración de identificadores de paquete, y el usuario debe llamar a la API GET Packages para obtener los identificadores de paquete más recientes.

En el caso de la API de actualización de la revisión del paquete único, solo los campos que se van a actualizar para un paquete dado deben estar presentes en la solicitud. Estos valores de campos de la solicitud sobrescribirán sus valores existentes y mantendrán todos los demás campos que no están presentes en la solicitud igual que los actuales para ese paquete específico. Los demás paquetes del conjunto permanecen tal como están.

Parámetros de la ruta de acceso

Nombre Descripción
productId Identificador del producto en el Centro de partners
packageId El id. exclusivo del paquete

Encabezados obligatorios

Encabezado Valor
Authorization: Bearer <Token> Con el identificador de la aplicación de Azure AD registrado en la cuenta del Centro de partners
X-Seller-Account-Id Id. de vendedor de la cuenta del Centro de partners

Parámetros de la solicitud

Nombre Escribir Descripción
packages Matriz de objetos Objeto que contiene los datos del módulo de paquetes [solo es necesario para la actualización completa del módulo]
packageUrl Cadena Obligatorio
languages Matriz de cadenas Obligatorio
arquitecturas Matriz de cadenas Obligatorio Debe contener una única arquitectura: Neutral, X86, X64, Arm, Arm64
isSilentInstall Booleano Obligatorio Debe marcarse como true si el instalador se ejecuta en modo silencioso sin necesidad de conmutadores o, de lo contrario, es false
installerParameters Cadena Obligatorio si isSilentInstall es false
genericDocUrl Cadena Obligatorio si packageType es exe Vínculo al documento que contiene detalles de los códigos de error personalizados para el instalador de tipo EXE
errorDetails Matriz de objetos Metadatos para contener códigos de error personalizados y detalles para los instaladores de tipo EXE.
errorScenario Cadena Identifique el escenario de error específico. [installationCancelledByUser, applicationAlreadyExists, installationAlreadyInProgress, diskSpaceIsFull, rebootRequired, networkFailure, packageRejectedDuringInstallation, installationSuccessful, miscellaneous]
errorScenarioDetails Matriz de objetos
errorValue Cadena Código de error que puede estar presente durante la instalación
errorUrl Cadena Dirección URL con los detalles del error
packageType Cadena Obligatorio [exe, msi]

Solicitud de ejemplo [actualización completa del módulo]

{
    "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",
    }]
}

Solicitud de ejemplo [actualización de la revisión de paquete único]

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

Encabezados de respuesta

Encabezado Valor
X-Correlation-ID Id. exclusivo de tipo GUID para cada solicitud. Se puede compartir con el equipo de soporte técnico para analizar cualquier problema.
Retry-After Tiempo en segundos que el cliente debe esperar antes de llamar a las API de nuevo debido a la limitación de volumen.

Parámetros de respuesta

Nombre Escribir Descripción
isSuccess Booleano
errors Matriz de objetos [Lista de mensajes de error o advertencia, si los hay]
code Cadena El código de error del mensaje
message String Descripción del error
Destino Cadena Entidad desde la que se originó el error
responseData Object
pollingUrl Cadena [Dirección URL de sondeo para obtener el estado de envío en caso de cualquier envío ya en curso]
ongoingSubmissionId Cadena [Id. de envío de cualquier envío ya en curso]

Respuesta de ejemplo

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

Confirma el nuevo conjunto de paquetes actualizados mediante las API de actualización de paquetes en el borrador de envío actual. Esta API devuelve una dirección URL de sondeo para realizar un seguimiento de la carga de paquetes.

Ruta de acceso: /submission/v1/product/{productId}/packages/commit
Método: POST

Parámetros de la ruta de acceso

Nombre Descripción
productId Identificador del producto en el Centro de partners

Encabezados obligatorios

Encabezado Valor
Authorization: Bearer <Token> Con el identificador de la aplicación de Azure AD registrado en la cuenta del Centro de partners
X-Seller-Account-Id Id. de vendedor de la cuenta del Centro de partners

Encabezados de respuesta

Encabezado Valor
X-Correlation-ID Id. exclusivo de tipo GUID para cada solicitud. Se puede compartir con el equipo de soporte técnico para analizar cualquier problema.
Retry-After Tiempo en segundos que el cliente debe esperar antes de llamar a las API de nuevo debido a la limitación de volumen.

Parámetros de respuesta

Nombre Escribir Descripción
isSuccess Booleano
errors Matriz de objetos [Lista de mensajes de error o advertencia, si los hay]
code Cadena El código de error del mensaje.
message String Descripción del error
Destino Cadena Entidad desde la que se originó el error
responseData Object
pollingUrl Cadena [Dirección URL de sondeo para obtener el estado de carga o envío de paquetes en caso de envío ya en curso]
ongoingSubmissionId Cadena [Id. de envío de cualquier envío ya en curso]

Respuesta de ejemplo

{
    "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 Get Current Draft Listing Assets

Captura los detalles de los recursos de descripción en el borrador del envío actual.

Ruta de acceso: /submission/v1/product/{productId}/listings/assets?languages={languages}
Método: GET

Parámetros de la ruta de acceso

Nombre Descripción
productId Identificador del producto en el Centro de partners

Parámetros de consulta

Nombre Descripción
languages [Opcional] Los idiomas de descripción se filtran como cadena separada por comas [límite de hasta 200 idiomas]. Si no está presente, se recuperan los datos de recursos de los primeros 200 idiomas de descripción disponibles. (Por ejemplo, "en-us, en-gb").

Encabezados obligatorios

Encabezado Valor
Authorization: Bearer <Token> Con el identificador de la aplicación de Azure AD registrado en la cuenta del Centro de partners
X-Seller-Account-Id Id. de vendedor de la cuenta del Centro de partners

Encabezados de respuesta

Encabezado Valor
X-Correlation-ID Id. exclusivo de tipo GUID para cada solicitud. Se puede compartir con el equipo de soporte técnico para analizar cualquier problema.
Retry-After Tiempo en segundos que el cliente debe esperar antes de llamar a las API de nuevo debido a la limitación de volumen.

Parámetros de respuesta

Nombre Escribir Descripción
isSuccess Booleano
errors Matriz de objetos Lista de mensajes de error o advertencia, si los hay
code Cadena El código de error del mensaje.
message String Descripción del error
Destino Cadena Entidad desde la que se originó el error
responseData Object
listingAssets Matriz de objetos Detalles de los recursos de descripción para cada idioma
language Cadena
storeLogos Matriz de objetos
screenshots Matriz de objetos
id Cadena
assetUrl Cadena Debe ser una dirección URL válida
imageSize Object
width Entero
height Entero

Respuesta de ejemplo

{   
"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 Create Listing Assets

Crea una nueva carga de recursos de descripción en el borrador del envío actual.

Actualización de los recursos de descripción

La API de envío de Microsoft Store para aplicaciones EXE o MSI usa direcciones URL de SAS generadas en tiempo de ejecución en almacenes de blobs para cada carga de recursos de imagen individual, junto con una llamada a la API Commit después de que la carga se completa correctamente. Para tener la capacidad de actualizar los recursos de descripción y, a su vez, poder agregar o quitar configuraciones regionales en el módulo de descripciones, se puede usar el siguiente enfoque:

  1. Utilice la API Create Listing Asset para enviar una solicitud relacionada con la carga de recursos junto con el idioma, el tipo y el recuento de recursos.
  2. En función del número de recursos solicitados, los identificadores de recurso se crean a petición y crearían una dirección URL de SAS a corto plazo y la enviarían de vuelta en el cuerpo de la respuesta en el tipo de recursos. Puede utilizar esta dirección URL para cargar recursos de imagen de tipo específico mediante clientes HTTP [Put Blob (API REST) - Azure Storage | Microsoft Docs].
  3. Después de cargar, puede usar la API Commit Listing Assets para enviar también la nueva información del id. de recurso recibida anteriormente de la llamada API anterior. La API única confirmará internamente los datos de los recursos de descripción después de la validación.
  4. Este enfoque sobrescribirá de manera eficaz todo el conjunto de imágenes anteriores del tipo de recurso en el idioma específico que se envía en la solicitud. Por lo tanto, se quitarán los recursos cargados previamente.

Ruta de acceso: /submission/v1/product/{productId}/listings/assets/create
Método: POST

Parámetros de la ruta de acceso

Nombre Descripción
productId Identificador del producto en el Centro de partners

Encabezados obligatorios

Encabezado Descripción
Authorization: Bearer <Token> Con el identificador de la aplicación de Azure AD registrado en la cuenta del Centro de partners
X-Seller-Account-Id Id. de vendedor de la cuenta del Centro de partners

Parámetros de la solicitud

Nombre Escribir Descripción
language Cadena Obligatorio
createAssetRequest Object Obligatorio
Instantánea Entero Obligatorio si ISV tiene que actualizar capturas de pantalla o agregar un nuevo idioma de descripción [1 - 10]
Logotipo Entero Obligatorio si ISV tiene que actualizar logotipos o agregar un nuevo idioma de descripción [1 o 2]

Encabezados de respuesta

Encabezado Descripción
X-Correlation-ID Id. exclusivo de tipo GUID para cada solicitud. Se puede compartir con el equipo de soporte técnico para analizar cualquier problema.
Retry-After Tiempo en segundos que el cliente debe esperar antes de llamar a las API de nuevo debido a la limitación de volumen.

Parámetros de respuesta

Nombre Escribir Descripción
isSuccess Booleano
errors Matriz de objetos Lista de mensajes de error o advertencia, si los hay
code Cadena El código de error del mensaje.
message String Descripción del error
Destino Cadena Entidad desde la que se originó el error
responseData Object
listingAssets Object Objeto que contiene detalles de StoreLogos y Screenshots que se van a cargar
language Cadena
storeLogos Matriz de objetos
screenshots Matriz de objetos
id Cadena
primaryAssetUploadUrl Cadena Dirección URL principal para cargar el recurso de descripción mediante la API REST de Azure Blob
secondaryAssetUploadUrl Cadena Dirección URL secundaria para cargar el recurso de descripción mediante la API REST de Azure Blob
httpMethod Método de HTTP El método HTTP necesario para cargar recursos a través de las direcciones URL de carga de recursos: principal o secundaria
httpHeaders Object Objeto con claves como encabezados obligatorios que van a estar presentes en la llamada a la API Upload a las direcciones URL de carga de recursos. Si el valor no está vacío, los encabezados deben tener valores específicos. De lo contrario, los valores se calculan durante la llamada API.

Respuesta de ejemplo

{
    "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 Commit Listing Assets

Confirma el nuevo recurso de descripción cargado mediante los detalles de la API Create Assets en el borrador del envío actual.

Ruta de acceso: /submission/v1/product/{productId}/listings/assets/commit
Método: PUT

Parámetros de la ruta de acceso

Nombre Descripción
productId Identificador del producto en el Centro de partners

Encabezados obligatorios

Encabezado Descripción
Authorization: Bearer <Token> Con el identificador de la aplicación de Azure AD registrado en la cuenta del Centro de partners
X-Seller-Account-Id Id. de vendedor de la cuenta del Centro de partners

Parámetros de la solicitud

Nombre Escribir Descripción
listingAssets Object
language Cadena
storeLogos Matriz de objetos
screenshots Matriz de objetos
id Cadena Debe ser un identificador existente que el usuario quiera conservar desde la API Get Current Listing Assets o un nuevo identificador con el que se cargó un nuevo recurso en la API Create Listing Assets.
assetUrl Cadena Debe ser la dirección URL del recurso existente que el usuario desea conservar desde la API Get Current Listing Assets o la dirección URL de carga principal o secundaria, con la que se cargó un nuevo recurso en la API Create Listing Assets. Debe ser una dirección URL válida

Solicitud de ejemplo

{
    "listingAssets": { 
        "language": "en-us",    
        "storeLogos": [
            {
                "id": "1234567890abcdefgh",
                "assetUrl": "https://contoso.com/blob=1234567890abcdefgh",
            }
        ],
        "screenshots": [
            {
                "id": "1234567891abcdefgh",
                "assetUrl": "https://contoso.com/blob=1234567891abcdefgh",
            }
        ]
    }
}

Encabezados de respuesta

Encabezado Descripción
X-Correlation-ID Id. exclusivo de tipo GUID para cada solicitud. Se puede compartir con el equipo de soporte técnico para analizar cualquier problema.
Retry-After Tiempo en segundos que el cliente debe esperar antes de llamar a las API de nuevo debido a la limitación de volumen.

Parámetros de respuesta

Nombre Escribir Descripción
isSuccess Booleano
errors Matriz de objetos Lista de mensajes de error o advertencia, si los hay
code Cadena El código de error del mensaje.
message String Descripción del error
Destino Cadena Entidad desde la que se originó el error
responseData Object
pollingUrl Cadena Dirección URL de sondeo para obtener el estado de cualquier envío en curso
ongoingSubmissionId Cadena Id. de envío de cualquier envío ya en curso

Respuesta de ejemplo

{
    "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 Module Status Polling

API para comprobar la preparación del módulo antes de que se pueda crear el envío. Además, valida el estado de carga de los paquetes.

Ruta de acceso: /submission/v1/product/{productId}/status
Método: GET

Parámetros de la ruta de acceso

Nombre Descripción
productId Identificador del producto en el Centro de partners

Encabezados obligatorios

Encabezado Descripción
Authorization: Bearer <Token> Con el identificador de la aplicación de Azure AD registrado en la cuenta del Centro de partners
X-Seller-Account-Id Id. de vendedor de la cuenta del Centro de partners

Encabezados de respuesta

Encabezado Descripción
X-Correlation-ID Id. exclusivo de tipo GUID para cada solicitud. Se puede compartir con el equipo de soporte técnico para analizar cualquier problema.
Retry-After Tiempo en segundos que el cliente debe esperar antes de llamar a las API de nuevo debido a la limitación de volumen.

Parámetros de respuesta

Nombre Escribir Descripción
isSuccess Booleano
errors Matriz de objetos Lista de mensajes de error o advertencia, si los hay
code Cadena El código de error del mensaje.
message String Descripción del error
Destino Cadena Entidad desde la que se originó el error
responseData Object
isReady Booleano Indica si todos los módulos están en estado listo, incluida la carga de paquetes
ongoingSubmissionId Cadena Id. de envío de cualquier envío ya en curso

Respuesta de ejemplo

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

API Create Submission

Crea un envío a partir del borrador actual de la aplicación MSI o EXE. La API comprueba:

  • El envío activo y produce un mensaje de error si existe un envío activo.
  • Si todos los módulos están en estado listo para crear el envío.
  • Cada campo del envío se valida según los requisitos de Store.

Ruta de acceso:/submission/v1/product/{productId}/submit
Método: POST

Parámetros de la ruta de acceso

Nombre Descripción
productId Identificador del producto en el Centro de partners

Encabezados obligatorios

Encabezado Descripción
Authorization: Bearer <Token> Con el identificador de la aplicación de Azure AD registrado en la cuenta del Centro de partners
X-Seller-Account-Id Id. de vendedor de la cuenta del Centro de partners

Encabezados de respuesta

Encabezado Descripción
X-Correlation-ID Id. exclusivo de tipo GUID para cada solicitud. Se puede compartir con el equipo de soporte técnico para analizar cualquier problema.
Retry-After Tiempo en segundos que el cliente debe esperar antes de llamar a las API de nuevo debido a la limitación de volumen.

Parámetros de respuesta

Nombre Escribir Descripción
isSuccess Booleano
errors Matriz de objetos Lista de mensajes de error o advertencia, si los hay
code Cadena El código de error del mensaje.
message String Descripción del error
Destino Cadena Entidad desde la que se originó el error
responseData Object
pollingUrl Cadena Dirección URL de sondeo para obtener el estado de preparación del módulo, incluida la carga de paquetes para el envío
submissionId Cadena Id. del envío recién creado
ongoingSubmissionId Cadena Id. de envío de cualquier envío ya en curso

Respuesta de ejemplo

{
    "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 Submission Status Polling

API para comprobar el estado del envío.

Ruta de acceso: /submission/v1/product/{productId}/submission/{submissionId}/status
Método: GET

Parámetros de la ruta de acceso

Nombre Descripción
productId Identificador del producto en el Centro de partners

Encabezados obligatorios

Encabezado Descripción
Authorization: Bearer <Token> Con el identificador de la aplicación de Azure AD registrado en la cuenta del Centro de partners
X-Seller-Account-Id Id. de vendedor de la cuenta del Centro de partners

Encabezados de respuesta

Encabezado Descripción
X-Correlation-ID Id. exclusivo de tipo GUID para cada solicitud. Se puede compartir con el equipo de soporte técnico para analizar cualquier problema.
Retry-After Tiempo en segundos que el cliente debe esperar antes de llamar a las API de nuevo debido a la limitación de volumen.

Parámetros de respuesta

Nombre Escribir Descripción
isSuccess Booleano
errors Matriz de objetos Lista de mensajes de error o advertencia, si los hay
code Cadena El código de error del mensaje.
message String Descripción del error
Destino Cadena Entidad desde la que se originó el error
responseData Object
publishingStatus Cadena Estado de publicación del envío: [INPROGRESS, PUBLISHED, FAILED, UNKNOWN]
hasFailed Booleano Indica si la publicación ha producido un error y no se reintentará.

Respuesta de ejemplo

{
    "isSuccess": true,
    "errors": [{
        "code": "badrequest",
        "message": "Error Message 1",
        "target": "listings"
        }, {
        "code": "warning",
        "message": "Warning Message 1",
        "target": "properties"
    }],
    "responseData": {
        "publishingStatus": "INPROGRESS",
        "hasFailed": false
    }
}

Ejemplos de código

En los artículos siguientes se proporcionan ejemplos de código detallados que muestran cómo usar la API de envío de Microsoft Store en distintos lenguajes de programación:

Ejemplo de C#: API de envío de Microsoft Store para aplicaciones MSI o EXE

En este artículo se proporcionan ejemplos de código de C# que muestran cómo usar la API de envío de Microsoft Store para aplicaciones MSI o EXE. Puede revisar cada ejemplo para obtener más información sobre la tarea que muestra o puede compilar todos los ejemplos de código de este artículo en una aplicación de consola.

Requisitos previos Estos ejemplos utilizan la biblioteca siguiente:

  • Paquete NuGet Newtonsoft.Json de Newtonsoft.

Programa principal El siguiente ejemplo implementa un programa de línea de comandos que llama a los otros métodos de ejemplo de este artículo para mostrar diferentes maneras de usar la API de envío de Microsoft Store. Para adaptar este programa para su propio uso:

  • Asigne la propiedad SellerId al identificador de vendedor de la cuenta del Centro de partners.
  • Asigne la propiedad ApplicationId al identificador de la aplicación que desea administrar.
  • Asigne las propiedades ClientId y ClientSecret al identificador de cliente y la clave de la aplicación, y reemplace la cadena tenantid en la dirección URL de TokenEndpoint por el identificador de inquilino de la aplicación. Para obtener más información, consulte Asociación de una aplicación de Azure AD a la cuenta del Centro de partners
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();

        }
    }
}

Clase auxiliar ClientConfiguration mediante C#

La aplicación de ejemplo utiliza la clase auxiliar ClientConfiguration para pasar datos de Azure Active Directory y datos de aplicación a cada uno de los métodos de ejemplo que utilizan la API de envío de Microsoft Store.

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; }
    }
}

Crear un envío de aplicación con C#

En el siguiente ejemplo se implementa una clase que utiliza varios métodos en la API de envío de Microsoft Store para actualizar un envío de aplicación.

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.
        }
    }
}

Clase auxiliar IngestionClient mediante C#

La clase IngestionClient proporciona métodos auxiliares que otros métodos de la aplicación de ejemplo utilizan para realizar las tareas siguientes:

  • Obtener un token de acceso de Azure AD para utilizarlo para llamar a los métodos en la API de envío de Microsoft Store. Después de obtener un token, tiene 60 minutos para utilizar este token en llamadas a la API de envío de Microsoft Store antes de que el token expire. Después de que el token expire, puede generar uno nuevo.
  • Procesar las solicitudes HTTP para la API de envío de Microsoft Store.
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;
        }
    }
}

Ejemplo de Node.js: API de envío de Microsoft Store para aplicaciones MSI o EXE

En este artículo se proporcionan ejemplos de código de Node.js que muestran cómo usar la API de envío de Microsoft Store para aplicaciones MSI o EXE. Puede revisar cada ejemplo para obtener más información sobre la tarea que muestra o puede compilar todos los ejemplos de código de este artículo en una aplicación de consola.

Requisitos previos Estos ejemplos utilizan la biblioteca siguiente:

  • node-fetch v2 [npm install node-fetch@2]

Crear un envío de aplicación con node.js

El siguiente ejemplo llama a los otros métodos de ejemplo de este artículo para mostrar diferentes maneras de usar la API de envío de Microsoft Store. Para adaptar este programa para su propio uso:

  • Asigne la propiedad SellerId al identificador de vendedor de la cuenta del Centro de partners.
  • Asigne la propiedad ApplicationId al identificador de la aplicación que desea administrar.
  • Asigne las propiedades ClientId y ClientSecret al identificador de cliente y la clave de la aplicación, y reemplace la cadena tenantid en la dirección URL de TokenEndpoint por el identificador de inquilino de la aplicación. Para obtener más información, consulte Asociación de una aplicación de Azure AD a la cuenta del Centro de partners

En el siguiente ejemplo se implementa una clase que utiliza varios métodos en la API de envío de Microsoft Store para actualizar un envío de aplicación.

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();

Asistente ClientConfiguration

La aplicación de ejemplo utiliza la clase auxiliar ClientConfiguration para pasar datos de Azure Active Directory y datos de aplicación a cada uno de los métodos de ejemplo que utilizan la API de envío de Microsoft Store.

/** 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;

Asistente IngestionClient mediante node.js

La clase IngestionClient proporciona métodos auxiliares que otros métodos de la aplicación de ejemplo utilizan para realizar las tareas siguientes:

  • Obtener un token de acceso de Azure AD para utilizarlo para llamar a los métodos en la API de envío de Microsoft Store. Después de obtener un token, tiene 60 minutos para utilizar este token en llamadas a la API de envío de Microsoft Store antes de que el token expire. Después de que el token expire, puede generar uno nuevo.
  • Procesar las solicitudes HTTP para la API de envío de Microsoft Store.
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;

Ayuda adicional

Si tiene preguntas sobre la API de envío de Microsoft Store o necesita ayuda para administrar los envíos con esta API, utilice los siguientes recursos:

  • Formule preguntas en nuestros foros.
  • Visite nuestra página de soporte técnico y solicite una de las opciones de soporte técnico asistido para el Centro de partners. Si se le pide que elija un tipo de problema y una categoría, elija Envío y certificación de la aplicación y Envío de una aplicación, respectivamente.