# Quickstart

> Search an ad library with a sandbox key, read the next page with `next_cursor`, test your error handling, then switch to a live key.

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

## 1. Get a sandbox key

[Sign up](https://publicadsapi.com/signup) and your sandbox key is on the dashboard. It starts `pads_test_`, returns sample ads from made-up advertisers and never uses a credit. Keep it in the `PADS_API_KEY` environment variable. An AI agent can get one itself: see [Agents](https://publicadsapi.com/docs/agents.md).

Your sandbox key is on https://publicadsapi.com/app/keys (it starts `pads_test_`). Read it from `PADS_API_KEY`.

## 2. Search the sandbox

One request to the Meta Ad Library: the keyword `running shoes`, ads shown in the UK. The other platforms work the same way, each with [its own filters](https://publicadsapi.com/docs/api/search.md).

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"])
```

200 OK (the first of 3 ads shown):

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

`query` echoes what was searched, defaults filled in. Each ad is the same [Ad record](https://publicadsapi.com/docs/api/ad-record.md) whichever platform it came from. The sandbox returns its sample ads whatever you search for, so the shapes are real but the ads don't match the keyword.

## 3. Read the next page

While `has_more` is `true`, send `next_cursor` back as `cursor` with the same parameters. A cursor only works with the search it came from.

cURL:

```bash
curl "https://api.publicadsapi.com/v1/meta/ads?q=running+shoes&country=GB&cursor=eyJwIjoibWV0YSIsImMiOiIzIiwicSI6IldZZVBvV0xGd1Bkei1OejUifQ" \
  -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&cursor=eyJwIjoibWV0YSIsImMiOiIzIiwicSI6IldZZVBvV0xGd1Bkei1OejUifQ', {
  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&cursor=eyJwIjoibWV0YSIsImMiOiIzIiwicSI6IldZZVBvV0xGd1Bkei1OejUifQ",
    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"])
```

Every page, in Node.js:

```js
const ads = [];
let cursor = null;
do {
  const url = new URL('https://api.publicadsapi.com/v1/meta/ads');
  url.searchParams.set('q', 'running shoes');
  url.searchParams.set('country', 'GB');
  if (cursor) url.searchParams.set('cursor', cursor);
  const res = await fetch(url, {
    headers: { authorization: `Bearer ${process.env.PADS_API_KEY}` },
    signal: AbortSignal.timeout(60_000),
  });
  const page = await res.json();
  if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`);
  ads.push(...page.ads);
  cursor = page.next_cursor;
} while (cursor && ads.length < 300);
```

Every page, in Python:

```python
import os
import requests

ads, cursor = [], None
while True:
    params = {"q": "running shoes", "country": "GB"}
    if cursor:
        params["cursor"] = cursor
    res = requests.get(
        "https://api.publicadsapi.com/v1/meta/ads",
        params=params,
        headers={"authorization": f"Bearer {os.environ['PADS_API_KEY']}"},
        timeout=60,
    )
    page = res.json()
    if not res.ok:
        raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}")
    ads += page["ads"]
    cursor = page["next_cursor"]
    if not cursor or len(ads) >= 300:
        break
```

## 4. Test your error handling

With a sandbox key, these searches answer the way a platform sometimes does, so you can build for them before going live. They work as `q`, `company` or the first part of a `domain` on every platform.

| Search for | Sandbox answer |
| --- | --- |
| `q=empty` | 200 with no ads (0 credits) |
| `q=blocked` | 503 UPSTREAM_BLOCKED with Retry-After |
| `q=timeout` | 504 TIMEOUT |
| `q=changed` | 502 UPSTREAM_CHANGED |

## 5. Go live

Create a live key on [Keys](https://publicadsapi.com/app/keys) (it starts `pads_live_` and is shown once) and send it in place of the sandbox key. Nothing else changes. A page with ads uses 1 credit (LinkedIn 2); an empty page uses none. A live call reads the platform as it runs and can take 2 to 15 seconds, so set your HTTP client's timeout to at least 60 seconds.

## Or give it to your coding agent

```text
Add ad library search to this project using the Public Ads API. Follow the quickstart: https://publicadsapi.com/docs/quickstart.md. Read my API key from the PADS_API_KEY environment variable. If it isn't set, get a sandbox key yourself with POST https://api.publicadsapi.com/v1/agent/signup (send your name and what we are building as name and use_case), store the api_key it returns in PADS_API_KEY, and give me the claim_url so I can keep the key. Test it against the sandbox first: try GET https://api.publicadsapi.com/v1/meta/ads?q=running+shoes&country=GB, follow next_cursor for the next page, and handle the scenario queries q=empty, q=blocked and q=timeout.
```

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