The AdScrape API gives you programmatic access to the same Meta Ad Library data as the dashboard: search, single-ad lookups, media downloads, competitor trackers, usage, and billing. Every call returns JSON with consistent envelopes — there are no XML or form-encoded responses.
- REST + JSONUTF-8 JSON in/out. Errors share the same envelope as success responses.
- One key, all surfacesSame key works from the dashboard, n8n, Zapier, your backend, or a notebook.
- Per-key analyticsEvery call is logged with endpoint, status, latency, and credits spent.
- Pay per useSearch bills 1 credit per ad delivered. Suggestions per result block. Media downloads per file. Tracker slots are a separate subscription — see Credit model.
- Media downloadsDownload or hotlink stored creatives via stable URLs — no Meta hot-link headaches in your app.
Create a key.
Sign in, then Settings → API Keys → Create. Copy the
sk_live_…once — you won't see it again.Fire your first search.
Returns ranked ads + a
session_tokenyou can drain page-by-page.
curl -X POST https://api.adscrape.in/api/v1/search \
-H "X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"query":"nike","max_results":25,"country":"US"}'Every billable endpoint requires a key in the X-API-Key header. Keys are scoped per user and can be revoked at any time from Settings → API Keys. Treat them like passwords — anything that calls AdScrape with your key spends your credits.
curl -X POST https://api.adscrape.in/api/v1/search \
-H "X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"query":"nike","max_results":25}'Session auth
The dashboard also authenticates via the adscrape_session cookie. When both are present the API key wins, so use one or the other per request.
AdScrape bills in credits, where 1 credit = 1 ad returned from search. Every paid plan includes a monthly credit allotment, and new accounts get 10 free credits. Top up anytime with credit packs — no monthly minimums. Every charge lands in your ledger at Settings → Billing. Reads from your own data are always free.
credits / month
credits / month
credits / month
POST /api/v1/searchper ad returned1 credit / adCharged only for ads delivered. Empty results cost 0.
POST /api/v1/search/nextper ad returned1 credit / adSame per-ad rate. Empty pages cost 0 — terminate cleanly.
POST /api/v1/suggestionsper result returned1 credit / resultBilled per delivered result (5-result block minimum). Cached 1h per (query, country, ad_type, status, media_type) — cache hits are free.
GET /api/v1/media/{archive_id}per download2 credits / downloadStreams Meta CDN bytes for one creative. Failed Meta fetch = no charge.
All GET endpointsfree—Reads from your own user data: ads, usage, billing, keys.
Worked example. A search for {"query":"nike","max_results":50} that returns 47 ads costs:
- 47 credits — one credit per delivered ad (
47 ads × 1 credit). - 0 credits if the page returned zero ads — you only pay for delivered results.
Your balance ticks down on success and is restored by top-ups, monthly plan grants, or manual ledger adjustments. There are five surfaces for working with it:
- Live balance
GET /api/v1/billing/credits/statusreturns current balance and rolling burn over the requested window. - Append-only ledger
GET /api/v1/billing/creditsexposes every delta with reason, ref_type, and ref_id — safe to mirror to a warehouse. - Plans via Razorpay
GET /api/v1/configincludesrazorpay_configured.POST /api/v1/billing/create-orderopens Standard Checkout;POST /api/v1/billing/verify-paymentconfirms the signature and grants credits or activates the plan. - Subscription grantsPlans add a fixed monthly grant. Inspect via
GET /api/v1/billing/subscription. - Failure refundsA 5xx mid-request never debits you — credits are only charged after the response is successfully streamed. Partial pages bill for actual rows delivered.
- Free readsAnything that returns data already in your account —
/ads/{id}, brand lookups, usage, billing — is permanently free.
curl https://api.adscrape.in/api/v1/billing/credits/status \
-H "X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Each API key inherits the rate limit of its owner's active subscription. Limits are applied per minute, per key, using a sliding-window counter in Redis (with an in-process fallback if Redis is unavailable). When a request would exceed the limit, the API returns 429 Too Many Requests with a Retry-After header and your client should back off.
Generous for prototypes.
Small dashboards, side projects.
Daily ETLs, creative tools.
High-volume / enterprise.
We also globally throttle scraping concurrency upstream of Meta so a single noisy customer cannot starve everyone else. If our pool is briefly saturated you'll see 502 Bad Gateway — treat the same as 429 and back off.
Exponential with jitter, ceiling 30 s. Stop retrying after ~5 attempts.
GET endpoints are safe to retry. For POST /search, opening a new session is also safe — sessions time out after 24h of inactivity.
from tenacity import retry, stop_after_attempt, wait_exponential_jitter
from httpx import HTTPStatusError
@retry(
stop=stop_after_attempt(5),
wait=wait_exponential_jitter(initial=2, max=30),
retry=lambda e: isinstance(e, HTTPStatusError) and e.response.status_code in (429, 502, 503),
)
def search(client, body):
r = client.post("/api/v1/search", json=body, timeout=60)
r.raise_for_status()
return r.json()Your current limit is shown at Settings → Billing and via GET /api/v1/billing/subscription.
A few hard limits keep the API stable for everyone. These are enforced server-side and can't be opted-out of without a custom plan.
- Search page size
max_resultson/searchis capped at 500.page_sizeon/search/nextis also capped at 500. Use cursor pagination to walk past 500 in one session. - Suggestions limit
limitis between 1 and 50. Cache TTL is 1 hour. - Media downloadsOne creative per
GET /api/v1/media/{archive_id}call (up to 80 MB). The download proxy only streams Meta CDN hosts (*.fbcdn.net,*.facebook.com,*.cdninstagram.com,*.instagram.com). Other origins return 403. In-app previews use stored/media/static/URLs when available. - Concurrency per keyUp to 8 concurrent requests per API key against scrape-driven endpoints. Excess requests queue briefly and may return 429 if the wait exceeds 30 s.
- Country coverageWe mirror Meta's coverage.
political_and_issue_adsdata is unavailable in jurisdictions where Meta itself does not publish it. - Personal-data fieldsThe Meta Ad Library does not expose user-level demographic data. The API never invents it; fields are
nullwhen missing. - Key revocationRevoked keys return 401 immediately. There is no grace period. Sessions opened with a revoked key are invalidated on next call.
The AdScrape API ships data sourced from the Meta Ad Library, which is itself a public transparency tool. Your use of the API must comply with the policy below in addition to Meta's own Ad Library terms.
- Permitted useMarket research, creative inspiration, competitive analysis, journalism, academic research, political ad transparency reporting, internal dashboards and product integrations.
- Prohibited useRe-identification of individuals from public ad records, building a paid mirror that undercuts Meta's policy obligations, training models that produce defamatory or deceptive political content, automated harassment, or any use that violates Meta's TOS.
- AttributionWhen you display ad creatives publicly, credit "Meta Ad Library" as the source. AdScrape attribution is appreciated but not required.
- Storage & retentionYou may cache responses indefinitely for internal product use. If you republish creatives, honor any takedown signals from Meta within 72 hours.
- Sharing keysAPI keys are personal to the issuing user. Sharing a key with a team is fine; reselling access or routing third-party customers through a single key is not — use plans+keys per customer instead.
- Abuse & throttlingWe reserve the right to revoke keys, lower rate limits, or block IP ranges that send traffic patterns indicating automated abuse (e.g. credential stuffing, DDoS, scraping our frontend instead of the API). You'll receive an email before any non-emergency action.
- Compliance escalationConcerns about content surfaced through the API — copyright, defamation, election integrity — should be reported to
abuse@adscrape.in. We forward to Meta where appropriate.
Errors share one shape: { "detail": string | object }. For credit errors, detail is an object so you can inspect the balance.
{
"detail": {
"error": "INSUFFICIENT_CREDITS",
"balance": 12,
"required": 100
}
}- 400Bad RequestYour payload failed validation. The response body explains which field.
- 401UnauthorizedMissing or invalid API key, or a revoked key.
- 402Payment Required
- 404Not FoundResource doesn't exist or isn't visible to you (e.g. another user's key).
- 429Too Many RequestsRate limit exceeded. Honor the Retry-After header before retrying.
- 500Server ErrorUnexpected failure on our side. Safe to retry with exponential backoff.
- 502Bad GatewayUpstream Meta API returned an error. Retry with backoff (start at 2s).
- 503Service UnavailableOptional feature isn't configured (e.g. enrichment models not installed on the API).
/api/v1/searchOpen a search 1 credit per ad returnedOpens a paginated search session. The first page is served immediately (DB-first; Meta scrape backfills if local data is short) and a session_token is returned for subsequent calls.
queryoptionalstringBrand name or keyword. Exactly one of query / page_id / ad_library_url is required.page_idoptionalstringNumeric Facebook Page ID. Bypasses keyword matching for exact-brand lookups.ad_library_urloptionalstringFull Meta Ad Library URL. Honors all of its query parameters.max_resultsoptionalintegerFirst-page size, 1–500. Walk past 500 via /search/next.Defaults to25.sort_byoptional"total_impressions" | "most_recent"Ranking applied at scrape time.Defaults to"total_impressions".countryoptionalstringISO-2 country, or ALL for global.Defaults to"ALL".statusoptional"ALL" | "ACTIVE" | "INACTIVE"Filter ads by Meta-reported activity status.Defaults to"ALL".media_typeoptional"all" | "image" | "video" | "meme"Creative media filter.Defaults to"all".ad_typeoptionalstringe.g. "political_and_issue_ads". Mirrors Meta's ad_type slug.Defaults to"all".exact_phraseoptionalbooleanUse Meta keyword_exact_phrase. Only applies to the keyword path.Defaults tofalse.
curl -X POST https://api.adscrape.in/api/v1/search \
-H "X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"query": "nike",
"max_results": 100,
"sort_by": "total_impressions",
"country": "US",
"status": "ACTIVE",
"media_type": "video"
}'{
"summary": {
"total_ads": 100,
"search_type": "keyword_unordered",
"identifier": "nike",
"sort_by": "total_impressions",
"unique_platforms": ["FACEBOOK", "INSTAGRAM"],
"pages_scraped": 1,
"execution_time_seconds": 1.42,
"credits_charged": 100
},
"ads": [
{
"metadata": { "ad_archive_id": "...", "page_name": "Nike", "page_id": "..." },
"ad_content": { "body": "Just Do It.", "title": "Air Max 90", "images": [...] },
"performance": { "impressions": "500K-1M", "spend": 14999 },
"distribution": { "publisher_platform": ["FACEBOOK", "INSTAGRAM"] },
"status": { "is_active": true }
}
],
"session_token": "0e2…",
"has_more": true,
"total_returned": 100
}/api/v1/search/nextContinue a search 1 credit per ad returnedPulls the next page of an open search using the session_token. The API is DB-first: cached rows are served before Meta. Already-returned ad_archive_id values are deduped per session. Meta pagination resumes only after the local cache is exhausted. Returns has_more: false once everything is drained.
session_tokenrequiredstringToken from the prior /search or /search/next response.page_sizeoptionalintegerPage size for this call, 1–500.Defaults to25.
curl -X POST https://api.adscrape.in/api/v1/search/next \
-H "X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"session_token":"0e2…","page_size":50}'/api/v1/suggestionsBrand autocomplete (Meta typeahead) 1 credit / result · 1h cache (free repeats)Same typeahead source Meta's own Ad Library search box uses — verification, IG handle, follower count, advertiser country. We don't know of anyone else publishing this. Billed per delivered result in 5-result blocks; the cached row hands you the right Page ID to feed straight into /search. Falls back to a keyword-search dedupe when typeahead misses.
queryrequiredstringBrand prefix, 1–120 chars.limitoptionalintegerMax suggestions to return, 1–50.Defaults to10.countryoptionalstringISO-2 country, or ALL.Defaults to"ALL".ad_typeoptionalstringMirrors Meta typeahead ad_type.Defaults to"all".statusoptional"ALL" | "ACTIVE" | "INACTIVE"Filters the typeahead referer's active_status.Defaults to"ALL".media_typeoptional"all" | "image" | "video" | "meme"Creative media filter.Defaults to"all".
{
"query": "nike",
"source": "typeahead",
"keywords": ["nike", "nike air", "nike running"],
"results": [
{
"page_id": "15087023444",
"brand_name": "Nike",
"category": "Athletic Apparel",
"avatar_url": "https://…",
"likes": 39000000,
"ig_followers": 305000000,
"verification": "BLUE_VERIFIED",
"advertiser_country": "US"
}
]
}/api/v1/ads/{archive_id}Get one ad freeReturns the stored snapshot of an ad we've ingested. Hits are O(1) on the DB; no Meta call. Useful after a search to retrieve the full record without re-scraping.
archive_idrequiredstringMeta's ad_archive_id (the stable identifier across renders).
{
"ad_archive_id": "933016365009192",
"page_id": "15087023444",
"page_name": "Nike",
"page_profile_picture_url": "https://…",
"page_is_verified": true,
"page_likes": 39000000,
"body": "Just Do It…",
"title": "Air Max 90",
"cta_text": "Shop now",
"cta_type": "SHOP_NOW",
"link_url": "https://www.nike.com/…",
"display_format": "image",
"is_active": true,
"start_date": "2025-05-02T00:00:00+00:00",
"end_date": "2025-06-04T00:00:00+00:00",
"publisher_platforms": ["FACEBOOK", "INSTAGRAM"],
"countries": ["US", "CA"],
"impressions_text": "500K-1M",
"impressions_upper": 1000000,
"spend_lower": 10000,
"spend_upper": 14999,
"currency": "USD",
"images": [ { "resized_image_url": "https://…" } ],
"videos": [],
"first_seen_at": "2025-05-02T18:11:22+00:00",
"last_refreshed_at": "2025-05-11T07:00:01+00:00"
}/api/v1/brands/{page_id}Brand profile + quick stats freeReturns the cached brand metadata (category, likes, verification) plus aggregates from your ingested ads: total stored, active stored, summed impressions upper bound.
{
"page_id": "15087023444",
"page_name": "Nike",
"page_alias": "nike",
"category": "Athletic Apparel",
"avatar_url": "https://…",
"profile_uri": "https://www.facebook.com/nike",
"likes": 39000000,
"is_verified": true,
"total_ads_seen": 4218,
"last_seen_at": "2025-05-11T07:00:01+00:00",
"stats": { "total_ads_in_store": 312, "active_ads_in_store": 224, "estimated_impressions": 412000000 }
}/api/v1/brands/{page_id}/adsList a brand's stored ads freePage through the ads you've already ingested for one brand. Useful for backfilling a local copy without re-scraping Meta.
active_onlyoptionalbooleanFilter to currently-active ads only.Defaults tofalse.limitoptionalintegerPage size, 1–200.Defaults to50.offsetoptionalintegerCursor offset.Defaults to0.
Meta CDN URLs are short-lived, signed, and frequently reject hot-linked browser requests. This endpoint streams the bytes server-side so you can persist creatives without dealing with referrer / CORS edge-cases. Bytes are not cached on our side — every request is a fresh pull from Meta.
/api/v1/media/{archive_id}Download a single creative 2 credits / downloadStreams either an image or a video frame stored against the ad. We refuse any URL whose host isn't an official Meta CDN — open relays are off-limits.
archive_idrequiredstringMeta ad_archive_id of an ad you've already ingested via /search.
kindoptional"image" | "video"Which media list to pull from on the stored ad.Defaults to"image".indexoptionalinteger0-based offset into the matching media list. Use when an ad has multiple creatives (carousels).Defaults to0.
curl -L -o ad.jpg "https://api.adscrape.in/api/v1/media/933016365009192?kind=image&index=0" \
-H "X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"- Response headers include
Content-Disposition: attachment; filename="ad-<id>-<i>.<ext>"so browsers download cleanly. - Files larger than 80 MB are truncated and the connection is closed — request a different
index. - Credits are only charged once the upstream request reaches 200 — failed Meta fetches return 502 and don't bill.
images / videos arrays on the ad object expose the full URL list, so you can iterate index = 0…N-1 deterministically.Search bills per ad (credits). Trackers use separate brand and keyword slot pools on paid plans (Pro and Enterprise include weekly reports; Starter is search-only). Manage trackers in Trackers; set the digest email via PATCH /api/v1/trackers/preferences. Sample reports: /reports/demo.
page_profile. Ad creatives are not scraped at save time — they are ingested later by the tracker snapshot job (scripts/run_tracker_sweep.py on a schedule). Each brand response includes a meta_ad_library_url for the same view as Meta's Ad Library page./api/v1/trackersList trackers + limits free/api/v1/trackers/preferencesSet report delivery email free/api/v1/trackers/brandsAdd brand tracker freepage_id (numeric). Optional typeahead fields (page_alias, likes, ig_username, …) improve the profile if Meta is partial. Returns page_profile + last_snapshot_at (null until first snapshot).
{
"id": "…",
"page_id": "15087023444",
"page_name": "Nike",
"country": "ALL",
"page_profile": {
"page_name": "Nike",
"facebook": { "handle": "@nike", "followers": 39600000, "verified": true },
"instagram": { "username": "@nike", "followers": 292000000, "verified": true },
"page_history": { "merged_pages_count": 0, "name_changes_count": 1, "created_at": "2008-05-21T…" },
"organization": { "name": "NIKE, Inc.", "address": "Beaverton, OR, United States" },
"admin_locations": [{ "country": "United States", "count": 98 }],
"meta_ad_library_url": "https://www.facebook.com/ads/library/?…&view_all_page_id=15087023444",
"ads_indexed": false
},
"last_snapshot_at": null,
"created_at": "2026-05-29T12:00:00+00:00"
}/api/v1/trackers/keywordsAdd keyword tracker freeCounts as one keyword slot. Ads are fetched on snapshot runs, not at create time.
/api/v1/trackers/demo/brandSample single-brand report (public) free/api/v1/trackers/demoSample weekly digest (public) freepython scripts/run_tracker_sweep.py daily (Render cron). Add --weekly on Mondays for digest emails. Admins can export active trackers via GET /api/v1/admin/trackers.Every billable call is logged with status, latency, and credits spent. The in-app view lives on the Dashboard (overview + quick actions at the top, full analytics under #usage). Legacy /settings/usage redirects there. Credits and plans are also on Billing. Mirror the same JSON from the endpoints below.
/api/v1/usage/summaryRolling usage summary freedaysoptionalintegerLook-back window in days.Defaults to30.
{
"days": 30,
"total_requests": 1241,
"total_credits": 9824,
"avg_duration_ms": 412.7,
"by_endpoint": [
{ "endpoint": "/api/v1/search", "requests": 800, "credits": 8000 },
{ "endpoint": "/api/v1/suggestions", "requests": 410, "credits": 1024 }
]
}/api/v1/billing/credits/statusCredit balance & burn freedaysoptionalintegerBurn window in days.Defaults to30.
{
"balance": 4200,
"credits_used": 842,
"credits_pending": 5000,
"period_days": 30,
"renewal_at": "2026-06-01T12:00:00+00:00"
}/api/v1/billing/creditsCredit ledger freeEvery credit charge, refund, top-up and monthly grant. Stable timestamps; safe to mirror into your warehouse.
/api/v1/billing/subscriptionCurrent subscription freeReturns the active plan, rate limit (rpm_limit), monthly credit allocation, and renewal date.
/api/v1/billing/create-orderCreate Razorpay order freeCreates a Razorpay order for a subscription plan or credit pack. Returns order_id, amount (smallest currency unit, e.g. USD cents), currency, and key_id for the checkout modal.
/api/v1/billing/verify-paymentVerify Razorpay payment freeVerifies razorpay_order_id, razorpay_payment_id, and razorpay_signature (HMAC-SHA256). On success, grants credits or activates the subscription.
POST /api/v1/billing/webhook/razorpay in the Razorpay dashboard (HMAC signature required). Handles order capture, subscription renewals, tracker-slot billing, and payment disputes. Plans and credits are not granted until payment is verified; lost disputes can revoke access.Admin API
Requires is_admin (users whose email is in ADMIN_EMAILS are promoted on login). Powers Admin → Users and Admin → Analytics.
/api/v1/admin/users/searchSearch users by email or name free/api/v1/admin/grant-planGrant plan for N days free/api/v1/admin/revoke-planRevoke paid plan (downgrade to free) free/api/v1/admin/grant-creditsGrant credits free/api/v1/admin/resolve-disputeResolve Razorpay dispute won/lost free/api/v1/admin/subscriptions/expiringList expiring subscriptions free/api/v1/admin/run-sweepRun subscription expiry sweep free/api/v1/admin/trackersExport active trackers freeThese endpoints back Settings → API Keys. They require a session cookie or another active API key (you can't bootstrap a key from zero — sign in via the dashboard first).
/api/v1/api-keysList your keys free[
{
"id": "key_01HZ…",
"name": "Production server",
"prefix": "sk_live_…abcd",
"is_active": true,
"last_used_at": "2026-05-10T18:11:22+00:00",
"created_at": "2025-12-01T08:00:00+00:00"
}
]/api/v1/api-keysCreate a key freenamerequiredstringHuman-readable label, shown only to you.
{
"id": "key_01HZ…",
"name": "Production server",
"full_key": "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"prefix": "sk_live_…abcd",
"created_at": "2025-12-01T08:00:00+00:00"
}full_key is only returned on creation — store it now or rotate./api/v1/api-keys/{key_id}Revoke a key freeMarks the key as revoked. The next request using it will get 401.
/api/v1/public/landing-previewMarketing homepage snapshot no authPowers the live tiles on the AdScrape landing page. Aggregates a small sample of suggestions, hero stats, and gallery thumbnails. Disabled unless the server sets PUBLIC_LANDING_PREVIEW=true.
{
"live": true,
"cached": true,
"suggestions_query": "nike",
"suggestions": [
{ "brand_name": "Nike", "category": "Athletic Apparel", "page_id": "15087023444" }
],
"hero_stats": [
{ "label": "Active ads", "value": "12" },
{ "label": "Est. reach (sample)", "value": "1.4M" },
{ "label": "Top platform", "value": "Instagram" }
],
"gallery": [ { "image_url": "https://…", "active": true, "snippet": "Just Do It…" } ]
}Prefer to click?
Every endpoint has a counterpart you can drive from the dashboard. Same data, no code.