Amazon Developer

as

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

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

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)

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.

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.

Last updated: Oct 13, 2026