Connect
Connect a Meta Ads account with the Zernio API, every step with code.
Choose a login mode
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. The classic ads-only flow covers headless connections and scoping sync to specific ad accounts.
OAuth scopes
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 two ids:
accountId: the connected posting or Meta Ads accountadAccountId: 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. It keeps 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 show exactly the Pages Meta's token grants. That covers GET /v1/connect/facebook/select-page and the Pages stored on a metaads account.
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. 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_declinedno_pages_grantedgranted_pages_not_listed
Each is 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.