API
Three on-ramps. Same data.
Ounie accounts hit REST endpoints with a bearer token. AI assistants connect via MCP and get a native tool surface. One-off scripts can skip the account and pay per call via x402 in USDC. All three return the same normalized ad rows.
POST
/api/scrapeauth: Bearer
Start a scrape job
Returns a job_id immediately. The actual run completes async (1–3 minutes); poll /api/jobs/{id} or wait on the webhook.
Body
{
"mode": "page", // page | search
"targets": ["https://www.facebook.com/nike"],
"max_ads": 500,
"ad_details": false, // +1 credit/ad: EU reach + demographics
"period": "last30d", // page mode: last24h | last7d | last14d | last30d
"active_status": "active", // page mode: all | active | inactive
"sort_by": "impressions_desc", // page mode: impressions_desc | most_recent
"country_code": "US", // page mode: 2-letter ISO, default ALL
"webhook_url": "https://your-host.com/hook" // optional
}Response
{ "ok": true, "job_id": "job_01HX...", "credits_reserved": 500 }GET
/api/jobs/{id}auth: Bearer
Check job status
Returns the job row plus a 10-minute signed download URL once succeeded.
Response
{
"job": { "status": "succeeded", "results_count": 412, "mode": "page", "targets": ["https://www.facebook.com/nike"], ... },
"preview_url": "https://...presigned..."
}GET
/api/jobs/{id}/export?format=csv|jsonauth: Bearer
Download the dataset
Streams the file directly. CSV is the default; JSON returns the full normalized shape with nested page + creative + eu objects.
POST
/api/x402/scrapeauth: none · x402
Pay-per-call (agents)
No account, no API key. Returns 402 with payment terms; sign and retry with X-Payment header. Synchronous; capped at 500 ads per call.
Body
{
"mode": "page",
"targets": ["https://www.facebook.com/hellofresh"],
"max_ads": 50,
"ad_details": false
}Response
// 402 Payment Required (initial)
{ "x402Version": 1, "accepts": [{ "scheme":"exact", "network":"eip155:8453", "maxAmountRequired": "600000", ... }] }
// 200 OK (after settled X-Payment header)
{ "ok": true, "count": 47, "ads": [...], "payment": { "tx_hash": "0x..." } }HTTP
/api/mcpMCP · Streamable HTTP + SSE
Model Context Protocol server
Drop our tool surface into any MCP host (Cursor, Claude desktop, ChatGPT GPTs, the Ounie AI Team, the AI SDK). Seven tools: scrape_facebook_ads, get_job_status, export_job_results, list_jobs, get_credit_balance, get_pricing, whoami. Auth: Bearer API key minted in the dashboard — or append ?api_key=fbk_live_… to the URL for MCP clients that can't set headers. Calls draw your Ounie credits; when the balance runs short the run is refused with a top-up link, so an agent can never overdraw.
Body
// .cursor/mcp.json (Cursor / any Streamable HTTP host)
{
"mcpServers": {
"facebook-ads": {
"url": "https://facebook-ads.ounie.com/api/mcp",
"headers": { "Authorization": "Bearer fbk_live_..." }
}
}
}Response
// Tool call: scrape_facebook_ads
{
"mode": "page",
"targets": ["https://www.facebook.com/gymshark"],
"max_ads": 250
}
→ { "ok": true, "job_id": "...", "credits_reserved": 250, "poll_with": { ... } }// One request. Async job.
const res = await fetch("https://facebook-ads.ounie.com/api/scrape", {
method: "POST",
headers: {
"Authorization": "Bearer ${API_KEY}",
"Content-Type": "application/json",
},
body: JSON.stringify({
mode: "page",
targets: ["https://www.facebook.com/nike"],
max_ads: 500,
active_status: "active",
}),
});
const { job_id } = await res.json();
// → poll /api/jobs/${job_id} or wait on the webhookSchema
Every ad row.
{
"id": "839212...",
"url": "https://www.facebook.com/ads/library/?id=839212...",
"page": {
"id": "1442...",
"name": "Nike",
"url": "https://www.facebook.com/nike",
"likes": 39400000,
"categories": ["Sportswear Store"]
},
"isActive": true,
"startDate": "2026-06-12",
"endDate": "",
"platforms": ["facebook", "instagram"],
"creative": {
"body": "Step into summer...",
"title": "Air Max Season",
"ctaText": "Shop Now",
"ctaType": "SHOP_NOW",
"linkUrl": "https://nike.com/...",
"displayFormat": "VIDEO",
"imageUrl": "https://...jpg",
"videoUrl": "https://...mp4",
"cardCount": 1
},
"variantCount": 24,
"currency": "USD",
"eu": {
"totalReach": 1240000,
"targetsEu": true,
"genderAudience": "All",
"ageMin": 18, "ageMax": 65
}
}Availability
What happens when the engine is down.
A scheduled probe checks the scrape engine. While it is unavailable we refuse up front and charge nothing — rather than quote a price for work we already know cannot finish.
- · REST and MCP:
503 upstream_unavailable. No credits are reserved. - · x402:
503instead of a 402 quote, so no signature is wasted. - · Both carry
Retry-After. Service resumes automatically.
Rate limits
- · REST: 60 jobs / min / key
- · x402: 5 req / sec / payer address
- · Max ads per job: 2,000 (REST) · 500 (x402)