# Search: GET /v1/{platform}/ads

> Each platform has its own search endpoint, which takes only the filters that platform's library has. Every one answers with the same envelope and the same `Ad` record.

Base URL: `https://api.publicadsapi.com` · Auth: `authorization: Bearer <key>` · Web version: https://publicadsapi.com/docs/api/search

| Endpoint | Library | Search by | Ads a page | Credits | Covers |
| --- | --- | --- | --- | --- | --- |
| [GET /v1/meta/ads](https://publicadsapi.com/docs/api/meta.md) | Meta Ad Library | `q` or `page_id` | up to 30 | 1 | Every country Meta runs ads in |
| [GET /v1/google/ads](https://publicadsapi.com/docs/api/google.md) | Google Ads Transparency Center | `advertiser_id` or `domain` | up to 100 | 1 | Every country Google runs ads in |
| [GET /v1/tiktok/ads](https://publicadsapi.com/docs/api/tiktok.md) | TikTok Commercial Content Library | `q` or `advertiser` | up to 36 | 1 | The EEA, the UK and Switzerland |
| [GET /v1/linkedin/ads](https://publicadsapi.com/docs/api/linkedin.md) | LinkedIn Ad Library | `q`, `company_id` or `company` | about 24 | 2 | Every country, the last 12 months |

There is no search across every platform at once: each library has different filters. To find one advertiser everywhere, make one call per platform.

## Parameters every platform takes

- `Authorization` (header, string, required): `Bearer <key>`. Keys starting `pads_test_` get the sandbox (sample ads, never charged); keys starting `pads_live_` read the platform and use credits.
- `date_from` (query, YYYY-MM-DD): Ads shown on or after this day, `YYYY-MM-DD`. Example: `date_from=2026-01-01`
- `date_to` (query, YYYY-MM-DD): Ads shown on or before this day, `YYYY-MM-DD`. Not before `date_from`. Example: `date_to=2026-10-21`
- `cursor` (query, string): The `next_cursor` of the page before. Send it with exactly the same other parameters: a cursor from a different search is `400 INVALID_INPUT`.
- `include` (query, comma-separated): `raw` adds each ad's own record from the platform as `raw`, for fields we don't map. Values: `raw`. Example: `include=raw`

TikTok needs a date window, so its default is the last 12 months. LinkedIn's dates must be within the last year. Each platform's page lists the rest.

## How parameters are read

- A parameter the platform doesn't take is `400 INVALID_INPUT` with `field` naming it, so a filter is never silently ignored.
- Each search needs exactly one subject (a keyword, an advertiser or a domain, by platform). None, or two, is `400 INVALID_INPUT`.
- Send each parameter once. Lists (`placements`, `countries`, `include`) are comma-separated.
- Country codes and filter values are read case-insensitively: `country=gb` is read as `GB`, `format=VIDEO` as `video`.
- An empty value is refused: leave the parameter out instead.

## Names from other APIs

Code written for another ad library API often uses other names. These are read as ours; sending a name and its alias together is refused.

| Endpoint | Also accepted | Read as |
| --- | --- | --- |
| `GET /v1/meta/ads` | `query` | `q` |
| `GET /v1/meta/ads` | `keyword` | `q` |
| `GET /v1/meta/ads` | `country_code` | `country` |
| `GET /v1/meta/ads` | `media_type` | `format` |
| `GET /v1/google/ads` | `country_code` | `country` |
| `GET /v1/google/ads` | `media_type` | `format` |
| `GET /v1/tiktok/ads` | `query` | `q` |
| `GET /v1/tiktok/ads` | `keyword` | `q` |
| `GET /v1/tiktok/ads` | `country_code` | `country` |
| `GET /v1/linkedin/ads` | `query` | `q` |
| `GET /v1/linkedin/ads` | `keyword` | `q` |
| `GET /v1/linkedin/ads` | `country` | `countries` |
| `GET /v1/linkedin/ads` | `country_code` | `countries` |

## The response

| Field | Type | What it is |
| --- | --- | --- |
| platform | string | The platform searched. |
| query | object | What was searched, defaults filled in, so you can check how your parameters were read. |
| ads | array of [Ad](https://publicadsapi.com/docs/api/ad-record.md) | This page of ads. Empty when nothing matched. |
| total_estimate | integer or null | The platform's own count of matching ads (Meta caps it at 50,000), or `null` where it gives none. |
| has_more | boolean | Whether another page follows. |
| next_cursor | string or null | Send as `cursor` for the next page; `null` on the last. |
| credits_used | integer | Credits this call used. |
| credits_remaining | integer or null | Live keys: credits left this month. Sandbox keys and Enterprise: `null`. |

200 OK:

```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": [
    {
      "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
    }
  ],
  "total_estimate": 5,
  "has_more": true,
  "next_cursor": "eyJwIjoibWV0YSIsImMiOiIzIiwicSI6IldZZVBvV0xGd1Bkei1OejUifQ",
  "credits_used": 0,
  "credits_remaining": null
}
```

## Paging

- A page holds what the platform shows in one go: up to 30 ads on Meta, 100 on Google, 36 on TikTok, about 24 on LinkedIn. A page can hold fewer.
- `next_cursor` carries the platform's own position and a fingerprint of your search. Send it with the same parameters; with any other search it is `400 INVALID_INPUT` on `cursor`.
- Each page is a call, and uses credits only when it has ads.
- Cursors don't expire on our side, but a platform's position can go stale after a while: page through a search in one go.

## Empty results

No matching ads is `200` with `"ads": []`, `"has_more": false` and `"credits_used": 0`. On Meta, an empty page is checked against a search that always has ads before we answer, so a silent block by the platform is never passed off as "no ads".

## How long a call takes

A live call reads the platform as it runs: usually 2 to 15 seconds, most of it the platform, and longer when a platform blocks and we retry on new IPs. We give up at 60 seconds with `504 TIMEOUT` (never charged), so set your client's timeout to at least 60 seconds. Run several searches side by side rather than one after another.

See also: [Authentication and keys](https://publicadsapi.com/docs/authentication.md) · [Meta](https://publicadsapi.com/docs/api/meta.md)
