Amazon Developer

as

Settings
Sign out
Notifications
Alexa
Amazon Appstore
Ring
AWS
Documentation
Support
Contact Us
My Cases
Devices
Build
Test
Publish
Connect
Documentation

Vitals API V2 Reference

This page gives the reference information that you need to integrate the Vitals API V2. For the metric sets, the metrics, and the dimensions, see the Vitals API V2 Overview.

Amazon continues to support version 1 (V1) of the Vitals API. Use V2 for new integrations. Migrate existing integrations to V2. For V1, see the Vitals API Reference. For the changes that you must make, see Migrate from V1.

Use of the Vitals API is subject to the Program Materials License Agreement.

Prerequisites

Before you send a request, make sure that you have the following items:

Requirement Details
Amazon Developer account Admin-level credentials
Security profile Create the security profile in the Developer Console, at My Settings > API Access.
Security profile attached to the Reporting API Attach the security profile to the Reporting API.
Client ID and client secret Get these values from the Web Settings tab of the security profile.
LWA scope adx_reporting::appstore:marketer
Package name The package name of your app, for example com.example.myapp. Your developer account must own the app.

The code examples on this page use Python 3.8 or later and the requests library.

Base URL

The base URL of the Vitals API V2 is https://developer.amazon.com/api/appstore.

Operations

The Vitals API V2 includes the following operations and endpoints:

Operation Method Endpoint Description
Check Freshness GET /v2/applications/{packageName}/vitals/{metricSetName} Returns the metadata and the freshness of a metric set
Query Metrics POST /v2/applications/{packageName}/vitals/{metricSetName}:query Returns metrics for each time period, with dimensions and filters
Query Issues POST /v2/applications/{packageName}/vitals/issuesMetricSet:query Returns the top crash issues or the top ANR issues

The endpoints use the following path parameters:

Parameter Description
packageName The package name of your app, for example com.example.myapp
metricSetName The name of a metric set, except issuesMetricSet. For the list, see Available metric sets.
  • issuesMetricSet supports Query Issues only. A Check Freshness request for it returns 404 RESOURCE_NOT_FOUND.
  • Query Metrics and Query Issues paths must end in :query.

Request headers

Header Required Value
Authorization Yes Bearer ACCESS_TOKEN. ACCESS_TOKEN is the access_token value from Login with Amazon.
Content-Type Yes, for POST application/json
Accept No application/json. The API sends only JSON.

Request rules

The rules in this section apply to the Query Metrics operation and to the Query Issues operation.

Time values

Use UTC timestamps in the format YYYY-MM-DDThh:mm:ssZ for startTime and endTime.

The time values follow these rules:

  • The time range includes the time periods of startTime and endTime. endTime must be the same as startTime or later.
  • For DAILY, the API uses the UTC date. For HOURLY, the API ignores minutes and seconds. For example, 2026-08-18T14:35:00Z selects the UTC period starting at 2026-08-18T14:00:00Z.
  • startTime must be within the latest 30 UTC dates for DAILY or the latest 15 UTC dates for HOURLY. The API counts these dates back from the freshness value. The date of the freshness value is the first of them.
  • The UTC date (DAILY) or UTC hour (HOURLY) of endTime must not be later than the freshness value.
  • For a time range that breaks these rules, the API sends 400 VALIDATION_ERROR with FIELD_OUT_OF_RANGE on the field that breaks the rule, timelineSpec.startTime or timelineSpec.endTime.
  • If no freshness value is available, the query returns no rows.

The API rejects these time values:

  • A date without a time, for example 2026-08-18
  • A time with fractions of a second, for example 2026-08-18T00:00:00.000Z
  • A time with a time zone offset, for example 2026-08-18T00:00:00+00:00
  • A time that ends with a lowercase z

Dimensions and filters

  • A filter key must name a dimension of the metric set. It need not appear in dimensions.
  • Use nonempty, case-sensitive strings for filter values. A countryCode value must be an uppercase ISO 3166-1 alpha-2 code, for example US. The API matches any value for each key and requires every key to match. An empty array has no effect.
  • A breakdown omits data without a value for any requested dimension.

Pagination

To get the next page, send nextPageToken as pageToken. Keep the request unchanged except for pageToken and, if needed, pageSize. Repeat until nextPageToken is null. Omit pageToken to start a new query.

Check Freshness

The Check Freshness operation returns the metadata of a metric set and the freshness of its data. The request has no body.

Check Freshness response fields

Field Type Always Present Notes
name String Yes The name of the metric set
description String Yes The description of the metric set
metrics[] Array of strings Yes All metrics of the metric set, for all aggregation periods. Some metrics support only DAILY. The order is not fixed.
dimensions[] Array of strings Yes The dimensions of the metric set. The order is not fixed.
freshnessInfo[] Array of objects Yes One entry for each aggregation period that has a freshness value. The array can have zero, one, or two entries.
freshnessInfo[].aggregationPeriod String Yes DAILY or HOURLY
freshnessInfo[].latestDataTimestamp String Yes The UTC start of the latest processed day (DAILY) or hour (HOURLY), in YYYY-MM-DDThh:mm:ssZ format

Check Freshness request example

GET /api/appstore/v2/applications/com.example.myapp/vitals/crashMetricSet HTTP/1.1
Host: developer.amazon.com
Authorization: Bearer ACCESS_TOKEN

Check Freshness response example

{
  "name": "crashMetricSet",
  "description": "App crash metrics",
  "metrics": [
    "crashRate",
    "crashRate7dUserWeighted",
    "crashRate28dUserWeighted",
    "userPerceivedCrashRate",
    "userPerceivedCrashRate7dUserWeighted",
    "userPerceivedCrashRate28dUserWeighted",
    "distinctDevices",
    "crashCount"
  ],
  "dimensions": [
    "versionCode",
    "countryCode",
    "deviceModel",
    "deviceType",
    "deviceOs",
    "osVersion"
  ],
  "freshnessInfo": [
    {"aggregationPeriod": "HOURLY", "latestDataTimestamp": "2026-08-18T14:00:00Z"},
    {"aggregationPeriod": "DAILY", "latestDataTimestamp": "2026-08-18T00:00:00Z"}
  ]
}

Query Metrics

The Query Metrics operation returns the metrics of one metric set for each time period in the time range that has data.

Query Metrics request parameters

Parameter Description Type Required
timelineSpec The aggregation period and the time range. For the rules, see Time values. Object Yes
timelineSpec.aggregationPeriod DAILY or HOURLY String Yes
timelineSpec.startTime The start of the time range, in the format YYYY-MM-DDThh:mm:ssZ String Yes
timelineSpec.endTime The end of the time range, in the same format. The value must be the same as startTime or later. String Yes
metrics[] The metrics to return. If you do not include metrics, the API returns all metrics of the metric set that support the aggregation period. Array of strings No
dimensions[] The dimensions for a breakdown. For slowStartRateMetricSet, dimensions must include startType. Array of strings No
filter The dimension values to include. Each key is a dimension name, and each value is an array of strings. For the rules, see Dimensions and filters. Object No
pageSize The maximum number of rows in one page. The default is 1,000. A value of 0 uses the default. A value of more than 100,000 uses 100,000. Integer No
pageToken The nextPageToken value from the previous response. Do not include pageToken in the first request. String No

Query Metrics request example

The dates in the Query Metrics and Query Issues request examples are illustrative. For a real request, choose dates based on Time values and Check Freshness.

{
  "timelineSpec": {
    "aggregationPeriod": "DAILY",
    "startTime": "2026-08-12T00:00:00Z",
    "endTime": "2026-08-18T00:00:00Z"
  },
  "metrics": ["crashRate", "crashCount", "distinctDevices"],
  "dimensions": ["versionCode"],
  "filter": {
    "countryCode": ["US", "CA"],
    "deviceType": ["FIRE_TV"]
  },
  "pageSize": 1000
}

Query Metrics response fields

Field Type Always Present Notes
freshness Object, or null Yes The freshness for the aggregation period of the query. The value is null if no freshness is available. In that case, rows is empty.
freshness.aggregationPeriod String Yes, if freshness is not null DAILY or HOURLY
freshness.latestDataTimestamp String Yes, if freshness is not null The UTC start of the latest processed day (DAILY) or hour (HOURLY), in YYYY-MM-DDThh:mm:ssZ format
resultCount Integer Yes The number of rows in this page
rows[] Array of objects Yes The rows of this page. The array is empty if no data matches the query.
nextPageToken String, or null Yes See Pagination.
availability Object Only for readyToUseMetricSet The state of the ready-to-use data for the query. For more information, see Ready-to-use availability.

Each row has the following fields:

Field Type Always Present Notes
startTime String Yes The start of the time period, in the format YYYY-MM-DDThh:mm:ssZ
aggregationPeriod String Yes DAILY or HOURLY
dimensions[] Array of objects, or null Yes One object with the fields name and value for each requested dimension, in the order of dimensions in the request. If the request has no dimensions, the field is null.
metrics[] Array of objects Yes One metric object for each requested metric. For the shape, see Metric objects.

Metric objects

A metric object has one of two shapes:

  • Rate or count metric: {"name": "crashRate", "value": 0.0142}
  • Duration metric: {"name": "coldStartAppLaunchTime", "statistics": {"Average": 1234.5678, "P90": 1500.0000, "P95": 1800.0000}}. The four duration metrics of the appLatencyMetricSet and the readyToUseMetricSet use this shape. The statistics object is always present. Each statistic can be null.

Rates are fractions (0.0142 means 1.42%), counts are integers, and duration statistics are in milliseconds. A null metric value or statistic means that the API cannot calculate it. For metric meanings, see Metric values.

Additional metric rules

The following rules apply to specific metrics:

  • A start is slow when app launch time is at least 5 seconds for a cold start or 2 seconds for a warm start.
  • For an HOURLY 7-day rate, the API combines the same UTC hour from each of the 7 days.

Query Metrics response example

In this example, the app has data only for August 17 and August 18.

{
  "freshness": {
    "aggregationPeriod": "DAILY",
    "latestDataTimestamp": "2026-08-18T00:00:00Z"
  },
  "resultCount": 2,
  "rows": [
    {
      "startTime": "2026-08-17T00:00:00Z",
      "aggregationPeriod": "DAILY",
      "dimensions": [
        {"name": "versionCode", "value": "151"}
      ],
      "metrics": [
        {"name": "crashRate", "value": 0.0142},
        {"name": "crashCount", "value": 912},
        {"name": "distinctDevices", "value": 54320}
      ]
    },
    {
      "startTime": "2026-08-18T00:00:00Z",
      "aggregationPeriod": "DAILY",
      "dimensions": [
        {"name": "versionCode", "value": "151"}
      ],
      "metrics": [
        {"name": "crashRate", "value": 0.0138},
        {"name": "crashCount", "value": 887},
        {"name": "distinctDevices", "value": 55012}
      ]
    }
  ],
  "nextPageToken": null
}

Duration metric example

The following example shows one row of an appLatencyMetricSet query without dimensions.

{
  "startTime": "2026-08-18T00:00:00Z",
  "aggregationPeriod": "DAILY",
  "dimensions": null,
  "metrics": [
    {
      "name": "coldStartAppLaunchTime",
      "statistics": {"Average": 1234.5678, "P90": 1500.0000, "P95": 1800.0000}
    },
    {
      "name": "warmStartAppLaunchTime",
      "statistics": {"Average": 412.2500, "P90": 620.0000, "P95": 710.5000}
    }
  ]
}

Ready-to-use availability

Each readyToUseMetricSet response has an availability object. The responses of the other metric sets do not have this object.

Field Type Always Present Notes
availability.state String Yes AVAILABLE or ACTION_REQUIRED
availability.reasonCode String Only for ACTION_REQUIRED READY_TO_USE_DATA_UNAVAILABLE
availability.message String Only for ACTION_REQUIRED A message that tells you what to do
availability.documentationUrl String Only for ACTION_REQUIRED A link to the instructions for ready-to-use data

The state is AVAILABLE when one or more of the requested ready-to-use metrics has data in the time range of the query. Otherwise, the state is ACTION_REQUIRED. The API checks all pages of the query, not only the current page. These are the possible causes of ACTION_REQUIRED:

  • Your app does not report that it is ready to use. On Fire OS, your app must call Activity.reportFullyDrawn(). On Vega OS, your app must call the callback from useReportFullyDrawn(). For the steps, see Available metric sets.
  • Amazon has no ready-to-use data for your app yet.
  • No freshness value is available for the aggregation period of the query.

With ACTION_REQUIRED, the response can have rows. In these rows, the statistics are null.

The following example shows an ACTION_REQUIRED response without rows.

{
  "freshness": {
    "aggregationPeriod": "DAILY",
    "latestDataTimestamp": "2026-08-18T00:00:00Z"
  },
  "resultCount": 0,
  "rows": [],
  "nextPageToken": null,
  "availability": {
    "state": "ACTION_REQUIRED",
    "reasonCode": "READY_TO_USE_DATA_UNAVAILABLE",
    "message": "Ready to Use data is not available for this app. This could mean the app has not been instrumented for Ready to Use, or data has not been generated yet. Please refer to the documentation below to verify your instrumentation setup, or reach out to your Amazon contact for assistance.",
    "documentationUrl": "https://developer.amazon.com/apps-and-games/blogs/2023/06/improve-app-startup-times"
  }
}

Query Issues

The Query Issues operation returns the top crash issues or the top ANR issues for the full time range. The response has one row for each issue. If you request dimensions, the response has one row for each combination of an issue and dimension values.

Query Issues request parameters

Parameter Description Type Required
timelineSpec The aggregation period and the time range. For the rules, see Time values. Object Yes
reportType CRASH or ANR String Yes
dimensions[] The dimensions for a breakdown. The dimensions are the same as for the crashMetricSet. Array of strings No
filter The dimension values to include. For the rules, see Dimensions and filters. Object No
pageSize The maximum number of rows in one page. The default is 25. A value of 0 uses the default. A value of more than 1,000 uses 1,000. Integer No
pageToken The nextPageToken value from the previous response. Do not include pageToken in the first request. String No

A Query Issues request does not have a metrics field. The API returns errorEventCount and affectedDeviceCount for each issue.

Query Issues request example

{
  "timelineSpec": {
    "aggregationPeriod": "DAILY",
    "startTime": "2026-08-05T00:00:00Z",
    "endTime": "2026-08-18T00:00:00Z"
  },
  "reportType": "CRASH",
  "filter": {"versionCode": ["152"]},
  "pageSize": 25
}

Query Issues response fields

Field Type Always Present Notes
rows[] Array of objects Yes The issues of this page. The array is empty if no issue matches the query.
rows[].crashDescriptor String Yes The identifier of the issue
rows[].reportText String, or null Yes One example stack trace of the issue. The example can be different in each response. The API can make a long stack trace shorter. The value is null or an empty string if no stack trace is available.
rows[].dimensions[] Array of objects, or null Yes One object with the fields name and value for each requested dimension. If the request has no dimensions, the field is null.
rows[].metrics[] Array of objects Yes Two metric objects, for errorEventCount and affectedDeviceCount. Each object has name and an integer value. For their meanings, see Available metric sets.
nextPageToken String, or null Yes The token for the next page. The value is null on the last page.

The response has no resultCount or freshness. Issues are ordered by descending errorEventCount.

Query Issues response example

{
  "rows": [
    {
      "crashDescriptor": "67f35eb2c1a94d0e",
      "reportText": "java.lang.IllegalStateException: Player is not ready\n\tat com.example.myapp.Player.start(Player.java:88)",
      "dimensions": null,
      "metrics": [
        {"name": "errorEventCount", "value": 78},
        {"name": "affectedDeviceCount", "value": 45}
      ]
    }
  ],
  "nextPageToken": null
}

Implementation examples

The following examples show Python code for the Vitals API V2. Run the examples in order, in one Python session. Each example uses the functions and the variables of the examples before it.

Obtain an access token

To get an access token, send a POST request to https://api.amazon.com/auth/o2/token, with these form fields:

  • grant_type: client_credentials
  • client_id: your client ID
  • client_secret: your client secret
  • scope: adx_reporting::appstore:marketer

The response has access_token and expires_in. The value of expires_in is the number of seconds until the token expires.

The following example keeps the token. It gets a new token 5 minutes before the token expires.

import time

import requests

# Values that you must supply
client_id = "<your client ID>"
client_secret = "<your client secret>"
package_name = "<your app package name>"

BASE_URL = "https://developer.amazon.com/api/appstore"
VITALS_URL = f"{BASE_URL}/v2/applications/{package_name}/vitals"

_token = {"value": None, "expires_at": 0.0}


def auth_headers():
    """Return the request headers with a valid LWA access token."""
    if time.time() > _token["expires_at"] - 300:
        response = requests.post(
            "https://api.amazon.com/auth/o2/token",
            data={
                "grant_type": "client_credentials",
                "client_id": client_id,
                "client_secret": client_secret,
                "scope": "adx_reporting::appstore:marketer",
            },
            timeout=30,
        )
        response.raise_for_status()
        token = response.json()
        _token["value"] = token["access_token"]
        _token["expires_at"] = time.time() + token["expires_in"]
    return {
        "Authorization": f"Bearer {_token['value']}",
        "Content-Type": "application/json",
        "Accept": "application/json",
    }

Send a request with retries

The following helper retries 429 and temporary errors. On the first 401, it refreshes the token and retries if an attempt remains. For the retry rules, see Retry strategy.

import random
import time

import requests

# Uses auth_headers() and _token from "Obtain an access token".

MAX_ATTEMPTS = 4
RETRYABLE_STATUS_CODES = {429, 500, 503, 504}


def send(method, url, body=None):
    """Send a request. Retry a throttled request or a temporary error."""
    token_renewed = False
    for attempt in range(1, MAX_ATTEMPTS + 1):
        headers = auth_headers()
        try:
            response = requests.request(
                method, url, headers=headers, json=body, timeout=60
            )
        except (requests.ConnectionError, requests.Timeout):
            if attempt == MAX_ATTEMPTS:
                raise
            time.sleep(2 ** attempt + random.uniform(0, 1))
            continue

        renew = response.status_code == 401 and not token_renewed
        if renew and attempt < MAX_ATTEMPTS:
            token_renewed = True
            _token["expires_at"] = 0.0  # Get a new token for the next attempt.
            continue
        if response.status_code not in RETRYABLE_STATUS_CODES:
            response.raise_for_status()
            return response.json()
        if attempt == MAX_ATTEMPTS:
            response.raise_for_status()

        retry_after = response.headers.get("Retry-After")
        if retry_after is not None:
            delay = float(retry_after)
        else:
            delay = 2 ** attempt + random.uniform(0, 1)
        time.sleep(delay)

Choose a time range

The following helper builds a time range that ends at the metric set's freshness value. Choose a range within the limits in Time values.

from datetime import datetime, timedelta

TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"


def time_range(metric_set_name, aggregation_period, periods):
    """Return (startTime, endTime) for the latest time periods of a metric set."""
    info = send("GET", f"{VITALS_URL}/{metric_set_name}")
    latest = {item["aggregationPeriod"]: item["latestDataTimestamp"] for item in info["freshnessInfo"]}
    end = datetime.strptime(latest[aggregation_period], TIME_FORMAT)
    step = timedelta(days=1) if aggregation_period == "DAILY" else timedelta(hours=1)
    start = end - step * (periods - 1)
    return start.strftime(TIME_FORMAT), end.strftime(TIME_FORMAT)

Query crash metrics

The following example gets the daily crash rate, the crash count, and the device count for each app version. The filter includes only Fire TV devices in the US and Canada.

start_time, end_time = time_range("crashMetricSet", "DAILY", 7)
crash_body = {
    "timelineSpec": {"aggregationPeriod": "DAILY", "startTime": start_time, "endTime": end_time},
    "metrics": ["crashRate", "crashCount", "distinctDevices"],
    "dimensions": ["versionCode"],
    "filter": {"countryCode": ["US", "CA"], "deviceType": ["FIRE_TV"]},
}

result = send("POST", f"{VITALS_URL}/crashMetricSet:query", crash_body)
for row in result["rows"]:
    values = {metric["name"]: metric["value"] for metric in row["metrics"]}
    print(row["startTime"], row["dimensions"], values)

Query app launch time

The duration metrics have statistics, not value. The following example gets the app launch times for cold starts and warm starts, for each device type.

start_time, end_time = time_range("appLatencyMetricSet", "DAILY", 7)
result = send("POST", f"{VITALS_URL}/appLatencyMetricSet:query", {
    "timelineSpec": {"aggregationPeriod": "DAILY", "startTime": start_time, "endTime": end_time},
    "metrics": ["coldStartAppLaunchTime", "warmStartAppLaunchTime"],
    "dimensions": ["deviceType"],
})

Query slow start rate

The slowStartRateMetricSet requires startType in dimensions. The following example gets the hourly slow start rate for the latest 24 hours, for cold starts only.

start_time, end_time = time_range("slowStartRateMetricSet", "HOURLY", 24)
slow_start_body = {
    "timelineSpec": {"aggregationPeriod": "HOURLY", "startTime": start_time, "endTime": end_time},
    "metrics": ["slowStartRate", "slowStartRate7dDeviceWeighted", "distinctDevices"],
    "dimensions": ["startType"],
    "filter": {"startType": ["COLD"]},
}

result = send("POST", f"{VITALS_URL}/slowStartRateMetricSet:query", slow_start_body)
for row in result["rows"]:
    values = {metric["name"]: metric["value"] for metric in row["metrics"]}
    print(row["startTime"], values["slowStartRate"], values["distinctDevices"])

Query ready-to-use time

The following example gets the ready-to-use time and checks the availability object.

from datetime import timezone

try:
    start_time, end_time = time_range("readyToUseMetricSet", "DAILY", 7)
except KeyError:
    # No freshness yet; query recent UTC dates to get the availability state.
    end = datetime.now(timezone.utc)
    start_time = (end - timedelta(days=6)).strftime(TIME_FORMAT)
    end_time = end.strftime(TIME_FORMAT)

ready_to_use_body = {
    "timelineSpec": {"aggregationPeriod": "DAILY", "startTime": start_time, "endTime": end_time},
    "metrics": ["coldStartReadyToUseTime", "warmStartReadyToUseTime"],
}

result = send("POST", f"{VITALS_URL}/readyToUseMetricSet:query", ready_to_use_body)
availability = result["availability"]
if availability["state"] == "ACTION_REQUIRED":
    print(availability["message"])
    print(availability["documentationUrl"])

for row in result["rows"]:
    for metric in row["metrics"]:
        print(row["startTime"], metric["name"], metric["statistics"]["Average"])

Paginate through results

The following function gets all pages of a query. It waits 0.4 seconds between two pages to stay under the rate limit. It returns the rows and the freshness object of the last page. For Query Issues, the second value is None, because the response has no freshness.

def query_all_rows(url, body):
    """Send a query. Return the rows of all pages and the freshness of the last page."""
    rows = []
    page_body = dict(body)
    page_body.pop("pageToken", None)
    while True:
        result = send("POST", url, page_body)
        rows.extend(result["rows"])
        if result["nextPageToken"] is None:
            return rows, result.get("freshness")
        page_body["pageToken"] = result["nextPageToken"]
        time.sleep(0.4)

Query top crash issues

The following example gets the first page of the top crash issues of version code 152 in the latest 14 days. The filter selects the version.

start_time, end_time = time_range("crashMetricSet", "DAILY", 14)
issues_body = {
    "timelineSpec": {"aggregationPeriod": "DAILY", "startTime": start_time, "endTime": end_time},
    "reportType": "CRASH",
    "filter": {"versionCode": ["152"]},
    "pageSize": 25,
}

result = send("POST", f"{VITALS_URL}/issuesMetricSet:query", issues_body)
for issue in result["rows"]:
    counts = {metric["name"]: metric["value"] for metric in issue["metrics"]}
    print(issue["crashDescriptor"], counts["errorEventCount"], counts["affectedDeviceCount"])
    print(issue["reportText"])

Error handling

See the following sections for how to handle errors.

Error response format

The API sends a JSON body with each error response. The HTTP status code is in the status line of the response. The body does not contain the status code or the request ID.

Field Type Always Present Notes
code String Yes The error code, for example VALIDATION_ERROR
message String Yes A description of the error
errors[] Array of objects Only for 400 VALIDATION_ERROR One entry for each problem
errors[].field String Yes The path of the field, for example timelineSpec.endTime, metrics[1], or filter.countryCode[0]
errors[].code String Yes The field error code. For the values, see Field error codes.
errors[].message String Yes A description of the problem

Each success response and each error response has an x-amzn-RequestId header. Record this value. If you contact Amazon Developer Support about a request, include this value.

Single-error example

The following response is for an HOURLY query that requests crashRate28dUserWeighted.

{
  "code": "VALIDATION_ERROR",
  "message": "Metric 'crashRate28dUserWeighted' is not available for HOURLY aggregation",
  "errors": [
    {
      "field": "metrics[0]",
      "code": "VALUE_NOT_ALLOWED",
      "message": "Metric 'crashRate28dUserWeighted' is not available for HOURLY aggregation"
    }
  ]
}

Multi-field validation example

The following response is for a query with "startTime": "2026-08-18" and "pageSize": -5.

{
  "code": "VALIDATION_ERROR",
  "message": "The request contains 2 invalid fields.",
  "errors": [
    {
      "field": "timelineSpec.startTime",
      "code": "FIELD_INVALID",
      "message": "timelineSpec.startTime must be a valid canonical UTC timestamp in YYYY-MM-DDThh:mm:ssZ format"
    },
    {
      "field": "pageSize",
      "code": "FIELD_OUT_OF_RANGE",
      "message": "pageSize must be non-negative, got: -5"
    }
  ]
}

Error codes

HTTP Status Code Cause Action
400 VALIDATION_ERROR The request has a problem, for example an unknown metric or an unknown metricSetName, or a Content-Type header that is not application/json. If the response has errors[], use it to find the problem. Correct the request, and then send the corrected request.
401 AUTHENTICATION_FAILED The access token is missing, expired, or not valid, or the security profile is not attached to the Reporting API. Get a new access token. Make sure that you attached the security profile to the Reporting API.
403 ACCESS_DENIED Your developer account does not own the app, or the Vitals API V2 is not available to your developer account. Make sure that your developer account owns the app. If your developer account owns the app, wait 1 minute and send the request again one time. If you get the error again, contact Amazon Developer Support. Include the x-amzn-RequestId value.
404 RESOURCE_NOT_FOUND No app has this package name, the path is not correct, or the HTTP method is not correct for the path. Make sure that the package name and the path are correct. Use GET for Check Freshness. Use POST for Query Metrics and Query Issues, with :query at the end of the path.
429 RATE_LIMIT_EXCEEDED You sent more requests than the rate limit permits. Wait for the number of seconds in the Retry-After header. Then send the request again.
500 INTERNAL_ERROR An unexpected error occurred in the service, or a service dependency failed during the query. Send the request again with exponential backoff, up to 3 times. If the response has a Retry-After header, wait for that number of seconds. For the wait times, see Retry strategy.
503 SERVICE_UNAVAILABLE A service dependency is temporarily unavailable, or a gateway cannot reach the service. Send the request again with exponential backoff, up to 3 times. If the response has a Retry-After header, wait for that number of seconds. For the wait times, see Retry strategy.
504 GATEWAY_TIMEOUT A gateway did not get a response from the service in time. Send the request again with exponential backoff, up to 3 times. If the response has a Retry-After header, wait for that number of seconds. For the wait times, see Retry strategy.

Field error codes

Field Error Code Meaning
FIELD_REQUIRED The request does not have a required field, or an array has an empty element.
FIELD_INVALID The value has the wrong format or the wrong type, or the field is not a supported field.
FIELD_TOO_LONG A versionCode value is longer than 32 characters.
FIELD_OUT_OF_RANGE The value is out of the permitted range. For example, the time range is too long, or pageSize is negative.
FIELD_NOT_UNIQUE A value or a JSON key occurs more than one time.
FIELD_IMMUTABLE The pageToken is not valid. Use the nextPageToken from the previous response, or omit pageToken to start a new query.
VALUE_NOT_ALLOWED The value is not in the set of permitted values. For example, the metric or the dimension is unknown. A null value in filter, or a null element in metrics or dimensions, also causes this code.

Common mistakes and fixes

Mistake Result Fix
Using a V1 time object 400 VALIDATION_ERROR Use a UTC timestamp string, such as 2026-08-18T00:00:00Z.
Using a V1 device type, such as AMAZON_FIRE_TV 400 VALIDATION_ERROR Use FIRE_TV, FIRE_TABLET, or OTHER.
Requesting a 28d metric with HOURLY 400 VALIDATION_ERROR Use DAILY.
Omitting startType from a slowStartRateMetricSet query 400 VALIDATION_ERROR Add startType to dimensions.
Reading value from a duration metric No value field Read statistics.Average, statistics.P90, or statistics.P95.

Rate limits, retries, and best practices

As a guideline, the API permits about 3 requests per second for each developer account, with short bursts of up to 10 requests. All Vitals API V2 operations share this limit, for all your apps. The limit can change, and the API can send a 429 response before you reach it. V1 of the Vitals API has a separate limit.

When you exceed the limit, the API sends a 429 RATE_LIMIT_EXCEEDED response with a Retry-After header. Wait for the number of seconds in the header before you send the request again.

Retry strategy

The send function sends a request again after 429, 500, 503, 504, a connection error, or a timeout, up to 3 times. If the response has a Retry-After header, the function waits for that number of seconds. Otherwise, it waits about 2, 4, and 8 seconds, plus up to 1 second of random jitter. After the first 401, the function gets a new access token and sends the request again if an attempt remains. The function does not retry other 4xx errors or failed token requests.

Production best practices

Practice Recommendation
Re-query recent periods Re-query recent periods and replace stored values; late data can change them.
Use error codes Use code, errors[].code, and errors[].field instead of parsing error messages.
Ignore unknown values Ignore response fields, metrics, and dimension values that you do not know; Amazon can add them during the beta.

Migrate from V1

Version 1 (V1) of the Vitals API continues to operate. The following table shows the changes that you must make to move to version 2 (V2).

Item V1 V2
Endpoint path /vitals/apps/{packageName}/{metricSetName} /v2/applications/{packageName}/vitals/{metricSetName}
Time values in requests An object, for example {"year": 2026, "month": 8, "day": 18, "hours": 14} A string, for example "2026-08-18T14:00:00Z"
Time range of Query Issues startTime and endTime at the top level of the body timelineSpec, with aggregationPeriod, startTime, and endTime
startTime in rows An object A string in the format YYYY-MM-DDThh:mm:ssZ
Freshness time values For example 2026-08-18 and 2026-08-18T14 For example 2026-08-18T00:00:00Z and 2026-08-18T14:00:00Z
Metric values Strings, for example "0.0142" Numbers, for example 0.0142
Duration metrics (app launch time and ready-to-use time) One value statistics, with Average, P90, and P95
status and requestId in the Query Metrics response Present Removed. The request ID is in the x-amzn-RequestId header.
deviceType values AMAZON_FIRE_TV or AMAZON_TABLETS or AMAZON_FIRE_PHONE or AUCC or CORO FIRE_TV or FIRE_TABLET or OTHER
Operating system dimension deviceOS deviceOs
App that your developer account does not own 404 NOT_FOUND 403 ACCESS_DENIED
Error body code, message, status code, message, and errors[] for validation errors

Last updated: Oct 13, 2026