as

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

Vitals API Reference

This guide includes reference information for you to use to integrate the Vitals API.

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

Prerequisites

Before making API calls, make sure you meet the following requirements:

Requirement Details
Amazon Developer Account Admin-level credentials required
Security Profile Created via Developer Console > My Settings > API Access
Security Profile mapped to API Attach the profile to the Reporting API
Client ID and Client Secret From the security profile's Web Settings tab
LWA Scope adx_reporting::appstore:marketer
Package Name Your app's package name (for example, com.example.myapp) must be owned by your vendor account
Python 3.6+ with requests library  

Base URL

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

Operations

The Vitals API includes the following operations and endpoints:

Operation Method Endpoint Description
Check Freshness GET /vitals/apps/{packageName}/{metricSetName} Latest available data timestamp
Query Metrics POST /vitals/apps/{packageName}/{metricSetName}:query Time-series metrics with dimensions/filters

Request

See the following sections for details on the parameters and validation rules.

Request parameters

Every query request must include the following parameters:

Parameter Description Type Required
timelineSpec.aggregationPeriod Granularity of the returned data: DAILY or HOURLY String Yes
timelineSpec.startTime Lower bound of the query window, as a calendar date in UTC: {year, month, day} Object Yes
timelineSpec.endTime Upper bound of the query window, as a calendar date in UTC: {year, month, day}. Must be greater than startTime. Object Yes
metrics[] Subset of metric names to return from the metric set (for example, crashRate, crashCount). Omit to return all metrics defined on the set. Array No
dimensions[] Dimensions to group results by (for example, versionCode, countryCode) Array No
filter Filter conditions as a JSON object. Each key is a dimension; its value is a list of allowed values (OR within a field, AND across fields). For example, {"countryCode": ["US","CA"], "deviceType": ["AMAZON_FIRE_TV"]}. String No
pageSize Max rows per page. Default: 1000. Max: 100,000. Integer No
pageToken Opaque cursor returned in the previous response's nextPageToken. Omit on the first request. String No
reportType Error type for issuesMetricSet only. One of CRASH or ANR. Not applicable to rate metric sets. String No

Response

See the following sections for details on the response fields.

Response fields

Field Always Present Notes
status Yes HTTP status code (200)
requestId Yes UUID for debugging; include in support tickets
freshness Yes Latest data timestamp for the queried period
resultCount Yes Number of rows in this page
rows[] Yes May be empty if no data matches
nextPageToken Only if more pages Pass in next request to get next page

Successful response (200)

{
  "status": 200,
  "requestId": "8a3f2c71-e4b9-4012-a8c1-9d5e7f3b2a10",
  "freshness": {
    "aggregationPeriod": "DAILY",
    "latestDataTimestamp": "2026-04-25T00:00:00Z"
  },
  "resultCount": 3,
  "rows": [
    {
      "startTime": {"year": 2026, "month": 4, "day": 25},
      "aggregationPeriod": "DAILY",
      "dimensions": [
        {"name": "versionCode", "value": "151"},
        {"name": "countryCode", "value": "US"}
      ],
      "metrics": [
        {"name": "crashRate", "value": "0.0142"},
        {"name": "distinctDevices", "value": "54320"}
      ]
    }
  ],
  "nextPageToken": "MjAyNi0wNC0yNXwxNA=="
}

Implementation examples

The following examples show Python code for various tasks using the Vitals API.

Obtain access token

Use your client ID and client secret to obtain an auth token. You must add the auth token to the header of each API request. The following example shows how to obtain an auth token and create the Authorization header using the token.

# Values that you need to provide
client_id = "<your client id>"
client_secret = "<your client secret>"
package_name = "<your app package name>"

BASE_URL = "https://developer.amazon.com/api/appstore"

scope = "adx_reporting::appstore:marketer"
data = {
    "grant_type": "client_credentials",
    "client_id": client_id,
    "client_secret": client_secret,
    "scope": scope
}
auth_response = requests.post("https://api.amazon.com/auth/o2/token", data=data)
auth_token = auth_response.json()["access_token"]

headers = {
    "Authorization": f"Bearer {auth_token}",
    "Content-Type": "application/json"
}

Check data freshness

The following example shows how to check the latest available data timestamp before querying.

freshness_url = f"{BASE_URL}/vitals/apps/{package_name}/crashMetricSet"
freshness_response = requests.get(freshness_url, headers=headers)
freshness = freshness_response.json()

for info in freshness["freshnessInfo"]:
    print(f"{info['aggregationPeriod']}: {info['latestDataTimestamp']}")

GET freshness response example

{
    "name": "crashMetricSet",
    "metrics": ["crashRate", "crashRate7dUserWeighted", "crashRate28dUserWeighted", "userPerceivedCrashRate", "userPerceivedCrashRate7dUserWeighted", "userPerceivedCrashRate28dUserWeighted",
"distinctDevices", "crashCount"],
    "dimensions": ["versionCode", "countryCode", "deviceModel", "deviceType", "deviceOS", "osVersion"],
    "freshnessInfo": [
        {"aggregationPeriod": "DAILY", "latestDataTimestamp": "2026-04-25"},
        {"aggregationPeriod": "HOURLY", "latestDataTimestamp": "2026-04-25T14"}
    ]
}

Convert local time to UTC

The following example shows how to convert a local datetime to UTC.

from datetime import datetime, timezone, timedelta


def utc_date_components(local_dt=None):
    """Convert a datetime to UTC year/month/day for the API.

    A naive local_dt is interpreted as system local time.
    Pass a tz-aware datetime to avoid ambiguity.
    """
    if local_dt is None:
        utc_dt = datetime.now(timezone.utc)
    else:
        if local_dt.tzinfo is None:
            local_dt = local_dt.astimezone()  # attach system local tz explicitly
        utc_dt = local_dt.astimezone(timezone.utc)
    return {"year": utc_dt.year, "month": utc_dt.month, "day": utc_dt.day}


# Example: "yesterday" in UTC (regardless of your local timezone)
yesterday_utc = datetime.now(timezone.utc) - timedelta(days=1)
start = utc_date_components(yesterday_utc)

Query metrics

The following example shows the full shape of a query against a rate metric set (crash, ANR, LMK). Comments call out which fields are required vs. optional.

metric_set = "crashMetricSet"   # or "anrMetricSet", "lmkMetricSet"
query_url = f"{BASE_URL}/vitals/apps/{package_name}/{metric_set}:query"

body = {
    # Required: time range and granularity
    "timelineSpec": {
        "aggregationPeriod": "DAILY", # DAILY / HOURLY
        "startTime": {"year": 2026, "month": 4, "day": 01},
        "endTime":   {"year": 2026, "month": 4, "day": 01},
    },

    # Optional: metrics from the metricset being queried
    "metrics": ["crashRate", "crashCount", "distinctDevices"],

    # Optional: break down by dimension (max 5)
    "dimensions": ["versionCode", "countryCode"],

    # Optional: AIP-160 filter expression
    "filter": {"countryCode": ["US", "CA"], "deviceType": ["AMAZON_FIRE_TV"]},

    # Optional: paging (default 1000, max 100000)
    "pageSize": 1000,
    "pageToken": "<from previous response>",
}

response = requests.post(query_url, headers=headers, json=body)
result = response.json()

Paginate through results

The following example shows how to fetch all results when the response includes a nextPageToken.

all_rows = []
body.pop("pageToken", None)
while True:
    result = requests.post(query_url, headers=headers, json=body).json()
    all_rows.extend(result["rows"])
    token = result.get("nextPageToken")
    if not token:
        break
    body["pageToken"] = token

Query top crash issues

The issuesMetricSet returns top crash/ANR/LMK issues ranked by frequency. Unlike rate metric sets, it aggregates over the entire time range and returns one row per unique crash signature with its stacktrace. ReportType specifies CRASH or ANR for the shared stacktraces.

issues_url = f"{BASE_URL}/vitals/apps/{package_name}/issuesMetricSet:query"
body = {
  "startTime": { "year": 2025, "month": 6, "day": 1 },
  "endTime": { "year": 2025, "month": 6, "day": 15 },
  "reportType": "CRASH",
  "dimensions": ["deviceType", "osVersion"],
  "filter": { "countryCode": ["US", "CA"] },
  "pageSize": 25,
  "pageToken": null
}
response = requests.post(issues_url, headers=headers, json=body)
result = response.json()

# Response shape (different from rate metric sets):
# {
#   "rows": [
#     {
#       "crashDescriptor": "67f35eb2...",
#       "reportText": "java.lang.IllegalStateException: ...",
#       "metrics": [
#         {"name": "errorEventCount", "value": "78"},
#         {"name": "affectedDeviceCount", "value": "45"}
#       ]
#     }
#   ],
#   "nextPageToken": "..."
# }

Error handling

See the following sections for how to handle errors.

Error response format

All errors return structured JSON:

{
  "code": "INVALID_ARGUMENT",
  "message": "Unknown metric 'bogus'. Valid metrics for crashMetricSet: crashRate, crashRate7dUserWeighted, ...",
  "status": 400
}

Error codes

HTTP Status Code Cause Action
400 INVALID_ARGUMENT Bad request params (invalid metric, dimension, date range) Fix the request; do not retry
403 UNAUTHORIZED Expired or invalid token Refresh token and retry
404 NOT_FOUND Unknown metric set name Check spelling of metricSetName
429 RATE_LIMITED Exceeded 3 req/sec Back off and retry with exponential delay
500 INTERNAL Server error Retry with backoff (up to 3 times)

Common mistakes and fixes

Mistake Error You'll See Fix
Missing timelineSpec "timelineSpec is required" Add the required timelineSpec object
Using HOURLY with 28d metric "crashRate28dUserWeighted not available for HOURLY" Switch to DAILY or remove the 28d metric
Date range too wide (hourly) "HOURLY queries limited to 15 days" Narrow to 15 days or fewer
Expired token 403 Forbidden Refresh the LWA token
Too many requests 429 Too Many Requests Add time.sleep() between calls

Retries and best practices

See the following sections for retry strategy and best practices.

Retry strategy example

import time

MAX_RETRIES = 3
RETRIABLE_CODES = {429, 500, 502, 503}

def query_with_retry(client, package, metric_set, body):
    for attempt in range(MAX_RETRIES):
        resp = client.query(package, metric_set, body)

        if resp.status_code == 200:
            return resp.json()

        if resp.status_code not in RETRIABLE_CODES:
            break  # Non-retriable error

        wait = (2 ** attempt) + 0.5  # 1.5s, 2.5s, 4.5s
        print(f"Retry {attempt + 1}/{MAX_RETRIES} after {wait}s (HTTP {resp.status_code})")
        time.sleep(wait)

    # Final attempt failed
    error = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {}
    raise RuntimeError(f"Failed after {MAX_RETRIES} retries: {resp.status_code} {error.get('message', '')}")

Production best practices

Practice Recommendation
Token caching Cache the token and refresh 5 min before expiry. Never request a new token per API call.
Check freshness first Always call the freshness endpoint before querying to avoid empty results.
Use pagination Default pageSize is 1000. For large dimension breakdowns, paginate to avoid timeouts.
Rate limiting Stay under 3 req/sec. Add time.sleep(0.4) between sequential calls.
Retry only retriable errors Retry 429, 500, 502, 503. Never retry 400 (fix the request instead).
Log requestId Every response includes requestId. Log it for debugging and support tickets.
Handle empty results A query returning rows: [] is not an error. It means there is no data for that date range.
Avoid over-fetching Request only the metrics you need. Omitting metrics[] returns all, which is slower.

Last updated: Jun 29, 2026