Automating recurring Analytics reports
Set up automatic, recurring Analytics report data for your data pipeline with fresh metrics on a schedule. By using the Reporting API with rolling date formulas, a single report request can run a scheduled call without manual modification--and deliver current data to wherever your pipeline needs it.
Use automated, recurring report data for the following:
- BI pipeline: Feed a daily or weekly KPI snapshot into a database for querying by a business intelligence tool
- Agentic readiness: Provide recurring input to an AI agent or automated analytics workflow
- Alerting: Monitor metrics on a schedule and trigger downstream alerts for anomalies
- ETL feed: Replace manual Workspace report exports with a programmatic feed into an existing Extract, Transform, Load (ETL) pipeline
- Consolidation: Aggregate Analytics data with other sources in a centralized reporting database
The endpoints described in this guide are routed through analytics.adobe.io. To use them, you must first create a client with access to the Adobe Developer Console. For more information, see Getting started with the Analytics API.
If you are new to the Analytics Reporting API, see KPI reports for an introduction to constructing report requests before using this guide. If your use case requires bulk file delivery to cloud storage with Adobe-managed scheduling, see Data Warehouse and Cloud locations APIs instead.
Scripting the workflow
The workflow presented in this guide is intended to be used in a script for automating the call requests and responses. Although Python is used as an example in this guide, you can choose any language for this flow:
-
Automating token retrieval: Authenticate each scheduled run with a new server-to-server access token
-
Building the recurring report request: Use date formulas to keep your report current on every scheduled run without modifying the request body
-
Scheduling the report call: Run the request on a recurring schedule using a Python script and cron
-
Processing the response: Parse the JSON response and route the data to your pipeline
Automating token retrieval
By incorporating a job scheduler into your script, you can automate the retrieval of an authorization token. Manual steps in an API client are not required. Each time your scheduler triggers the script, the script POSTs your stored credentials to Adobe Identity Management Service (IMS), receives a fresh token, and uses it immediately for the report request in the same run. Because tokens expire after 24 hours, scripting a scheduled re-fetch at the start of every run is the recommended pattern for recurring jobs.
Your script should make the token authorizaton call with the following endpoint:
POST https://ims-na1.adobelogin.com/ims/token/v3
Token retrieval request and response examples
Click the Request tab in the following example to see a cURL request for this endpoint. Click the Response tab to see a successful JSON response for the request.
data-slots=heading, code
data-repeat=2
data-languages=CURL,JSON
Request
curl -X POST \
"https://ims-na1.adobelogin.com/ims/token/v3" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&scope={SCOPES}"
Response
{
"access_token": "eyJhbGciOiJSUzI1NiIsIng1dCI6Ik...",
"token_type": "bearer",
"expires_in": 86399
}
Request example details
-
The
client_idandclient_secretvalues are available in your Adobe Developer Console project under OAuth Server-to-Server. Store both as environment variables in your OS or scheduler rather than hardcoding them in your script. Theclient_secretyou create on the developer console generally has no expiration. -
The
scopevalues required for your integration are listed in your Developer Console project. Required scopes vary depending on the Analytics API operations your integration performs. -
This is the first scripted call at the start of each scheduled run to receive a fresh token. Do not cache tokens across daily runs.
Response example details
-
The
access_tokenvalue is the bearer token used in theAuthorizationheader of each subsequent Analytics API request. -
The
expires_invalue is in seconds. A value of86399indicates the token expires in approximately 24 hours. -
The
token_typevalue is alwaysbearerfor the client credentials grant type.
Request parameters
grant_typeclient_credentials for server-to-server authenticationclient_idclient_secretscopeResponse parameters
access_tokenAuthorization: Bearer {ACCESS_TOKEN}. Note the space between the word "Bearer" and its value.token_typeexpires_inBuilding the recurring report request
Build a report request that returns the current data window on every scheduled run by using a rolling date formula in the dateRange field. The API evaluates the formula server-side, so the same request body delivers fresh data each time without modification.
If you have worked through the KPI reports guide, the structure of this request is almost identical, except for the difference in specifying the date range.
Use the following endpoint to make the request:
POST https://analytics.adobe.io/api/{GLOBAL_COMPANY_ID}/reports
Date range formulas
Specify dateRange in globalFilters as a formula string in the format <start>/<end>. Each component combines a base unit representing the current calendar period with an optional shift modifier.
Base units:
thtdtwtmtqtyShift the base unit by appending -Nx or +Nx, where N is the number of periods and x is the unit code. Common formulas for recurring reports:
td-7d/tdtd/td+1dth-24h/thtm-1m/tmtq-1q/tqty-1y/tyConstraints:
- Date formulas are only supported in the
globalFiltersarray. They are not available for metric-level filters. - Date formulas are only available for ranked reports. They are not supported for real-time reports.
- Formulas evaluate relative to the timezone configured for your report suite. A report suite without timezone configuration returns a
400error.
Request and response examples
Click the Request tab in the following example to see a cURL request for this endpoint. Click the Response tab to see a successful JSON response for the request.
data-slots=heading, code
data-repeat=2
data-languages=CURL,JSON
Request
curl -X POST \
"https://analytics.adobe.io/api/{GLOBAL_COMPANY_ID}/reports" \
-H "accept: application/json" \
-H "x-api-key: {CLIENT_ID}" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"rsid": "examplersid",
"globalFilters": [{"type": "dateRange", "dateRange": "td-7d/td"}],
"metricContainer": {
"metrics": [
{"columnId": "0", "id": "metrics/visits"},
{"columnId": "1", "id": "metrics/orders"},
{"columnId": "2", "id": "metrics/revenue", "sort": "desc"}
]
},
"dimension": "variables/daterangeday",
"settings": {"countRepeatInstances": true, "limit": 7, "page": 0}
}'
Response
{
"totalPages": 1,
"firstPage": true,
"lastPage": true,
"numberOfElements": 7,
"number": 0,
"totalElements": 7,
"columns": {
"dimension": {
"id": "variables/daterangeday",
"type": "time"
},
"columnIds": ["0", "1", "2"]
},
"rows": [
{
"itemId": "1260524",
"value": "May 24, 2026",
"data": [682171, 18722, 2288544.73]
},
{
"itemId": "1260523",
"value": "May 23, 2026",
"data": [676125, 15219, 2325169.57]
},
{
"itemId": "1260522",
"value": "May 22, 2026",
"data": [667478, 19093, 2355620.19]
}
],
"summaryData": {
"filteredTotals": [4618723, 134867, 15478399.06],
"totals": [4618723, 134867, 15478399.06]
}
}
Request example details
- The
dateRangevaluetd-7d/tdrequests the rolling 7-day window ending at the current day. The API evaluates this formula server-side relative to your report suite timezone, so the same request body returns the current window on every scheduled run. dimension: variables/daterangedaybreaks the results into one row per day.limit: 7matches the formula window, returning one row per day in the range.metrics/revenueis sorted descending ("sort": "desc"), so the highest-revenue days appear first in the response.
Response example details
- The response shows only three of seven rows for readability. Each row
dataarray aligns with thecolumnIdsorder: index0= visits, index1= orders, index2= revenue. - The
valuefield contains the human-readable date label for each row. The API resolves the date formula to actual calendar dates in the report suite timezone. summaryData.totalsprovides aggregated metric totals across all rows in the response.totalPages: 1withlastPage: trueindicates all results fit within a single page.
Request parameters
rsidglobalFilterstype: dateRange and a date formula stringglobalFilters[].typedateRange for date range filteringglobalFilters[].dateRangetd-7d/td to define a rolling window evaluated server-side on every runmetricContainermetricContainer.metricsmetricContainer.metrics[].columnId0. Determines the index of each metric values in rows[].datametricContainer.metrics[].idmetrics/visits, metrics/orders, metrics/revenue)metricContainer.metrics[].sortasc or descdimensionvariables/daterangeday for daily breakdownssettings.countRepeatInstancestruesettings.limit50. For a 7-day formula, use 7 to return one row per daysettings.page0Response parameters
totalPagesfirstPagelastPagenumberOfElementsnumber0totalElementscolumns.dimension.idcolumns.columnIdsrows[].datarowsrows[].itemIdrows[].valuerows[].datacolumns.columnIdssummaryData.totalssummaryData.filteredTotalsScheduling the report call
Use any external scheduler to run your report script on a recurring interval. The Analytics API has no built-in scheduling requirement. The date formula in the request body determines the data window. Your scheduler determines when the script runs.
The following Python script combines both API calls from this guide into a single runnable script.
Python script
import os
import requests
CLIENT_ID = os.environ["ANALYTICS_CLIENT_ID"]
CLIENT_SECRET = os.environ["ANALYTICS_CLIENT_SECRET"]
SCOPES = os.environ["ANALYTICS_SCOPES"]
GLOBAL_COMPANY_ID = os.environ["ANALYTICS_GLOBAL_COMPANY_ID"]
REPORT_SUITE_ID = os.environ["ANALYTICS_REPORT_SUITE_ID"]
def get_access_token():
response = requests.post(
"https://ims-na1.adobelogin.com/ims/token/v3",
data={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"scope": SCOPES,
},
)
response.raise_for_status()
return response.json()["access_token"]
def run_report(access_token):
body = {
"rsid": REPORT_SUITE_ID,
"globalFilters": [{"type": "dateRange", "dateRange": "td-7d/td"}],
"metricContainer": {
"metrics": [
{"columnId": "0", "id": "metrics/visits"},
{"columnId": "1", "id": "metrics/orders"},
{"columnId": "2", "id": "metrics/revenue", "sort": "desc"},
]
},
"dimension": "variables/daterangeday",
"settings": {"countRepeatInstances": True, "limit": 7, "page": 0},
}
response = requests.post(
f"https://analytics.adobe.io/api/{GLOBAL_COMPANY_ID}/reports",
headers={
"accept": "application/json",
"x-api-key": CLIENT_ID,
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
},
json=body,
)
response.raise_for_status()
return response.json()
if __name__ == "__main__":
token = get_access_token()
data = run_report(token)
print(f"Retrieved {len(data['rows'])} rows for the past 7 days")
Example schedule definition
Although schedule definitions vary across schedulers, most schedulers invoke the same script logic on a configured schedule.
If using Cron, you can schedule to run the script daily at 08:00 with the following line to your crontab (crontab -e):
0 8 * * * /usr/bin/python3 /opt/scripts/analytics_report.py >> /var/log/analytics_report.log 2>&1
Error handling
-
Call
raise_for_status()after each request to surface non-2xx responses as exceptions. Log the full response body. The Analytics API typically includes amessagefield with a specific error description. -
Implement a retry with exponential backoff for transient errors (
429,500,503). -
A
400on the report request often indicates a misconfiguredrsidor a report suite without timezone configuration. See Date range formulas for timezone requirements.
Processing the response
The report response is a JSON object. Each entry in the rows array represents one dimension value--for variables/daterangeday, one entry per day. The data array in each row contains metric values in the same order as columns.columnIds.
For the example request in this guide, data[0] = visits, data[1] = orders, and data[2] = revenue.
Writing to CSV
import csv
from datetime import datetime
rows = data["rows"]
run_date = datetime.utcnow().strftime("%Y-%m-%d")
with open(f"analytics_kpis_{run_date}.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["date", "visits", "orders", "revenue"])
for row in rows:
writer.writerow([row["value"]] + row["data"])
For database insertion, iterate rows the same way and build your INSERT or upsert statements from row["value"] and row["data"]. The implementation depends on your database driver and schema.
If your use case requires file delivery to cloud storage rather than in-process data handling, see Data Warehouse and Cloud Locations for bulk export with Adobe-managed scheduling and delivery to S3, GCS, Azure Blob, and SFTP destinations.
Related resources
- KPI reports: Introduction to building report requests with the Analytics Reporting API
- Advanced reports: Working with segments, metric filters, and date range comparisons
- Getting started with the Analytics API: Authentication setup and Developer Console configuration
- Data Warehouse and Cloud Locations: Bulk file export with Adobe-managed scheduling and delivery to cloud storage destinations
Status codes
The main advantages of using the Report API endpoint in a data pipeline (instead of a scheduled report shared to a location) are control and reliability:
No human dependency A UI scheduled report requires someone to set it up, maintain it, and notice when it breaks. The API pipeline is code; it is versioned, testable, and owned by the system.
Structured data, not a file UI scheduled reports deliver a formatted file (Excel, CSV, PDF) designed for human reading. The API returns clean JSON that maps directly to a database table with no parsing of formatted cells, merged headers, or summary rows.
Direct pipeline integration The API response goes straight into Snowflake in the same script run. A UI report requires a file to land somewhere, then something else to pick it up, parse it, and load it; this requres extra steps and extra failure points.
Programmatic control You can change the date range, metrics, filters, or destination in code. A UI report requires someone to log in and reconfigure it.
Reliability and alerting Airflow retries failures automatically and alerts your team. A broken UI scheduled report typically just stops delivering silently until someone notices the dashboard is stale.
The honest tradeoff For a simple use case such as one report, one recipient, and no downstream system, the UI scheduled report is faster to set up and requires no engineering. The API pipeline is worth the investment when the data needs to feed a system rather than a person.