Monetization Metrics API Reference
This guide includes reference information for you to use to integrate the Monetization Metrics API.
Use of the Monetization Metrics API is subject to the Program Materials License Agreement.
- Prerequisites
- Base URL
- Operations
- Request
- Response
- Implementation examples
- Error handling
- Retries and best practices
- Related topics
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 |
| Login with Amazon (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 |
pip install requests |
Base URL
The base URL of the Monetization Metrics API is https://developer.amazon.com/api/appstore.
Operations
The Monetization Metrics API includes the following operations and endpoints:
| Operation | Method | Endpoint | Description |
|---|---|---|---|
| Check Freshness | GET | /v2/applications/{packageName}/monetization/{metricSetName} |
Latest available data timestamp |
| Query Metrics | POST | /v2/applications/{packageName}/monetization/{metricSetName}:query |
Time-series metrics with dimensions/filters |
The {metricSetName} path parameter accepts the following metric sets: subsSignupMetricSet, subsRenewalMetricSet, subsReactivationMetricSet, subsCancellationMetricSet, subsActiveMetricSet, subsHealthMetricSet, subsCancellationSurveyMetricSet, and subsFulfillmentMetricSet.
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 an ISO 8601 UTC timestamp string in %Y-%m-%dT%H:%M:%SZ format, for example "2026-07-01T00:00:00Z". Both bounds are inclusive. Fields finer than the aggregation period are ignored, not rejected: minutes/seconds are dropped on HOURLY queries (T10:15:00Z is treated as hour 10), and hours and finer are dropped on DAILY queries. |
String | Yes |
timelineSpec.endTime |
Upper bound of the query window as an ISO 8601 UTC timestamp string in %Y-%m-%dT%H:%M:%SZ format. Must be on or after startTime; both bounds are inclusive. Finer-than-granularity fields are ignored, same rule as startTime. An HOURLY endTime of T11:30:45Z is treated as hour 11, so the 11th hour is still included. |
String | Yes |
metrics[] |
Subset of metric names to return from the metric set. Omit to return all metrics defined on the set. | Array | No |
dimensions[] |
Dimensions to group results by (for example, subscriptionType, marketplace). |
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, {"marketplace": ["US"]}. |
Object | No |
pageSize |
Max rows per page. Default: 1,000. Max: 100,000. | Integer | No |
pageToken |
Opaque cursor returned in the previous response's nextPageToken. Omit on the first request. |
String | No |
Validation rules
The API validates every query request before executing it. A request that violates any of the following rules is rejected with 400 VALIDATION_ERROR; the response body's message field describes the violation. The following examples show the message returned for each rule.
| Rule | Error if violated |
|---|---|
endTime must be greater than or equal to startTime |
timelineSpec.endTime must be on or after startTime |
HOURLY timestamps must fall within the last 15 days |
timelineSpec.startTime must be within the last 15 days |
DAILY timestamps must fall within the last 30 days |
timelineSpec.startTime must be within the last 30 days |
| Metric name must exist in the metric set | metrics contains '…' which is not supported by '…MetricSet'; allowed: […] |
| Dimension name must exist | dimensions contains '…' which is not a known dimension; allowed: […] |
| Dimension must be supported by the metric set | dimensions contains '…' which is not supported by '…MetricSet'; allowed: […] |
subsActiveMetricSet and subsCancellationSurveyMetricSet support DAILY only |
'..MetricSet' does not support HOURLY aggregation |
marketplace must be a supported code |
Invalid value 'UK' for dimension 'marketplace'. Valid: [CA, US, MX, JP, FR, IT, DE, BR, ES, IN, AU, GB] |
metrics must not contain duplicates |
metrics contains 'fulfilled' more than once |
dimensions must not contain duplicates |
dimensions contains 'marketplace' more than once |
pageSize must not be negative |
pageSize must be non-negative, got: -5 |
| Timestamps must be valid ISO 8601 UTC instants | Invalid startTime: '2026-02-30T00:00:00Z' is not a valid date |
| At most 100 values per filter key | 400 VALIDATION_ERROR |
pageToken must be a valid cursor from the previous response, reused with the same request shape |
pageToken does not match this request. The request shape (metricSet, timelineSpec, dimensions, or filter) changed since the cursor was issued. Start a new query. (FIELD_IMMUTABLE) |
termSku is free text and passed through as supplied, so a misspelled termSku filter value returns an empty result set rather than an error. All other dimensions are validated against their fixed set of values and reject unrecognized values with 400 VALIDATION_ERROR.Response
The response shape differs by operation. The freshness check (GET) returns a freshnessInfo array with one entry per aggregation period, while the query endpoint (POST) returns a single freshness object matching the requested period.
GET freshness response fields
| Field | Always Present | Notes |
|---|---|---|
name |
Yes | Metric set name |
description |
Yes | Metric set description |
metrics |
Yes | Array of available metric names |
dimensions |
Yes | Array of available dimension names |
freshnessInfo[] |
Yes | One entry per aggregation period supported by this metric set. DAILY-only metric sets such as subsActiveMetricSet return a single DAILY entry. |
freshnessInfo[].aggregationPeriod |
Yes | DAILY or HOURLY |
freshnessInfo[].latestDataTimestamp |
Yes | Latest fully-complete bucket as an ISO 8601 UTC instant. DAILY returns T00:00:00Z (whole UTC day complete); HOURLY returns Thh:00:00Z (that hh:00–hh:59 hour complete). |
Successful response (200)
Headers
HTTP/1.1 200 OK
Content-Type: application/json
x-amzn-RequestId: 0d4db8e3-c535-4ba8-bdce-614eb6490570
Body
{
"name": "subsSignupMetricSet",
"description": "New subscription signups and signup revenue",
"metrics": ["signups", "signupRevenue"],
"dimensions": ["marketplace", "termSku", "deviceType", "deviceOs", "subscriptionType"],
"freshnessInfo": [
{"aggregationPeriod": "DAILY", "latestDataTimestamp": "2026-07-06T00:00:00Z"},
{"aggregationPeriod": "HOURLY", "latestDataTimestamp": "2026-07-06T14:00:00Z"}
]
}
Interpreting latestDataTimestamp: The value is an ISO 8601 UTC instant labeling the latest fully-complete aggregation bucket. The label is the bucket's start instant, and the entire bucket is complete. For HOURLY, Thh:00:00Z means the hh:00–hh:59 hour is complete (for example, 2026-07-06T14:00:00Z means the 14:00–14:59 hour is done). For DAILY, YYYY-MM-DDT00:00:00Z means that whole UTC day is complete (for example, 2026-07-06T00:00:00Z means all of July 6 is done).
POST query response fields
| Field | Always Present | Notes |
|---|---|---|
freshness |
Yes | Latest data timestamp for the queried aggregation 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)
Headers
HTTP/1.1 200 OK
Content-Length: 735
Content-Type: application/json
Date: Mon, 21 Sep 2026 13:13:56 GMT
x-amzn-RequestId: 912d79f5-e9b6-4466-b438-b41a96c30acf
Body
{
"freshness": {
"aggregationPeriod": "DAILY",
"latestDataTimestamp": "2026-07-06T00:00:00Z"
},
"resultCount": 3,
"rows": [
{
"startTime": "2026-07-01T00:00:00Z",
"aggregationPeriod": "DAILY",
"dimensions": [
{"name": "subscriptionType", "value": "FREE_TRIAL"},
{"name": "marketplace", "value": "US"}
],
"metrics": [
{"name": "signups", "value": 2341},
{"name": "signupRevenue", "value": 12345.6}
]
}
],
"nextPageToken": "MjAyNi0wNy0wMXwxNA=="
}
Implementation examples
The following examples show Python code for various tasks using the Monetization Metrics API.
DAILY timestamps within the last 30 days and HOURLY timestamps within the last 15 days, so replace them with dates inside that window. Use the Check data freshness example to find the latest available date.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.
import requests
# 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}/v2/applications/{package_name}/monetization/subsSignupMetricSet"
freshness_response = requests.get(freshness_url, headers=headers)
freshness = freshness_response.json()
for info in freshness["freshnessInfo"]:
print(f"{info['aggregationPeriod']}: {info['latestDataTimestamp']}")
Query signup metrics
The following example shows how to query daily signup metrics.
query_url = f"{BASE_URL}/v2/applications/{package_name}/monetization/subsSignupMetricSet:query"
body = {
"timelineSpec": {
"aggregationPeriod": "DAILY",
"startTime": "2026-07-01T00:00:00Z",
"endTime": "2026-07-07T00:00:00Z"
},
"metrics": ["signups", "signupRevenue"]
}
response = requests.post(query_url, headers=headers, json=body)
result = response.json()
Query with dimensions
The following example shows how to break down signups by subscription type and marketplace.
body = {
"timelineSpec": {
"aggregationPeriod": "DAILY",
"startTime": "2026-07-01T00:00:00Z",
"endTime": "2026-07-07T00:00:00Z"
},
"metrics": ["signups", "signupRevenue"],
"dimensions": ["subscriptionType", "marketplace"]
}
response = requests.post(query_url, headers=headers, json=body)
result = response.json()
Query with filters
The following example shows how to narrow results to only free trial signups in the US.
body = {
"timelineSpec": {
"aggregationPeriod": "DAILY",
"startTime": "2026-07-01T00:00:00Z",
"endTime": "2026-07-07T00:00:00Z"
},
"metrics": ["signups"],
"filter": {"subscriptionType": ["FREE_TRIAL"], "marketplace": ["US"]}
}
response = requests.post(query_url, headers=headers, json=body)
result = response.json()
Query hourly metrics
The following example shows how to query hourly data for near-real-time subscription monitoring.
body = {
"timelineSpec": {
"aggregationPeriod": "HOURLY",
"startTime": "2026-07-07T08:00:00Z",
"endTime": "2026-07-07T11:00:00Z"
},
"metrics": ["signups"],
"dimensions": ["subscriptionType"]
}
To cover the whole day, set startTime to T00:00:00Z and endTime to T23:00:00Z. One row per hour is returned. Minutes and seconds in either bound are ignored (dropped to the enclosing hour).
body = {
"timelineSpec": {
"aggregationPeriod": "HOURLY",
"startTime": "2026-07-07T00:00:00Z",
"endTime": "2026-07-07T23:00:00Z"
},
"metrics": ["signups"],
"dimensions": ["subscriptionType"]
}
response = requests.post(query_url, headers=headers, json=body)
result = response.json()
Query fulfillment metrics
The following example shows how to query receipt delivery performance.
fulfillment_url = f"{BASE_URL}/v2/applications/{package_name}/monetization/subsFulfillmentMetricSet:query"
body = {
"timelineSpec": {
"aggregationPeriod": "DAILY",
"startTime": "2026-07-01T00:00:00Z",
"endTime": "2026-07-07T00:00:00Z"
},
"metrics": [
"totalReceipts",
"fulfilled",
"unavailable",
"pending",
"p50AcknowledgementDelay",
"p90AcknowledgementDelay",
"p95AcknowledgementDelay"
],
"filter": {"purchaseChannel": ["QS"]}
}
response = requests.post(fulfillment_url, headers=headers, json=body)
result = response.json()
Query subscription health
The following example shows how to track auto-renewal changes and grace period events.
health_url = f"{BASE_URL}/v2/applications/{package_name}/monetization/subsHealthMetricSet:query"
body = {
"timelineSpec": {
"aggregationPeriod": "DAILY",
"startTime": "2026-07-01T00:00:00Z",
"endTime": "2026-07-07T00:00:00Z"
},
"metrics": ["autoRenewalOff", "autoRenewalOn", "gracePeriodEntries", "gracePeriodRecoveries"],
"dimensions": ["subscriptionType"]
}
response = requests.post(health_url, headers=headers, json=body)
result = response.json()
Query cancellation survey responses
The following example shows how to break down surveyed cancellations by the reason customers selected in the cancellation survey. This metric set supports DAILY granularity only.
survey_url = f"{BASE_URL}/v2/applications/{package_name}/monetization/subsCancellationSurveyMetricSet:query"
body = {
"timelineSpec": {
"aggregationPeriod": "DAILY",
"startTime": "2026-07-01T00:00:00Z",
"endTime": "2026-07-07T00:00:00Z"
},
"metrics": ["surveyedCancellations"],
"dimensions": ["cancellationReason"]
}
response = requests.post(survey_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 = []
page_token = None
while True:
body = {
"timelineSpec": {
"aggregationPeriod": "DAILY",
"startTime": "2026-07-01T00:00:00Z",
"endTime": "2026-07-07T00:00:00Z"
},
"metrics": ["signups"],
"dimensions": ["subscriptionType", "marketplace"],
"pageSize": 100
}
if page_token:
body["pageToken"] = page_token
response = requests.post(query_url, headers=headers, json=body)
result = response.json()
all_rows.extend(result["rows"])
page_token = result.get("nextPageToken")
if not page_token:
break
Error handling
All errors return structured JSON. The HTTP status line carries the status class (4xx/5xx); the body carries a machine-readable code, a human-readable message, and (for validation failures) a field-level errors[] array identifying each offending field.
| Field | Always Present | Notes | |
|---|---|---|---|
| 1 | code |
Yes | Machine-readable error code from the standard enum (for example, VALIDATION_ERROR) |
| 2 | message |
Yes | Human-readable description of the error |
| 3 | errors[] |
Only for validation failures | Field-level details; each entry has field (a dot-notation path to the offending element, with an array index where applicable, for example metrics[1] or filter.deviceType[0]), code (a field-level code, such as VALUE_NOT_ALLOWED or FIELD_INVALID), and message |
Single-error example
{
"code": "VALIDATION_ERROR",
"message": "metrics contains 'bogus' which is not supported by 'subsSignupMetricSet'; allowed: [signupRevenue, signups]",
"errors": [
{
"field": "metrics[0]",
"code": "VALUE_NOT_ALLOWED",
"message": "metrics contains 'bogus' which is not supported by 'subsSignupMetricSet'; allowed: [signupRevenue, signups]"
}
]
}
Multi-field validation example
{
"code": "VALIDATION_ERROR",
"message": "The request contains 3 invalid fields.",
"errors": [
{
"field": "metrics[1]",
"code": "VALUE_NOT_ALLOWED",
"message": "metrics contains 'bogusRate' which is not supported by 'subsSignupMetricSet'; allowed: [signupRevenue, signups]"
},
{
"field": "dimensions[0]",
"code": "VALUE_NOT_ALLOWED",
"message": "dimensions contains 'startType' which is not a known dimension; allowed: [deviceOs, deviceType, marketplace, subscriptionType, termSku]"
},
{
"field": "filter.deviceType[1]",
"code": "VALUE_NOT_ALLOWED",
"message": "Invalid value 'AMAZON_FIRE_TV' for dimension 'deviceType'. Valid: [FIRE_TABLET, FIRE_TV, OTHER]"
}
]
}
Error codes
| HTTP Status | Code | Cause | Action |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Bad request params (invalid metric, dimension, filter value, date range) | Fix the request; don't retry |
| 401 | AUTHENTICATION_FAILED | Missing, invalid, or expired access token | Refresh token and retry |
| 403 | ACCESS_DENIED | The authenticated caller lacks permission for this app or API | Verify the package name belongs to your vendor account and your security profile is mapped to the Reporting API |
| 404 | RESOURCE_NOT_FOUND | Package name is incorrect or it's empty | Check the spelling of the package name |
| 429 | RATE_LIMIT_EXCEEDED | Exceeded 5 req/sec | Back off and retry with exponential delay |
| 500 | INTERNAL_ERROR | Unexpected server error | Retry with backoff (up to 3 times) |
| 503 | SERVICE_UNAVAILABLE | The service or a dependency is temporarily unavailable | Retry with backoff |
Common mistakes and fixes
| Mistake | Error You'll See | Fix |
|---|---|---|
Missing timelineSpec |
"timelineSpec is required" | Add the required timelineSpec object |
Using HOURLY with subsActiveMetricSet |
"subsActiveMetricSet does not support HOURLY aggregation" | Switch to DAILY |
startTime or endTime outside the queryable window |
"timelineSpec.startTime must be within the last 15 days" (FIELD_OUT_OF_RANGE) |
Move the offending timestamp inside the window: last 30 days for DAILY (up to yesterday), last 15 days for HOURLY |
| Expired token | 401 AUTHENTICATION_FAILED | Refresh the LWA token |
| Too many requests | 429 Too Many Requests | Add time.sleep() between calls |
| Using wrong dimension for metric set | "dimensions contains 'renewalType' which is not supported by 'subsSignupMetricSet'; allowed: […]" | Use the correct dimension for that metric set |
Reusing a page token after changing dimensions, filter, or timelineSpec |
FIELD_IMMUTABLE: "pageToken does not match this request" |
Start a new query without the old token. Changing only pageSize between pages is allowed. |
Misspelled metricSetName in the URL |
400 VALIDATION_ERROR: "Unsupported metricSetName: …; allowed: […]" |
Use one of the metric set names listed in the error message |
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, 504}
def query_with_retry(url, headers, body):
for attempt in range(MAX_RETRIES):
resp = requests.post(url, headers=headers, json=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 five minutes 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 1,000. For large dimension breakdowns, paginate to avoid timeouts. |
| Rate limiting | Stay under 5 req/sec. Add time.sleep(0.2) between sequential calls. |
| Retry only retriable errors | Retry 429, 500, 502, 503, and 504. Never retry 400 (fix the request instead). |
| Handle empty results | A query returning rows: [] isn't 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. |
Related topics
- Monetization Metrics API Overview: Available metrics and dimensions
- API Explorer: Interactive API explorer on the developer portal
- Explore the API: Link to the API Explorer in the Developer Console
Last updated: Oct 13, 2026

