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:

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:

  1. Automating token retrieval: Authenticate each scheduled run with a new server-to-server access token

  2. Building the recurring report request: Use date formulas to keep your report current on every scheduled run without modifying the request body

  3. Scheduling the report call: Run the request on a recurring schedule using a Python script and cron

  4. 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

Response example details

Request parameters

Name
Required
Type
Description
grant_type
required
string
The OAuth grant type. Must be client_credentials for server-to-server authentication
client_id
required
string
The Client ID from your Adobe Developer Console OAuth Server-to-Server credential
client_secret
required
string
The Client Secret from your Adobe Developer Console OAuth Server-to-Server credential
scope
required
string
Space-separated list of permission scopes. Required values are listed in your Developer Console project

Response parameters

Name
Type
Description
access_token
string
The bearer token to include as Authorization: Bearer {ACCESS_TOKEN}. Note the space between the word "Bearer" and its value.
token_type
string
The token type
expires_in
integer
Token validity period in seconds

Building 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:

Code
Period
th
Current hour
td
Current day
tw
Current week
tm
Current month
tq
Current quarter
ty
Current year

Shift 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:

Formula
Date range
td-7d/td
Last 7 days
td/td+1d
Today
th-24h/th
Last 24 hours
tm-1m/tm
Last month
tq-1q/tq
Last quarter
ty-1y/ty
Last year

Constraints:

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

Response example details

Request parameters

Name
Required
Type
Description
rsid
required
string
The report suite ID
globalFilters
required
array
Filter container. For a recurring report, include a single object with type: dateRange and a date formula string
globalFilters[].type
required
string
The filter type. Use dateRange for date range filtering
globalFilters[].dateRange
required
string
The date range for the report. Use a formula string such as td-7d/td to define a rolling window evaluated server-side on every run
metricContainer
required
object
Container for the metrics array
metricContainer.metrics
required
array
List of metrics to include in the report
metricContainer.metrics[].columnId
required
string
Column position in the report, starting from 0. Determines the index of each metric values in rows[].data
metricContainer.metrics[].id
required
string
Metric identifier (e.g., metrics/visits, metrics/orders, metrics/revenue)
metricContainer.metrics[].sort
optional
string
Sort direction for this metric column. Accepts asc or desc
dimension
required
string
The dimension to use for organizing into rows. Use variables/daterangeday for daily breakdowns
settings.countRepeatInstances
optional
boolean
Whether to count repeat instances of a dimension value. Defaults to true
settings.limit
optional
integer
Maximum number of rows to return. Defaults to 50. For a 7-day formula, use 7 to return one row per day
settings.page
optional
integer
Page index for paginated results, starting at 0

Response parameters

Name
Type
Description
totalPages
integer
Total number of pages in the result set
firstPage
boolean
Whether this is the first page of results
lastPage
boolean
Whether this is the last page of results
numberOfElements
integer
Number of rows returned on this page
number
integer
Current page index, starting at 0
totalElements
integer
Total number of rows across all pages
columns.dimension.id
string
The dimension identifier used in the report
columns.columnIds
array
Ordered list of column IDs. Each index maps to the corresponding metric value in rows[].data
rows
array
Report data rows, one entry per dimension value
rows[].itemId
string
Unique identifier for the dimension item
rows[].value
string
Human-readable label for the dimension value (e.g., a formatted date string for time dimensions)
rows[].data
array
Metric values for this row, in the same order as columns.columnIds
summaryData.totals
array
Aggregated metric totals across all rows in the report
summaryData.filteredTotals
array
Aggregated metric totals after any applied filters

Scheduling 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

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.

Status codes

HTTP code
Meaning
Description
200
Success
The request is successful.
400
Bad Request
The request was improperly constructed, missing key information, and/or contained incorrect syntax.
401
Authentication failed
The request did not pass an authentication check. Your access token may be missing or invalid.
403
Forbidden
The resource was found, but you do not have the right credentials to view it.
404
Not found
The requested resource could not be found on the server.
500
Internal server errors
This is a server-side error. If you are making many simultaneous calls, you may be reaching the API limit and need to filter your results.

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.