# Search Ad Library ads

> Find ads in Meta's Ad Library by keyword, with their copy, media, landing page and run dates.

- **Request:** `GET https://api.lurkapi.com/v1/facebook/adLibrary/search/ads`
- **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard))
- **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)).
- **MCP tool:** `facebook_search_ads` on `https://api.lurkapi.com/mcp`
- **Freshness:** responses are cached for up to 30 minutes
- **Try it live:** https://lurkapi.com/docs/facebook/search-ads#try (no signup; free tool: [Facebook Ad Library search](https://lurkapi.com/free-tools/facebook-ad-library-search))
- **Platform:** [Facebook](https://lurkapi.com/docs/facebook.md) (facebook.com/ads/library)
- **Web page:** https://lurkapi.com/docs/facebook/search-ads

## When to use this

Use it to see how a whole market advertises: the angles, offers and formats that keep running. For one advertiser's ads, use company ads (facebook_company_ads) instead. `status` defaults to all; pass `active` for ads running now.

Keyword search across every ad in the Meta Ad Library, the same as typing into facebook.com/ads/library. Filter by country, status, media type, language and date range. The default sort puts the most-seen ads first.

**Pagination.** The first page returns up to 30 ads plus `searchResultsCount`. Pass `cursor` back (with the same filters) for the next page: cursor pages return up to 10 ads, which is Facebook's own page size, and `searchResultsCount` is null. `cursor` is null on the last page.

## Parameters

All parameters go in the query string.

| Name | Type | Required | Default | Allowed values | Description | Example |
| --- | --- | --- | --- | --- | --- | --- |
| `query` | string | yes |  |  | Keywords to search ad text for, e.g. `running shoes`. | `running shoes` |
| `search_type` | string | no | `keyword_unordered` | `keyword_unordered`, `keyword_exact_phrase` | `keyword_unordered` matches all words in any order; `keyword_exact_phrase` matches the phrase as typed. |  |
| `country` | string | no | `ALL` |  | Where the ad ran: a 2-letter ISO country code (US, GB, DE…) or ALL. One country per request. | `US` |
| `status` | string | no | `all` | `all`, `active`, `inactive` | Only ads that are running now (active), only stopped ads (inactive), or both (all). | `active` |
| `media_type` | string | no | `all` | `all`, `image`, `video`, `meme`, `image_and_meme`, `none` | Creative format. `meme` is Facebook's name for image + text; `none` means text-only. |  |
| `ad_type` | string | no | `ALL` | `ALL`, `POLITICAL_AND_ISSUE_ADS`, `EMPLOYMENT_ADS`, `HOUSING_ADS`, `CREDIT_ADS` | Ad category. Political ads also carry spend and impressions ranges. |  |
| `language` | string | no |  |  | Language of the ad text as an ISO 639-1 code, e.g. `en` or `es`. |  |
| `sort_by` | string | no | `total_impressions` | `total_impressions`, `relevancy_monthly_grouped` | `total_impressions`: most-seen ads first. `relevancy_monthly_grouped`: most recent first. |  |
| `start_date` | string | no |  |  | Only ads shown on or after this date (YYYY-MM-DD). The ad itself may have launched earlier. |  |
| `end_date` | string | no |  |  | Only ads shown on or before this date (YYYY-MM-DD). |  |
| `cursor` | string | no |  |  | The `cursor` from the previous response, to get the next page. Keep every other param the same. |  |
| `trim` | boolean | no | `false` | `true`, `false` | `true` drops image and video URLs (inside `snapshot.cards` too) for a much smaller response. All text is kept. |  |

## Example request

Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard).

curl:

```bash
curl "https://api.lurkapi.com/v1/facebook/adLibrary/search/ads?query=running+shoes&country=US&status=active" \
  -H "x-api-key: YOUR_API_KEY"
```

JavaScript:

```js
const params = new URLSearchParams({
  query: "running shoes",
  country: "US",
  status: "active",
});
const res = await fetch(`https://api.lurkapi.com/v1/facebook/adLibrary/search/ads?${params}`, {
  headers: { "x-api-key": process.env.LURKAPI_KEY },
});
const data = await res.json();
if (!data.success) throw new Error(`${data.code}: ${data.error}`);
console.log(data.searchResults);
```

Python:

```python
import os
import requests

res = requests.get(
    "https://api.lurkapi.com/v1/facebook/adLibrary/search/ads",
    params={
        "query": "running shoes",
        "country": "US",
        "status": "active",
    },
    headers={"x-api-key": os.environ["LURKAPI_KEY"]},
    timeout=60,
)
data = res.json()
if not data["success"]:
    raise RuntimeError(f"{data['code']}: {data['error']}")
print(data["searchResults"])
```

## Example response

A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`.

200 OK (application/json):

```json
{
  "success": true,
  "credits_remaining": 999,
  "credits_charged": 1,
  "searchResults": [
    {
      "ad_archive_id": "1279489107485101",
      "ad_id": null,
      "categories": [
        "UNKNOWN"
      ],
      "collation_count": 3,
      "collation_id": "955107020207956",
      "contains_digital_created_media": false,
      "contains_sensitive_content": false,
      "currency": "",
      "end_date": 1790751600,
      "end_date_string": null,
      "fev_info": null,
      "gated_type": "ELIGIBLE",
      "has_user_reported": false,
      "hide_data_status": "NONE",
      "impressions_with_index": {
        "impressions_text": null,
        "impressions_index": -1
      },
      "is_aaa_eligible": false,
      "is_active": true,
      "menu_items": [],
      "page_id": "103032366405927",
      "page_is_deleted": false,
      "page_name": "HOKA",
      "publisher_platform": [
        "FACEBOOK",
        "INSTAGRAM"
      ],
      "reach_estimate": null,
      "regional_regulation_data": {
        "finserv": {
          "is_deemed_finserv": false,
          "is_limited_delivery": false
        },
        "tw_anti_scam": {
          "is_limited_delivery": false
        }
      },
      "report_count": null,
      "snapshot": {
        "additional_info": null,
        "branded_content": null,
        "brazil_tax_id": null,
        "byline": null,
        "caption": "HOKA.com",
        "cards": [],
        "country_iso_code": null,
        "cta_text": "Learn more",
        "cta_type": "LEARN_MORE",
        "disclaimer_label": null,
        "display_format": "IMAGE",
        "ec_certificates": [],
        "event": null,
        "extra_images": [],
        "extra_links": [],
        "extra_texts": [],
        "extra_videos": [],
        "images": [
          {
            "image_crops": [],
            "original_image_url": "https://scontent-sin11-2.xx.fbcdn.net/v/t39.35426-6/660610392_9315190863…",
            "resized_image_url": "https://scontent-sin6-3.xx.fbcdn.net/v/t39.35426-6/658157694_15247761589…",
            "watermarked_resized_image_url": ""
          }
        ],
        "is_reshared": false,
        "link_description": "From first summits to 5Ks, HOKA designs running shoes and gear built to …",
        "link_url": "https://www.hoka.com/en/us/fly-human-fly/?utm_source=facebookinstagram&u…",
        "page_categories": [
          "Outdoor & Sporting Goods Company"
        ],
        "page_id": "103032366405927",
        "page_is_deleted": false,
        "page_like_count": 1218804,
        "page_name": "HOKA",
        "page_profile_picture_url": "https://scontent-sin2-1.xx.fbcdn.net/v/t39.35426-6/661719637_14799039102…",
        "page_profile_uri": "https://facebook.com/hoka",
        "root_reshared_post": null,
        "title": "FLY HUMAN FLY™",
        "videos": [],
        "body": {
          "text": "The legs give out. The heart doesn’t. Together We Fly Higher."
        }
      },
      "spend": null,
      "start_date": 1775026800,
      "start_date_string": null,
      "state_media_run_label": null,
      "targeted_or_reached_countries": [],
      "total_active_time": null,
      "url": null
    },
    {
      "ad_archive_id": "1672311423863159",
      "ad_id": null,
      "categories": [
        "UNKNOWN"
      ],
      "collation_count": null,
      "collation_id": null,
      "contains_digital_created_media": false,
      "contains_sensitive_content": false,
      "currency": "",
      "end_date": 1790751600,
      "end_date_string": null,
      "fev_info": null,
      "gated_type": "ELIGIBLE",
      "has_user_reported": false,
      "hide_data_status": "NONE",
      "impressions_with_index": {
        "impressions_text": null,
        "impressions_index": -1
      },
      "is_aaa_eligible": false,
      "is_active": true,
      "menu_items": [],
      "page_id": "9062006483",
      "page_is_deleted": false,
      "page_name": "REI",
      "publisher_platform": [
        "FACEBOOK",
        "INSTAGRAM"
      ],
      "reach_estimate": null,
      "regional_regulation_data": {
        "finserv": {
          "is_deemed_finserv": false,
          "is_limited_delivery": false
        },
        "tw_anti_scam": {
          "is_limited_delivery": false
        }
      },
      "report_count": null,
      "snapshot": {
        "additional_info": null,
        "branded_content": null,
        "brazil_tax_id": null,
        "byline": null,
        "caption": "rei.com",
        "cards": [],
        "country_iso_code": null,
        "cta_text": "Shop now",
        "cta_type": "SHOP_NOW",
        "disclaimer_label": null,
        "display_format": "VIDEO",
        "ec_certificates": [],
        "event": null,
        "extra_images": [],
        "extra_links": [],
        "extra_texts": [],
        "extra_videos": [],
        "images": [],
        "is_reshared": false,
        "link_description": "Trail-running gear",
        "link_url": "https://www.rei.com/c/trail-running-shoes?cm_mmc=sm_fbig_76501-_-hero_tr…",
        "page_categories": [
          "Outdoor & Sporting Goods Company"
        ],
        "page_id": "9062006483",
        "page_is_deleted": false,
        "page_like_count": 2113405,
        "page_name": "REI",
        "page_profile_picture_url": "https://scontent-sin6-3.xx.fbcdn.net/v/t39.35426-6/753881065_89714526678…",
        "page_profile_uri": "https://facebook.com/REI",
        "root_reshared_post": null,
        "title": "Get your gear for the trail",
        "videos": [
          {
            "video_hd_url": "https://video-sin2-2.xx.fbcdn.net/o1/v/t2/f2/m366/AQNiLrO3Mqup5to6yp2shI…",
            "video_preview_image_url": "https://scontent-sin11-1.xx.fbcdn.net/v/t39.35426-6/753145822_1584018949…",
            "video_sd_url": "https://video-sin11-1.xx.fbcdn.net/o1/v/t2/f2/m412/AQOUVO8Jd7h4piW3xQtRw…",
            "watermarked_video_hd_url": null,
            "watermarked_video_sd_url": null
          }
        ],
        "body": {
          "text": "Shoes required. Fun type may vary."
        }
      },
      "spend": null,
      "start_date": 1785481200,
      "start_date_string": null,
      "state_media_run_label": null,
      "targeted_or_reached_countries": [],
      "total_active_time": null,
      "url": null
    }
  ],
  "searchResultsCount": 37660,
  "cursor": "AQHTNrjEvTw_VUW75_xQkUb5_yjH682WwzNgh8oZPUIyUZdNkk1m_9q-4jP_law5Dv9p"
}
```

## Response fields

Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing.

| Field | Type | Description |
| --- | --- | --- |
| `success` | `true` | Always true here; errors have `success: false`. |
| `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. |
| `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). |
| `searchResults` | `object[]` | Matching ads. |
| `searchResults[].ad_archive_id` | `string`, nullable | The Ad Library id. Pass it as `id` to the ad details endpoint. |
| `searchResults[].ad_id` | `string`, nullable | Meta's internal ad id. null in every response we've seen; use the Ad Library id. |
| `searchResults[].page_id` | `string`, nullable | The advertiser's Facebook page id. Pass it as `pageId` to the company ads endpoint. |
| `searchResults[].page_name` | `string`, nullable | The advertiser's page name. |
| `searchResults[].is_active` | `boolean` | Whether the ad is running now. |
| `searchResults[].start_date` | `number`, nullable | When the ad started running, Unix seconds (UTC). |
| `searchResults[].end_date` | `number`, nullable | When the ad stopped, or its latest delivery date if still running, Unix seconds (UTC). |
| `searchResults[].start_date_string` | `string`, nullable | The same date as ISO 8601 text when Facebook includes it; often null, so prefer the Unix seconds field. |
| `searchResults[].end_date_string` | `string`, nullable | The same date as ISO 8601 text when Facebook includes it; often null, so prefer the Unix seconds field. |
| `searchResults[].total_active_time` | `number`, nullable | Seconds the ad has run, when Facebook reports it; usually null. |
| `searchResults[].publisher_platform` | `string[]` | Where it ran, uppercase: FACEBOOK, INSTAGRAM, AUDIENCE_NETWORK, MESSENGER, THREADS. |
| `searchResults[].collation_count` | `number`, nullable | How many near-identical versions are grouped under this ad. A high count means the advertiser is scaling it; null for a single version. |
| `searchResults[].collation_id` | `string`, nullable | Id shared by the grouped versions of this ad; null for a single version. |
| `searchResults[].snapshot` | `object` | The creative: copy, media, landing page and advertiser details. |
| `searchResults[].snapshot.page_id` | `string`, nullable | The advertiser's page id (same as the ad's). |
| `searchResults[].snapshot.page_name` | `string`, nullable | The advertiser's page name as shown on the ad. |
| `searchResults[].snapshot.page_profile_uri` | `string`, nullable | The advertiser's Facebook page URL. |
| `searchResults[].snapshot.page_profile_picture_url` | `string`, nullable | The advertiser's profile picture, 60×60 px. |
| `searchResults[].snapshot.page_like_count` | `number`, nullable | Followers of the advertiser's page. |
| `searchResults[].snapshot.page_categories` | `string[]` | Page categories, e.g. ["Outdoor & Sporting Goods Company"]. |
| `searchResults[].snapshot.page_is_deleted` | `boolean` | Whether the advertiser's page has been deleted. |
| `searchResults[].snapshot.title` | `string`, nullable | Headline under the creative; null when there is none. |
| `searchResults[].snapshot.caption` | `string`, nullable | Display link under the creative, usually the domain (e.g. HOKA.com). |
| `searchResults[].snapshot.link_url` | `string`, nullable | Landing page the ad sends people to, UTM tags included. |
| `searchResults[].snapshot.link_description` | `string`, nullable | Text under the headline; null when there is none. |
| `searchResults[].snapshot.cta_text` | `string`, nullable | Button label, e.g. "Shop now". |
| `searchResults[].snapshot.cta_type` | `string`, nullable | Button type, e.g. SHOP_NOW, LEARN_MORE, NO_BUTTON. |
| `searchResults[].snapshot.display_format` | `string`, nullable | Creative format, e.g. IMAGE, VIDEO, DCO (dynamic creative), DPA (dynamic product ad) or MULTI_IMAGES. For DCO and DPA ads the creative is in `cards`. |
| `searchResults[].snapshot.images` | `object[]` | Image creatives. Empty for video and card-based ads, and with trim=true. |
| `searchResults[].snapshot.images[].original_image_url` | `string`, nullable | Full-size image URL. Facebook CDN links expire after a few days; download what you need. |
| `searchResults[].snapshot.images[].resized_image_url` | `string`, nullable | 600 px image URL, good for thumbnails. |
| `searchResults[].snapshot.images[].watermarked_resized_image_url` | `string` | Watermarked copy; empty or null in every response we've seen. |
| `searchResults[].snapshot.images[].image_crops` | `any[]` | Facebook internal field; an empty array in every response we've seen. Crop hints for the image. |
| `searchResults[].snapshot.videos` | `object[]` | Video creatives. Empty for image and card-based ads, and with trim=true. |
| `searchResults[].snapshot.videos[].video_hd_url` | `string`, nullable | HD video file URL; null if Facebook has no HD version. |
| `searchResults[].snapshot.videos[].video_sd_url` | `string`, nullable | SD video file URL. |
| `searchResults[].snapshot.videos[].video_preview_image_url` | `string`, nullable | Poster frame (still image) URL. |
| `searchResults[].snapshot.videos[].watermarked_video_hd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. |
| `searchResults[].snapshot.videos[].watermarked_video_sd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. |
| `searchResults[].snapshot.cards` | `object[]` | Carousel, dynamic-creative and product versions, each with its own copy and media. With trim=true the text stays and media URLs are null. |
| `searchResults[].snapshot.cards[].title` | `string`, nullable | This version's headline. |
| `searchResults[].snapshot.cards[].body` | `string`, nullable | This version's primary text. |
| `searchResults[].snapshot.cards[].caption` | `string`, nullable | This version's display link, usually the domain. |
| `searchResults[].snapshot.cards[].cta_text` | `string`, nullable | Button label, e.g. "Shop now". |
| `searchResults[].snapshot.cards[].cta_type` | `string`, nullable | Button type, e.g. SHOP_NOW, LEARN_MORE. |
| `searchResults[].snapshot.cards[].link_url` | `string`, nullable | This version's landing page URL. |
| `searchResults[].snapshot.cards[].link_description` | `string`, nullable | Text under the headline; null when there is none. |
| `searchResults[].snapshot.cards[].original_image_url` | `string`, nullable | Full-size image URL; null for video versions. |
| `searchResults[].snapshot.cards[].resized_image_url` | `string`, nullable | 600 px image URL; null for video versions. |
| `searchResults[].snapshot.cards[].watermarked_resized_image_url` | `string` | Watermarked copy; empty or null in every response we've seen. |
| `searchResults[].snapshot.cards[].image_crops` | `any[]` | Facebook internal field; an empty array in every response we've seen. Crop hints for the image. |
| `searchResults[].snapshot.cards[].video_hd_url` | `string`, nullable | HD video file URL; null for image versions. |
| `searchResults[].snapshot.cards[].video_sd_url` | `string`, nullable | SD video file URL; null for image versions. |
| `searchResults[].snapshot.cards[].video_preview_image_url` | `string`, nullable | Poster frame URL; null for image versions. |
| `searchResults[].snapshot.cards[].watermarked_video_hd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. |
| `searchResults[].snapshot.cards[].watermarked_video_sd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. |
| `searchResults[].snapshot.byline` | `string`, nullable | Line under the page name, when shown; null or empty in most ads. |
| `searchResults[].snapshot.disclaimer_label` | `string`, nullable | "Paid for by" line on political and issue ads; null otherwise. |
| `searchResults[].snapshot.country_iso_code` | `string`, nullable | Facebook internal field; null in every response we've seen. A country code on some ads. |
| `searchResults[].snapshot.is_reshared` | `boolean` | Whether the ad reshares an existing post (see `root_reshared_post`). |
| `searchResults[].snapshot.root_reshared_post` | `any` | The original post when `is_reshared` is true; null otherwise. |
| `searchResults[].snapshot.branded_content` | `any` | Paid partnership details when the ad is branded content; null otherwise. |
| `searchResults[].snapshot.additional_info` | `any` | Facebook internal field; null in every response we've seen. |
| `searchResults[].snapshot.brazil_tax_id` | `any` | Advertiser tax id shown on ads under Brazil's rules; null otherwise. |
| `searchResults[].snapshot.ec_certificates` | `any[]` | Facebook internal field; an empty array in every response we've seen. |
| `searchResults[].snapshot.event` | `any` | Event details on event ads; null otherwise. |
| `searchResults[].snapshot.extra_images` | `any[]` | More images from dynamic creative; usually empty. |
| `searchResults[].snapshot.extra_links` | `any[]` | More landing page URLs from dynamic creative; usually empty. |
| `searchResults[].snapshot.extra_texts` | `any[]` | More text variations from dynamic creative; usually empty. |
| `searchResults[].snapshot.extra_videos` | `any[]` | More videos from dynamic creative; usually empty. |
| `searchResults[].snapshot.body` | `object`, nullable | Primary text as `{ text }`; null when the copy is only in `cards`. |
| `searchResults[].snapshot.body.text` | `string`, nullable | The ad's primary text. |
| `searchResults[].categories` | `string[]` | Special ad categories Facebook assigned. ["UNKNOWN"] for ordinary ads. |
| `searchResults[].targeted_or_reached_countries` | `string[]` | Countries the ad targeted or reached, when Facebook discloses them; empty in most results. |
| `searchResults[].currency` | `string` | Currency code of `spend` (e.g. USD) when spend is reported; an empty string otherwise. |
| `searchResults[].spend` | `any` | Spend range, reported for political and issue ads only; null otherwise. |
| `searchResults[].impressions_with_index` | `object` | Impressions range, reported for political and issue ads only. |
| `searchResults[].impressions_with_index.impressions_text` | `string`, nullable | The range as Facebook shows it; null when not reported. |
| `searchResults[].impressions_with_index.impressions_index` | `number` | Facebook's bucket number for the range; -1 when not reported. |
| `searchResults[].reach_estimate` | `any` | Estimated reach, which Facebook reports for some EU-delivered ads; null otherwise. |
| `searchResults[].url` | `string`, nullable | Ad Library link for this ad. Often null; build it as https://www.facebook.com/ads/library/?id=<Ad Library id>. |
| `searchResults[].contains_digital_created_media` | `boolean` | Whether Facebook labels the creative as AI-generated or digitally altered. |
| `searchResults[].contains_sensitive_content` | `boolean` | Facebook internal field; false in every response we've seen. Facebook's sensitive-content flag. |
| `searchResults[].gated_type` | `string` | Facebook internal field; ELIGIBLE (lowercase in ad details) in every response we've seen. |
| `searchResults[].has_user_reported` | `boolean` | Whether the viewing user reported the ad. Always false: we read the Ad Library logged out. |
| `searchResults[].hide_data_status` | `string` | Facebook internal field; NONE in every response we've seen. |
| `searchResults[].is_aaa_eligible` | `boolean` | Facebook flag, true on a few ads; meaning undocumented. Ad details' `aaa_info` holds the related data when present. |
| `searchResults[].menu_items` | `any[]` | Facebook internal field; an empty array in every response we've seen. |
| `searchResults[].page_is_deleted` | `boolean` | Whether the advertiser's page has been deleted. |
| `searchResults[].regional_regulation_data` | `object` | Delivery limits under regional ad rules. |
| `searchResults[].regional_regulation_data.finserv` | `object` | Financial-services ad rules. |
| `searchResults[].regional_regulation_data.finserv.is_deemed_finserv` | `boolean` | Facebook treats the ad as a financial-services ad. |
| `searchResults[].regional_regulation_data.finserv.is_limited_delivery` | `boolean` | Delivery is limited under financial-services rules. |
| `searchResults[].regional_regulation_data.tw_anti_scam` | `object` | Taiwan's anti-scam ad rules. |
| `searchResults[].regional_regulation_data.tw_anti_scam.is_limited_delivery` | `boolean` | Delivery is limited under Taiwan's anti-scam rules. |
| `searchResults[].report_count` | `number`, nullable | Times the ad was reported, when disclosed; null in every response we've seen. |
| `searchResults[].fev_info` | `any` | Facebook internal field; null in every response we've seen. |
| `searchResults[].state_media_run_label` | `any` | Label for ads run by state-controlled media; null otherwise. |
| `searchResultsCount` | `number`, nullable | Total matching ads, as Facebook estimates it. Only on the first page; null on cursor pages. |
| `cursor` | `string`, nullable | Pass as `cursor` for the next page. null on the last page. |

## Pagination

Pass the response's `cursor` back as `cursor`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit.

The first five pages, in JavaScript:

```js
let cursor = null;
for (let page = 1; page <= 5; page++) {
  const params = new URLSearchParams({ query: "running shoes", country: "US", status: "active" });
  if (cursor) params.set("cursor", cursor);
  const res = await fetch(`https://api.lurkapi.com/v1/facebook/adLibrary/search/ads?${params}`, {
    headers: { "x-api-key": process.env.LURKAPI_KEY },
  });
  const data = await res.json();
  if (!data.success) throw new Error(`${data.code}: ${data.error}`);
  console.log(data.searchResults);
  cursor = data.cursor;
  if (!cursor) break; // last page
}
```

## Caching and freshness

Responses are cached for up to 30 minutes, so a repeat call within that window can return the same data. It still costs 1 credit.

## Errors

Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors).

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. |
| 401 | `invalid_api_key` | The key is unknown or was revoked. |
| 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. |
| 405 | `method_not_allowed` | Endpoints take GET with query params. |
| 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. |
| 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. |
| 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. |
| 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. |
| 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. |

## Use it in Claude

Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `facebook_search_ads` tool. Ask in plain English, for example:

Ask Claude:

```text
Use LurkAPI's facebook_search_ads with query "running shoes", country "US", status "active" and summarize what you find.
```

Claude calls `facebook_search_ads` with arguments like these, and each call costs 1 credit:

Tool arguments:

```json
{
  "query": "running shoes",
  "country": "US",
  "status": "active"
}
```

Tool results skip nulls and empty lists to save tokens, and `trim` defaults to `true`.

---

Next: [Ad Library ads by company](https://lurkapi.com/docs/facebook/company-ads.md)
