If you’re building apps for Fire TVs or Fire Tablets, you’re likely familiar with the process of updating your app through the Amazon Developer Console. Each time you update your app’s APK, you probably want to update the description, screenshots, and metadata to reflect the last improvements. Did you know can make these updates through our new App Submission API in addition to the Developer Console?
This API can help you save time by handling app submission programmatically and eliminating the need for manual interaction with the Developer Console. By using this REST API, you can streamline updates and extend your existing automation workflows. In this step guide, we’ll walk through how to use the App Submission API.
Before we get started, let’s cover some key concepts.
ℹ️ Note: You will need access to the Developer Console to get your developer credentials and App IDs.
The App Submission API supports uploading app files, managing metadata, and submitting your apps. With code that makes HTTP calls to the API, you can integrate the submission process directly into your CI/CD pipelines for continuous deployment.
When working with the API, there are two concepts that you should be familiar with: Edits and ETags.
Edits are like containers or sessions that hold all the pending changes you want to make to your app. Updating your app begins with creating a new Edit. Then, all of your changes—such as new APK uploads, localization listing updates, and replacement logos—are applied to the Edit. When you’re ready, you commit the Edit, which submits the changes for validation and publishing.
ETags are unique identifiers that the API uses to make sure concurrent updates within your Edit don’t overwrite one another. Because it’s RESTful, the App Submission API has endpoints to GET various resources, such as an APK, screenshot, or preview video. The response headers for these GET requests contain an ETag, which is a unique value that identifies the resource in its latest state.
When submitting requests that affect these resources, make sure to include the ETag for the resource in your request header to allow the server to verify that you are making changes to the most up-to-date version of the given resource. All PUT and DELETE requests—and some POST requests—require you to provide the ETag for verification.
The first time you use the App Submission API, there are a few things to set up to facilitate authentication. Calls to the API require you to include a bearer auth token and you obtain this token through Login with Amazon (LWA):
1. Sign in to the Amazon Developer Console.
2. Navigate to Settings > Security Profiles.
3. Click Create a New Security Profile.
4. Provide a name and description for your new security profile.
5. Then, click Save.
6. You’ll see general information for your newly created security profile. Click the Web Settings tab.
7. On the Web Settings page, you’ll see a client ID and client secret. Copy them and store them securely. You’ll use these values to request an LWA access token.
8. Next, navigate to Apps & Services > API Access.
9. Under App Submission API, select the existing security profile that you just created. Click Attach.
Now that you have enabled the App Submission API to be used with this security profile, you can request your LWA token.
10. Navigate to the Login with Amazon page.
11. Begin by selecting your security profile.
12. Then, click Confirm.
When using LWA, a consent privacy notice will be displayed to users whenever you request access to their personal data.
13. On the next screen, provide a URL that points to your organization’s consent privacy notice.
14. Then, click Save.
This completes your LWA configuration.
Now, you can send a request to obtain an LWA access token, authenticating with the client ID and secret for your security profile. An example curl request looks like this:
$ curl \
--url https://api.amazon.com/auth/O2/token
--request POST \
--header 'Content-Type: application/json' \
--data '{"grant_type":"client_credentials","client_id":"SECURITY-PROFILE-CLIENT-ID","client_secret":"SECURITY-PROFILE-CLIENT-SECRET","scope":"appstore::apps:readwrite"}'
{"access_token":"Atc|MQEBIEfESLSMKydIgiJPi-fhYvSSyOPXzgNvvT7WaWoBxKVfyCzexE31mmhC5w6rI-uI7kuoIAf4hSxIK33HSXeyO570fEHeb4gere8t5bUShE3iZgjXOQa5nGlNXsf01ZvOjig8M0Zdjd5s2_q5-2xMq6nQEZdCR3MOQtcM1SqZdLmnmEiTEQ-PwTWTAXAovkbY4i5NAdp9hhpvDneh7zQuOjDqnWyBqxG-qjCcxtRFVEhx9TZRDhsCUbUUiDLCFKMqWz8","scope":"appstore::apps:readwrite","token_type":"bearer","expires_in":3600}
Copy the resulting access_token value, which you’ll use to authenticate all subsequent App Submission API requests. You’ll use the bearer auth token by adding an Authorization header that looks similar to this:
Authorization: Bearer Atc|MQEBIEfESLSMKydIgiJPi-fhYvSSyOPXzgNvvT7WaWoBxKVfyCzexE31mmhC5w6rI-uI7kuoIAf4hSxIK33HSXeyO570fEHeb4gere8t5bUShE3iZgjXOQa5nGlNXsf01ZvOjig8M0Zdjd5s2_q5-2xMq6nQEZdCR3MOQtcM1SqZdLmnmEiTEQ-PwTWTAXAovkbY4i5NAdp9hhpvDneh7zQuOjDqnWyBqxG-qjCcxtRFVEhx9TZRDhsCUbUUiDLCFKMqWz8
Note: This token expires after 3600 seconds (1 hour). You can always use the above request to generate a new access_token if your current one expires.
For convenience, store your access token as an environment variable in your terminal session.
$ export \
TOKEN="Atc|MQEBIEfESLSMKydIgiJPi-fhYvSSyOPXzgNvvT7WaWoBxKVfyCzexE31mmhC5w6rI-uI7kuoIAf4hSxIK33HSXeyO570fEHeb4gere8t5bUShE3iZgjXOQa5nGlNXsf01ZvOjig8M0Zdjd5s2_q5-2xMq6nQEZdCR3MOQtcM1SqZdLmnmEiTEQ-PwTWTAXAovkbY4i5NAdp9hhpvDneh7zQuOjDqnWyBqxG-qjCcxtRFVEhx9TZRDhsCUbUUiDLCFKMqWz8"
All endpoints for the App Submission API include an App ID as a path parameter.
1. From the Developer Console, navigate to the App List.
2. Find your app in the list and click on it. You’ll arrive at the first step of the Current Version section for your app.
3. Scroll to the bottom of the page, under Additional Information, to find your App ID.
4. Copy this value.
The base URL for the API is https://developer.amazon.com/api/appstore, and the API version is v1.
All request endpoint URLs start like this:
https://developer.amazon.com/api/appstore/{apiVersion}/applications/{appId}
Therefore, requests related to our example application will go to URLs that start with the following:
https://developer.amazon.com/api/appstore/v1/applications/amzn1.devportal.mobileapp.a486d052fb504c08a80c221db7479ecb
5. For convenience, store the API root path as an environment variable in your terminal session.
$ export API_ROOT_PATH="https://developer.amazon.com/api/appstore/v1/applications/amzn1.devportal.mobileapp.a486d052fb504c08a80c221db7479ecb"
Send a test request to verify that your LWA token is valid and the API path is correct. We’ll send a request for details about the currently active Edit for our app. We don’t expect there to be one, but we can at least verify our API request is successful.
Looking at the API reference to get the active edit for an app, our API request will be:
$ curl \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits
{}
Note that we are using the $TOKEN and $API_ROOT_PATH environment variables. We’ll use these throughout this process.
The API request is successful, with the {} in the response indicating that we do not have an active edit for our app.
“Request is not authorized”
When sending API requests, you may receive this response:
{"message":"Request is not authorized."}
This likely indicates that your LWA access token is invalid or has expired. Send another request to obtain an LWA access token. Then, reassign the TOKEN environment variable to the new value.
“API Version not supported”
If you receive an HTTP response code of 400 and the errorMessage of API Version not supported, ensure that you are using v1 for the apiVersion part of your API root path.
“No app found with the entered inputs”
If you receive an HTTP response code of 404 and the errorMessage of No app found with the entered inputs, check to make sure that you have added your App ID to the API root path correctly.
“Unable to fetch the request scope for uri”
If you receive a response message that says Unable to fetch the request scope for uri, check that the remainder of your request URL (after API_ROOT_PATH) hits a valid endpoint, as listed in the API reference. In addition, ensure you are using the correct request command (such as GET, POST, PUT, or DELETE) for that endpoint.
Working with the App Submission API begins with creating a new Edit. You will prepare various changes to your app within this Edit, and then deploy all of your changes at one time.
Create a new Edit
To create a new Edit, send a POST request to the /edits path:
$ curl \
--request POST \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits
{"id":"amzn1.devportal.apprelease.a81b826e3e3743ea8a7886ac77f52c56","status":"IN_PROGRESS"}
The response contains the id of the newly created Edit. This time, when we resend the request to retrieve the currently active Edit, we receive information about this newly created Edit
$ curl \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits
{"id":"amzn1.devportal.apprelease.a81b826e3e3743ea8a7886ac77f52c56","status":"IN_PROGRESS"}
For convenience, set the Edit ID as a new environment variable in your terminal session.
$ export \
EDIT_ID="amzn1.devportal.apprelease.a81b826e3e3743ea8a7886ac77f52c56"
Updating your Amazon App often involves managing the app’s APK files. To list the APKs for your app, send the following request:
$ curl \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/apks
[{"versionCode":10000,"id":"M8NUQH070UH2I","name":"AAB1"}]
To retrieve details about a specific APK, use the APK ID, as one would expect for RESTful API conventions:
$ curl \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/apks/M8NUQH070UH2I
{"versionCode":10000,"id":"M8NUQH070UH2I","name":"AAB1"}
To upload an APK to associate with your Edit, your request will look similar to the following:
$ curl \
--request POST \
--header "Authorization: Bearer $TOKEN" \
--header "Content-type: application/octet-stream" \
--header "fileName: app-release-02.apk" \
--data-binary @/absolute/path/to/app-release-02.apk \
--url $API_ROOT_PATH/edits/$EDIT_ID/apks/upload
{"versionCode":10001,"id":"M4UQXH84SMZ4","name":"APK1"}
The response shows the ID of the newly uploaded APK.
You can also replace (with PUT) or delete (with DELETE) an APK. However, these methods require you to include the ETag for the APK resource. The ETag is available in the response header when you GET a resource. For example, to get the ETag for the APK that just uploaded, the request and response would look like this:
$ curl \
--include \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/apks/M4UQXH84SMZ4
HTTP/1.1 200 OK
Server: Server
Date: Mon, 06 Jan 2025 22:17:24 GMT
Content-Type: application/json
Content-Length: 55
Connection: keep-alive
x-amz-rid: H9SBRPW0R3WMK95ZVP62
x-amzn-RequestId: b3c3cd98-c5c5-4578-9efc-94e17d8d13df
ETag: 43d8a4205f9c59155cd7a104cc627d9b6694c3f2
Vary: Content-Type,Accept-Encoding,User-Agent
Strict-Transport-Security: max-age=47474747; includeSubDomains; preload
{"versionCode":10001,"id":"M4UQXH84SMZ4","name":"APK1"}
Adding --include to our curl call shows the response headers. The header shows the ETag for this APK resource is 43d8a4205f9c59155cd7a104cc627d9b6694c3f2. To delete this APK, we would include this value in an If-match request header:
$ curl \
--include \
--request DELETE \
--header "Authorization: Bearer $TOKEN" \
--header "If-Match: 43d8a4205f9c59155cd7a104cc627d9b6694c3f2" \
--url $API_ROOT_PATH/edits/$EDIT_ID/apks/M4UQXH84SMZ4
HTTP/1.1 204 No Content
…
Retrieving the APK details again shows that it was successfully deleted:
$ curl \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/apks/M4UQXH84SMZ4 | json_pp
{
"errors" : [
{
"errorCode" : "error_apk_not_found",
"errorMessage" : "No APK found with the given APK ID."
}
],
"httpCode" : 404,
"message" : "Not Found"
}
If your APK file is exceptionally large, then use the /large/upload endpoint.
Every app submission can have listing details for multiple languages. A listing takes the following form:
{
"language": "string",
"title": "string",
"fullDescription": "string",
"shortDescription": "string",
"recentChanges": "string",
"featureBullets": [
"string"
],
"keywords": [
"string"
]
}
Note: keywords is an array of strings; each string must be a single word with no spaces.
You can find the list of supported languages here. To see all of the localized listings for your app, run the following command:
$ curl \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/listings | json_pp
{
"listings" : {
"en-US" : {
"featureBullets" : [
"Images of coffee"
],
"fullDescription" : "This is a store that sells digital assets for use in websites and other promotional materials.",
"keywords" : [
"digital",
"images",
"assets",
"Coffee"
],
"language" : "en-US",
"recentChanges" : null,
"shortDescription" : "This is a store that sells digital assets for use in websites and other promotional materials.",
"title" : "Digital Assets Store"
}
}
}
The above call returns all localization listings for the app, which currently only has one: en-US. Append the language to the GET endpoint path to retrieve details for a specific listing.
$ curl \
--include \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/listings/en-US
HTTP/1.1 200 OK
…
ETag: 5c8149f2f9f3d9b818e89ec9b797648bd32d7cec
…
{"language":"en-US","title":"Digital Assets Store","fullDescription":"This is a store that sells digital assets for use in websites and other promotional materials.","shortDescription":"This is a store that sells digital assets for use in websites and other promotional materials.","recentChanges":null,"featureBullets":["Images of coffee"],"keywords":["digital","images","assets","Coffee"]}
Note that we’ve added the --include argument to show the response headers, which include the ETag for this localization listing resource. For example, we can use this ETag to update the listing:
$ curl \
--include \
--request PUT \
--header "Authorization: Bearer $TOKEN" \
--header "If-Match: 5c8149f2f9f3d9b818e89ec9b797648bd32d7cec" \
--url $API_ROOT_PATH/edits/$EDIT_ID/listings/en-US \
--data '{"language":"en-US", "title":"DAS: Digital Assets Store", "fullDescription":"The Digital Assets Store sells images and logos for use in websites and other promotional materials.", "shortDescription":"Promotional/website images and logos for sale.", "recentChanges":"Updated title and descriptions, added keywords", "featureBullets":["Images of coffee"], "keywords":["digital","images","AI-generated","Coffee"]}'
HTTP/1.1 200 OK
…
ETag: 8d531dc2aa6ed59072055cfc83e86c419a7032a9
…
{"language":"en-US","title":"DAS: Digital Assets Store","fullDescription":"The Digital Assets Store sells images and logos for use in websites and other promotional materials.","shortDescription":"Promotional/website images and logos for sale.","recentChanges":"Updated title and descriptions, added keywords","featureBullets":["Images of coffee"],"keywords":["digital","images","Coffee","AI-generated"]}
If you would like to add a new listing for a localization that doesn’t exist, first GET the listing to retrieve its ETag from the response headers. The response body will be empty, but you must use the ETag for the empty listing in a subsequent PUT request to create the new listing.
To delete an existing localization listing, use the ETag and send a DELETE request.
Every Amazon App also has basic details which take on this form:
{
"defaultLanguage": "string",
"contactWebsite": "string",
"contactEmail": "string",
"contactPhone": "string"
}
With the App Submission API, you can GET your app’s details with the following request. If you want to modify these details with a PUT request, you must retrieve the ETag from the response headers.
$ curl \
--include \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/details
…
ETag: 10963098c5cf18378c3d47058b5355d44dd5d050
…
{"defaultLanguage":"en-US","contactWebsite":null,"contactEmail":"amazon_app_developer@example.com","contactPhone":null}
Then, a request to update your app details would look like this:
$ curl \
--include \
--request PUT \
--header "If-Match: 10963098c5cf18378c3d47058b5355d44dd5d050" \
--header "Content-type: application/json" \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/details \
--data '{"defaultLanguage":"en-US","contactWebsite":"https://developer.amazon.com","contactEmail":"appdevboss@example.com","contactPhone":"111-222-3333"}'
HTTP/1.1 200 OK
…
ETag: 0560792b04b84d0893379f79fc5464c03178ba77
…
{"defaultLanguage":"en-US","contactWebsite":"https://developer.amazon.com","contactEmail":"appdevboss@example.com","contactPhone":"111-222-3333"}
To see the different target devices specified for an APK, send the following request, ensuring the APK ID is in the request path:
$ curl \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/apks/M8NUQH070UH2I/targeting \
| json_pp
{
"amazonDevices" : [
{
"id" : "amzn1.appstore.device.1S3STLD1YMVDD",
"name" : "Funai 4K - Fire TV F560 Series",
"reason" : {},
"status" : "NOT_TARGETING"
},
{
"id" : "amzn1.appstore.device.2NHA4WR6DZVME",
"name" : "Fire TV 2-Series",
"reason" : {},
"status" : "NOT_TARGETING"
},
{
"id" : "amzn1.appstore.device.D8MCACTJ5P8M",
"name" : "Fire HD 10 (2023)",
"reason" : {},
"status" : "TARGETING"
},
…
{
"id" : "amzn1.appstore.device.F5BFMITYX6KI",
"name" : "Galaxy S4",
"reason" : {
"details" : [
"android:minSdkVersion = '24'; device requires '21'"
],
"reason" : "The device is incompatible with manifest."
},
"status" : "DISABLED"
}
],
"otherAndroidDevices" : "TARGETING"
}
The list of devices in the resulting JSON is extensive, with the status for each device indicating whether the specified APK applies. To modify the device targeting information for an APK, fetch the ETag from the response headers for the above request, then send a subsequent PUT request with an updated JSON body.
Each localized listing for your app also has associated images and videos. The endpoints for different images begin with the localization listing path, followed by the imageType.
You can find the list of possible imageType values here. For example, to retrieve information about the large-icons for the en-US listing, send the following request:
$ curl \
--include \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/listings/en-US/large-icons
HTTP/1.1 200 OK
…
ETag: 8d531dc2aa6ed59072055cfc83e86c419a7032a9
…
{"images":[{"id":"amzn1.dex.asset.69c2f76ae9604091a4dd2e10f8ec9a50"}]}
To delete an existing image, send a DELETE request, appending the image asset ID to the path. Don’t forget to include the ETag in the request header.
$ curl \
--request DELETE \
--header "If-Match: 8d531dc2aa6ed59072055cfc83e86c419a7032a9" \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/listings/en-US/large-icons/amzn1.dex.asset.69c2f76ae9604091a4dd2e10f8ec9a50
The API has endpoints to delete all images of a specific imageType or delete a single, specific image.
To upload a new image, send a POST request with application/octet-stream content. For example:
$ curl \
--request POST \
--header "Authorization: Bearer $TOKEN" \
--header "Content-type: application/octet-stream" \
--data-binary @/absolute/path/to/image-file.png \
--url $API_ROOT_PATH/edits/$EDIT_ID/listings/en-US/large-icons/upload
{"image":{"id":"amzn1.dex.asset.5e90a58830e048a796aef9db6ebf6a02"}}
Videos work similarly, except there aren’t different types of videos.
To retrieve information about all videos for a specific localization listing, send the following GET request:
$ curl \
--include \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/listings/en-US/videos
HTTP/1.1 200 OK
…
ETag: ee763ba20a0fc41c263ad5d9547cab1f1b021015
…
{"videos":[]}
To upload a video, send a POST request to the same endpoint path, including the ETag in the If-Match header. Similar to images, there is an endpoint to delete all videos for a listing or a specific video based on its asset ID.
When you commit an Edit, the review and publishing procedure will begin immediately by default. However, to set a future release date for the Edit, use the /availability endpoint. To see the current publishing date settings, send the following request:
$ curl \
--include \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/availability
HTTP/1.1 200 OK
…
ETag: ee763ba20a0fc41c263ad5d9547cab1f1b021015
…
{"publishingDate":{"dateTime":null,"zoneId":null}}
To modify the availability, use the ETag from the previous request in a PUT request:
$ curl \
--request PUT \
--header "If-Match: ee763ba20a0fc41c263ad5d9547cab1f1b021015" \
--header "Content-type: application/json" \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/availability \
--data '{"publishingDate":{"dateTime":"2025-03-01T12:00:00","zoneId":"US/Pacific"}}'
{"publishingDate":{"dateTime":"2025-03-01T12:00:00","zoneId":"US/Pacific"}}
After making all the necessary updates in your active Edit, you are ready to commit the Edit. Before you can send the POST request to commit the Edit, retrieve the ETag for the current Edit resource:
$ curl \
--include \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits
…
ETag: 437b8a79adec568ce90e5d3c275808a36fdb5869
…
{"id":"amzn1.devportal.apprelease.a81b826e3e3743ea8a7886ac77f52c56","status":"IN_PROGRESS"}
Use the ETag to commit your Edit:
$ curl \
--request POST \
--header "If-Match: 437b8a79adec568ce90e5d3c275808a36fdb5869" \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/$EDIT_ID/commit
{"id":"amzn1.devportal.apprelease.a81b826e3e3743ea8a7886ac77f52c56"}
After committing an Edit, verify the status has changed:
$ curl \
--request GET \
--header "Authorization: Bearer $TOKEN" \
--url $API_ROOT_PATH/edits/
{"id":"amzn1.devportal.apprelease.a81b826e3e3743ea8a7886ac77f52c56","status":"SUBMITTED"}
In the Developer Console, you’ll see that your app now has an Upcoming Version based on the Edit you committed.
That’s it! You have successfully used the App Submission API to handle all the steps for updating your app through API requests rather than the Developer Console UI.
The App Submission API makes it easier for developers to streamline their app submission process. Integrating your code within existing CI/CD pipelines can bring automation and consistency to your app submission process, removing the need for manual intervention.
For more information, check out the following resources: