# Meta Ad Library: GET /v1/meta/ads

> Search Facebook, Instagram, Messenger, Audience Network and Threads ads in the Meta Ad Library by keyword or by advertiser, with Meta's own filters: country, active status, media, placements, category, language and sort.

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

- **Ads a page**: up to 30. One page per call; `next_cursor` for more.
- **Credits**: 1 credit a page. Only when the page has ads. Empty pages, errors and the sandbox are free.
- **Covers**: Every country Meta runs ads in. Search by `q` or `page_id`.

## Request

cURL:

```bash
curl "https://api.publicadsapi.com/v1/meta/ads?q=running+shoes&country=GB" \
  -H "authorization: Bearer $PADS_API_KEY"
```

Node.js:

```js
const res = await fetch('https://api.publicadsapi.com/v1/meta/ads?q=running+shoes&country=GB', {
  headers: { authorization: `Bearer ${process.env.PADS_API_KEY}` },
});

const result = await res.json();
if (!res.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
console.log(result.ads.length, 'ads; next page:', result.next_cursor);
```

Python:

```python
import os
import requests

res = requests.get(
    "https://api.publicadsapi.com/v1/meta/ads?q=running+shoes&country=GB",
    headers={"authorization": f"Bearer {os.environ['PADS_API_KEY']}"},
    timeout=60,
)
result = res.json()
if not res.ok:
    raise RuntimeError(f"{result['error']['code']}: {result['error']['message']}")
print(len(result["ads"]), "ads; next page:", result["next_cursor"])
```

## Parameters

Every filter here is the library's own, run by the platform, so pages are full and `total_estimate` counts what matches. Anything else is `400 INVALID_INPUT` naming it.

- `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.
- `q` (query, string, send one of q, page_id): A keyword, matched against the ad's text as Meta's own search box does. Also accepted as `query` or `keyword`. Example: `q=running shoes`
- `page_id` (query, string, send one of q, page_id): One advertiser's ads: its Facebook page id, the `advertiser.id` of any of its Meta ads. Example: `page_id=100000000000001`
- `exact` (query, boolean): `true` matches `q` as a whole phrase. Default `false`.
- `country` (query, string): Where the ad was shown: an ISO 3166-1 alpha-2 code such as `GB`, `US` or `DE`, or `ALL`. Two letters that are no country (`ZZ`) are refused. Default `ALL`. Also accepted as `country_code`. Example: `country=GB`
- `active` (query, boolean): `true`: only ads running now. `false`: only ads that have stopped. Left out: both.
- `format` (query, string): Meta's media filter. `none` is ads with no image or video. Left out: every format. One of: `image`, `video`, `meme`, `none`. Also accepted as `media_type`.
- `placements` (query, comma-separated): Comma-separated: ads that ran on any of these. Left out: every placement. Values: `facebook`, `instagram`, `messenger`, `audience_network`, `threads`. Example: `placements=facebook,instagram`
- `category` (query, string): Meta's ad category. `political_and_issue` ads carry spend and impressions ranges. One of: `all`, `political_and_issue`, `housing`, `employment`, `financial`. Default `all`.
- `language` (query, string): The ad's language, ISO 639-1 such as `en`. Left out: every language. Example: `language=en`
- `sort` (query, string): `impressions`: most seen first, as Meta ranks them. `newest`: most recently started first. One of: `impressions`, `newest`. Default `impressions`.
- `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`

## Response

200 OK (sandbox; the first ad of the page):

```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
}
```

## What Meta ads carry

- `advertiser.id` is the Facebook page id: pass it as `page_id` for every ad that page runs.
- `status` is `active` or `inactive`. `first_shown` and `last_shown` are the days Meta says the ad started and stopped (the latest day seen, for an ad still running).
- `format`: `image`, `video`, `carousel`, `dynamic` (catalogue ads) or `text`. `placements` lists where it ran.
- `variants` is how many versions of the ad Meta groups under it.
- `metrics` (spend, impressions and reach ranges) is filled only for political and issue ads; for others it is `null`. The EU's reach figures for any ad delivered in the EU are in its detail.
- `total_estimate` is Meta's own count, which Meta caps at 50,000.

## Limits to know

- Meta keeps ads that ran in the EU or UK for a year after they stop, political and issue ads for seven years, and other ads (in the US, for example) only while they run. Outside the EU and UK, `active=false` finds political and issue ads only.
- The first page is the quickest. Later pages are fetched by scrolling Meta's page, so each takes longer than the first; a pull of a few hundred ads takes about a minute in all.
- An empty page is checked against a search that always has ads before we answer, so a silent block by Meta is never passed off as "no ads".
- Media addresses are Meta's and expire within days: download what you keep.

## One ad with its detail

`GET /v1/ads/{id}` with an `id` from this search (1 credit). For an ad delivered in the EU: the total EU reach, reach by country, age and gender, the ages, genders and places targeted, and who paid and who benefited (the EU's Digital Services Act data). For a political or issue ad: spend and impressions. For other ads, in the US for example: the ad itself, with no reach or targeting. See [One ad](https://publicadsapi.com/docs/api/ads.md).

See also: [Search overview](https://publicadsapi.com/docs/api/search.md) · [Google](https://publicadsapi.com/docs/api/google.md)
