Instead of opening the dashboard every morning, push a daily digest of new ads from your followed brands into Slack, email, or any other tool your team already uses. Run this on a cron at e.g. 09:00 local time.
Endpoints used
GET /open/v1/brands— list brands your workspace already followsGET /open/v1/brands/{brand_id}/ads— pull new ads for each brandGET /open/v1/brands/search— optional, only if you need to discover brands first
New to the API? Read the Quickstart for auth and the response envelope.
The shape of the job
- List your followed brands.
- For each brand, fetch ads launched since yesterday.
- Aggregate and post to your destination.
If you haven't followed any brands yet, swap step 1 for a one-time search via GET /brands/search?keyword=<brand name> — see Bootstrap via search at the end.
Step 1 — List your followed brands
GET /open/v1/brands returns the brands your workspace follows. Pagination is page-based (not cursor-based) — page + page_size, max 100.
curl -sS \
-H "X-API-Key: $ATRIA_API_KEY" \
"https://api.tryatria.com/open/v1/brands?page=1&page_size=100"import os
import requests
resp = requests.get(
"https://api.tryatria.com/open/v1/brands",
headers={"X-API-Key": os.environ["ATRIA_API_KEY"]},
params={"page": 1, "page_size": 100},
timeout=30,
)
for brand in resp.json()["data"]["items"]:
print(brand["id"], brand["name"])const url = new URL("https://api.tryatria.com/open/v1/brands");
url.searchParams.set("page", "1");
url.searchParams.set("page_size", "100");
const resp = await fetch(url, {
headers: { "X-API-Key": process.env.ATRIA_API_KEY },
});
const { data } = await resp.json();
for (const brand of data.items) {
console.log(brand.id, brand.name);
}If you follow more than 100 brands, walk page=2, 3, … until a page returns fewer than page_size items.
Step 2 — Fetch yesterday's new ads from one brand
GET /open/v1/brands/{brand_id}/ads returns a brand's ads with the same filtering and sort options as the global /ads endpoint, scoped to that brand. Use start_date to drop ads launched before your cutoff. Pagination here is cursor-based.
curl -sS \
-H "X-API-Key: $ATRIA_API_KEY" \
"https://api.tryatria.com/open/v1/brands/m1234567890123456/ads?start_date=2025-04-11&sort_by=newest&page_size=50"import os
import requests
brand_id = "m1234567890123456"
resp = requests.get(
f"https://api.tryatria.com/open/v1/brands/{brand_id}/ads",
headers={"X-API-Key": os.environ["ATRIA_API_KEY"]},
params={
"start_date": "2025-04-11", # yesterday
"sort_by": "newest",
"page_size": 50,
},
timeout=30,
)
data = resp.json()["data"]
print(f"{len(data['items'])} new ads (cursor={data['cursor']})")const brandId = "m1234567890123456";
const url = new URL(
`https://api.tryatria.com/open/v1/brands/${brandId}/ads`,
);
url.searchParams.set("start_date", "2025-04-11"); // yesterday
url.searchParams.set("sort_by", "newest");
url.searchParams.set("page_size", "50");
const resp = await fetch(url, {
headers: { "X-API-Key": process.env.ATRIA_API_KEY },
});
const { data } = await resp.json();
console.log(`${data.items.length} new ads (cursor=${data.cursor})`);If the response includes a cursor, there are more results to fetch — pass it back as cursor=... on the next call until cursor is null.
Step 3 — Put it together (cron-able script)
Wire Step 1 and Step 2 into a single job, with pagination helpers wrapping each.
import datetime
import os
from typing import Iterator
import requests
API_KEY = os.environ["ATRIA_API_KEY"]
BASE = "https://api.tryatria.com/open/v1"
def followed_brands() -> Iterator[dict]:
"""Yield every brand the workspace follows, across pagination."""
page = 1
while True:
resp = requests.get(
f"{BASE}/brands",
headers={"X-API-Key": API_KEY},
params={"page": page, "page_size": 100},
timeout=30,
)
resp.raise_for_status()
items = resp.json()["data"]["items"]
yield from items
if len(items) < 100:
return
page += 1
def fetch_new_ads(brand_id: str, since: str) -> Iterator[dict]:
"""Yield every ad for `brand_id` whose start_date >= since."""
params = {"start_date": since, "sort_by": "newest", "page_size": 50}
cursor = None
while True:
if cursor:
params["cursor"] = cursor
resp = requests.get(
f"{BASE}/brands/{brand_id}/ads",
headers={"X-API-Key": API_KEY},
params=params,
timeout=30,
)
resp.raise_for_status()
data = resp.json()["data"]
yield from data["items"]
cursor = data.get("cursor")
if not cursor:
return
def build_digest():
yesterday = (datetime.date.today() - datetime.timedelta(days=1)).isoformat()
digest = [] # list of (brand, ads) pairs
for brand in followed_brands():
ads = list(fetch_new_ads(brand["id"], since=yesterday))
if ads:
digest.append((brand, ads))
return digest
if __name__ == "__main__":
report = build_digest()
if not report:
print("No new ads since yesterday.")
for brand, ads in report:
print(f"\n{brand['name']} ({brand['id']}) — {len(ads)} new ad(s)")
for ad in ads:
print(f" [{ad['display_format']}] {ad['title']}")const API_KEY = process.env.ATRIA_API_KEY;
const BASE = "https://api.tryatria.com/open/v1";
async function* followedBrands() {
let page = 1;
while (true) {
const url = new URL(`${BASE}/brands`);
url.searchParams.set("page", String(page));
url.searchParams.set("page_size", "100");
const resp = await fetch(url, { headers: { "X-API-Key": API_KEY } });
const items = (await resp.json()).data.items;
for (const brand of items) yield brand;
if (items.length < 100) return;
page++;
}
}
async function* fetchNewAds(brandId, since) {
let cursor = null;
while (true) {
const url = new URL(`${BASE}/brands/${brandId}/ads`);
url.searchParams.set("start_date", since);
url.searchParams.set("sort_by", "newest");
url.searchParams.set("page_size", "50");
if (cursor) url.searchParams.set("cursor", cursor);
const resp = await fetch(url, { headers: { "X-API-Key": API_KEY } });
const { data } = await resp.json();
for (const ad of data.items) yield ad;
cursor = data.cursor;
if (!cursor) return;
}
}
async function buildDigest() {
const yesterday = new Date(Date.now() - 86_400_000).toISOString().slice(0, 10);
const digest = []; // [{ brand, ads }, ...]
for await (const brand of followedBrands()) {
const ads = [];
for await (const ad of fetchNewAds(brand.id, yesterday)) ads.push(ad);
if (ads.length) digest.push({ brand, ads });
}
return digest;
}
const report = await buildDigest();
if (report.length === 0) console.log("No new ads since yesterday.");
for (const { brand, ads } of report) {
console.log(`\n${brand.name} (${brand.id}) — ${ads.length} new ad(s)`);
for (const ad of ads) console.log(` [${ad.display_format}] ${ad.title}`);
}Bootstrap via search
If you haven't followed brands yet, use GET /brands/search to look them up by name. The endpoint takes a keyword (case-insensitive substring) and returns brands across the public ad libraries.
import os
import requests
resp = requests.get(
"https://api.tryatria.com/open/v1/brands/search",
headers={"X-API-Key": os.environ["ATRIA_API_KEY"]},
params={"keyword": "acme", "page_size": 10},
timeout=30,
)
for brand in resp.json()["data"]["items"]:
print(brand["id"], brand["name"], f"({brand.get('ad_library')})")const url = new URL("https://api.tryatria.com/open/v1/brands/search");
url.searchParams.set("keyword", "acme");
url.searchParams.set("page_size", "10");
const resp = await fetch(url, {
headers: { "X-API-Key": process.env.ATRIA_API_KEY },
});
const { data } = await resp.json();
for (const brand of data.items) {
console.log(brand.id, brand.name, `(${brand.ad_library})`);
}Pick the brand IDs you care about, follow them in the Atria web app, then the Step 3 script will pick them up automatically.
Parameter notes
start_dateis inclusive and matches an ad's launch date — not the date it was indexed. Pick a window that suits your cadence (daily, weekly).- The cursor loop is safe to retry: the same cursor returns the same items, so a transient network blip won't duplicate or skip ads.
- 50 per page is a balance between request count and payload size. Increase up to
250if you're paging deep watchlists. GET /brandsuses page-based pagination (page+page_size, max 100).GET /brands/{id}/adsuses cursor pagination — keep the two loops separate.
Going further
- Cache last-seen ad IDs per brand to dedupe against an earlier run.
- Pipe the digest into Slack with a
chat.postMessagewebhook, or format as HTML for SendGrid. - Run twice a day with a 12-hour window for fresher coverage.
