REST API v1 · Resource
Ads
Create ads (always paused), read their results and charges, and manage Facebook Lead Ads forms.
API reference · Ads
Last updated
Endpoints
Paths are relative to https://fliok.com/api/v1. Each endpoint names the scope and the lowest key role it needs; a key without them gets 403 (see Authentication).
Meta ads made in Fliok: Click-to-WhatsApp ads, Instagram ads (a boosted post, or an ad that opens Instagram Direct), and one ad whose button opens WhatsApp or Instagram. Plus Facebook Lead Ads forms. Every change goes through the same code as the dashboard, with the same checks.
- Early access. Ads open for a workspace once Meta approves Fliok's ads permissions. Until then every
/adsendpoint answers404 not_available, as the dashboard does. Lead Ads are not behind this door. - Ads are always created paused. Nothing you create spends money until you publish it.
- Spending needs ads:spend. Publishing or resuming an ad, raising a budget, and approving a suggestion that moves budget need the
ads:spendscope. A key must be made with ads:spend named; no existing key has it, and no role gets it by default. Without it you get403 insufficient_scope. Pausing, archiving and lowering a budget need onlyads:write, so a key can always stop the spend. - The same confirmation as the dashboard. To publish or resume, send
confirmBudgetMinorequal to the ad's daily budget in minor units (paise, cents). To raise a budget, send it equal to the new budget. A mismatch is409 budget_mismatch. - Plan and add-ons. Creating, publishing and raising budgets need Automation & CRM on your plan (402 otherwise). Instagram ads need the Instagram add-on; ads that open either app also need the WhatsApp + Instagram ads add-on. Pausing and archiving work on any plan.
- Connecting is a browser flow. Connect the Facebook ad account in the dashboard.
GET /integrations/meta-adsgives itsconnectUrl;DELETE /integrations/meta-adsdisconnects it. - Not in the API. The AI parts (writing ad copy, AI plans and reviews, Autopilot settings) stay in the dashboard: they are billed from your AI wallet and set how Fliok's AI acts for you. Ad videos are uploaded in the dashboard; images go in by URL with
POST /ads/images.
Ads
22- GET
/ads/accountThe connected Facebook ad account and Page: status, currency, time zone, last sync, Meta's account status. Never the tokenScopeads:read· viewer role or higherNeeds an API key - GET
/adsEvery Meta ad made in Fliok, newest first. ?type=all|whatsapp|instagram|both. Each with kind, status, budget, audience and last resultsScopeads:read· viewer role or higherNeeds an API key - GET
/ads/{id}One ad of any kind: status, daily budget, audience, schedule, creative and last-synced resultsScopeads:read· viewer role or higherNeeds an API key - GET
/ads/analyticsThe Analytics tab: totals against the period before, a daily series, a row per ad and the funnel. ?period=7|28|90 or custom, ?channel=Scopeads:read· viewer role or higherNeeds an API key - GET
/ads/analytics/exportThe Analytics tab's per-ad rows as CSV, with the same query as GET /ads/analyticsScopeads:read· viewer role or higherNeeds an API key - GET
/ads/chargesWhat Meta says the ad account spent this month and last, and how it pays. Payment method for admin keys. ?fresh=1 asks Meta againScopeads:read· viewer role or higherNeeds an API key - GET
/ads/performanceMeta's figures for ?days=7|28 and the period before, joined with each ad's leads in Fliok. ?ad=<id> for one adScopeads:read· viewer role or higherNeeds an API key - POST
/ads/whatsappCreate a Click-to-WhatsApp ad, always paused, through the dashboard's create code. Needs the plan. Upload the image first with POST /ads/imagesScopeads:write· admin role or higherNeeds an API key - POST
/ads/instagramCreate an Instagram ad, always paused: boost one of your posts or open Instagram Direct. Needs the Instagram add-on and the planScopeads:write· admin role or higherNeeds an API key - POST
/ads/bothCreate one ad that opens WhatsApp or Instagram Direct, always paused. Needs the Instagram and WhatsApp + Instagram ads add-ons and the planScopeads:write· admin role or higherNeeds an API key - POST
/ads/{id}/statusPause or archive an ad (ads:write, any plan). Publish or resume it: also needs ads:spend, the plan and confirmBudgetMinor equal to its daily budgetScopeads:write· admin role or higherNeeds an API key - POST
/ads/{id}/budgetChange an ad's daily budget. Lowering needs ads:write; raising also needs ads:spend, the plan and confirmBudgetMinor equal to the new budgetScopeads:write· admin role or higherNeeds an API key - POST
/ads/refreshRe-read every ad's status and results from Meta now. At most once a minute; inside that, throttled is true and stored figures returnScopeads:write· admin role or higherNeeds an API key - POST
/ads/imagesFetch an ad image (JPG or PNG, up to 8 MB) from a public https URL and upload it to the ad account. Returns imageHash and imageKeyScopeads:write· admin role or higherNeeds an API key - GET
/ads/targetingMeta's audience search: ?type=place&q= for cities and regions (optional country) or ?type=interest&q=. Use the results when creating an adScopeads:read· admin role or higherNeeds an API key - GET
/ads/recommendationsFliok's open suggestions for your ads and the recent changes made to them, by a person or by Autopilot. ?ad=<id> for one adScopeads:read· viewer role or higherNeeds an API key - POST
/ads/recommendations/{id}/decisionApprove, dismiss or snooze a suggestion. Approving runs its stored change; approving a budget move also needs ads:spend and confirmBudgetMinorScopeads:write· admin role or higherNeeds an API key - GET
/lead-adsFacebook Lead Ads: the connected Page, whether Fliok gets its leads, and its forms with their questions and settings. Never a tokenScopeads:read· admin role or higherNeeds an API key - POST
/lead-ads/subscribeSubscribe Fliok to the connected Page's leads, so each new lead arrives as a contact and a CRM lead. No bodyScopeads:write· admin role or higherNeeds an API key - GET
/lead-ads/forms/{formId}One Lead Ads form: its questions, how answers map to contact fields, the stage, owner, follow-up and last importScopeads:read· admin role or higherNeeds an API key - PUT
/lead-ads/forms/{formId}Save how a form's answers become a contact and lead: field map, stage, owner and the WhatsApp follow-up templateScopeads:write· admin role or higherNeeds an API key - POST
/lead-ads/forms/{formId}/importImport a form's leads from Meta's last 90 days as contacts and CRM leads. Leads already in Fliok are skippedScopeads:write· admin role or higherNeeds an API key
Request bodies
What these endpoints take, field by field. A body that doesn’t match answers 400 naming the field.
POST /ads/whatsapp
JSON body. A Click-to-WhatsApp ad, created paused. Optional Idempotency-Key header.
| Field | Type | What it is |
|---|---|---|
namerequired | string | The ad’s name, 1–80 characters. |
countriesrequired | string[] | Two-letter country codes, 1–25 (e.g. ["IN"]). |
ageMinrequired | integer | Youngest age shown the ad, 18–65. |
ageMaxrequired | integer | Oldest age, 18–65, not below ageMin. |
genderrequired | string | all, men or women. |
advantageAudiencerequired | boolean | Let Meta widen the audience when it finds more people likely to reply. |
locations | object[] | Cities or regions from GET /ads/targeting?type=place: { type, key, name, countryCode, region }. Each must be in one of the countries. |
interests | object[] | Interests from GET /ads/targeting?type=interest: { id, name }. |
dailyBudgetrequired | string | The daily budget in the ad account’s currency, as you would type it ("500"). At least Meta’s minimum. |
startTime | string | ISO 8601 with a zone. Leave out to start when published. |
endTime | string | ISO 8601 with a zone, more than 24 hours after the start. |
primaryTextrequired | string | The ad’s text, up to 1,000 characters. |
headlinerequired | string | Up to 100 characters. |
greetingrequired | string | The message customers see when WhatsApp opens, up to 300 characters. |
prefillrequired | string | The message typed for the customer, up to 150 characters. |
format | string | image (default), video, carousel or catalog, where Meta allows it for this kind of ad. |
imageHash | string | From POST /ads/images. Needed for an image ad. |
imageKey | string | From POST /ads/images, with imageHash. |
video | object | A video uploaded in the dashboard. Needed for a video ad. |
cards | object[] | A carousel’s 2–10 cards, each with its own image. |
leadTag | string | A tag put on every contact this ad brings in, up to 40 characters. |
planId | string | The AI plan this ad was made from, if any (marked used). |
recommendationId | string | The suggestion this ad answers, if any (closed when it is made). |
Returns 201 { ok, ad } — the ad, paused. Publish it with POST /ads/{id}/status.
POST /ads/instagram
JSON body. An Instagram ad, created paused. Needs the Instagram add-on. Optional Idempotency-Key header.
| Field | Type | What it is |
|---|---|---|
typerequired | string | boost_post (one of your own posts) or instagram_direct (opens a chat in Instagram Direct). |
namerequired | string | The ad’s name, 1–80 characters. |
countriesrequired | string[] | Two-letter country codes, 1–25 (e.g. ["IN"]). |
ageMinrequired | integer | Youngest age shown the ad, 18–65. |
ageMaxrequired | integer | Oldest age, 18–65, not below ageMin. |
genderrequired | string | all, men or women. |
advantageAudiencerequired | boolean | Let Meta widen the audience when it finds more people likely to reply. |
locations | object[] | Cities or regions from GET /ads/targeting?type=place: { type, key, name, countryCode, region }. Each must be in one of the countries. |
interests | object[] | Interests from GET /ads/targeting?type=interest: { id, name }. |
dailyBudgetrequired | string | The daily budget in the ad account’s currency, as you would type it ("500"). At least Meta’s minimum. |
startTime | string | ISO 8601 with a zone. Leave out to start when published. |
endTime | string | ISO 8601 with a zone, more than 24 hours after the start. |
mediaId | string | boost_post: the post’s id, from GET /instagram/posts/recent. |
goal | string | boost_post: engagement or traffic. |
link | string | boost_post with goal traffic: the https:// address people go to. |
callToAction | string | boost_post: the button, from Meta’s list (e.g. LEARN_MORE). |
primaryText | string | instagram_direct: the ad’s text, up to 1,000 characters. |
greeting | string | instagram_direct: what customers see when the chat opens, up to 300 characters. |
iceBreakers | string[] | instagram_direct: 1–3 questions customers can tap, up to 80 characters each. |
format | string | instagram_direct: image (default), video, carousel or catalog, where Meta allows it for this kind of ad. |
imageHash | string | instagram_direct: From POST /ads/images. Needed for an image ad. |
imageKey | string | instagram_direct: From POST /ads/images, with imageHash. |
video | object | instagram_direct: A video uploaded in the dashboard. Needed for a video ad. |
cards | object[] | instagram_direct: A carousel’s 2–10 cards, each with its own image. |
planId | string | The AI plan this ad was made from, if any (marked used). |
recommendationId | string | The suggestion this ad answers, if any (closed when it is made). |
Returns 201 { ok, ad } — the ad, paused. Publish it with POST /ads/{id}/status.
POST /ads/both
JSON body. One ad whose button opens WhatsApp or Instagram Direct, created paused. Needs the Instagram and WhatsApp + Instagram ads add-ons. Optional Idempotency-Key header.
| Field | Type | What it is |
|---|---|---|
namerequired | string | The ad’s name, 1–80 characters. |
countriesrequired | string[] | Two-letter country codes, 1–25 (e.g. ["IN"]). |
ageMinrequired | integer | Youngest age shown the ad, 18–65. |
ageMaxrequired | integer | Oldest age, 18–65, not below ageMin. |
genderrequired | string | all, men or women. |
advantageAudiencerequired | boolean | Let Meta widen the audience when it finds more people likely to reply. |
locations | object[] | Cities or regions from GET /ads/targeting?type=place: { type, key, name, countryCode, region }. Each must be in one of the countries. |
interests | object[] | Interests from GET /ads/targeting?type=interest: { id, name }. |
dailyBudgetrequired | string | The daily budget in the ad account’s currency, as you would type it ("500"). At least Meta’s minimum. |
startTime | string | ISO 8601 with a zone. Leave out to start when published. |
endTime | string | ISO 8601 with a zone, more than 24 hours after the start. |
primaryTextrequired | string | The ad’s text, up to 1,000 characters. |
headlinerequired | string | Up to 100 characters. |
greetingrequired | string | What customers see when the chat opens, up to 300 characters. |
iceBreakersrequired | string[] | 1–3 questions customers can tap, up to 80 characters each. |
format | string | image (default), video, carousel or catalog, where Meta allows it for this kind of ad. |
imageHash | string | From POST /ads/images. Needed for an image ad. |
imageKey | string | From POST /ads/images, with imageHash. |
video | object | A video uploaded in the dashboard. Needed for a video ad. |
cards | object[] | A carousel’s 2–10 cards, each with its own image. |
planId | string | The AI plan this ad was made from, if any (marked used). |
recommendationId | string | The suggestion this ad answers, if any (closed when it is made). |
Returns 201 { ok, ad } — the ad, paused. Publish it with POST /ads/{id}/status.
POST /ads/{id}/status
JSON body. pause and archive need ads:write. publish and resume also need ads:spend and the plan. An archived ad can’t be turned back on. Optional Idempotency-Key header.
| Field | Type | What it is |
|---|---|---|
actionrequired | string | pause, archive, publish or resume. resume is for an ad that has run before. |
confirmBudgetMinor | integer | publish and resume: the ad’s daily budget in minor units, exactly as GET /ads/{id} shows it (dailyBudgetMinor). |
Returns { ok, ad } — the ad with the status Meta reports.
POST /ads/{id}/budget
JSON body. Lowering needs ads:write; raising also needs ads:spend and the plan. Up to 12 changes an hour. Optional Idempotency-Key header.
| Field | Type | What it is |
|---|---|---|
dailyBudgetrequired | string | The new daily budget in the ad’s currency, as you would type it ("650"). |
confirmBudgetMinor | integer | When raising: the new budget in minor units (65000 for ₹650). |
Returns { ok, actionId, beforeMinor, afterMinor }
POST /ads/images
JSON body. Fliok downloads the image from a public https:// address (private and internal addresses are refused) and uploads it to your ad account.
| Field | Type | What it is |
|---|---|---|
urlrequired | string | The image’s https:// address. JPG or PNG, up to 8 MB. |
use | string | single (default), card (a carousel card) or cover (a video’s cover). |
ratio | number | For cards: the width-to-height shape all cards share (1 for square). |
Returns { ok, imageHash, imageKey, width, height } — pass imageHash and imageKey to a create call.
POST /ads/recommendations/{id}/decision
JSON body. Approving runs the suggestion’s own stored change. A budget move also needs ads:spend. Optional Idempotency-Key header.
| Field | Type | What it is |
|---|---|---|
actionrequired | string | approve, dismiss or snooze. |
confirmBudgetMinor | integer | Approving a budget move: the raised ad’s new budget in minor units, as the suggestion shows it. |
Returns { ok, status, actionId, recommendations }, or { ok, status: "open_form", href } for a “make a new ad” suggestion (finish it in the dashboard).
PUT /lead-ads/forms/{formId}
JSON body. The whole setting for one form; send every field.
| Field | Type | What it is |
|---|---|---|
pageIdrequired | string | The connected Page’s id, from GET /lead-ads. |
formName | string | The form’s name, for your records. |
fieldMaprequired | object | Question key → contact field (as GET /lead-ads lists in contactFields). Up to 100. |
stageIdrequired | string | The CRM stage new leads start in, or null for the first stage. |
ownerIdrequired | string | The member new leads are given to, or null. |
followUprequired | object | { enabled, templateName, language, consentQuestion }: an approved WhatsApp template sent to new leads who agreed in the form. |
Returns { ok: true }
Examples
Create a Click-to-WhatsApp ad (paused)
curl -X POST "https://fliok.com/api/v1/ads/images" \
-H "Authorization: Bearer wa_YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"url":"https://cdn.ashasarees.in/diwali.jpg"}'
curl -X POST "https://fliok.com/api/v1/ads/whatsapp" \
-H "Authorization: Bearer wa_YOUR_API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: diwali-ad-1" \
-d '{"name":"Diwali sarees","countries":["IN"],"ageMin":25,"ageMax":55,"gender":"women",
"advantageAudience":true,"dailyBudget":"500","primaryText":"New Banarasi silk, 20% off till Diwali.",
"headline":"Diwali sale","greeting":"Hi! Which saree caught your eye?","prefill":"Show me the Diwali sarees",
"format":"image","imageHash":"7d1c0f9e2b4a","imageKey":"ad-images/7c1e2d4a/1080x1080-3f2a.jpg"}'// 201 Created — always paused
{
"ok": true,
"ad": {
"kind": "whatsapp",
"id": "5b0e8c4a-2f71-4d0b-9a63-0c1e7f2d9b18",
"name": "Diwali sarees",
"status": "paused",
"effectiveStatus": "PAUSED",
"dailyBudgetMinor": 50000,
"currency": "INR",
"countries": ["IN"],
"ageMin": 25,
"ageMax": 55,
"genders": "women",
"headline": "Diwali sale",
"format": "image",
"publishedAt": null,
"insights": { "spend": 0, "impressions": 0, "reach": 0, "clicks": 0, "conversationsStarted": 0, "costPerConversation": null },
"leads": 0,
"createdAt": "2026-10-29T06:30:00.000Z"
}
}Publish it (needs ads:spend), then pause it
curl -X POST "https://fliok.com/api/v1/ads/5b0e8c4a-2f71-4d0b-9a63-0c1e7f2d9b18/status" \
-H "Authorization: Bearer wa_YOUR_SPEND_KEY" -H "Content-Type: application/json" \
-d '{"action":"publish","confirmBudgetMinor":50000}'
curl -X POST "https://fliok.com/api/v1/ads/5b0e8c4a-2f71-4d0b-9a63-0c1e7f2d9b18/status" \
-H "Authorization: Bearer wa_YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"action":"pause"}'// 200 OK
{ "ok": true, "ad": { "kind": "whatsapp", "id": "5b0e8c4a-…", "status": "active", "effectiveStatus": "IN_PROCESS", "publishedAt": "2026-10-29T06:41:12.000Z", … } }
// 403 — a key without ads:spend asked to publish
{ "error": "insufficient_scope", "required": "ads:spend", "message": "This API key doesn't have the ads:spend scope. …" }Read your ads and their results
curl "https://fliok.com/api/v1/ads?type=whatsapp" \
-H "Authorization: Bearer wa_YOUR_API_KEY"
curl "https://fliok.com/api/v1/ads/analytics?period=28" \
-H "Authorization: Bearer wa_YOUR_API_KEY"// 200 OK
{
"ads": [
{
"kind": "whatsapp",
"id": "c2d7a1f0-6b3e-4e59-8a14-7f0b9d3c5e21",
"name": "Festive drapes",
"status": "active",
"effectiveStatus": "ACTIVE",
"dailyBudgetMinor": 30000,
"currency": "INR",
"insights": { "spend": 4125, "impressions": 48210, "reach": 31877, "clicks": 1290, "conversationsStarted": 212, "costPerConversation": 19.46 },
"leads": 64,
"insightsSyncedAt": "2026-10-29T06:00:04.000Z"
}
]
}Import a Lead Ads form’s leads
curl "https://fliok.com/api/v1/lead-ads" \
-H "Authorization: Bearer wa_YOUR_ADMIN_KEY"
curl -X POST "https://fliok.com/api/v1/lead-ads/forms/1029384756/import" \
-H "Authorization: Bearer wa_YOUR_ADMIN_KEY"// 200 OK — leads already in Fliok are skipped
{ "ok": true, "imported": 37, "skipped": 4 }Rate limits
The buckets these endpoints use. Burst is how many requests you can make back-to-back from a full bucket; sustained is how fast it refills.
| Bucket | Endpoints | Burst | Sustained |
|---|---|---|---|
| Reads | /messages · /conversations · /contacts · /templates · /phone-numbers · GET /webhooks · Instagram, LinkedIn, ads and catalog reads · GET /payment-requests/{id} | 600 | 60 / sec |
| Ad budgets | POST /ads/{id}/budget | 12 | 12 / hour |
| Ad images | POST /ads/images | 30 | 1 / 2 sec |
| Ad suggestions | POST /ads/recommendations/{id}/decision | 30 | 30 / 10 min |
| Ad charges from Meta | GET /ads/charges?fresh=1 | 3 | 1 / min |
| Lead Ads import | POST /lead-ads/forms/{formId}/import | 3 | 3 / 10 min |
Every limit, the RateLimit-* headers and what to do on a 429: Rate limits.