# Public Ads API > One API for the public ad libraries of Meta, Google, TikTok and LinkedIn. GET /v1/{platform}/ads searches one platform with that library's own filters; GET /v1/ads/{id} returns one ad with all its detail. Every platform answers with the same Ad record. Free plan: 100 credits a month. Everything an agent needs to integrate, in one file. Last updated: 2026-10-21. - API address: https://api.publicadsapi.com (every path starts /v1) - Home: https://publicadsapi.com/?ref=llms-full - Sign up (free, sandbox key at once): https://publicadsapi.com/signup?ref=llms-full - Keys, once signed in: https://publicadsapi.com/app/keys - OpenAPI 3.1 (one schema per platform): https://publicadsapi.com/openapi.json - Docs as Markdown: https://publicadsapi.com/docs.md, and https://publicadsapi.com/docs/api/meta.md, https://publicadsapi.com/docs/api/google.md, https://publicadsapi.com/docs/api/tiktok.md, https://publicadsapi.com/docs/api/linkedin.md - Public Ads API is not affiliated with Meta, Google, TikTok or LinkedIn. ## Authentication Send `Authorization: Bearer ` on every call. `pads_test_` keys are the sandbox: sample ads from made-up advertisers, never charged, no rate limit. `pads_live_` keys read the platform and use credits; shown once. Keep the key in `PADS_API_KEY`, server side. No key yet? An agent can sign itself up: `POST https://api.publicadsapi.com/v1/agent/signup` (no auth, JSON `{"name": "...", "use_case": "..."}`, both required) returns 201 with `api_key` (sandbox) and `claim_url`. Then `POST https://api.publicadsapi.com/v1/agent/live-key` with the sandbox key as Bearer returns a live key with 50 free credits a month. Give `claim_url` to your user: opening it while signed in moves the keys into their account. Out of free credits: 402 QUOTA_EXCEEDED with `claim_url`. ## Search: GET /v1/{platform}/ads Send exactly one subject. A parameter the platform does not take is 400 INVALID_INPUT with `field`. Every platform also takes `date_from` and `date_to` (YYYY-MM-DD, when shown), `cursor` (the last page's next_cursor, with the same other parameters) and `include=raw` (the platform's own record as `raw`). Country codes and options are case-insensitive. ### GET /v1/meta/ads: Meta Ad Library Up to 30 ads a page, 1 credit. Keyword or Facebook page id. Outside the EU and UK Meta keeps non-political ads only while they run. metrics only on political and issue ads. Pages after the first are slower (Meta scrolls). total_estimate capped at 50,000. - One of: `q`, `page_id` - Filters: `exact` (true|false, default false), `country` (ISO 3166 alpha-2 or ALL, default ALL), `active` (true|false), `format` (image|video|meme|none), `placements` (facebook|instagram|messenger|audience_network|threads), `category` (all|political_and_issue|housing|employment|financial, default all), `language` (ISO 639-1), `sort` (impressions|newest, default impressions), `date_from` (YYYY-MM-DD), `date_to` (YYYY-MM-DD) - Aliases: query=q, keyword=q, country_code=country, media_type=format ### GET /v1/google/ads: Google Ads Transparency Center Up to 100 ads a page, 1 credit. No keyword search: advertiser_id (AR...) or verified domain. political=true needs country. status is always unknown; text ads are images: their text is null unless we read it (text_source ocr). - One of: `advertiser_id`, `domain` - Filters: `country` (ISO 3166 alpha-2 or ALL, default ALL), `format` (text|image|video), `surface` (search|youtube|shopping|maps|play), `political` (true|false, default false), `date_from` (YYYY-MM-DD), `date_to` (YYYY-MM-DD) - Aliases: country_code=country, media_type=format ### GET /v1/tiktok/ads: TikTok Commercial Content Library Up to 36 ads a page, 1 credit. EEA, UK and Switzerland only (no US ads). q also matches creator posts; advertiser (exact name) for one advertiser. metrics.reach is a range. - One of: `q`, `advertiser` - Filters: `country` (AT|BE|BG|HR|CY|CZ|DK|EE|FI|FR|DE|GR|HU|IE|IT|LV|LT|LU|MT|NL|PL|PT|RO|SK|SI|ES|SE|IS|LI|NO|GB|CH, default GB), `sort` (last_shown|first_shown, default last_shown), `date_from` (YYYY-MM-DD, default a year before date_to), `date_to` (YYYY-MM-DD, default today) - Aliases: query=q, keyword=q, country_code=country ### GET /v1/linkedin/ads: LinkedIn Ad Library About 24 ads a page, 2 credits. LinkedIn blocks about half of lookups and we retry each on a new IP, hence 2 credits. Dates within the last year. 503 UPSTREAM_BLOCKED is more common: retry after Retry-After. - One of: `q`, `company_id`, `company` - Filters: `countries` (comma-separated ISO 3166 alpha-2), `date_from` (YYYY-MM-DD), `date_to` (YYYY-MM-DD) - Aliases: query=q, keyword=q, country=countries, country_code=countries ## Response Same envelope from every platform. `query` echoes what ran, defaults filled in. Empty: `"ads": []`, 0 credits. `credits_remaining` is null for sandbox keys. ```json {"platform":"meta","query":{"q":"running shoes","page_id":null,"exact":false,"country":"GB","active":null,"format":null,"placements":null,"category":"all","language":null,"sort":"impressions","date_from":null,"date_to":null},"ads":["..."],"total_estimate":5,"has_more":true,"next_cursor":"eyJwIjoibWV0YSIsImMiOiIzIiwicSI6IldZZVBvV0xGd1Bkei1OejUifQ","credits_used":0,"credits_remaining":null} ``` An Ad (null, never a missing key, for what a platform does not have): ```json {"id":"meta_900000000000001","platform":"meta","platform_ad_id":"900000000000001","library_url":"https://www.facebook.com/ads/library/?id=900000000000001","advertiser":{"id":"100000000000001","name":"Northwind Running Co","domain":null,"library_url":"https://www.facebook.com/ads/library/?view_all_page_id=100000000000001"},"status":"active","first_shown":"2026-08-04T07:00:00Z","last_shown":"2026-10-18T07:00:00Z","format":"image","text":{"headline":"Trail shoes, half price this week","body":"Grip that holds on wet rock. Free returns for 60 days.","description":null,"cta":"Shop now"},"text_source":"platform","link_url":"https://northwind-running.example/trail","media":[{"type":"image","url":"https://example.com/sandbox-media/meta-trail.jpg","preview_url":null,"width":1080,"height":1080}],"placements":["facebook","instagram","audience_network","messenger"],"regions":null,"metrics":{"impressions":null,"reach":null,"spend":null},"variants":1} ``` - `id` is ours: `_`, Google `google__`. Stable across searches. - `status`: active, inactive or unknown. `format`: text, image, video, carousel, dynamic, unknown. Dates ISO 8601 UTC. - `metrics.impressions`, `reach`: `{min, max}` ranges; `spend` adds `currency`. - `media[].url` is the platform's address and expires (Meta's within days): download what you keep. ## One ad: GET /v1/ads/{id} The Ad plus `detail`: `variants` (text and media of each version), `regions` (country, first_shown, last_shown, impressions, reach), `targeting` (age_min, age_max, genders, locations, used), `eu_reach` (Meta: total, by_country, by_age_gender) and `payer` (payer, beneficiary). Credits as the platform's search; 404 AD_NOT_FOUND is free. Only `include` is accepted. The rich detail is for ads shown in the EU (and every TikTok ad); a US-only ad has its dates and the ad itself. ## Errors Every 4xx and 5xx: `{"error": {"code", "message"}}`. Switch on `code`. No error uses credits. Every response has `X-Request-Id`. - INVALID_INPUT (400): `field` names the parameter - INVALID_URL (400) - UNAUTHENTICATED (401) - INVALID_KEY (401) - QUOTA_EXCEEDED (402): upgrade_url, add_card_url, or claim_url for an agent - PAYMENT_REQUIRED (402): upgrade_url - AD_NOT_FOUND (404) - ENDPOINT_NOT_FOUND (404) - RATE_LIMITED (429): retry after Retry-After - UPSTREAM_BLOCKED (503): retry after Retry-After - UPSTREAM_CHANGED (502): retry later; we are alerted - TIMEOUT (504): retry once - BUSY (503): retry after Retry-After - INTERNAL (500): retry once ## Credits, plans and limits - A page with ads or one ad found: 1 credit, LinkedIn 2. Empty pages, errors, sandbox calls: 0. - Headers: X-Credits-Used, X-Credits-Remaining; live keys also X-RateLimit-Limit, -Remaining, -Reset (monthly credits). - Free 100 credits a month (with a card, then $0.0025 each); Starter $19 a month for 8,000; Growth $49 for 25,000. US dollars; credits renew on the 1st (UTC). - Per live key: 5 requests a second on Free, 20 Starter, 50 Growth; over it 429 RATE_LIMITED. - A live call takes 10 to 40 seconds; at 60 we answer 504 TIMEOUT. Set a 60 second client timeout. ## Sandbox test searches With a sandbox key, `q`, `company` or a `domain`'s first label set to: empty (200 with no ads (0 credits)); blocked (503 UPSTREAM_BLOCKED with Retry-After); timeout (504 TIMEOUT); changed (502 UPSTREAM_CHANGED). Ad ids `meta_blocked`, `meta_timeout`, `meta_changed` (also google_, tiktok_, linkedin_) do the same for one ad. Sandbox pages hold 3 ads. ## Example ```bash curl "https://api.publicadsapi.com/v1/meta/ads?q=running+shoes&country=GB" \ -H "Authorization: Bearer $PADS_API_KEY" ```