Meta Ads
Create, boost and measure Facebook and Instagram ads with the Zernio API, using classic or business login.
Create, boost and measure Facebook and Instagram ads with POST /v1/ads/create and a connected Meta Ads token. This section covers the campaign tree (campaigns, ad sets, ads, creatives), targeting and audiences, the ad types that need their own request shape, measurement (insights, pixels, conversions) and the reads you need around a running ad account. You need a connection with the ads scopes and an ad account (act_<n>) it can act on. Use an existing ad account or create one in the customer's own business portfolio. Ads are included with usage-based billing; a legacy plan without ads gets a 403 on every call here.
Quick reference
| Property | Value |
|---|---|
| Hierarchy | Campaign, ad set, ad (created in one call, or level by level) |
| Goals | 11 values for goal: engagement, traffic, awareness, video_views, lead_generation, lead_conversion, conversions, app_promotion, catalog_sales, page_likes, page_visits |
| Buying types | AUCTION (default) and RESERVED (see Reach and Frequency) |
| Budget levels | CBO (campaign) and ABO (ad set); reads tell you which one a campaign uses |
| Bid strategies | LOWEST_COST_WITHOUT_CAP, LOWEST_COST_WITH_BID_CAP, COST_CAP, LOWEST_COST_WITH_MIN_ROAS |
| Creative formats | Single image, video, carousel (2 to 10 cards), dynamic creative, per-placement assets, catalog, instant form, messaging, call (see Creatives) |
| Call to action values | The callToAction enum, plus VIEW_INSTAGRAM_PROFILE and the 3 messaging CTAs on boost (see the reference) |
| Special ad categories | HOUSING, EMPLOYMENT, CREDIT, ISSUES_ELECTIONS_POLITICS, FINANCIAL_PRODUCTS_SERVICES, ONLINE_GAMBLING_AND_GAMING |
| Image formats | JPEG, PNG (public URL or base64), max 30 MB |
| Video formats | MP4, MOV, max 4 GB |
| Ad previews | Yes, Meta iframe HTML for about 60 formats, before or after create (see Previews) |
| Dry-run validation | Yes, validateOnly: true validates the whole tree and creates nothing |
| Insights | Synced metrics on /v1/ads/tree, live Meta queries and async report runs (see Insights) |
| Audiences | Customer list, website, lookalike, engagement, saved targeting |
| Conversions | Yes, server-side events with hashed PII, dedup, Event Match Quality and Limited Data Use |
| Lead forms | Yes, forms plus leads by webhook or polling |
| Scheduling | Yes, ad set start and end dates, editable after launch |
| Analytics | Yes |
Before you start
Creating Meta ads needs 4 things on the Meta side: a Meta Business account with at least one ad account (act_<n>, existing or created through the API), a Facebook Page (ads publish as the Page, and Instagram placements need an Instagram professional account linked to that Page), the ads scopes on the connected account, and a pixel or dataset for any conversion-optimized goal (conversion campaigns).
An Instagram account connected with the default Instagram Login cannot run ads: that login never grants ads scopes. A facebook or metaads account must exist in the same profile, or ads calls on the Instagram accountId return 422 linked_account_required.
Meta reviews every ad. A successful create means the ad exists, not that it delivers: reviewStatus (in_review, approved, rejected, with_issues) is separate from the delivery status, and both appear on every node the tree returns.
Connect
Call GET /v1/connect/facebook/ads with profileId. Meta has two login options:
- Classic, the default (
loginMode=classic), uses the connected Facebook posting account and its token. An active parent withads_managementandads_readcan be reused without another OAuth round trip. - Facebook Login for Business (
loginMode=business, also accepted onGET /v1/connect/instagram/ads) creates an independentmetaadsconnection with a Business Integration System User token. No posting account is created or required. Open the returnedauthUrlin a browser to complete consent.
Meta needs a Page as the actor for ad creatives, so select a Page for either path when creating ads. Business login refuses a grant with no Page (no_pages_granted). Connections and billing explains which accounts each path creates and which ones bill. The connecting accounts guide covers Page selection, and the classic ads-only flow covers headless connections and scoping sync to specific ad accounts.
OAuth scopes
| Scope | What it enables |
|---|---|
ads_management | Create and manage campaigns, ad sets, ads, creatives, audiences, pixels and reservations |
ads_read | Read ad accounts and insights |
pages_manage_ads | Create, list and archive lead forms on the Page |
leads_retrieval | Read submitted leads from lead ads |
ads_management covers campaign management, Reach and Frequency and the Business Manager list. Creating an ad account also requires business_management and business admin access. Accounts connected before ads were enabled on the team do not carry these scopes: reconnect them, and read Account health to see what a connected account was granted. The general scope rule is on the connecting accounts guide.
Find your ad account id
Ad-account operations use accountId (the connected posting or Meta Ads account) and adAccountId (the Meta ad account). Call GET /v1/ads/accounts to list the ad accounts the connection can reach. Creating a new ad account instead takes an active metaads account as accountId and the owning businessId.
import Zernio from '@zernio/node';
const zernio = new Zernio();
const { data: adAccounts } = await zernio.adaccounts.listAdAccounts({
query: { accountId: '66b2e19d8c3f5a7e9d0b1c2d' }
});
const adAccountId = adAccounts.accounts[0].id;Response (200):
{
"accounts": [
{
"id": "act_1234567890",
"name": "Acme",
"currency": "USD",
"accountStatus": 1,
"timezoneName": "America/New_York",
"minimumDailyBudget": 1,
"selectable": true,
"unusableReason": null
}
]
}accounts[].id is the adAccountId for every other call. Budgets and bids are whole units of currency (75 is $75.00 on a USD account), never cents.
Create an ad account
POST /v1/ads/accounts creates a Meta ad account in the customer's own business portfolio. Discover portfolios with GET /v1/ads/businesses, then send accountId (an active metaads connection), businessId, name, currency and timezoneId. The connection needs business_management and business admin access. System-user tokens can return an empty businesses list; supply the known business ID in that case.
timezoneId is Meta's numeric timezone ID, not an IANA name. The reference links Meta's timezone list. Use the returned adAccountId with the existing ads endpoints.
The new account has no payment method. The customer must add one in Ads Manager before ads deliver; Zernio cannot add it through the API. Meta caps how many ad accounts a business can create and may require business verification. Closing an account does not guarantee more capacity, and an ad account can never truly be deleted once created.
This call is not idempotent and Zernio never automatically retries it. After a timeout or a 502 with details.creationStatus: "unknown", check the business in Ads Manager before creating again. A 201 with connectionUpdated: false still means the account exists: follow the returned recovery instructions to reconnect with its ID and the previous scoped IDs, instead of repeating the create.
Connections and billing
A Meta Ads connection is its own account, with platform: "metaads" and its own accountId. What else exists next to it depends on how it was connected:
| How it was connected | Accounts created | Where its Pages live |
|---|---|---|
Business login (loginMode=business) | One metaads account with parentAccountId: null. No Facebook account is created. | In the metaads account. GET /v1/accounts/{accountId}/facebook-page lists the granted Pages and selectedPageId (add refresh=true after granting more); PUT with selectedPageId changes the default Page. |
| Classic, from an existing Facebook account | One metaads account whose parentAccountId is that Facebook account. If the parent already has ads_management and ads_read, no OAuth runs. Pass accountId when the profile has several Facebook accounts. | In the parent Facebook account. |
| Classic, through OAuth with no Facebook account for the selected Page | The metaads account, plus a Facebook account for that Page with enabled: false. | In that hidden Facebook account. |
The hidden Facebook account is not shown in the dashboard, cannot be posted to (ACCOUNT_NOT_ENABLED_FOR_POSTING) and is not billed. GET /v1/accounts returns it with enabled: false. Connecting the same Page later through GET /v1/connect/facebook, as the same Facebook user, turns that row into a normal Facebook account with the same accountId, and from then on it bills.
A metaads account is for ads only. It cannot serve organic Facebook or Instagram Insights: the Page insights and Instagram account insights endpoints answer 400 INVALID_FIELD_VALUE for its accountId. For posting, the inbox and organic Insights on the same Page, connect it with GET /v1/connect/facebook and select-page (or connect the Instagram account). That creates a separate facebook account with its own accountId, billed on its own.
Billing counts every active account once across your team, metaads included:
- One
metaadsaccount counts once, however many ad accounts it reaches. Running ads also meter separately per active ad (Ads pricing). - A Facebook Page and an Instagram account connected for posting count once each. A business-login
metaadsaccount plus the same Page connected organically is 2 accounts. - Hidden (
enabled: false) and disconnected accounts do not count. - The first 2 accounts are free. Connecting another without a card on file answers
402withcode: "PAYMENT_REQUIRED"andreason: "free_tier_exceeded"; on an OAuth redirect it arrives aserror=payment_required. See Payment method required.
Pricing has the rates.
A Page is missing
The Page lists (GET /v1/connect/facebook/select-page and the Pages stored on a metaads account) show exactly the Pages Meta's token grants. Zernio does not filter out Pages already connected elsewhere. A missing Page was not ticked on Meta's Page step. Meta pre-fills the previous selection, so ask the user to connect again, click "Edit previous settings" and tick the Page. When no Page is granted at all, the redirect carries error=no_facebook_pages and an error_reason (pages_permission_declined, no_pages_granted or granted_pages_not_listed), described in If it fails.
Headless connections
In the classic headless flow, the connect_token on the redirect is valid for 15 minutes. Send it as X-Connect-Token to the list and select Page calls. Business login has no headless Page step and does not accept X-Connect-Token for one: pass pageId on GET /v1/connect/facebook/ads, or change the default Page afterwards with PUT /v1/accounts/{accountId}/facebook-page.
The campaign tree
Every Meta ad lives in a 3-level tree. Each level owns different settings, and that decides which endpoint you call:
| Level | Owns | Page |
|---|---|---|
| Campaign | Objective (goal), bid strategy, spend cap, the budget on CBO campaigns | Campaigns |
Ad set (adSetId) | Targeting, schedule, optimization goal, promotedObject, the budget on ABO campaigns | Ad sets |
| Ad | The creative: headline, body, media, call to action, link | Creatives |
You rarely build the tree level by level. POST /v1/ads/create creates all 3 in one call, POST /v1/ads/boost wraps an existing post the same way, and GET /v1/ads/tree reads the hierarchy back with rolled-up metrics. Which level holds the budget (CBO or ABO) decides which update endpoint accepts a budget change; the campaigns page explains budgetLevel.
Pages
Build the ads
Campaigns
Create the full tree in one call, campaign-only creates, bid strategy and reading the tree
Ad sets
Budget, status, delivery settings and the learning phase after launch
Creatives
Attach to an ad set, video, carousel, per-placement assets, Advantage+ enhancements, swap a creative
Creative library
Standalone creatives and the ad account image library
Previews
Render an ad or a draft creative as Meta renders it
Targeting
Geo, demographic and interest targeting, and how to look up ids
Audiences
Customer list, website, lookalike and engagement audiences
Boost a post
Turn a published post into an ad
Creative testing
N ads in one ad set to test variations
Conversion campaigns
Conversion goals and the promoted object
Catalog ads
Advantage+ catalog ads from a product set
Messaging and call ads
Ads that open WhatsApp, Messenger or Instagram Direct, or dial a number
Click-to-WhatsApp ads
The deprecated CTWA endpoint, kept for existing integrations
Lead forms
Create instant forms and receive leads
Reach and Frequency
Quote, reserve and buy fixed-price reserved campaigns
Measure what they did
Insights
Synced metrics, the ROAS and status fields, breakdowns, live Meta queries and report runs
Pixels
Create, share and read pixels through the tracking tags API
Conversions
Send server-side conversion events and read Event Match Quality
URL tracking tags
Read and set the click-URL parameters on an ad
Operate and look things up
Lifecycle
Dry run, pause, resume, duplicate and delete
Account reads
Change log, studies, finances, labels, budget schedules, businesses, Instagram identities, app promotion
Ad Library
Search the public ad archive
Ad comments
Read comments on ads, dark posts included
Reference
Enums, media limits, what you cannot do and Meta error codes
Common errors
3 errors are section-wide rather than endpoint-specific.
| Error | Cause | Fix |
|---|---|---|
403 { "error": "Ads add-on required" } | The team is on a legacy plan without ads, or the Meta token is missing the ads permissions | Ads are included with usage-based billing; otherwise reconnect the account so the consent screen requests the ads scopes |
403 ads_allowance_exceeded | The team has no payment method on file and has reached its 500 free live ads | Add a card to resume creating and resuming ads |
422 linked_account_required | The call names an Instagram account connected with Instagram Login, which never carries ads scopes | Connect a facebook or metaads account on the same profile and call with its accountId |
Everything below the platform boundary comes back as type: "platform_error" with Meta's own message and subcode in platformError. The reference lists the subcodes worth recognizing on sight.
Related
- Connecting accounts: the ads-only connect flow and Page selection.
- Facebook and Instagram: the posting accounts an ad runs as.
- Create standalone ad: every field of the create request.
- Webhooks for ads:
lead.receivedand ad status events. - Error handling: the envelope every
4xxuses, includingplatform_errorwith Meta's own code.
Sender IDs
Send one-way international SMS from a brand name like ZERNIO instead of a phone number, with POST /v1/sms/sender-ids and the regular send endpoint.
Campaigns
Create a Meta campaign, ad set and ad in one call, set the bid strategy and its value rule sets, and read the campaign tree back with rolled-up metrics.