# Raw Meta ads

> Live ads from the public Meta Ad Library in one normalized schema: search ads by keyword, get one ad, search advertisers, and list one advertiser's ads. $0.01 per call.

Source: https://dev.virlo.ai/docs/raw/meta-ads
Markdown: https://dev.virlo.ai/docs/raw/meta-ads.md
Section: Explore data

## About the Virlo API (applies to every page)

- Base URL: `https://api.virlo.ai/v1`. Every request needs the header `Authorization: Bearer YOUR_API_KEY` (keys start with `virlo_tkn_`).
- Responses are JSON inside a `data` field, except the `/v1/webhooks` endpoints, which return the object or array directly. Field names are snake_case.
- Prices are in US dollars from a prepaid balance. 1 credit = $0.01. The `X-Cost` response header on each successful response is the exact charge. Errors are free.
- Slow jobs return an ID. Check its status every 15 seconds (or whatever `retry_after_seconds` says) until `finalized` is `true`.
- All docs pages: https://dev.virlo.ai/llms.txt. Every page in one file: https://dev.virlo.ai/llms-full.txt. MCP server for AI assistants: https://dev.virlo.ai/api/mcp/mcp.

---

Live ads from the public Meta Ad Library, normalized into one schema. Virlo reads the Ad Library when you call, and the answer comes back in the same request.

**At a glance**

- **What it does:** Finds ads and advertisers in the Meta Ad Library as they are right now.
- **You send:** A keyword, an ad ID, an advertiser name or an advertiser's Facebook page ID, plus optional filters.
- **You get back:** Ads with their text, images, videos, dates and platforms, or advertisers with their page ID and follower counts.
- **Cost:** $0.01 (1 credit) per call, including a call that finds nothing. Errors are free.
- **Synchronous.** No job to poll. Each call returns one page. Most calls answer in 2 to 8 seconds, and a slow one can take about 25, so set your client timeout to at least 30 seconds.
- **Media links expire.** Image and video URLs are Facebook CDN links that stop working after a few days. Download what you need to keep.
- **Spend and reach are ranges, and mostly `null`.** The Ad Library shows reach for ads shown in the EU or UK, and spend, reach and impressions for political and issue ads. Every other ad has `null` there.
- **Unprocessed.** No AI fields or history. To collect Meta ads next to TikTok, YouTube and Instagram research, turn on `meta_ads_enabled` on an [agent](https://dev.virlo.ai/docs/agents). For live TikTok, YouTube and Instagram data, see [Raw data](https://dev.virlo.ai/docs/raw).

## Which one do I need?

- **Ads that mention a product, topic or brand:** [search ads](https://dev.virlo.ai/docs/raw/meta-ads#ad-search)
- **One ad you already have the ID of:** [one ad](https://dev.virlo.ai/docs/raw/meta-ads#ad)
- **The page ID of an advertiser:** [search advertisers](https://dev.virlo.ai/docs/raw/meta-ads#advertiser-search)
- **Everything one advertiser is running:** [an advertiser's ads](https://dev.virlo.ai/docs/raw/meta-ads#advertiser-ads), by page ID or by name

## Filters

Search ads and an advertiser's ads take three optional filters. Search advertisers takes `country` only.

- `country` (string, optional): Country where the ads were shown: a 2-letter ISO code such as `US`, `GB` or `DE`, or `ALL`. Default `ALL`. `UK` is read as `GB`.
- `status` (string, optional): `active` (running now), `inactive` (stopped) or `all`. Default `active`.
- `media_type` (string, optional): Ads that contain this media: `image`, `video`, `meme`, `none` (text only) or `all`. Default `all`.

## The ad shape

A single ad comes back as `{ "data": { ... } }`. A list comes back as `{ "data": [ ... ], "pagination": { "next_cursor": "...", "has_more": true } }`. Timestamps are ISO 8601 in UTC. Counts are numbers or `null`, never strings.

Every ad, from any route, has these fields:

- `id` (string, optional): The Ad Library ID of the ad. Pass it to [one ad](https://dev.virlo.ai/docs/raw/meta-ads#ad).
- `url` (string, optional): The ad in the public Ad Library.
- `advertiser` (object, optional): `page_id`, `name`, `avatar_url` and `url` (the advertiser's Facebook page). `name` and `avatar_url` can be `null`.
- `is_active` (boolean | null, optional): `true` while the ad is running.
- `started_at` (string | null, optional): First day the ad ran.
- `ended_at` (string | null, optional): Last day the ad ran. `null` while the ad is active.
- `publisher_platforms` (string[], optional): Where the ad runs, lowercase: `facebook`, `instagram`, `messenger`, `audience_network`, `threads`.
- `media_type` (string, optional): `video` when any creative has a video, else `image` when any has an image, else `none` (text only).
- `creatives` (array, optional): The ad's creatives. A carousel or catalog ad has one per card. Each has `body` (primary text), `title`, `description` (the line under the headline, often a price on catalog ads), `caption` (the display domain), `cta_text`, `cta_type`, `link_url`, `image_urls` and `videos` (`hd_url`, `sd_url`, `preview_image_url`). Text fields can be `null`. Can be empty.
- `variant_count` (number | null, optional): How many ads the Ad Library groups under this one because they use the same creative. `null` when not grouped. Always `null` on [one ad](https://dev.virlo.ai/docs/raw/meta-ads#ad).
- `page_categories` (string[], optional): Categories of the advertiser's page.
- `reach` (object | null, optional): People reached, as `{ "lower", "upper" }`. For an ad shown in the EU or UK the number is exact, so `lower` equals `upper`. Political and issue ads get a range. `null` for every other ad.
- `spend` (object | null, optional): Amount spent, as `{ "lower", "upper", "currency" }`, with an ISO currency code such as `USD`. Political and issue ads only, otherwise `null`.
- `impressions` (object | null, optional): Impressions, as `{ "lower", "upper" }`. Political and issue ads only, otherwise `null`.
- `platform_data` (object, optional): Ad Library fields outside the shared schema, in snake_case. A key is missing when the ad has no value. Keys: `display_format` (such as `image`, `video`, `carousel`, `dco` or `dpa`), `collation_id`, `ad_categories` (such as `political`), `paid_for_by`, `page_like_count`, `contains_digitally_created_media`, `targeted_countries`, and for ads shown in the EU or UK `audience` (`gender`, `age_min`, `age_max`, `locations`), `payer` and `beneficiary`.

In a range, a `null` bound is open. `{ "lower": 1000000, "upper": null }` means more than 1 million.

---

## Search ads

**Endpoint:** `GET https://api.virlo.ai/v1/raw/meta/ads/search`

Ads whose text or advertiser matches a keyword, in the Ad Library's own order. The first page has about 30 ads and each later page about 10.

Cost per request: $0.01

### Query parameters

- `q` (string, optional): Required. The keyword or phrase, 1 to 200 characters. It matches ad text and advertiser names.
- `country, status, media_type` (string, optional): Optional [filters](https://dev.virlo.ai/docs/raw/meta-ads#filters).
- `cursor` (string, optional): `next_cursor` from the previous page. See [paging](https://dev.virlo.ai/docs/raw/meta-ads#pagination).

**cURL request:**

```bash
curl -G "https://api.virlo.ai/v1/raw/meta/ads/search" \
  --data-urlencode "q=running shoes" \
  --data-urlencode "country=US" \
  --data-urlencode "media_type=video" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response:**

```json
{
  "data": [
    {
      "id": "1184620937415528",
      "url": "https://www.facebook.com/ads/library/?id=1184620937415528",
      "advertiser": {
        "page_id": "15087023444",
        "name": "Nike",
        "avatar_url": "https://scontent.xx.fbcdn.net/v/avatar.jpg",
        "url": "https://www.facebook.com/nike"
      },
      "is_active": true,
      "started_at": "2026-10-20T07:00:00.000Z",
      "ended_at": null,
      "publisher_platforms": ["facebook", "instagram"],
      "media_type": "video",
      "creatives": [
        {
          "body": "Built for the long run. More cushion, less weight.",
          "title": "Pegasus. Your everyday trainer.",
          "description": null,
          "caption": "nike.com",
          "cta_text": "Shop now",
          "cta_type": "shop_now",
          "link_url": "https://www.nike.com/running",
          "image_urls": [],
          "videos": [
            {
              "hd_url": "https://video.xx.fbcdn.net/v/example-hd.mp4",
              "sd_url": "https://video.xx.fbcdn.net/v/example-sd.mp4",
              "preview_image_url": "https://scontent.xx.fbcdn.net/v/poster.jpg"
            }
          ]
        }
      ],
      "variant_count": 3,
      "page_categories": ["Sportswear", "Product/service"],
      "reach": null,
      "spend": null,
      "impressions": null,
      "platform_data": {
        "display_format": "video",
        "collation_id": "2049316685821054",
        "page_like_count": 39494133
      }
    }
  ],
  "pagination": {
    "next_cursor": "eyJ2IjoxLCJjIjoiTTRuNXA2In0",
    "has_more": true
  }
}
```

---

## One ad

**Endpoint:** `GET https://api.virlo.ai/v1/raw/meta/ads/:ad_id`

One ad by its Ad Library ID. An ad shown in the EU or UK also carries its exact reach and, in `platform_data`, its audience, payer and beneficiary.

Cost per request: $0.01

### Path parameters

- `ad_id` (string, optional): The Ad Library ID, digits only: the `id` of an ad from any other route, or the `id=` in an Ad Library link.

**cURL request:**

```bash
curl "https://api.virlo.ai/v1/raw/meta/ads/1096372158840215" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response:**

```json
{
  "data": {
    "id": "1096372158840215",
    "url": "https://www.facebook.com/ads/library/?id=1096372158840215",
    "advertiser": {
      "page_id": "15087023444",
      "name": "Nike",
      "avatar_url": "https://scontent.xx.fbcdn.net/v/avatar.jpg",
      "url": "https://www.facebook.com/nike"
    },
    "is_active": false,
    "started_at": "2026-10-06T07:00:00.000Z",
    "ended_at": "2026-10-24T07:00:00.000Z",
    "publisher_platforms": ["facebook", "instagram"],
    "media_type": "image",
    "creatives": [
      {
        "body": "New colours for the season. Free returns for members.",
        "title": "Air Max: new colours",
        "description": null,
        "caption": "nike.com",
        "cta_text": "Shop now",
        "cta_type": "shop_now",
        "link_url": "https://www.nike.com/gb/air-max",
        "image_urls": ["https://scontent.xx.fbcdn.net/v/creative.jpg"],
        "videos": []
      }
    ],
    "variant_count": null,
    "page_categories": ["Sportswear", "Product/service"],
    "reach": { "lower": 412890, "upper": 412890 },
    "spend": null,
    "impressions": null,
    "platform_data": {
      "display_format": "image",
      "page_like_count": 39494133,
      "audience": {
        "gender": "All",
        "age_min": 18,
        "age_max": 65,
        "locations": ["United Kingdom"]
      },
      "payer": "NIKE, Inc.",
      "beneficiary": "NIKE, Inc."
    }
  }
}
```

---

## Search advertisers

**Endpoint:** `GET https://api.virlo.ai/v1/raw/meta/advertisers/search`

Advertisers (Facebook pages) whose name matches, in the Ad Library's own order, usually 5 to 15. Use a result's `page_id` with [an advertiser's ads](https://dev.virlo.ai/docs/raw/meta-ads#advertiser-ads).

This route returns one list with no `pagination`. A search that finds nothing returns `{ "data": [] }` and costs $0.01.

Cost per request: $0.01

### Query parameters

- `q` (string, optional): Required. The advertiser's name or part of it, 1 to 200 characters.
- `country` (string, optional): Optional. Changes which pages the Ad Library suggests. See [filters](https://dev.virlo.ai/docs/raw/meta-ads#filters).

### Advertiser fields

- `page_id` (string, optional): The advertiser's Facebook page ID.
- `name, avatar_url` (string | null, optional): Page name and avatar. The avatar link expires.
- `url` (string, optional): The advertiser's Facebook page.
- `verified` (boolean | null, optional): Whether the page has a verified badge.
- `likes` (number | null, optional): Facebook page likes.
- `category` (string | null, optional): The page's category.
- `instagram_handle` (string | null, optional): The linked Instagram account, without `@`.
- `instagram_followers` (number | null, optional): Followers of the linked Instagram account.
- `platform_data` (object, optional): Keys that appear when there is a value: `page_alias`, `entity_type`, `verification` (such as `blue_verified`), `instagram_verified` and `country`.

**cURL request:**

```bash
curl -G "https://api.virlo.ai/v1/raw/meta/advertisers/search" \
  --data-urlencode "q=nike" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response:**

```json
{
  "data": [
    {
      "page_id": "15087023444",
      "name": "Nike",
      "avatar_url": "https://scontent.xx.fbcdn.net/v/avatar.jpg",
      "url": "https://www.facebook.com/nike",
      "verified": true,
      "likes": 39494133,
      "category": "Sportswear Store",
      "instagram_handle": "nike",
      "instagram_followers": 290887065,
      "platform_data": {
        "page_alias": "nike",
        "verification": "blue_verified",
        "instagram_verified": true
      }
    }
  ]
}
```

---

## An advertiser's ads

**Endpoint:** `GET https://api.virlo.ai/v1/raw/meta/advertisers/:page_id/ads`

The ads of one advertiser. There are two routes, and each returns the same shape:

- `GET /v1/raw/meta/advertisers/:page_id/ads` takes the advertiser's Facebook page ID. Use it when you know exactly which advertiser you want.
- `GET /v1/raw/meta/advertisers/ads?name=` takes a name. Virlo uses the first live advertiser the Ad Library suggests for that name. The `advertiser` object in the response says which one it used. If it is the wrong one, call [search advertisers](https://dev.virlo.ai/docs/raw/meta-ads#advertiser-search) and use the `:page_id` route.

The response adds `advertiser` next to `data` and `pagination`: `{ "data": [ ... ], "advertiser": { ... }, "pagination": { ... } }`. It has the [advertiser fields](https://dev.virlo.ai/docs/raw/meta-ads#advertiser-search), plus `platform_data.ad_count`, the number of ads that match your filters as the Ad Library counts them (it stops at 50,001). Called with `:page_id`, `category`, `instagram_handle` and `instagram_followers` are `null`.

Cost per request: $0.01

### Path parameters

- `page_id` (string, optional): The advertiser's Facebook page ID, digits only: `page_id` from advertiser search, or `advertiser.page_id` from any ad. Only on the `:page_id` route.

### Query parameters

- `name` (string, optional): Required on `/v1/raw/meta/advertisers/ads`, and not accepted on the `:page_id` route. The advertiser's name, 1 to 200 characters.
- `country, status, media_type` (string, optional): Optional [filters](https://dev.virlo.ai/docs/raw/meta-ads#filters).
- `cursor` (string, optional): `next_cursor` from the previous page. See [paging](https://dev.virlo.ai/docs/raw/meta-ads#pagination). With `name`, every later call reuses the advertiser the first call found.

**By page ID request:**

```bash
curl -G "https://api.virlo.ai/v1/raw/meta/advertisers/15087023444/ads" \
  --data-urlencode "country=US" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**By name request:**

```bash
curl -G "https://api.virlo.ai/v1/raw/meta/advertisers/ads" \
  --data-urlencode "name=nike" \
  --data-urlencode "country=US" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response:**

```json
{
  "data": [
    {
      "id": "1184620937415528",
      "url": "https://www.facebook.com/ads/library/?id=1184620937415528",
      "advertiser": {
        "page_id": "15087023444",
        "name": "Nike",
        "avatar_url": "https://scontent.xx.fbcdn.net/v/avatar.jpg",
        "url": "https://www.facebook.com/nike"
      },
      "is_active": true,
      "started_at": "2026-10-20T07:00:00.000Z",
      "ended_at": null,
      "publisher_platforms": ["facebook", "instagram"],
      "media_type": "video",
      "creatives": [
        {
          "body": "Built for the long run. More cushion, less weight.",
          "title": "Pegasus. Your everyday trainer.",
          "description": null,
          "caption": "nike.com",
          "cta_text": "Shop now",
          "cta_type": "shop_now",
          "link_url": "https://www.nike.com/running",
          "image_urls": [],
          "videos": [
            {
              "hd_url": "https://video.xx.fbcdn.net/v/example-hd.mp4",
              "sd_url": "https://video.xx.fbcdn.net/v/example-sd.mp4",
              "preview_image_url": "https://scontent.xx.fbcdn.net/v/poster.jpg"
            }
          ]
        }
      ],
      "variant_count": 3,
      "page_categories": ["Sportswear", "Product/service"],
      "reach": null,
      "spend": null,
      "impressions": null,
      "platform_data": {
        "display_format": "video",
        "page_like_count": 39494133
      }
    }
  ],
  "advertiser": {
    "page_id": "15087023444",
    "name": "Nike",
    "avatar_url": "https://scontent.xx.fbcdn.net/v/avatar.jpg",
    "url": "https://www.facebook.com/nike",
    "verified": true,
    "likes": 39494133,
    "category": "Sportswear Store",
    "instagram_handle": "nike",
    "instagram_followers": 290887065,
    "platform_data": {
      "page_alias": "nike",
      "verification": "blue_verified",
      "ad_count": 1240
    }
  },
  "pagination": {
    "next_cursor": "eyJ2IjoxLCJjIjoiSzJqM2g0In0",
    "has_more": true
  }
}
```

---

## Paging through results

Search ads and an advertiser's ads return one page per call: about 30 ads on the first page, then about 10 on each page after it. Search advertisers does not page.

- **Keep going while `has_more` is `true`.** Send the same request again with `cursor` set to `next_cursor`. On the last page `next_cursor` is `null` and `has_more` is `false`.
- **There is no `limit`.** The Ad Library decides how many ads are on a page.
- **Keep the filters the same.** A cursor works only with the request that produced it: the same route, input and filters. Send `country=GB` with a cursor from a `country=US` call and you get `400` with `invalid_cursor`.
- **Cursors are opaque and expire after 6 hours.** Don't build or change one. A changed, expired or mismatched cursor returns `400` with `invalid_cursor`: start again from page 1.
- **Each page is a separate call** and costs $0.01.

**Page 2:**

```bash
curl -G "https://api.virlo.ai/v1/raw/meta/ads/search" \
  --data-urlencode "q=running shoes" \
  --data-urlencode "country=US" \
  --data-urlencode "media_type=video" \
  --data-urlencode "cursor=eyJ2IjoxLCJjIjoiTTRuNXA2In0" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

## Costs and errors

- **$0.01 (1 credit) per call** on every route. A call that succeeds but finds nothing (an empty page or an empty advertiser list) is charged.
- **Errors cost nothing.** That includes every `4xx` and `5xx` below. `X-Cost` shows the charge in dollars and `X-Credits-Used` in credits.
- **Not saved.** Virlo does not keep these results for you. Call again to get fresh data, and download media you want to keep before the links expire.

Errors come back with `message`, `error`, `statusCode` and `code` ([more on errors](https://dev.virlo.ai/docs/errors)):

| Error | When it happens |
| - | - |
| **400** `validation_error` | A missing or bad parameter: no `q`, a `q` or `name` over 200 characters, an `ad_id` or `page_id` that is not all digits, a `country` that is not `ALL` or a 2-letter ISO code, a `status` or `media_type` outside the list, `name` on the page ID route, or a parameter the route doesn't take. |
| **400** `invalid_cursor` | The cursor was changed, has expired, belongs to a different request, or was sent with different filters. Start again from page 1. |
| **402** `insufficient_credits` | Your balance is below $0.01. |
| **404** `not_found` | No ad has that ID, no advertiser has that page ID, or no live advertiser matches the name. |
| **429** `rate_limit_exceeded` | Your key sent too many requests. Wait `retry_after` seconds. |
| **502** `upstream_error` | Virlo could not get the data from the Ad Library this time. Retry after a few seconds. |
| **503** `service_unavailable` | Raw data is briefly unavailable. Retry later. |
| **504** `request_timeout` | The Ad Library took too long to answer. Retry after a few seconds. |

---

More in Explore data:

- [Search videos and creators](https://dev.virlo.ai/docs/search.md)
- [Pop culture & News](https://dev.virlo.ai/docs/trends.md)
- [Content Trends](https://dev.virlo.ai/docs/content-trends.md)
- [Sounds](https://dev.virlo.ai/docs/sounds.md)
- [Hooks](https://dev.virlo.ai/docs/hooks.md)
- [Hashtags](https://dev.virlo.ai/docs/hashtags.md)
- [Videos](https://dev.virlo.ai/docs/videos.md)
- [Raw data](https://dev.virlo.ai/docs/raw.md)
