Ad library data for lead scoring in Clay and n8n
Check if a company is running ads from its domain in Clay or n8n, map the fields into a lead score and send new competitor ads to Slack via webhooks.
The AdLib team · · 7 min read
A company that runs ads on four platforms this week has budget, a marketing team and probably a reason to talk to you. That makes ad library data for lead scoring one of the cheaper signals you can add to a list: one request per domain, a yes or no per platform, and a date for the newest ad. This guide shows how to wire it into Clay and n8n.
One thing up front: we have no native Clay integration and no n8n node yet. Everything below uses Clay's HTTP API column and n8n's HTTP Request and Webhook nodes, which work with any REST API. It is a few minutes of setup, not a one-click install.
Check if a company is running ads with one request
GET /v1/domains/{domain}/check resolves the domain to advertiser accounts on every platform and then looks for current ads on each. It costs 3 credits per domain, and nothing if no platform could be checked.
curl -H "X-API-Key: $ADLIB_API_KEY" \
"https://api.adlibapi.com/v1/domains/nike.com/check"This is the response for nike.com from 26 Oct 2026, trimmed to four of the six platforms:
{
"data": {
"domain": "nike.com",
"advertising": true,
"platforms": [
{
"platform": "meta",
"advertiser_id": "meta:15087023444",
"advertiser": "Nike",
"score": 1,
"advertising": true,
"active_ads": 2941,
"latest_ad_at": "2026-10-26T08:00:00+01:00",
"sample_ad_ids": ["meta:1702938977100376", "meta:1559738104701476", "meta:1057679207068133"]
},
{
"platform": "linkedin",
"advertiser_id": "linkedin:2029",
"advertiser": "Nike",
"score": 1,
"advertising": true,
"active_ads": 2,
"latest_ad_at": "2026-10-26T21:25:47+01:00",
"sample_ad_ids": ["linkedin:1427944723", "linkedin:1430153573"]
},
{
"platform": "pinterest",
"advertiser_id": "pinterest:Nike",
"advertiser": "Nike",
"score": 1,
"advertising": false,
"active_ads": 0,
"latest_ad_at": null,
"sample_ad_ids": []
},
{
"platform": "x",
"advertiser_id": "x:415859364",
"advertiser": "Nike",
"score": 0.75,
"advertising": null,
"active_ads": null,
"error": "x: upstream error: x ads-search: HTTP 503 Over capacity (code 130)"
}
],
"checked_at": "2026-10-26T20:25:46Z"
}
}Google and TikTok were also advertising: true in the full response. The top-level fields tell you most of what a lead score needs.
| Field | Meaning |
|---|---|
data.advertising |
true if any platform shows a current ad, false if all checked platforms show none, null if no platform could be checked |
platforms[].platform |
meta, google, tiktok, linkedin, pinterest or x |
platforms[].advertising |
Same logic for one platform; null when that platform failed |
platforms[].active_ads |
The platform's own count when it reports one, otherwise what we counted in a sample of up to 10 ads |
platforms[].latest_ad_at |
Last time an ad of this advertiser was shown |
platforms[].advertiser_id |
The id to use for follow-up calls, such as changes or a watchlist |
platforms[].score |
How sure we are this advertiser belongs to the domain (0 to 1) |
platforms[].error |
Why a platform could not be checked |
Two rules behind those values are worth knowing. On platforms without an active status, an ad last shown in the past 7 days counts as advertising. And only the best advertiser per platform with a score of 0.7 or more gets checked, so a name collision like "Nike Football" does not turn into a false yes. How those scores come about is in finding advertiser IDs from a domain.
Read active_ads as a rough size, not a precise number. Meta reported 2,941 for Nike. Where the platform reports no count, we count active ads in a small sample, so the number is capped by that sample.
Clay: the HTTP API column
In Clay, add an enrichment column using the HTTP API integration and fill it like this:
- Method: GET
- Endpoint:
https://api.adlibapi.com/v1/domains/{{Domain}}/check, where{{Domain}}is your column with the bare domain (nike.com, not https://www.nike.com/de) - Headers:
X-API-Keywith your key as the value. - Query parameter (optional):
platform=meta,google,linkedinif you only care about some platforms.
Run it on a few rows first and look at the JSON. Then map the fields you need into their own columns:
| Clay column | Source in the response |
|---|---|
| Advertising now | data.advertising |
| Meta active | the advertising value of the entry in data.platforms where platform is meta |
| LinkedIn active | same for linkedin |
| Platforms advertising | count of data.platforms entries with advertising true |
| Latest ad | the newest latest_ad_at across platforms |
| Meta advertiser id | advertiser_id of the meta entry |
The counts and "newest of" columns are easiest as formula columns over the JSON. Keep the advertiser ids. You will need them to look at the actual ads later or to watch the account.
If data.advertising is null, nothing could be checked and you were not charged. Rerun those rows later instead of treating them as "not advertising".
n8n: the HTTP Request node
In n8n, put an HTTP Request node after whatever produces your domains (a Google Sheets trigger, a CRM node, a webhook):
- Method: GET
- URL:
https://api.adlibapi.com/v1/domains/{{ $json.domain }}/check - Authentication: Generic Credential Type, then Header Auth, with name
X-API-Keyand your key as the value. The key then lives in n8n's credential store, not in the workflow. - Options: turn on batching to space out requests if you send hundreds of rows. A 429 response carries a
Retry-Afterheader.
The output item has the response under data. A Code node can flatten it into one row per lead:
return $input.all().map(({ json }) => {
const p = json.data.platforms || [];
const active = p.filter((x) => x.advertising === true);
const latest = p.map((x) => x.latest_ad_at).filter(Boolean).sort().pop() || null;
return {
json: {
domain: json.data.domain,
advertising: json.data.advertising,
platforms_advertising: active.length,
platforms: active.map((x) => x.platform).join(","),
on_linkedin: active.some((x) => x.platform === "linkedin"),
latest_ad_at: latest,
meta_id: p.find((x) => x.platform === "meta")?.advertiser_id || null,
},
};
});From there, write back to your CRM or sheet like any other n8n data.
Ad library data for lead scoring: a starting model
Here is a starting point. The weights are ours to pick and yours to change; we have not tested them against conversion data.
| Signal | Source | Points |
|---|---|---|
| Advertising on at least one platform | data.advertising true |
20 |
| Each additional platform | platforms_advertising minus 1 |
10 each |
| Advertising on LinkedIn | linkedin entry advertising true |
15 (B2B spend) |
| Latest ad in the past 7 days | latest_ad_at |
10 |
| New ads launched recently | /changes, see below |
5 per started ad, up to 25 |
The last row needs one more call per company, for the accounts that are already advertising. Take the advertiser_id from the check and ask for recent launches:
curl -H "X-API-Key: $ADLIB_API_KEY" \
"https://api.adlibapi.com/v1/advertisers/meta:15087023444/changes?since=2026-10-01&types=started"Each returned event is billed at 1 credit, so only run this for the companies that passed the first filter. A company that launched a dozen ads this month is in a different situation from one with the same two ads since spring. For more on reading those dates, see ad run length as a performance signal.
Costs for a list: 3 credits per domain for the check, plus whatever the follow-ups return. Plans are on /pricing.
Competitor ads into n8n and Slack with watchlist webhooks
Lead scoring is a one-off check. For competitors you want the opposite: we crawl the accounts on a schedule and tell you when something changes.
Create a watchlist with the advertiser ids (from the check or from GET /v1/advertisers/resolve) and the URL of an n8n Webhook node:
curl -X POST -H "X-API-Key: $ADLIB_API_KEY" -H "Content-Type: application/json" \
"https://api.adlibapi.com/v1/watchlists" \
-d '{
"name": "Competitors",
"advertisers": ["meta:15087023444", "google:AR18378488041124659201", "linkedin:2029"],
"webhook_url": "https://n8n.example.com/webhook/adlib",
"interval_hours": 24
}'interval_hours goes from 1 to 168 and defaults to 24. After each crawl that finds something, we POST an advertiser.changes event:
{
"event": "advertiser.changes",
"watchlist_id": "…",
"advertiser_id": "meta:15087023444",
"crawled_at": "…",
"summary": { "new": 2, "started": 0, "stopped": 1, "changed": 0 },
"new": [ { "type": "discovered", "ad_id": "meta:…", "at": "…", "ad": { } } ],
"started": [],
"stopped": [ { "type": "stopped", "ad_id": "meta:…", "at": "…" } ],
"changed": []
}In n8n:
- Webhook node, method POST, path
adlib. Enable the raw body option so you can verify the signature. - Signature check. Every delivery has an
X-AdLib-Signatureheader of the formt=<unix>,v1=<hex>, wherev1is the HMAC-SHA256 of<t>.<body>with your signing secret. Compute it with the Crypto node or a Code node and stop the workflow if it does not match. - IF node on
{{ $json.body.summary.new }} > 0, or whatever you care about. - Slack node, posting something like "{{ advertiser_id }}: {{ summary.new }} new, {{ summary.stopped }} stopped". Include the ad ids so people can open them.
Before pointing a real watchlist at it, send a test event with POST /v1/webhooks/test and the URL. You get a signed webhook.ping. Failed deliveries are retried with backoff up to 8 times, and GET /v1/webhooks/deliveries shows status, attempts and your endpoint's HTTP status for the last 30 days, which helps when n8n was down for a deploy.
Stopped ads stay retrievable after they vanish from the platform's library, under the same id. That is the part the libraries themselves do not do, see why ad libraries forget ended ads.
When to use which endpoint
Use the domain check for lists: it is one flat price per company and answers the yes or no. Use resolve when you need every account behind a brand, including the uncertain ones, to review by hand. Use watchlists for the handful of companies you follow every week.
If you sell to companies that advertise, /for/companies has more examples, and the full reference is on /for/developers. Start with ten rows in Clay or n8n and look at the JSON before you build the score.
Questions people ask
- How can I check if a company is running ads?
- Call GET /v1/domains/{domain}/check with the company's domain. It returns advertising true, false or null overall and per platform (Meta, Google, TikTok, LinkedIn, Pinterest, X), with the matched advertiser, active ad count and the date of the latest ad.
- Is there a native Clay integration or n8n node for AdLib?
- Not yet. Both work today through their generic HTTP features: Clay's HTTP API column and n8n's HTTP Request and Webhook nodes, with your key in the X-API-Key header.
- How much does a domain check cost?
- 3 credits per domain, and nothing if no platform could be checked (advertising is null). Resolving a domain to advertiser ids costs 10 credits, or 0 if nothing is found.
- What counts as advertising in the domain check?
- A platform counts as advertising when the matched advertiser has an active ad, or, on platforms without an active status, an ad shown in the last 7 days. Only advertisers scoring 0.7 or more for the domain are checked.