Skip to content
adsrunning

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
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.

ParameterExampleMatches
domainclay.comThe company's website domain
linkedin13018048A numeric LinkedIn company ID, or a LinkedIn company URL
companyIdCatalogue UUIDadsrunning'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.

examples
"https://www.Clay.com:443/pricing?ref=api" → clay.com
"CLAY.COM" → clay.com
linkedin="https://www.linkedin.com/company/clay-hq/" → LinkedIn URL selector

When 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 fieldMeaning
inputYour selector after normalization, as kind and value: the host for domain, the slug or numeric ID for linkedin
matchedcompanyId, domain and linkedinId of the chosen company
candidatesHow many companies matched before ranking
pickedByexact, 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.

OperationEndpointDefault credits
Advertiser lookupGET /v1/advertisers1
Ads pageGET /v1/linkedin/ads1 per 25 ads returned, min 1
Ads page with includeGET /v1/linkedin/ads?include=…×2, once
LinkedIn ad countGET /v1/linkedin/ads/count1
Google ad countGET /v1/google/ads/count1, zero included
Meta ad countGET /v1/meta/ads/count1, zero included
Ad detailGET /v1/linkedin/ads/{adId}1
Ad collectionPOST /v1/{platform}/ads/collections2 per 25 maxAds, reserved
Collection with detailsdetails: true×3
Collection status and adsGET /v1/ad-collections/…0
AccountGET /v1/me0

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.

ParameterRequiredDescription
domain | linkedin | companyIdExactly oneIdentifies the company. See Identity.
curl
curl "https://adsrunning.com/v1/advertisers?domain=clay.com" \
  -H "Authorization: Bearer emk_live_…"
200 OK · application/json
{
  "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.

ParameterRequiredDescription
domain | linkedin | companyIdExactly oneIdentifies the company. See Identity.
cursorOptionalnextCursor from the previous page
limitOptional1–100. Default 25
liveOptionaltrue means runEnd is null or at least today UTC. Default false
includeOptionalComma list: impressions,targeting,countries. No duplicates; any order
curl
curl "https://adsrunning.com/v1/linkedin/ads?domain=clay.com&limit=1" \
  -H "Authorization: Bearer emk_live_…"
200 OK · application/json
{
  "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.

ParameterRequiredDescription
domain | linkedin | companyIdExactly oneIdentifies the company. See Identity.
curl
curl "https://adsrunning.com/v1/linkedin/ads/count?domain=clay.com" \
  -H "Authorization: Bearer emk_live_…"
200 OK · application/json
{
  "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.

ParameterRequiredDescription
domain | linkedin | companyIdExactly onedomain checks that domain. linkedin and companyId check the company's domain.
curl
curl "https://adsrunning.com/v1/google/ads/count?domain=clay.com" \
  -H "Authorization: Bearer emk_live_…"
200 OK · application/json
{
  "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.

202 Accepted · Retry-After: 5
{
  "status": "pending",
  "domain": "clay.com",
  "retryAfterSeconds": 5
}

This loop retries while the response has Retry-After: 202, 429 and 503.

shell · curl
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
StatusCodeWhen
400invalid_requestThe domain has no registrable domain, or is an excluded host such as a social network or link shortener
402insufficient_creditsYour balance is below 1 credit, even when the price is 0
404google_not_coveredThe resolved company's domain is missing, excluded or not registrable
429rate_limitedYour 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
503google_unavailableThe 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.

ParameterRequiredDescription
domain | linkedin | companyId | facebookExactly onefacebook 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
curl "https://adsrunning.com/v1/meta/ads/count?domain=clay.com" \
  -H "Authorization: Bearer emk_live_…"
200 OK · application/json
{
  "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.

202 Accepted · Retry-After: 5
{
  "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.

ParameterRequiredDescription
adIdYes, in pathThe ad ID from an ads page
domain | linkedin | companyIdExactly oneIdentifies the company. See Identity.
curl
curl "https://adsrunning.com/v1/linkedin/ads/1362645846?domain=clay.com" \
  -H "Authorization: Bearer emk_live_…"
200 OK · application/json
{
  "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.

ParameterRequiredDescription
domain | linkedin | companyIdOptionalAt most one valid selector; ignored
curl
curl "https://adsrunning.com/v1/me" \
  -H "Authorization: Bearer emk_live_…"
200 OK · application/json
{
  "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.

shell · curl + jq
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
done

Pages 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

FieldMeaning
importedAtLatest time adsrunning stored a change to this company or its ads. Not the first import, and not proof that every ad is known.
lastSweptAtLast time adsrunning checked the company's LinkedIn library. Omitted when it has not been swept.
observedAtGoogle and Meta counts: when the collector observed the count. Advertisers’ stored Meta observation: when the count was accepted.
expiresAtGoogle 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.

429 Too Many Requests · Retry-After: 1
{
  "code": "rate_limited",
  "message": "API request limit reached. Please try again later.",
  "retryable": true
}
CodeStatusRetryableWhen
invalid_request400NoMalformed 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_cursor400NoCursor expired, altered, or from another key, query or collection page size
invalid_key401NoMissing or unknown key
key_revoked401NoThe key was revoked
insufficient_credits402NoBalance too low; no payload or debit. Google and Meta need 1 credit
credit_limit_exceeded402NoA collection reservation is above maxCredits; nothing is created
request_rejected403 / 404NoAccount not eligible, or ad not found for this company
company_unknown404NoNo company matches the selector
collection_not_found404NoUnknown collection, another account's, or completed over 30 days ago
idempotency_conflict409NoThe Idempotency-Key was used with a different platform or body
collection_failed409NoAds read of a failed collection, up to 7 days after it completed
collection_expired410NoAds read more than 7 days after the collection completed
google_not_covered / meta_not_covered404NoThe company's domain is missing or not registrable; on counts, also excluded
facebook_page_not_found404NoWebsite has no Facebook link
meta_page_not_counted404NoNo page ID found, unowned, deleted or actioned page; the message names the reason
rate_limited429YesRate, concurrency or global limit, a new check past 50 pending targets per platform an hour, or a pending collection cap
unavailable503YesTransient database failure; Retry-After: 1. Follow the retryable field
unavailable503NoAnother 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_unavailable503YesCheck failed, or the collector is busy, unreachable or slow (also the Meta page check timing out when a collection starts); see Retry-After
website_unreadable503YesWebsite could not be read; see Retry-After
collection_unavailable503YesCollections 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.

MCP connection
URL: https://adsrunning.com/mcp
Authorization: Bearer emk_live_…
ToolReturns
get_advertiserCompany profile, LinkedIn totals, Google and Meta observations. Same as GET /v1/advertisers
list_adsA page of LinkedIn ads, 10 by default, up to 100. Same as GET /v1/linkedin/ads
count_adsAd 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_adOne LinkedIn ad with its details. Same as GET /v1/linkedin/ads/{adId}
get_accountYour balance and limits. Free. Same as GET /v1/me
start_ad_collectionCollect up to 500 LinkedIn, Google or Meta ads. Same as POST /v1/{platform}/ads/collections, with idempotencyKey in the arguments
get_ad_collectionCollection status. Free. Same as GET /v1/ad-collections/{id}
list_collection_adsA 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…