Spend, impressions and clicks from the connected ad platforms, joined to the conversions they produced. REST endpoints, parameters, and response schemas.
| Endpoint | Method | Path | Permission |
|---|---|---|---|
| List ad changes What changed in the ad accounts, when and by whom, paged | GET | /v1/ads/changes | metrics:read |
| List ad entities Ad entities and their settings, now or as of a day, paged | GET | /v1/ads/entities | metrics:read |
| List ad search terms Search terms ranked by spend, paged | GET | /v1/ads/search-terms | metrics:read |
| List ad stats Every ad entity's numbers for every day, paged | GET | /v1/ads/stats | metrics:read |
Base URL https://app.sourceloop.ai/api/v1
List ad changes
What changed in the ad accounts, when and by whom, paged
GET /v1/ads/changes
Requires metrics:read
Every change to an ad entity, one row per field, oldest first: old and new value, the actor type (user, api, platform_automation, sourceloop_action, rule, unknown) and actor. `from` and `to` are calendar days in the workspace time zone, both inclusive, at most 400 days. History reaches back as far as each platform kept it at the first sync (Google 30 days); Sourceloop keeps everything after.
Paginate with `cursor` until `meta.has_more` is false, other parameters unchanged.
curl -s -X GET "https://app.sourceloop.ai/api/v1/ads/changes?website=acme.com&from=2026-09-01&to=2026-09-30" \
-H "Authorization: Bearer $SOURCELOOP_API_KEY" const res = await fetch(
"https://app.sourceloop.ai/api/v1/ads/changes?website=acme.com&from=2026-09-01&to=2026-09-30",
{
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
},
},
);
const data = await res.json(); import os, requests
res = requests.get(
"https://app.sourceloop.ai/api/v1/ads/changes?website=acme.com&from=2026-09-01&to=2026-09-30",
headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json() Query parameters
websitestringWebsite domain, e.g. acme.com. Required only when the key covers more than one.
fromstringrequiredExample
2026-09-01tostringINCLUSIVE. Default today in the workspace timezone.
Example
2026-09-30entity_typestringComma-separated or repeated.
Example
campaign,ad_groupactor_typestringuser, api, platform_automation, sourceloop_action, rule, unknown; comma-separated or repeated.
Example
userplatformstringAllowed values 10
google_adsmeta_adsopenai_adstiktok_adslinkedin_adsmicrosoft_adspinterest_adsreddit_adssnapchat_adstwitter_ads
campaign_idstringThe campaign and everything under it.
limitintegerDefault
5000cursorstringOpaque. Pass `next_cursor` from the previous response verbatim, with the other parameters unchanged.
Responses
-
200One page of rows -
400Something was wrong with the request or the credential. -
403Something was wrong with the request or the credential. -
429Too many requests for this workspace in the current minute (`rate-limit-exceeded`), the monthly allowance used (`quota-exceeded`), or too many analytics queries running at once (`too-many-concurrent`). Wait Retry-After seconds and repeat the request unchanged. -
503The analytics store failed or an unexpected error occurred (`upstream-unavailable`). Retry after Retry-After seconds. -
504The query took too long (`upstream-timeout`). Narrow the window or add a filter, then retry.
List ad entities
Ad entities and their settings, now or as of a day, paged
GET /v1/ads/entities
Requires metrics:read
Every campaign, ad group, ad, keyword, budget, bid strategy, audience, conversion action and the rest, with typed settings (status, budget and period, bid strategy and targets, match type, URLs, labels, targeting) and parent ids. `as_of=YYYY-MM-DD` returns the settings as they stood at the end of that day in the workspace time zone; settings history starts at each account's first sync, so an earlier day returns nothing. Money is in units of each ad account's currency.
Paginate with `cursor` until `meta.has_more` is false, other parameters unchanged. Rows are ordered by ad account, entity type and id.
curl -s -X GET "https://app.sourceloop.ai/api/v1/ads/entities?website=acme.com" \
-H "Authorization: Bearer $SOURCELOOP_API_KEY" const res = await fetch(
"https://app.sourceloop.ai/api/v1/ads/entities?website=acme.com",
{
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
},
},
);
const data = await res.json(); import os, requests
res = requests.get(
"https://app.sourceloop.ai/api/v1/ads/entities?website=acme.com",
headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json() Query parameters
websitestringWebsite domain, e.g. acme.com. Required only when the key covers more than one.
entity_typestringDefault every type.
Allowed values 16
accountcampaignbudgetbid_strategyad_groupadcreativeassetasset_groupkeywordnegative_keywordshared_setaudienceconversion_actionlabelproduct_group
platformstringAllowed values 10
google_adsmeta_adsopenai_adstiktok_adslinkedin_adsmicrosoft_adspinterest_adsreddit_adssnapchat_adstwitter_ads
statusstringPlatform statuses, comma-separated or repeated.
Example
ENABLED,PAUSEDcampaign_idstringThe campaign and everything under it.
as_ofstringExample
2026-09-30include_nativebooleanAdd the platform's full object as `native`.
Default
falselimitintegerDefault
5000cursorstringOpaque. Pass `next_cursor` from the previous response verbatim, with the other parameters unchanged.
Responses
-
200One page of rows -
400Something was wrong with the request or the credential. -
403Something was wrong with the request or the credential. -
429Too many requests for this workspace in the current minute (`rate-limit-exceeded`), the monthly allowance used (`quota-exceeded`), or too many analytics queries running at once (`too-many-concurrent`). Wait Retry-After seconds and repeat the request unchanged. -
503The analytics store failed or an unexpected error occurred (`upstream-unavailable`). Retry after Retry-After seconds. -
504The query took too long (`upstream-timeout`). Narrow the window or add a filter, then retry.
List ad search terms
Search terms ranked by spend, paged
GET /v1/ads/search-terms
Requires metrics:read
What people searched before the ads showed, one row per (campaign, ad group, matched keyword, search term, match type), totalled over `from`..`to` (reporting days, both inclusive, at most 366) and ranked by spend, largest first. Platform metrics only: a search term is not on the visit, so Sourceloop cannot credit outcomes to it. `zero_conversions=true` with `min_spend` lists the terms that spent and converted nothing on the platform (negative keyword candidates).
Spend is converted to the workspace currency at each day's rate (`currency=workspace`, default) or left in the account currency (`currency=native`). Paginate with `cursor` until `meta.has_more` is false, other parameters unchanged.
curl -s -X GET "https://app.sourceloop.ai/api/v1/ads/search-terms?website=acme.com&from=2026-09-01&to=2026-09-30" \
-H "Authorization: Bearer $SOURCELOOP_API_KEY" const res = await fetch(
"https://app.sourceloop.ai/api/v1/ads/search-terms?website=acme.com&from=2026-09-01&to=2026-09-30",
{
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
},
},
);
const data = await res.json(); import os, requests
res = requests.get(
"https://app.sourceloop.ai/api/v1/ads/search-terms?website=acme.com&from=2026-09-01&to=2026-09-30",
headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json() Query parameters
websitestringWebsite domain, e.g. acme.com. Required only when the key covers more than one.
fromstringrequiredExample
2026-09-01tostringINCLUSIVE. Default today in the workspace timezone.
Example
2026-09-30platformstringAllowed values 10
google_adsmeta_adsopenai_adstiktok_adslinkedin_adsmicrosoft_adspinterest_adsreddit_adssnapchat_adstwitter_ads
campaign_idstringad_group_idstringmin_spendnumberIn the answer's currency.
zero_conversionsbooleanDefault
falsecurrencystringAllowed values 2
workspacenative
Default
workspacelimitintegerDefault
5000cursorstringOpaque. Pass `next_cursor` from the previous response verbatim, with the other parameters unchanged.
Responses
-
200One page of rows -
400Something was wrong with the request or the credential. -
403Something was wrong with the request or the credential. -
429Too many requests for this workspace in the current minute (`rate-limit-exceeded`), the monthly allowance used (`quota-exceeded`), or too many analytics queries running at once (`too-many-concurrent`). Wait Retry-After seconds and repeat the request unchanged. -
503The analytics store failed or an unexpected error occurred (`upstream-unavailable`). Retry after Retry-After seconds. -
504The query took too long (`upstream-timeout`). Narrow the window or add a filter, then retry.
List ad stats
Every ad entity's numbers for every day, paged
GET /v1/ads/stats
Requires metrics:read
The bulk read for loading ad data into a warehouse: raw daily rows at one level (account, campaign, ad set or ad), in key order, a page at a time. Use `/query` to answer a question about ads; use this to copy them.
`from` and `to` are the platforms' reporting days and BOTH are inclusive; at most 400 days per request. `to` defaults to today in the workspace timezone.
Paginate with `cursor`, taking `next_cursor` from the previous response, until `meta.has_more` is false. Keep every other parameter unchanged while paging; a cursor replayed against a different request is refused. Rows are ordered by (stat_date, platform, account_id, campaign_id, ad_set_id, ad_id).
Spend and platform conversion value are converted to the workspace currency at each day's exchange rate (`currency=workspace`, the default; `converted_from` names the source currency), or left in each ad account's own currency (`currency=native`). A day with no published rate stays in the account currency, so always read `currency` per row.
Platforms restate the last 7 to 14 days as conversions settle. `synced_at` on each row and `meta.freshness` (each connected ad account's status and last sync) say how settled the numbers are; re-read that tail on every load.
curl -s -X GET "https://app.sourceloop.ai/api/v1/ads/stats?website=acme.com&from=2026-09-01" \
-H "Authorization: Bearer $SOURCELOOP_API_KEY" const res = await fetch(
"https://app.sourceloop.ai/api/v1/ads/stats?website=acme.com&from=2026-09-01",
{
method: "GET",
headers: {
Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
},
},
);
const data = await res.json(); import os, requests
res = requests.get(
"https://app.sourceloop.ai/api/v1/ads/stats?website=acme.com&from=2026-09-01",
headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json() Query parameters
websitestringWebsite domain, e.g. acme.com. Required only when the key covers more than one.
levelstringrequiredAllowed values 4
accountcampaignad_setad
fromstringrequiredFirst reporting day, inclusive.
Example
2026-09-01tostringLast reporting day, INCLUSIVE. Default today in the workspace timezone.
Example
2026-09-30platformstring[]Repeatable or comma-separated. Default every platform.
currencystringAllowed values 2
workspacenative
Default
workspacelimitintegerDefault
5000cursorstringOpaque. Pass `next_cursor` from the previous response verbatim, with the other parameters unchanged.
Responses
-
200One page of rows -
400Something was wrong with the request or the credential. -
403Something was wrong with the request or the credential. -
429Too many requests for this workspace in the current minute (`rate-limit-exceeded`), the monthly allowance used (`quota-exceeded`), or too many analytics queries running at once (`too-many-concurrent`). Wait Retry-After seconds and repeat the request unchanged. -
503The analytics store failed or an unexpected error occurred (`upstream-unavailable`). Retry after Retry-After seconds. -
504The query took too long (`upstream-timeout`). Narrow the window or add a filter, then retry.