Daily New-Ad Digest

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 follows
  • GET /open/v1/brands/{brand_id}/ads — pull new ads for each brand
  • GET /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

  1. List your followed brands.
  2. For each brand, fetch ads launched since yesterday.
  3. 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_date is 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 250 if you're paging deep watchlists.
  • GET /brands uses page-based pagination (page + page_size, max 100). GET /brands/{id}/ads uses 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.postMessage webhook, or format as HTML for SendGrid.
  • Run twice a day with a 12-hour window for fresher coverage.