Zernio
Zernio
PlatformsMeta Ads

Build

CampaignsAd SetsCreativesCreative LibraryPreviews

Target

TargetingCustom Audiences

Ad types

Boost a PostCreative TestingConversion CampaignsCatalog AdsMessaging & Call AdsClick-to-WhatsApp AdsLead Gen FormsReach & Frequency

Measure

InsightsMeta PixelsConversions APIAd URL Tracking Tags

Operate

Duplicate & LifecycleAccount & Ops ReadsAd LibraryAd CommentsEnums, Limits & Error Codes
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
Meta Ads

Meta Pixels

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


List the pixels an ads account already has, and create the one your conversion campaigns, website audiences and server-side events point at, with GET and POST /v1/accounts/{accountId}/tracking-tags. 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.

List the pixels you have

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 for promotedObject.pixelId on conversion campaigns, for pixelId on a website audience and for 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, at least one of them: name, enableAutomaticMatching (Meta's Advanced Matching), automaticMatchingFields, firstPartyCookieStatus and 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"), so stop using a pixel by unsharing it or disabling 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, which is what 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, and DELETE removes one. A pixel created on an ad account that no Business Manager owns comes back with ownerBusinessId: null and 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. GET .../tracking-tags/{tagId}/users lists the users, POST on the same path assigns one, and GET .../tracking-tags/{tagId}/partners lists 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 it the call answers 403 asking you to reconnect the Meta ads account.
  • Removing a user is not possible through Meta's API: the Business SDK has no delete on the pixel's assigned users, DELETE answers "Unsupported delete request" (code 100, subcode 33), and re-assigning with no tasks is refused. DELETE .../users/{userId} answers 501; remove the user in Business Settings, Data sources, Pixels.
  • partners[] (id, name) are the pixel's shared agencies, read only. Sharing with a new partner is not possible: /{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: GET, POST, PATCH and DELETE on /v1/accounts/{accountId}/tracking-tags/{tagId}/events. Custom conversions live on the ad account, so 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, so changing type, siteEvent or urlContains answers 400: 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) as checks[], each with key, title, description, result and, when Meta suggests a fix, 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: no theme edits and no code on the merchant's side. accountId is the Meta ads account that owns the pixel and 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), so a server-side Purchase you send for the same order through the Conversions API with that eventId is deduplicated by Meta. 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, so installing a different Meta pixel replaces the previous one and names it in replacedTagId, while 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, tags every Zernio tag on the store), and 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. 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 with the Meta pixel base code (fbevents.js, init with your pixel id, PageView) to a widget area of the active theme, 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, and pixel code you pasted by hand is never touched. homepageCheck reports whether the pixel appears in the homepage HTML (found, not_found, unreachable, or 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, and 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 and 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, so what a row holds is 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 and 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.

Common errors

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

List the pixels you haveCreate 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 statsCommon errorsRelated