Skip to content
Developers
Open app

Query metrics

GET
/v1/metrics/insights
const url = 'https://api.focal.inc/v1/metrics/insights?space_id=6916bc3d-e1ab-425b-ac25-eefada01a9a2&ad_account_id=f0958ef1-b662-425b-8794-79ca648489c7&start_date=2027-03-01&end_date=2027-03-31&metric=spend%2Cimpressions%2Cclicks&group_by=date&granularity=day';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try {
const response = await fetch(url, options);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}

Aggregate ad-platform performance over a date window, grouped by date, ad, ad set, creative, media, or a media attribute.

Values are windowed to start_date–end_date. That is the difference from the metrics block on a media read, which reports lifetime totals.

group_by=media_column groups by the customer’s own tagging (brief, hook, actor, concept…), so the usefulness of that answer tracks how consistently their library is tagged. group_by=date returns one row per bucket; more than 1000 buckets page like any other grouping. Campaign and account grouping aren’t offered — filter by ad_campaign_id, or issue one request per ad account.

All id filters combine as one OR set: a row counts when it sits under any id named in any of ad_campaign_id, ad_set_id, ad_id or media_id. To intersect them, run one filter kind per call.

Campaign and ad-set ids to filter by come from GET /v1/metrics/ad-campaigns and /v1/metrics/ad-sets. Look up a row’s entity by its key: group_by=ad with GET /v1/metrics/ads/{id}, group_by=creative with GET /v1/metrics/creatives/{id}.

Metric keys come from GET /v1/metrics/features.

Required scope: metrics:read — a scope of its own, deliberately not covered by media:read, because ad spend and revenue are a different sensitivity class from asset metadata. (all:read does cover it.)

space_id is required and decides what you can see. The caller must be able to read that space (404 otherwise, so a space you can’t reach is indistinguishable from one that doesn’t exist), and an ad account is returned only when it is shared with that space or with the whole organization — or is private and was connected by the calling user. Rate-limit cost: 10.

space_id
required
string format: uuid

Example

6916bc3d-e1ab-425b-ac25-eefada01a9a2

Required. The single space to query, as a UUID. The API reads one space at a time — comma-separated lists and wildcards are not accepted, and there is no implicit default.

ad_account_id
required
string

Example

f0958ef1-b662-425b-8794-79ca648489c7

Ad accounts to query, comma-separated. Figures are summed across them without currency conversion.

start_date
required
string
/^\d{4}-\d{2}-\d{2}$/

Example

2027-03-01

First day of the window, inclusive.

end_date
required
string
/^\d{4}-\d{2}-\d{2}$/

Example

2027-03-31

Last day of the window, inclusive.

metric
required
string

Example

spend,impressions,clicks

Metric keys to return, comma-separated. See GET /v1/metrics/features.

group_by
required
string
Allowed values: date ad ad_set creative media media_column

What each row represents.

granularity
string
Allowed values: day week month quarter

Bucket size for group_by=date. Default day.

media_column_id
string format: uuid

Required for group_by=media_column.

ad_campaign_id
string

Count ads under these campaigns, comma-separated. All id filters combine as one OR set: a row counts when it sits under any id named in any of ad_campaign_id, ad_set_id, ad_id or media_id. To intersect them, run one filter kind per call.

ad_set_id
string

Count ads under these ad sets, comma-separated. All id filters combine as one OR set: a row counts when it sits under any id named in any of ad_campaign_id, ad_set_id, ad_id or media_id. To intersect them, run one filter kind per call.

ad_id
string

Count these ads, comma-separated. All id filters combine as one OR set: a row counts when it sits under any id named in any of ad_campaign_id, ad_set_id, ad_id or media_id. To intersect them, run one filter kind per call.

media_id
string

Count ads running these media assets, comma-separated. All id filters combine as one OR set: a row counts when it sits under any id named in any of ad_campaign_id, ad_set_id, ad_id or media_id. To intersect them, run one filter kind per call.

sort
string

One of the requested metrics, or name; prefix with - for descending (-spend). Defaults to the first metric descending. Ignored for group_by=date, which is always chronological.

limit
integer
>= 1 <= 500

Max rows per page (1–500). Default 50.

cursor
string

Pagination cursor returned as next_cursor from the previous page.

OK

Media typeapplication/json
object
data
required
Array
Any of:
object
key
required

Identifier of the grouped entity — an ad / ad set id, a creative fingerprint, a media id, or an attribute option id.

string
name
required

Display name of the grouped entity, when it has one.

string
nullable
thumbnail_url
required
string
nullable
media_ids
required

The media assets an ad or creative row resolves to — pass to GET /v1/media. Empty for every other grouping; under group_by=media the row’s key is itself the media id. Media rows and media_ids only include media in space_id.

Array<string>
metrics
required

The requested metrics, keyed by metric key. A metric the platforms reported nothing for reads as 0.

object
key
additional properties
number
next_cursor
required

Pass back as cursor to fetch the next page. null on the last page.

string
nullable
has_more
required

true when another page is available. Equivalent to next_cursor !== null.

boolean

Example

{
"data": [
{
"key": "0c4a206c-ec59-4a8d-b882-e557d0fd5291",
"name": "Hero video – 4:5 cut",
"thumbnail_url": "https://scontent.xx.fbcdn.net/v/t45.1600-4/489012345_120210987654321100_n.jpg",
"media_ids": [
"786beeda-55ac-4025-bf6e-43b2918e84a9"
],
"metrics": {
"spend": 1842.5,
"impressions": 412380,
"clicks": 6931
}
}
],
"next_cursor": "eyJ0IjoiMjAyNy0wMy0wNFQxMDoxNTowMC4wMDBaIiwiaSI6Ijc4NmJlZWRhLTU1YWMtNDAyNS1iZjZlLTQzYjI5MThlODRhOSJ9",
"has_more": true
}

The request was malformed (invalid cursor, unknown type, validation failure, etc.). error.code is bad_request.

Media typeapplication/json

Canonical error envelope. code is stable across releases and intended for programmatic handling; message is human-readable. details only appears for validation failures and lists the offending fields.

object
error
required
object
code
required
string
Allowed values: unauthenticated forbidden not_found bad_request conflict rate_limited internal_error
message
required
string
details
Array<object>
object
field
string
issue
required
string

Example

{
"error": {
"code": "bad_request",
"message": "Invalid request body",
"details": [
{
"field": "space_id",
"issue": "Invalid UUID"
}
]
}
}

Missing, invalid, or disabled token. error.code is unauthenticated.

Media typeapplication/json

Canonical error envelope. code is stable across releases and intended for programmatic handling; message is human-readable. details only appears for validation failures and lists the offending fields.

object
error
required
object
code
required
string
Allowed values: unauthenticated forbidden not_found bad_request conflict rate_limited internal_error
message
required
string
details
Array<object>
object
field
string
issue
required
string

Example

{
"error": {
"code": "unauthenticated",
"message": "Bearer token is missing or invalid"
}
}

The token doesn’t have the scope this endpoint requires. error.code is forbidden.

Media typeapplication/json

Canonical error envelope. code is stable across releases and intended for programmatic handling; message is human-readable. details only appears for validation failures and lists the offending fields.

object
error
required
object
code
required
string
Allowed values: unauthenticated forbidden not_found bad_request conflict rate_limited internal_error
message
required
string
details
Array<object>
object
field
string
issue
required
string

Example

{
"error": {
"code": "forbidden",
"message": "Token doesn't have the required scope"
}
}

Resource doesn’t exist or is outside the token’s space allow-list. The two cases share a response shape so a restricted token can’t probe for the existence of out-of-scope spaces. error.code is not_found.

Media typeapplication/json

Canonical error envelope. code is stable across releases and intended for programmatic handling; message is human-readable. details only appears for validation failures and lists the offending fields.

object
error
required
object
code
required
string
Allowed values: unauthenticated forbidden not_found bad_request conflict rate_limited internal_error
message
required
string
details
Array<object>
object
field
string
issue
required
string

Example

{
"error": {
"code": "not_found",
"message": "Not found"
}
}

Rate limit exceeded. error.code is rate_limited. Wait the number of seconds in the Retry-After header before retrying. Quotas are per-token; see the “Rate limiting” section at the top of this document.

Media typeapplication/json

Canonical error envelope. code is stable across releases and intended for programmatic handling; message is human-readable. details only appears for validation failures and lists the offending fields.

object
error
required
object
code
required
string
Allowed values: unauthenticated forbidden not_found bad_request conflict rate_limited internal_error
message
required
string
details
Array<object>
object
field
string
issue
required
string

Example

{
"error": {
"code": "rate_limited",
"message": "Rate limit of 1000 units per 60s exceeded (this request cost 5)"
}
}
Retry-After
integer
Example
12

Seconds to wait before retrying.