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.
1Get a sandbox key
Sign up 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.
PADS_API_KEY.Log in2Search 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.
curl "https://api.publicadsapi.com/v1/meta/ads?q=running+shoes&country=GB" \ -H "authorization: Bearer $PADS_API_KEY"
{
"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 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.
3Read 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 "https://api.publicadsapi.com/v1/meta/ads?q=running+shoes&country=GB&cursor=eyJwIjoibWV0YSIsImMiOiIzIiwicSI6IldZZVBvV0xGd1Bkei1OejUifQ" \ -H "authorization: Bearer $PADS_API_KEY"
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);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:
break4Test 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 |
5Go live
Create a live key on 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
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.