Public API · v1
adsrunning API
Look up a company and read its advertising: every LinkedIn ad with its copy, media, impressions and targeting, ad counts for LinkedIn, Google and Meta, and collections of up to 500 LinkedIn, Google or Meta ads. JSON over HTTPS, or MCP for AI assistants. Paid 200 responses report the credits spent and your balance.
Look up a company's LinkedIn ads and its LinkedIn, Google and Meta ad counts, or collect its ads. JSON over HTTPS, or MCP. Paid calls report the credits spent and your balance.
- Base URL
- https://adsrunning.com/v1
- Free credits with your first key
- 50
- Rate limits, per account
- 10 req/s · 4 in flight
- Starting credits and account limits
- 50 free credits · 10 req/s · 4 in flight
Authentication
Send your key in the Authorization header. Only /v1/openapi.json works without a key. Keys start with emk_live_. Create and revoke them on your account page; adsrunning shows a key once, at creation.
curl "https://adsrunning.com/v1/me" \
-H "Authorization: Bearer emk_live_…"A missing or malformed key returns 401 invalid_key. A revoked key returns 401 key_revoked. Never put keys in URLs or client-side code. Keep at most 10 active keys.
Identity selectors
Name the company with exactly one of these query parameters. Zero or two selectors returns 400 invalid_request. Meta counts also accept facebook (a page URL or handle, trimmed and lowercased). Other routes reject facebook as an unknown parameter. Ad collections take the selector in their JSON body.
| Parameter | Example | Matches |
|---|---|---|
| domain | clay.com | The company's website domain |
| 13018048 | A numeric LinkedIn company ID, or a LinkedIn company URL | |
| companyId | Catalogue UUID | adsrunning's own ID, from any earlier response |
Domain normalization
adsrunning lowercases the input, then strips the scheme, path, query, fragment, port and one leading www. What remains must be a valid host name.
"https://www.Clay.com:443/pricing?ref=api" → clay.com
"CLAY.COM" → clay.com
linkedin="https://www.linkedin.com/company/clay-hq/" → LinkedIn URL selectorWhen a domain matches several companies
adsrunning picks the company with the most library ads, then the most employees, then the lowest catalogue ID. LinkedIn vanity URLs match the imported slug only; LinkedIn is never fetched. Bare slugs are not accepted.
| resolution field | Meaning |
|---|---|
| input | Your selector after normalization, as kind and value: the host for domain, the slug or numeric ID for linkedin |
| matched | companyId, domain and linkedinId of the chosen company |
| candidates | How many companies matched before ranking |
| pickedBy | exact, or library_ad_count_desc_employees_desc_id_asc |
An unknown company returns 404 company_unknown and costs nothing. Duplicate, unknown, conflicting or invalid parameters and unsupported platforms return 400 invalid_request.
Credits
Each successful paid request reports its charge and your balance in usage.creditsCharged and usage.balance.
| Operation | Endpoint | Default credits |
|---|---|---|
| Advertiser lookup | GET /v1/advertisers | 1 |
| Ads page | GET /v1/linkedin/ads | 1 per 25 ads returned, min 1 |
| Ads page with include | GET /v1/linkedin/ads?include=… | ×2, once |
| LinkedIn ad count | GET /v1/linkedin/ads/count | 1 |
| Google ad count | GET /v1/google/ads/count | 1, zero included |
| Meta ad count | GET /v1/meta/ads/count | 1, zero included |
| Ad detail | GET /v1/linkedin/ads/{adId} | 1 |
| Ad collection | POST /v1/{platform}/ads/collections | 2 per 25 maxAds, reserved |
| Collection with details | details: true | ×3 |
| Collection status and ads | GET /v1/ad-collections/… | 0 |
| Account | GET /v1/me | 0 |
Prices are defaults and can change. At these defaults, a full 100-ad page with impressions costs 8 credits: ceil(100 / 25) × 1 × 2.
A collection reserves its price for maxAds when accepted, then charges the returned ads and refunds the rest. A 50-ad collection reserves 4 credits; if it returns 20 ads, it costs 2 and refunds 2. Failed and empty collections cost nothing.
Before a paid read, your balance must cover the maximum charge for the requested limit. The final charge uses the returned items, with a minimum of 1 page unit.
Count 202s, 204, 404 and errors before a debit commits are free. Google and Meta counts need a balance of at least 1 credit, even at a price of 0. A committed charge can survive a delivery failure; retries are new billable requests. Buying credits and plan grants are not available yet.
Endpoints
These endpoints use GET. Company-data routes take a selector as a query parameter. LinkedIn has counts, stored ads pages and ad detail. Google and Meta have counts here; their ads and ad detail paths return 400 invalid_request naming their collection and count routes. To fetch LinkedIn, Google or Meta ads on demand, POST /v1/{platform}/ads/collections, then read /v1/ad-collections/{id} and its /ads pages; their contract is in /v1/openapi.json. Examples use illustrative data.
GET /v1/advertisers
Resolve a company and see whether and where it advertises. Returns the profile even when the company has no stored ads. 1 credit by default. Google and Meta show their stored count observation, or null; this never queues a Google check.
| Parameter | Required | Description |
|---|---|---|
| domain | linkedin | companyId | Exactly one | Identifies the company. See Identity. |
curl "https://adsrunning.com/v1/advertisers?domain=clay.com" \
-H "Authorization: Bearer emk_live_…"{
"company": {"id":"6f1c0ace-2c87-4a51-9171-113f58425b10","name":"Clay","domain":"clay.com","description":"Tools for go-to-market teams.","country":"United States","industry":"Software Development","employees":200,"size":"51-200","logoUrl":null,"linkedinUrl":"https://www.linkedin.com/company/13018048/","socials":{},"classification":null},
"resolution": {"input":{"kind":"domain","value":"clay.com"},"matched":{"companyId":"6f1c0ace-2c87-4a51-9171-113f58425b10","domain":"clay.com","linkedinId":"13018048"},"candidates":1,"pickedBy":"exact"},
"platforms": {
"linkedin": {
"advertiserId": "13018048",
"total": 184,
"live": 22,
"last365": 131,
"formats": {
"image": 120,
"video": 30,
"carousel": 34,
"text": 0,
"message": 0,
"job": 0,
"event": 0,
"follow": 0,
"article": 0,
"spotlight": 0,
"unknown": 0
},
"importedAt": "2026-04-14T06:12:00Z",
"lastSweptAt": "2026-04-13T22:40:00Z"
},
"google": {
"count": {
"kind": "range",
"min": 100,
"max": 200
},
"observedAt": "2026-04-12T09:30:00Z",
"expiresAt": "2026-05-12T09:30:00Z"
},
"meta": {
"count": {
"kind": "exact",
"count": 12
},
"pageId": "104731215086347",
"scope": {
"activity": "active",
"countries": "ALL"
},
"observedAt": "2026-04-10T18:05:00Z",
"expiresAt": null
}
},
"usage": {"creditsCharged":1,"balance":4989}
}GET /v1/linkedin/ads
Page through every stored LinkedIn ad for a company, latest first, with no 365-day window. 1 credit per 25 ads returned by default; ×2 with include.
| Parameter | Required | Description |
|---|---|---|
| domain | linkedin | companyId | Exactly one | Identifies the company. See Identity. |
| cursor | Optional | nextCursor from the previous page |
| limit | Optional | 1–100. Default 25 |
| live | Optional | true means runEnd is null or at least today UTC. Default false |
| include | Optional | Comma list: impressions,targeting,countries. No duplicates; any order |
curl "https://adsrunning.com/v1/linkedin/ads?domain=clay.com&limit=1" \
-H "Authorization: Bearer emk_live_…"{
"company": {"id":"6f1c0ace-2c87-4a51-9171-113f58425b10","name":"Clay","domain":"clay.com","description":"Tools for go-to-market teams.","country":"United States","industry":"Software Development","employees":200,"size":"51-200","logoUrl":null,"linkedinUrl":"https://www.linkedin.com/company/13018048/","socials":{},"classification":null},
"resolution": {"input":{"kind":"domain","value":"clay.com"},"matched":{"companyId":"6f1c0ace-2c87-4a51-9171-113f58425b10","domain":"clay.com","linkedinId":"13018048"},"candidates":1,"pickedBy":"exact"},
"platform": "linkedin",
"items": [
{
"id": "1362645846",
"companyId": "6f1c0ace-2c87-4a51-9171-113f58425b10",
"format": "image",
"headline": "See how Clay is scaling in Europe",
"body": "Build your next workflow.",
"runStart": "2026-03-01",
"runEnd": null,
"discoveredAt": "2026-03-02",
"sponsored": false,
"sponsorName": null,
"media": null,
"callToAction": "Learn more",
"textOnly": false,
"adType": "SPONSORED",
"detailStatus": "complete"
}
],
"nextCursor": "v1.opaque-encrypted-cursor",
"usage": {"creditsCharged":1,"balance":4989}
}Unrequested impressions, targeting and countries are omitted. An empty filtered or exhausted page is a charged 200; a company with no stored ads returns an uncharged, bodyless 204.
GET /v1/linkedin/ads/count
Count a company's stored LinkedIn ads without reading them. live counts ads still running today (UTC); last365 counts ads that started in the past 365 days. 1 credit by default.
| Parameter | Required | Description |
|---|---|---|
| domain | linkedin | companyId | Exactly one | Identifies the company. See Identity. |
curl "https://adsrunning.com/v1/linkedin/ads/count?domain=clay.com" \
-H "Authorization: Bearer emk_live_…"{
"company": {"id":"6f1c0ace-2c87-4a51-9171-113f58425b10","name":"Clay","domain":"clay.com","description":"Tools for go-to-market teams.","country":"United States","industry":"Software Development","employees":200,"size":"51-200","logoUrl":null,"linkedinUrl":"https://www.linkedin.com/company/13018048/","socials":{},"classification":null},
"resolution": {"input":{"kind":"domain","value":"clay.com"},"matched":{"companyId":"6f1c0ace-2c87-4a51-9171-113f58425b10","domain":"clay.com","linkedinId":"13018048"},"candidates":1,"pickedBy":"exact"},
"platform": "linkedin",
"total": 184,
"live": 22,
"last365": 131,
"freshness": {
"importedAt": "2026-04-14T06:12:00Z",
"lastSweptAt": "2026-04-13T22:40:00Z"
},
"usage": {"creditsCharged":1,"balance":4989}
}GET /v1/google/ads/count
Count a domain's Google ads. Works for any domain. count is exact, or a range whose max can be null. domain is the registrable domain checked. A domain selector skips the catalogue, so company and resolution are null. 1 credit by default, zero included.
| Parameter | Required | Description |
|---|---|---|
| domain | linkedin | companyId | Exactly one | domain checks that domain. linkedin and companyId check the company's domain. |
curl "https://adsrunning.com/v1/google/ads/count?domain=clay.com" \
-H "Authorization: Bearer emk_live_…"{
"company": null,
"resolution": null,
"platform": "google",
"domain": "clay.com",
"count": {
"kind": "range",
"min": 100,
"max": 200
},
"observedAt": "2026-04-12T09:30:00Z",
"expiresAt": "2026-05-12T09:30:00Z",
"usage": {"creditsCharged":1,"balance":4989}
}A domain without a stored count queues a check. While the check runs, the response is a free 202 with Retry-After. Wait that many seconds, then send the same request again. A stored count returns a charged 200.
{
"status": "pending",
"domain": "clay.com",
"retryAfterSeconds": 5
}This loop retries while the response has Retry-After: 202, 429 and 503.
while :; do
curl -sS -D headers.txt -o count.json --get \
"https://adsrunning.com/v1/google/ads/count" --data-urlencode "domain=clay.com" \
-H "Authorization: Bearer $ADSRUNNING_KEY"
delay=$(awk 'tolower($1) == "retry-after:" { print $2 + 0 }' headers.txt)
[ -n "$delay" ] || break
sleep "$delay"
done
cat count.json| Status | Code | When |
|---|---|---|
| 400 | invalid_request | The domain has no registrable domain, or is an excluded host such as a social network or link shortener |
| 402 | insufficient_credits | Your balance is below 1 credit, even when the price is 0 |
| 404 | google_not_covered | The resolved company's domain is missing, excluded or not registrable |
| 429 | rate_limited | Your account queued checks for 50 new domains in the past rolling hour, and this domain needs a new check. Polling a queued domain does not count. Retry after Retry-After |
| 503 | google_unavailable | The check failed or the collector is busy. Retry after Retry-After |
GET /v1/meta/ads/count
Count active ads for a Facebook page across all countries, from the collector's newest count under 30 days old or a new check. Returns domain (the website checked), facebook.handle, pageId, pageName, observedAt and expiresAt. domain and facebook selectors skip the catalogue: company and resolution are null. 1 credit by default, zero included.
| Parameter | Required | Description |
|---|---|---|
| domain | linkedin | companyId | facebook | Exactly one | facebook checks a page URL or handle directly (trimmed and lowercased). domain finds the website’s Facebook link. Company selectors use the company’s website. |
curl "https://adsrunning.com/v1/meta/ads/count?domain=clay.com" \
-H "Authorization: Bearer emk_live_…"{
"company": null,
"resolution": null,
"platform": "meta",
"count": {
"kind": "exact",
"count": 12
},
"pageId": "104731215086347",
"scope": {
"activity": "active",
"countries": "ALL"
},
"observedAt": "2026-04-10T18:05:00Z",
"expiresAt": "2026-05-10T18:05:00Z",
"domain": "clay.com",
"facebook": {
"handle": "clay"
},
"pageName": "Clay",
"usage": {"creditsCharged":1,"balance":4989}
}A pending website or page check returns a free 202 with Retry-After. facebook is {handle} once the page is known and null while the website check runs; domain is null for a facebook selector. Wait for Retry-After, then repeat the request.
{
"status": "pending",
"domain": "clay.com",
"facebook": null,
"retryAfterSeconds": 5
}A ready count, including zero, is a charged 200. Missing Facebook links or uncountable pages return free 404s. At least 1 credit is required before checking. Meta has its own cap of 50 pending targets per hour; past it, a target that needs a new check returns 429.
GET /v1/linkedin/ads/{adId}
Read one LinkedIn ad of a company. Always includes impressions, countries and targeting, with null or empty values when unknown. 1 credit by default. An ad the company does not have returns 404 request_rejected, free.
| Parameter | Required | Description |
|---|---|---|
| adId | Yes, in path | The ad ID from an ads page |
| domain | linkedin | companyId | Exactly one | Identifies the company. See Identity. |
curl "https://adsrunning.com/v1/linkedin/ads/1362645846?domain=clay.com" \
-H "Authorization: Bearer emk_live_…"{
"company": {"id":"6f1c0ace-2c87-4a51-9171-113f58425b10","name":"Clay","domain":"clay.com","description":"Tools for go-to-market teams.","country":"United States","industry":"Software Development","employees":200,"size":"51-200","logoUrl":null,"linkedinUrl":"https://www.linkedin.com/company/13018048/","socials":{},"classification":null},
"resolution": {"input":{"kind":"domain","value":"clay.com"},"matched":{"companyId":"6f1c0ace-2c87-4a51-9171-113f58425b10","domain":"clay.com","linkedinId":"13018048"},"candidates":1,"pickedBy":"exact"},
"platform": "linkedin",
"ad": {
"id": "1362645846",
"companyId": "6f1c0ace-2c87-4a51-9171-113f58425b10",
"format": "image",
"headline": "See how Clay is scaling in Europe",
"body": "Build your next workflow.",
"runStart": "2026-03-01",
"runEnd": null,
"discoveredAt": "2026-03-02",
"sponsored": false,
"sponsorName": null,
"media": null,
"callToAction": "Learn more",
"textOnly": false,
"adType": "SPONSORED",
"detailStatus": "complete",
"impressions": {
"min": 10000,
"max": 50000,
"text": "10k–50k"
},
"countries": [
{
"country": "France",
"pct": "38%",
"min": 3800,
"max": 19000
}
],
"targeting": {
"language": [
"English"
],
"location": [
"France"
],
"excluded": {
"language": [],
"location": []
},
"normalized": {
"language": [
"en"
],
"location": [
"FR"
],
"excluded": {
"language": [],
"location": []
}
}
}
},
"usage": {"creditsCharged":1,"balance":4989}
}GET /v1/me
Read your balance, rate limits and key summaries. Free, but rate limited like any call. Never returns secrets. Lists revoked keys with revokedAt. An optional valid selector is accepted and ignored.
| Parameter | Required | Description |
|---|---|---|
| domain | linkedin | companyId | Optional | At most one valid selector; ignored |
curl "https://adsrunning.com/v1/me" \
-H "Authorization: Bearer emk_live_…"{
"balance": 4985,
"rateLimits": {
"requestsPerSecond": 10,
"concurrent": 4
},
"keys": [
{
"id": "0b7e1200-8466-40f0-82da-73537d087843",
"displayPrefix": "ab12cd34",
"name": "CRM sync",
"createdAt": "2026-03-02T10:12:00Z",
"lastUsedAt": "2026-04-14T14:02:00Z",
"revokedAt": null
}
]
}Pagination
Ads pages return nextCursor. Send it back as cursor with the same selector and page options to read the next page. The cursor is opaque: do not parse or build it. It expires 24 hours after the first page, without extension. nextCursor is null on the last page.
cursor=""
while :; do
set -- --data-urlencode "domain=clay.com" --data-urlencode "limit=100"
[ -z "$cursor" ] || set -- "$@" --data-urlencode "cursor=$cursor"
page=$(curl --fail-with-body -sS --get "https://adsrunning.com/v1/linkedin/ads" "$@" \
-H "Authorization: Bearer $ADSRUNNING_KEY") || break
# A bodyless 204 means there are no stored ads.
[ -z "$page" ] && break
echo "$page" | jq '.items[]'
cursor=$(echo "$page" | jq -r '.nextCursor // empty')
[ -z "$cursor" ] && break
donePages walk all stored ads by runStart descending (unknown dates last), then ID descending. There is no traversal cap. Cursors bind to the API key, normalized query and selected company; continuations keep that company even if candidate ranking changes.
An expired, altered, wrong-key or changed-query cursor returns 400 invalid_cursor. Start again without cursor.
Freshness
| Field | Meaning |
|---|---|
| importedAt | Latest time adsrunning stored a change to this company or its ads. Not the first import, and not proof that every ad is known. |
| lastSweptAt | Last time adsrunning checked the company's LinkedIn library. Omitted when it has not been swept. |
| observedAt | Google and Meta counts: when the collector observed the count. Advertisers’ stored Meta observation: when the count was accepted. |
| expiresAt | Google and Meta counts: when the observation expires. Advertisers’ stored Meta observation: null. |
On LinkedIn routes, a 204 No Content means the company is known but has no stored ads. It costs nothing. Advertisers still returns the company profile.
Errors
Every error has the same envelope. retryable is true for rate_limited, google_unavailable, meta_unavailable, website_unreadable, collection_unavailable and transient database failures (unavailable with Retry-After: 1). Wait for Retry-After seconds, then repeat the request. Responses use Cache-Control: private, no-store.
{
"code": "rate_limited",
"message": "API request limit reached. Please try again later.",
"retryable": true
}| Code | Status | Retryable | When |
|---|---|---|---|
| invalid_request | 400 | No | Malformed parameters or body, Google or Meta ads or detail paths, an unusable Google/Meta domain, a Facebook page the collector rejects, or collection dates or filters a platform cannot apply |
| invalid_cursor | 400 | No | Cursor expired, altered, or from another key, query or collection page size |
| invalid_key | 401 | No | Missing or unknown key |
| key_revoked | 401 | No | The key was revoked |
| insufficient_credits | 402 | No | Balance too low; no payload or debit. Google and Meta need 1 credit |
| credit_limit_exceeded | 402 | No | A collection reservation is above maxCredits; nothing is created |
| request_rejected | 403 / 404 | No | Account not eligible, or ad not found for this company |
| company_unknown | 404 | No | No company matches the selector |
| collection_not_found | 404 | No | Unknown collection, another account's, or completed over 30 days ago |
| idempotency_conflict | 409 | No | The Idempotency-Key was used with a different platform or body |
| collection_failed | 409 | No | Ads read of a failed collection, up to 7 days after it completed |
| collection_expired | 410 | No | Ads read more than 7 days after the collection completed |
| google_not_covered / meta_not_covered | 404 | No | The company's domain is missing or not registrable; on counts, also excluded |
| facebook_page_not_found | 404 | No | Website has no Facebook link |
| meta_page_not_counted | 404 | No | No page ID found, unowned, deleted or actioned page; the message names the reason |
| rate_limited | 429 | Yes | Rate, concurrency or global limit, a new check past 50 pending targets per platform an hour, or a pending collection cap |
| unavailable | 503 | Yes | Transient database failure; Retry-After: 1. Follow the retryable field |
| unavailable | 503 | No | Another dependency failed, a collector answer was rejected or malformed, or demo/source mode. Retry-After: 60 when a limiter store is down |
| google_unavailable / meta_unavailable | 503 | Yes | Check failed, or the collector is busy, unreachable or slow (also the Meta page check timing out when a collection starts); see Retry-After |
| website_unreadable | 503 | Yes | Website could not be read; see Retry-After |
| collection_unavailable | 503 | Yes | Collections are temporarily unavailable; see Retry-After |
Limits span all your keys: 10 requests/s (burst 10), 4 concurrent per account and 24 globally. When all your slots are busy, the 429 asks you to retry after 1 second. A pre-auth network limit (5 requests/s, burst 100) and a global limit (10/s, burst 200) apply first and return 429 with Retry-After: 2. In demo and source modes, only the OpenAPI document works; other /v1 routes return 503 unavailable.
MCP server
Use adsrunning from an AI assistant through its MCP server. It serves stateless Streamable HTTP and takes the same API key as REST. Sign in to the setup page for client configuration.
URL: https://adsrunning.com/mcp
Authorization: Bearer emk_live_…| Tool | Returns |
|---|---|
| get_advertiser | Company profile, LinkedIn totals, Google and Meta observations. Same as GET /v1/advertisers |
| list_ads | A page of LinkedIn ads, 10 by default, up to 100. Same as GET /v1/linkedin/ads |
| count_ads | Ad counts. platform is linkedin (default), google or meta. Google and Meta are checked on demand; facebook accepts a page URL or handle only with meta. Same as GET /v1/{platform}/ads/count |
| get_ad | One LinkedIn ad with its details. Same as GET /v1/linkedin/ads/{adId} |
| get_account | Your balance and limits. Free. Same as GET /v1/me |
| start_ad_collection | Collect up to 500 LinkedIn, Google or Meta ads. Same as POST /v1/{platform}/ads/collections, with idempotencyKey in the arguments |
| get_ad_collection | Collection status. Free. Same as GET /v1/ad-collections/{id} |
| list_collection_ads | A page of collected ads, 25 by default. Free. Same as GET /v1/ad-collections/{id}/ads |
Tools use the same credits and limits as REST. Retrying a paid call can charge again. A count or collection 202 returns the pending body; repeat the count, or call get_ad_collection, after retryAfterSeconds. A 204 returns data: null.
Endpoint reference
Explore the OpenAPI contract and test requests against this origin. Requests go directly to adsrunning.
Loading endpoint reference…