Two Minute Reports Logo
Data

Run query

Run a data query across accounts and connectors and return the aggregated result rows.
POST /teams/{teamId}/data/run-query

Runs a data query across one or more accounts and returns the aggregated rows. You specify the accounts to pull from, the dimensions to group by, the metrics to aggregate, a date range, and optional filters and sort. Accounts from different connectors can be combined in a single query.

Authentication: Bearer token · Role: editor · Permission:data:read · Rate limit: 30 / 60s
This endpoint is expensive and long-running. The query runs synchronously — the request blocks until the result is ready. Large queries can take several minutes, and a query that does not finish within ~5 minutes is aborted and returns a 500. Because of this, the route has a tight rate limit of 30 requests per 60 seconds (lower than the default 120 / 60s). Set a generous client timeout, avoid sending queries in a tight loop, and don't retry a slow query until the previous one has returned.

Path parameters

teamId
string · uuid v7 required
The ID of the team that owns the accounts being queried.

Request body

accounts
string[] required
The accounts to query, 1–100 entries. Each account ID is prefixed with its connector ID followed by an underscore (e.g. gadw_1234567890 for a Google Ads account). Accounts from different connectors can be mixed in one query. An ID that does not start with a known connector prefix is rejected.
dimensions
string[]
Field IDs to group rows by. Up to 50. Omit for a single, fully-aggregated row.
metrics
string[]
Field IDs to aggregate. Up to 50.
dateRange
string | object
The reporting window. Either a preset string or a custom object. Defaults to last_7_days when omitted.Presets:today, yesterday, last_7_days, last_30_days, this_month, last_month, last_3_months, last_6_months, last_year, this_year.Custom object:{ "startDate": "YYYY-MM-DD", "endDate": "YYYY-MM-DD" }. Both dates are required and must be in YYYY-MM-DD format.
sort
object[]
Sort order, up to 20 entries. Each entry is { "fieldId": string, "direction": "asc" | "desc" }.
filters
object[]
Filters to apply, up to 50 entries. Each entry is { "fieldId": string, "operator": string, "value": string }. operator is one of equals, notEquals, contains, notContains, greaterThan, lesserThan. value is always a string.
Not sure what to put in a query? Look these up first:

Request examples

Two accounts, grouped by campaign, with spend and clicks over the last 30 days.

{
  "accounts": ["gadw_1234567890", "fads_act_9876543210"],
  "dimensions": ["campaign_name"],
  "metrics": ["cost", "clicks"],
  "dateRange": "last_30_days"
}
curl -X POST https://api.twominutereports.com/v1/teams/0190f8b0-1a2b-7c3d-8e4f-5a6b7c8d9e0f/data/run-query \
  -H "Authorization: Bearer tmrc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "accounts": ["gadw_1234567890", "fads_act_9876543210"],
    "dimensions": ["campaign_name"],
    "metrics": ["cost", "clicks"],
    "dateRange": "last_30_days"
  }'

Response

On success, data is an array of row objects. Each key is a requested field ID and each value is the cell value (a string or number). The set of keys matches the dimensions and metrics you requested.

Rows for the "Filters & sort" example above.

{
  "success": true,
  "data": [
    { "campaign_name": "Brand - Search", "cost": 1820.45, "clicks": 5421, "conversions": 132 },
    { "campaign_name": "Brand - Display", "cost": 640.10, "clicks": 2210, "conversions": 38 },
    { "campaign_name": "Brand - Retargeting", "cost": 305.77, "clicks": 980, "conversions": 21 }
  ]
}
Copyright © 2026