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.pixelIdon conversion campaignspixelIdon a website audiencedestinationIdon 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:
nameenableAutomaticMatching(Meta's Advanced Matching)automaticMatchingFieldsfirstPartyCookieStatusdataUseSetting
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.
| Call | Does |
|---|---|
GET .../tracking-tags/{tagId}/users | Lists the users |
POST .../tracking-tags/{tagId}/users | Assigns one |
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"] }userIdis the business-scoped user id fromGET /v1/ads/businesses/users.tasksare Meta's pixel tasks:AA_ANALYZE,ADVERTISE,ANALYZE,EDITandUPLOAD. Anything else answers400.- Assigning a user who already has access replaces their tasks.
- The list returns
users[]withid,nameandtasks. - A pixel on a personal ad account has no business, so its list is empty and an assign answers
400. - Both need the
business_managementpermission on the connecting Meta user, who must be an admin of the business that owns the pixel. - Without that permission the call answers
403asking 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.
| Field | On create | On update |
|---|---|---|
name | Required | Yes |
type | A Meta custom conversion type: PURCHASE, ADD_TO_CART, LEAD, COMPLETE_REGISTRATION, CONTENT_VIEW, INITIATED_CHECKOUT, SEARCH, SUBSCRIBE, OTHER and the rest of CustomConversion.CustomEventType | No |
siteEvent | Alternative to type: purchase becomes PURCHASE on the Purchase event, page_view becomes OTHER on PageView | No |
urlContains | Adds a URL rule (the URL contains this text). Without a siteEvent it matches page loads, as Events Manager's URL-based conversions do | No |
defaultValue | The conversion's default value | Yes |
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 event | Meta event | Data |
|---|---|---|
| Page viewed | PageView | |
| Product viewed | ViewContent | variant as content_ids, value, currency |
| Added to cart | AddToCart | line total as value, currency, contents |
| Search submitted | Search | search_string |
| Checkout started | InitiateCheckout | every line, num_items, value, currency |
| Payment info submitted | AddPaymentInfo | as checkout started |
| Checkout completed | Purchase | order 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:
foundnot_foundunreachableskipped, withverifyHomepage: 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 insidebars), so you can check a site before installing tags, every Zernio widget on the site with its platform and whether it isactive
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.reason | Cause |
|---|---|
insufficient_permissions | The connected user is not an Administrator (lacks edit_theme_options). |
wordpress_com_plan | WordPress.com strips scripts on plans without plugin support. |
scripts_stripped | The site removed the script: a multisite admin who is not a Super Admin, or DISALLOW_UNFILTERED_HTML is set. |
no_widget_areas | The theme has no widget areas (block themes such as Twenty Twenty-Five use the Site Editor). |
widgets_api_unavailable | The 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.pixelIda 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.
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.