Zernio
APIFor you
Zernio
APIFor you
Platforms

Meta Ads

Overview

Set up

Connect

Build

CampaignsAd setsCreativesCreative libraryPreviewsBoost a postLead Gen Forms

Target

TargetingCustom Audiences

Ad types

Creative testingConversion campaignsCatalog adsMessaging & call adsClick-to-WhatsApp adsReach & frequency

Measure

InsightsMeta PixelsConversions APIAd URL tracking tags

Operate

Duplicate & lifecycleAccount & ops readsAd LibraryAd comments

Reference

Enums, limits & error codes
Dashboard
OverviewPlatformsAPI ReferenceResources
Meta Ads

Meta Pixels

Create, read, rename and share a Meta Pixel, manage its users and custom conversions, read stats and diagnostics, and install it on Shopify or WordPress.


Use GET and POST /v1/accounts/{accountId}/tracking-tags to list an ads account's pixels and create new ones. Conversion campaigns, website audiences and server-side events all point at a pixel.

Before you start

Zernio exposes pixels under the platform-neutral tracking tags API, where kind is pixel on Meta.

It runs on the Meta ads account you already connected, so there is no extra consent screen. accountId is that ads account.

The act_<n> ad account ids come from GET /v1/ads/accounts.

Read the tag

GET /v1/accounts/{accountId}/tracking-tags lists every pixel the connected ads account can see.

Run it first. The pixel a campaign needs usually exists already, and the create below is not idempotent.

Add adAccountId to scope the list to one ad account. Omit it and each name carries the ad account it was discovered on. The list omits code.

import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: existing } = await zernio.trackingtags.listTrackingTags({
  path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d' },
  query: { adAccountId: 'act_1234567890' }
});

Response (200):

{
  "platform": "metaads",
  "tags": [
    {
      "id": "1729525464415281",
      "name": "Website pixel",
      "platform": "metaads",
      "kind": "pixel",
      "status": "active",
      "lastFiredTime": 1804150800,
      "installed": true,
      "creationTime": 1804064400,
      "ownerBusinessId": "1049283746152738",
      "ownerAdAccountId": "act_1234567890"
    }
  ]
}

GET /v1/accounts/{accountId}/tracking-tags/{tagId} returns one pixel with the same fields plus its install code.

Create a pixel

adAccountId and name are the only inputs. Meta owns the pixel through the Business Manager that owns the ad account.

const { data: created } = await zernio.trackingtags.createTrackingTag({
  path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d' },
  body: { adAccountId: 'act_1234567890', name: 'Website pixel' }
});

const pixelId = created.tag.id;

Response (201):

{
  "platform": "metaads",
  "tag": {
    "id": "1729525464415281",
    "name": "Website pixel",
    "platform": "metaads",
    "kind": "pixel",
    "status": "active",
    "code": "<script>!function(f,b,e,v,n,t,s){...}</script>",
    "lastFiredTime": null,
    "installed": false,
    "creationTime": 1804064400,
    "ownerBusinessId": "1049283746152738",
    "ownerAdAccountId": "act_1234567890"
  }
}

tag.id is the pixel id. Use it for:

  • promotedObject.pixelId on conversion campaigns
  • pixelId on a website audience
  • destinationId on the Conversions API

Creating a pixel does not install it. Put code on the site, or skip the snippet and send events server-side, which a new pixel accepts immediately.

installed is derived from lastFiredTime, so a null there means the pixel has never fired.

The create is not idempotent: every call makes another pixel. Never auto-retry one. On a 502, confirm with GET /v1/accounts/{accountId}/tracking-tags before sending it again.

A 400 means an invalid adAccountId, an ad account outside a Business Manager, or the per-business pixel cap.

Rename and update

PATCH /v1/accounts/{accountId}/tracking-tags/{tagId} updates a whitelist of fields. Send at least one of them:

  • name
  • enableAutomaticMatching (Meta's Advanced Matching)
  • automaticMatchingFields
  • firstPartyCookieStatus
  • dataUseSetting

automaticMatchingFields takes Meta's terse codes: em email, ph phone, fn first name, ln last name, ge gender, db date of birth, ct city, st state, zp ZIP, country and external_id.

curl -X PATCH "https://zernio.com/api/v1/accounts/66b2e19d8c3f5a7e9d0b1c2d/tracking-tags/1729525464415281" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Website pixel (renamed)", "enableAutomaticMatching": true }'

Response (200), the re-fetched tag:

{
  "platform": "metaads",
  "tag": {
    "id": "1729525464415281",
    "name": "Website pixel (renamed)",
    "kind": "pixel",
    "status": "active",
    "lastFiredTime": 1804150800,
    "installed": true
  }
}

There is no delete. Meta has no API for it: a DELETE on a pixel answers "Unsupported delete request".

To stop using a pixel, unshare it or disable it in Events Manager.

Share a pixel with another ad account

A pixel works only in the ad account it was created on until you share it. Sharing lets another account's campaigns and audiences use it.

await zernio.trackingtags.addTrackingTagSharedAccount({
  path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d', tagId: '1729525464415281' },
  body: { adAccountId: 'act_9876543210' }
});

Response (201):

{ "platform": "metaads", "ok": true }

GET on the same path returns sharedAccounts[] with each account's id, name and businessId. DELETE removes one.

No Business Manager. A pixel created on an ad account that no Business Manager owns comes back with ownerBusinessId: null. It cannot be shared at all, because Meta rejects the share. Claim the ad account into a Business Manager first.

These endpoints share pixels with ad accounts. People and partner businesses are under Users and partners.

Users and partners

A Business Manager pixel has people and system users with access, and partner businesses it is shared with.

CallDoes
GET .../tracking-tags/{tagId}/usersLists the users
POST .../tracking-tags/{tagId}/usersAssigns one
GET .../tracking-tags/{tagId}/partnersLists the partners
await zernio.trackingtags.assignTrackingTagUser({
  path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d', tagId: '1729525464415281' },
  body: { userId: '122101234567890123', tasks: ['ANALYZE', 'ADVERTISE'] }
});

Response (200):

{ "platform": "metaads", "userId": "122101234567890123", "tasks": ["ANALYZE", "ADVERTISE"] }
  • userId is the business-scoped user id from GET /v1/ads/businesses/users.
  • tasks are Meta's pixel tasks: AA_ANALYZE, ADVERTISE, ANALYZE, EDIT and UPLOAD. Anything else answers 400.
  • Assigning a user who already has access replaces their tasks.
  • The list returns users[] with id, name and tasks.
  • A pixel on a personal ad account has no business, so its list is empty and an assign answers 400.
  • Both need the business_management permission on the connecting Meta user, who must be an admin of the business that owns the pixel.
  • Without that permission the call answers 403 asking you to reconnect the Meta ads account.
  • partners[] (id, name) are the pixel's shared agencies, read only.

Removing a user is not possible through Meta's API. The Business SDK has no delete on the pixel's assigned users, and re-assigning with no tasks is refused.

Meta's DELETE answers "Unsupported delete request" (code 100, subcode 33). Zernio's DELETE .../users/{userId} answers 501. Remove the user in Business Settings, Data sources, Pixels.

Sharing with a new partner is not possible either: /{pixel}/agencies answers "(#3) Application does not have the capability to make this API call".

Custom conversions

A pixel's conversion events are its custom conversions. Manage them with GET, POST, PATCH and DELETE on /v1/accounts/{accountId}/tracking-tags/{tagId}/events.

Custom conversions live on the ad account. These calls read adAccountId (default: the pixel's owner ad account) and keep only the conversions that use this pixel.

FieldOn createOn update
nameRequiredYes
typeA Meta custom conversion type: PURCHASE, ADD_TO_CART, LEAD, COMPLETE_REGISTRATION, CONTENT_VIEW, INITIATED_CHECKOUT, SEARCH, SUBSCRIBE, OTHER and the rest of CustomConversion.CustomEventTypeNo
siteEventAlternative to type: purchase becomes PURCHASE on the Purchase event, page_view becomes OTHER on PageViewNo
urlContainsAdds a URL rule (the URL contains this text). Without a siteEvent it matches page loads, as Events Manager's URL-based conversions doNo
defaultValueThe conversion's default valueYes

The rule is built from those fields. The conversion matches the standard event behind siteEvent (or behind type), plus the URL condition when urlContains is set.

Meta's update takes only the name and default value. Changing type, siteEvent or urlContains answers 400, so create a new conversion instead.

const { data } = await zernio.trackingtags.createTrackingTagEvent({
  path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d', tagId: '1729525464415281' },
  body: { name: 'Thank-you page purchase', siteEvent: 'purchase', urlContains: '/thank-you', defaultValue: 40 }
});

Response (201):

{
  "platform": "metaads",
  "event": {
    "id": "1183640298476512",
    "name": "Thank-you page purchase",
    "type": "PURCHASE",
    "siteEvent": "purchase",
    "status": "active",
    "defaultValue": 40,
    "urlContains": "/thank-you"
  }
}

The create is idempotent by name. An active conversion with the same name on this pixel is returned instead of a duplicate.

DELETE archives the conversion (state: "archived"), because Meta has no hard delete for custom conversions. It stays readable with status: "archived", and deleting an archived one again is a no-op.

Conversion campaigns can optimize for a custom conversion by its id.

Diagnostics

GET /v1/accounts/{accountId}/tracking-tags/{tagId}/diagnostics returns Meta's health checks for the pixel: the Dynamic Ads checks Events Manager shows.

Each entry in checks[] has key, title, description and result. When Meta suggests a fix, it also has an actionUrl into Events Manager.

{
  "platform": "metaads",
  "checks": [
    {
      "key": "pixel_content_ids_match_catalog",
      "title": "Content IDs match catalog",
      "description": "The content IDs your pixel sends should match the IDs in your catalog.",
      "result": "passed",
      "actionUrl": "https://business.facebook.com/events_manager2/..."
    }
  ]
}

Diagnostics exist on Meta only; other platforms answer 501.

Install on a Shopify store

POST /v1/accounts/{accountId}/tracking-tags/{tagId}/install puts the pixel on a connected Shopify store through Zernio's Shopify app pixel. There are no theme edits and no code on the merchant's side.

accountId is the Meta ads account that owns the pixel. storeAccountId is the Shopify account.

The store then sends these events, each with an event id:

Shopify eventMeta eventData
Page viewedPageView
Product viewedViewContentvariant as content_ids, value, currency
Added to cartAddToCartline total as value, currency, contents
Search submittedSearchsearch_string
Checkout startedInitiateCheckoutevery line, num_items, value, currency
Payment info submittedAddPaymentInfoas checkout started
Checkout completedPurchaseorder total as value, currency, contents, order_id

content_ids are Shopify variant ids.

The Purchase event id is shopify_order_{orderId} (the numeric order id). Send a server-side Purchase for the same order through the Conversions API with that eventId, and Meta deduplicates it.

Events follow the store's customer privacy settings. Where consent is required, the pixel fires only for visitors who allow marketing.

const { data } = await zernio.trackingtags.installTrackingTagOnStore({
  path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d', tagId: '1729525464415281' },
  body: { storeAccountId: '66b2e19d8c3f5a7e9d0b1c3e' }
});

Response (200):

{
  "platform": "metaads",
  "install": {
    "storeAccountId": "66b2e19d8c3f5a7e9d0b1c3e",
    "platform": "shopify",
    "shopDomain": "acme.myshopify.com",
    "tagPlatform": "metaads",
    "siteTagId": "1729525464415281",
    "installed": true,
    "installedTagId": "1729525464415281",
    "webPixelId": "2031620387",
    "tags": [
      { "platform": "metaads", "siteTagId": "1729525464415281" },
      { "platform": "tiktokads", "siteTagId": "D00IKHRC77UE0J0RTNHG" }
    ],
    "replacedTagId": null
  }
}

The call is idempotent. A store runs one Zernio web pixel that holds one tag per ad platform.

Installing a different Meta pixel replaces the previous one and names it in replacedTagId. The store's TikTok, Google, LinkedIn, Pinterest, X or OpenAI tags keep firing.

GET on the same path with ?storeAccountId= returns the install status. installedTagId is whichever Meta pixel the store fires now, and tags is every Zernio tag on the store.

DELETE with ?storeAccountId= removes the Meta pixel and leaves the other platforms' tags. Removing a pixel the store does not fire changes nothing and answers 409 invalid_resource_state.

Shopify tracking pixels has the event mapping for every platform.

When the store must re-approve

Stores connected before 28 September 2026 have not granted the pixel permissions (write_pixels, read_customer_events).

On those, all three calls answer 409 reconnect_required with a fresh consent link:

{
  "error": "The Shopify store acme.myshopify.com has not granted write_pixels, read_customer_events. Send the merchant to details.authUrl to re-approve the Zernio app (the account id stays the same), then retry.",
  "type": "permission_error",
  "code": "reconnect_required",
  "param": "storeAccountId",
  "platform": "shopify",
  "details": {
    "missingScopes": ["write_pixels", "read_customer_events"],
    "authUrl": "https://acme.myshopify.com/admin/oauth/authorize?client_id=..."
  }
}

Send the merchant to details.authUrl, or run the Shopify connect flow again with your own redirect_url. The store keeps its account id, so retry the install afterwards.

Custom-app stores. Stores connected with a custom-app Admin token get the same answer. A pixel belongs to the app that installs it, so those stores must connect through the Zernio app.

Install on a WordPress site

The same endpoint puts the pixel on a connected WordPress site (WordPress.com or self-hosted). Pass the WordPress account as storeAccountId.

Zernio adds a Custom HTML widget to a widget area of the active theme. The widget holds the Meta pixel base code (fbevents.js, init with your pixel id, PageView).

Zernio then reads the widget back to confirm WordPress kept the <script> tag.

It picks a footer area when the theme has one, otherwise the first active area. Pass sidebarId to choose.

const { data } = await zernio.trackingtags.installTrackingTagOnStore({
  path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d', tagId: '1729525464415281' },
  body: { storeAccountId: '66b2e19d8c3f5a7e9d0b1c4f' }
});

Response (200):

{
  "platform": "metaads",
  "install": {
    "storeAccountId": "66b2e19d8c3f5a7e9d0b1c4f",
    "platform": "wordpress",
    "siteUrl": "https://example.com",
    "tagPlatform": "metaads",
    "siteTagId": "1729525464415281",
    "installed": true,
    "method": "wordpress_widget",
    "widgetId": "custom_html-2",
    "sidebarId": "sidebar-1",
    "sidebarName": "Footer",
    "created": true,
    "homepageCheck": "found"
  }
}

The widget carries a Zernio marker, so the call is idempotent per pixel. Calling it again updates (or moves) the same widget and returns created: false.

Several pixels can run on one site, one widget each. Pixel code you pasted by hand is never touched.

homepageCheck reports whether the pixel appears in the homepage HTML:

  • found
  • not_found
  • unreachable
  • skipped, with verifyHomepage: false

not_found can simply be a page cache that has not refreshed yet. The widget read-back is the authoritative check.

GET with ?storeAccountId= returns the install status plus:

  • a read-only preflight (ready, reason, and the theme's widget areas in sidebars), so you can check a site before installing
  • tags, every Zernio widget on the site with its platform and whether it is active

DELETE with ?storeAccountId= removes every widget Zernio created for the pixel and reports removed.

When WordPress cannot run the pixel

WordPress keeps a <script> tag only for users with the unfiltered_html capability.

When the site cannot run the pixel, Zernio removes anything it wrote. It answers 422 tracking_tag_install_blocked with the cause in details.reason:

details.reasonCause
insufficient_permissionsThe connected user is not an Administrator (lacks edit_theme_options).
wordpress_com_planWordPress.com strips scripts on plans without plugin support.
scripts_strippedThe site removed the script: a multisite admin who is not a Super Admin, or DISALLOW_UNFILTERED_HTML is set.
no_widget_areasThe theme has no widget areas (block themes such as Twenty Twenty-Five use the Site Editor).
widgets_api_unavailableThe site has no widgets REST API (WordPress older than 5.8, or disabled by a security plugin).
{
  "error": "This WordPress.com plan strips <script> tags from site content. On a Business or Commerce plan, install Meta's official \"Meta pixel for WordPress\" plugin (official-facebook-pixel) instead.",
  "type": "invalid_request_error",
  "code": "tracking_tag_install_blocked",
  "details": { "reason": "wordpress_com_plan" }
}

In each case the site owner can install Meta's official Meta pixel for WordPress plugin instead, or on a block theme add the pixel code to the footer template part with a Custom HTML block.

Firing stats

GET /v1/accounts/{accountId}/tracking-tags/{tagId}/stats returns aggregated event counts between startTime and endTime (Unix seconds).

const { data: stats } = await zernio.trackingtags.getTrackingTagStats({
  path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d', tagId: '1729525464415281' },
  query: { aggregation: 'event', startTime: 1809216000, endTime: 1809475200 }
});

Response (200):

{
  "platform": "metaads",
  "stats": {
    "aggregation": "event",
    "startTime": 1809216000,
    "endTime": 1809475200,
    "rows": [
      { "aggregation": "event", "data": [{ "value": 3 }] }
    ]
  }
}

Zernio echoes the query back and passes each row through untouched. A row holds Meta's own AdsPixelStatsResult for the aggregation you asked for.

Read the keys you get rather than coding against a fixed shape.

aggregation defaults to event. It also accepts host, url, url_by_rule, pixel_fire, device_type, device_os, browser_type, had_pii, custom_data_field, match_keys, event_source, event_detection_method, event_processing_results, event_total_counts and event_value_count.

Both time bounds are optional. Omit them for Meta's own default window.

If it fails

A 403 means the connection has no ads access:

{
  "error": "Ads add-on required"
}

Ads are included with usage-based billing. So this is either a legacy plan without ads or a Meta token missing the ads permissions.

Reconnect the account (scopes).

A 501 platform_not_supported means the operation does not exist on the platform of the account you called. The capability matrix lists what every platform supports.

Related

  • Pixels and tracking tags: the same API on TikTok, Google, LinkedIn, Pinterest, X and OpenAI.
  • Conversion campaigns: the promotedObject.pixelId a pixel feeds.
  • Conversions: send server-side events to the same pixel.
  • Audiences: build a retargeting audience off it.
  • URL tracking tags: the click-URL parameters, which are a different thing.
  • Create tracking tag and Tag stats: every field.
Was this page helpful?

Insights

Read Meta ad performance: rolled-up metrics, demographic and placement breakdowns, live Graph queries and async report runs.

Conversions API

Send server-side conversion events to a Meta pixel with POST /v1/ads/conversions, dedupe them against the browser pixel, and read Event Match Quality back.

On this page

Before you startRead the tagCreate a pixelRename and updateShare a pixel with another ad accountUsers and partnersCustom conversionsDiagnosticsInstall on a Shopify storeWhen the store must re-approveInstall on a WordPress siteWhen WordPress cannot run the pixelFiring statsIf it failsRelated