Skip to content New SourceLoop MCP: chat with your attribution data in Claude, ChatGPT & Cursor
SourceLoop
API reference

Ads

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

websitestring

Website domain, e.g. acme.com. Required only when the key covers more than one.

fromstringrequired

Example 2026-09-01

tostring

INCLUSIVE. Default today in the workspace timezone.

Example 2026-09-30

entity_typestring

Comma-separated or repeated.

Example campaign,ad_group

actor_typestring

user, api, platform_automation, sourceloop_action, rule, unknown; comma-separated or repeated.

Example user

platformstring

Allowed values 10

  • google_ads
  • meta_ads
  • openai_ads
  • tiktok_ads
  • linkedin_ads
  • microsoft_ads
  • pinterest_ads
  • reddit_ads
  • snapchat_ads
  • twitter_ads

campaign_idstring

The campaign and everything under it.

limitinteger

Default 5000

cursorstring

Opaque. Pass `next_cursor` from the previous response verbatim, with the other parameters unchanged.

Responses

  • 200 One page of rows
  • 400 Something was wrong with the request or the credential.
  • 403 Something was wrong with the request or the credential.
  • 429 Too 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.
  • 503 The analytics store failed or an unexpected error occurred (`upstream-unavailable`). Retry after Retry-After seconds.
  • 504 The 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

websitestring

Website domain, e.g. acme.com. Required only when the key covers more than one.

entity_typestring

Default every type.

Allowed values 16

  • account
  • campaign
  • budget
  • bid_strategy
  • ad_group
  • ad
  • creative
  • asset
  • asset_group
  • keyword
  • negative_keyword
  • shared_set
  • audience
  • conversion_action
  • label
  • product_group

platformstring

Allowed values 10

  • google_ads
  • meta_ads
  • openai_ads
  • tiktok_ads
  • linkedin_ads
  • microsoft_ads
  • pinterest_ads
  • reddit_ads
  • snapchat_ads
  • twitter_ads

statusstring

Platform statuses, comma-separated or repeated.

Example ENABLED,PAUSED

campaign_idstring

The campaign and everything under it.

as_ofstring

Example 2026-09-30

include_nativeboolean

Add the platform's full object as `native`.

Default false

limitinteger

Default 5000

cursorstring

Opaque. Pass `next_cursor` from the previous response verbatim, with the other parameters unchanged.

Responses

  • 200 One page of rows
  • 400 Something was wrong with the request or the credential.
  • 403 Something was wrong with the request or the credential.
  • 429 Too 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.
  • 503 The analytics store failed or an unexpected error occurred (`upstream-unavailable`). Retry after Retry-After seconds.
  • 504 The 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

websitestring

Website domain, e.g. acme.com. Required only when the key covers more than one.

fromstringrequired

Example 2026-09-01

tostring

INCLUSIVE. Default today in the workspace timezone.

Example 2026-09-30

platformstring

Allowed values 10

  • google_ads
  • meta_ads
  • openai_ads
  • tiktok_ads
  • linkedin_ads
  • microsoft_ads
  • pinterest_ads
  • reddit_ads
  • snapchat_ads
  • twitter_ads

campaign_idstring

ad_group_idstring

min_spendnumber

In the answer's currency.

zero_conversionsboolean

Default false

currencystring

Allowed values 2

  • workspace
  • native

Default workspace

limitinteger

Default 5000

cursorstring

Opaque. Pass `next_cursor` from the previous response verbatim, with the other parameters unchanged.

Responses

  • 200 One page of rows
  • 400 Something was wrong with the request or the credential.
  • 403 Something was wrong with the request or the credential.
  • 429 Too 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.
  • 503 The analytics store failed or an unexpected error occurred (`upstream-unavailable`). Retry after Retry-After seconds.
  • 504 The 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

websitestring

Website domain, e.g. acme.com. Required only when the key covers more than one.

levelstringrequired

Allowed values 4

  • account
  • campaign
  • ad_set
  • ad

fromstringrequired

First reporting day, inclusive.

Example 2026-09-01

tostring

Last reporting day, INCLUSIVE. Default today in the workspace timezone.

Example 2026-09-30

platformstring[]

Repeatable or comma-separated. Default every platform.

currencystring

Allowed values 2

  • workspace
  • native

Default workspace

limitinteger

Default 5000

cursorstring

Opaque. Pass `next_cursor` from the previous response verbatim, with the other parameters unchanged.

Responses

  • 200 One page of rows
  • 400 Something was wrong with the request or the credential.
  • 403 Something was wrong with the request or the credential.
  • 429 Too 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.
  • 503 The analytics store failed or an unexpected error occurred (`upstream-unavailable`). Retry after Retry-After seconds.
  • 504 The query took too long (`upstream-timeout`). Narrow the window or add a filter, then retry.

Track every conversion to its true source

Capture and send full attribution data from every signup, lead, booking, and sale to your CRM and ad platforms, so you know exactly what's driving revenue.

Without SourceLoop

Untagged

Kayden Floyd

kayden@abc.com

  • SourceUnknown
  • MediumUnknown
  • CampaignUnknown
  • Landing pageUnknown
Journey
No touchpoints captured

With SourceLoop

Auto-tagged

Kayden Floyd

kayden@abc.com · Acme Co.

  • Channel Paid Social
  • CampaignFree_demo
  • Landing page/pricing
Journey
Synced to HubSpot Google Ads Meta