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

Custom Audiences

Create Meta customer list, website, lookalike, engagement and saved targeting audiences with POST /v1/ads/audiences, and fill a customer list with hashed uploads.


Create an audience with POST /v1/ads/audiences and a type, then target it from a create call.

Before you start

You need accountId and adAccountId.

Creates are not idempotent, so never auto-retry one.

Create an audience

Customer lists are filled with POST /v1/ads/audiences/{audienceId}/users. Every other type fills itself from the pixel, source or seed you point it at.

typeBuilt fromFields
customer_listEmails and phones you upload, SHA-256 hashed server-siderows through /users, optional customerFileSource
websitePixel visitorspixelId, retentionDays (at most 180), optional urlContains or a raw Meta rule
lookalikeAn existing audience as seedsourceAudienceId, country, ratio (0.01 to 0.2)
meta_engagementPeople who engaged with a Page, an Instagram account or a videosee engagement audiences
saved_targetingA stored targeting spec, no memberssee saved targeting

Create a customer list

This is the list every integrator builds first: an audience filled from your own records. Create it empty, then upload the rows.

import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: created } = await zernio.adaudiences.createAdAudience({
  body: {
    accountId: '66b2e19d8c3f5a7e9d0b1c2d',
    adAccountId: 'act_1234567890',
    type: 'customer_list',
    name: 'US customers'
  }
});

const audienceId = created.audience.id;

Response (201):

{
  "audience": {
    "id": "66e5b2c3d4f5a6b7c8d9e0f1",
    "platformAudienceId": "6123456789",
    "name": "US customers",
    "type": "customer_list"
  },
  "message": "Audience created"
}

audience.id is the audienceId for the other endpoints here.

platformAudienceId is what audienceInclude and audienceExclude take in a targeting spec, and what a lookalike takes as its seed.

List them, with size and status:

GET /v1/ads/audiences?accountId=...&adAccountId=...

GET /v1/ads/audiences/{audienceId} adds fresh data from Meta.

customerFileSource declares where the rows came from and defaults to USER_PROVIDED_ONLY.

Add users to a customer list

Call POST /v1/ads/audiences/{audienceId}/users with plaintext email or phone per row, up to 10,000 per request. Zernio normalizes and SHA-256 hashes every value before it reaches Meta.

const { data: uploaded } = await zernio.adaudiences.addUsersToAdAudience({
  path: { audienceId: '66e5b2c3d4f5a6b7c8d9e0f1' },
  body: {
    users: [
      { email: 'user@example.com' },
      { phone: '+14155551234' }
    ]
  }
});

Response (200):

{ "message": "Users added", "numReceived": 2, "numInvalid": 0 }

numInvalid counts the rows Meta could not read. Each call adds to the audience rather than replacing it.

Remove users from a customer list

Call DELETE /v1/ads/audiences/{audienceId}/users with the same rows you uploaded, up to 10,000 per request. Zernio hashes them the same way, so a row removes the person it added.

const { data: removed } = await zernio.adaudiences.removeUsersFromAdAudience({
  path: { audienceId: '66e5b2c3d4f5a6b7c8d9e0f1' },
  body: {
    users: [{ email: 'user@example.com' }]
  }
});

Response (200):

{ "message": "Users removed from audience", "numReceived": 1, "numInvalid": 0, "invalidEntrySamples": {} }

Meta applies the removal asynchronously, so the audience size drops later. Removal is Meta only: other networks return a 422.

Lookalike audiences

type: "lookalike" needs a seed. Create it after the list above and pass that list's platformAudienceId as sourceAudienceId.

ratio is the share of the country's population to match, 0.01 (the closest 1%) to 0.2:

{
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "adAccountId": "act_1234567890",
  "type": "lookalike",
  "name": "LAL 1% of US customers",
  "sourceAudienceId": "6123456789",
  "country": "US",
  "ratio": 0.01
}

The response has the same shape as the create above, with type: "lookalike".

Meta builds the audience from the seed's matched members. Upload the rows before creating the lookalike, and check the seed's size on GET /v1/ads/audiences/{audienceId}.

Website audiences

type: "website" takes a pixelId and retentionDays.

Omit urlContains for every visitor of the pixel. Set it to narrow the audience to visitors of URLs containing that substring:

{
  "type": "website",
  "name": "Checkout visitors 30d",
  "pixelId": "1729525464415281",
  "retentionDays": 30,
  "urlContains": "/checkout"
}

Raw rules. A raw Meta rule replaces the one Zernio builds. It must be Meta's flexible rule shape, with event_sources, retention_seconds and filter on every entry of inclusions.rules.

A rule missing one is a 400 here, instead of Meta's Invalid rule JSON format (subcode 1713098).

The pre-2018 flat shapes are not accepted by Meta at all (subcode 1870029).

Engagement audiences

type: "meta_engagement" builds an audience of people who engaged with a Page, an Instagram professional account or a video:

const { data: engagers } = await zernio.adaudiences.createAdAudience({
  body: {
    accountId: '66b2e19d8c3f5a7e9d0b1c2d',
    adAccountId: 'act_1234567890',
    type: 'meta_engagement',
    name: 'Page engagers 90d',
    engagementSource: 'page',
    sourceId: '811889972008357',
    retentionDays: 90
  }
});

Response (201):

{
  "audience": {
    "id": "66e5b2c3d4f5a6b7c8d9e0f3",
    "platformAudienceId": "6123456791",
    "name": "Page engagers 90d",
    "type": "meta_engagement"
  },
  "message": "Audience created"
}
FieldValues
engagementSourcepage, instagram or video
sourceIdThe Page id, Instagram account id or video id
retentionDays1 to 365
event (optional)Defaults per source: page_engaged, ig_business_profile_all, video_watched
rule (optional)A raw Meta engagement rule, forwarded verbatim, for sources beyond the 3 built-in ones (events, canvas, lead forms)

The source must be eligible for engagement audiences: an active Page or Instagram account with tracked engagement, or a video with views.

An ineligible source returns Meta's subcode 1713151 ("Invalid Event Name") verbatim. That is Meta's eligibility check, not a wrong event value.

Meta uses 2 wire formats here:

  • a modern rule shape for Page and Instagram sources
  • a legacy flat one for video, which rejects the modern shape with subcode 1870049

Zernio picks the right one, so the request above is all there is.

LinkedIn's engagement type is a different audience with different fields, on LinkedIn Ads.

Saved targeting

type: "saved_targeting" stores a reusable targeting spec on Zernio, with no members and no adAccountId.

Reference it later as savedTargetingId on POST /v1/ads/create. Inline targeting fields merge on top of it.

{
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "type": "saved_targeting",
  "name": "US, 25 to 55, DevOps",
  "spec": {
    "countries": ["US"],
    "ageMin": 25,
    "ageMax": 55,
    "interests": [{ "id": "6003139266461", "name": "DevOps" }]
  }
}

Ads created from a saved targeting snapshot the spec at creation and are unaffected by later edits.

Rename an audience

PUT /v1/ads/audiences/{audienceId} updates name and description on platform audiences (uploaded, website, lookalike, engagement). The change is written to Meta first and mirrored locally.

Their rules are immutable, so spec returns a 400 on them. spec is editable on saved_targeting audiences only.

DELETE /v1/ads/audiences/{audienceId} removes the audience on Meta and locally.

If it fails

A 422 on /users means the audience is not a customer list:

{
  "error": "Audience is not a customer_list type or has no platform ID yet",
  "type": "invalid_request_error"
}

Only customer_list audiences take uploads. The other types fill themselves.

A 400 with type: "platform_error" carries Meta's subcode in platformError. The reference explains 1713151 and 1870049.

Related

  • Targeting: the fields a saved targeting spec holds and audienceInclude on a create.
  • Pixels: the pixelId a website audience reads from.
  • Create custom audience and Add users to audience and Remove users from audience: every field.
Was this page helpful?

Targeting

Target Meta ads by country, city, region, ZIP, age, gender and interests, and resolve Meta's opaque location and interest ids with GET /v1/ads/targeting/search.

Creative testing

Test Meta creative variations by creating N ads inside one ad set with the creatives[] shape on POST /v1/ads/create.

On this page

Before you startCreate an audienceCreate a customer listAdd users to a customer listRemove users from a customer listLookalike audiencesWebsite audiencesEngagement audiencesSaved targetingRename an audienceIf it failsRelated