---
title: "Create custom audience"
method: POST
path: "/v1/ads/audiences"
tags: ["Ad Audiences"]
---

# Create custom audience

`POST /v1/ads/audiences`

Create a custom audience. `customer_list` is supported on Meta, Google, X, LinkedIn, TikTok, and Pinterest;
`website` and `lookalike` are Meta-only; `company_list`, `engagement` and `website_retargeting` are LinkedIn-only.
`saved_targeting` stores a reusable TargetingSpec (no member upload, no adAccountId) that you reference later via
`savedTargetingId` on `POST /v1/ads/create`.

How the audience gets filled depends on the type:

- `customer_list` is created empty. Add members with `POST /v1/ads/audiences/{audienceId}/users`.
  On TikTok and Pinterest the audience is provisioned lazily on that first upload (until then its status is `pending`).
- `company_list` is filled AT CREATION from the `companies` array below, which is required. To change the list
  afterwards send the new full list to `POST /v1/ads/audiences/{audienceId}/companies` (a replace, not a merge).
  The `/users` endpoint rejects these audiences with a 422.
- `website`, `website_retargeting`, `engagement`, `meta_engagement` and `lookalike` fill themselves from the pixel,
  engagement source or seed audience you point them at. They take no member upload at all.

Create is not idempotent, never auto-retry.

## Request body

- union
  - object — customer_list, website, or lookalike audience (uploaded or derived from a source).
    - `accountId` string, required
    - `adAccountId` string, required — Platform ad account ID. Must start with act_ for Meta; bare platform id for others (Google customer id, X/TikTok/LinkedIn/Pinterest account id).
    - `name` string, required
    - `description` string
    - `type` 'customer_list' | 'company_list' | 'engagement' | 'meta_engagement' | 'website' | 'website_retargeting' | 'lookalike', required
    - `matchRules` object[] — Required for website_retargeting audiences (LinkedIn only). Each rule is a URL pattern; a member who visits any matching page enters the segment. Needs the LinkedIn Insight Tag installed on the customer's site — the segment only starts filling once the tag reports visits. The response's `platformAudienceId` is the LinkedIn adSegment id, valid for downstream use. These segments appear in GET /v1/ads/audiences with `type: website_retargeting` once LinkedIn has finished building them.
      - `matchType` 'EXACT' | 'STARTS_WITH' | 'CONTAINS' | 'ENDS_WITH', required
      - `matchValue` string, required
    - `sourceType` 'VIDEO_ADS' | 'LEAD_GEN_FORMS' | 'ORGANIZATION_PAGES' | 'EVENT_PAGES' | 'SINGLE_IMAGE_ADS' — Required for engagement audiences (LinkedIn only): what members engaged with — a video/leadgen/single-image ad campaign, a Company Page or an Event page.
    - `trigger` string — Required for engagement audiences. The action, validated by LinkedIn against `sourceType`. Common values: VIDEO_ADS FIRST_QUARTILE / MIDPOINT / THIRD_QUARTILE / FULL_COMPLETE; LEAD_GEN_FORMS VIEW_FORM / LEAD_FORM_SUBMIT; ORGANIZATION_PAGES VIEW / CTA_CLICK; EVENT_PAGES RSVPED / VIDEO_VIEWED / ENGAGEMENT / CLICK.
    - `lookbackDays` 30 | 60 | 90 | 180 | 365 — Required for engagement audiences. Rolling window.
    - `engagementSources` string[] — Required for engagement audiences. Campaign URNs for the ad source types, organization URNs for pages and events. LinkedIn creates one rule per source, all sharing the same trigger and lookbackDays.
    - `companies` object[] — Required for company_list audiences (LinkedIn only): plain-text company rows for account targeting. Each row needs at least one identifier. Not hashed, LinkedIn matches these against its own company graph. LinkedIn recommends 1,000+ companies for a usable match rate and takes up to 48h to process the list. Replace the list later with POST /v1/ads/audiences/{audienceId}/companies.
      - `name` string
      - `domain` string
      - `website` string
      - `linkedinPageUrl` string
    - `pixelId` string — Required for website audiences
    - `retentionDays` integer — Required for website (max 180) and meta_engagement (max 365) audiences.
    - `engagementSource` 'page' | 'instagram' | 'video' — Required for meta_engagement audiences (Meta only): what people engaged with. `page` = a Facebook Page, `instagram` = an IG professional account, `video` = a video. The source object must be eligible for engagement audiences or Meta rejects with subcode 1713151 ("Invalid Event Name"), surfaced verbatim.
    - `sourceId` string — Required for meta_engagement: the Page / IG account / video id.
    - `event` string — meta_engagement only. The engagement event; defaults per source (page → page_engaged, instagram → ig_business_profile_all, video → video_watched). Ignored when `rule` is provided.
    - `sourceAudienceId` string — Required for lookalike audiences
    - `country` string — 2-letter code, required for lookalike audiences
    - `ratio` number — Required for lookalike audiences
    - `urlContains` string — website only. Narrows the audience from all visitors to visitors of URLs containing this substring. Ignored when `rule` is supplied.
    - `rule` object — Optional raw Meta rule, replacing the one we build. Omit it for all visitors of `pixelId`, or use `urlContains` for the common page-match case. For `website` this is Meta's Flexible Audience Rule and is VALIDATED before we call Meta: every entry in `inclusions.rules` (and `exclusions.rules`) must carry `event_sources`, `retention_seconds` AND `filter`. Meta rejects a rule missing any of the three with code 100 / subcode 1713098 ("Invalid rule JSON format"), so a bad shape is a 400 here instead. The pre-2018 flat shapes (`{url: ...}`, `{event: ...}`) are not accepted by Meta at all (subcode 1870029). Example, visitors of /checkout in the last 30 days: `{"inclusions":{"operator":"or","rules":[{"event_sources":[{"id":"<pixelId>","type":"pixel"}],"retention_seconds":2592000,"filter":{"operator":"and","filters":[{"field":"url","operator":"i_contains","value":"/checkout"}]}}]}}` Note Meta DERIVES `retention_days` from `retention_seconds` and stores `event_sources[].id` as a number, so a rule read back will not be byte-identical to the one you sent. For `meta_engagement` the rule is forwarded verbatim and NOT validated: that type has two dialects (the `video` source uses a legacy flat array), so no single schema covers both.
    - `customerFileSource` string — Data source declaration for GDPR compliance (customer_list only)
  - object — A reusable, stored TargetingSpec. No member upload step, no adAccountId, the spec is the audience. Reference it later via `savedTargetingId` on POST /v1/ads/create.
    - `type` 'saved_targeting', required
    - `accountId` string, required — Social account ID on the target ad platform.
    - `name` string, required
    - `description` string
    - `spec` TargetingSpec, required — Normalized, platform-agnostic ad-targeting spec. Every field is optional, an empty object targets the platform's default broadest audience. Field names are camelCase and identical across `POST /v1/ads/create` (the `targeting` object), `POST /v1/ads/targeting/reach-estimate`, and `saved_targeting` audiences, so a spec resolved once can be reused verbatim. Entity ids (`regions[].key`, `cities[].key`, `zips[].key`, `metros[].key`, `interests[].id`, `behaviors[].id`) are the platform's opaque identifiers resolved via `GET /v1/ads/targeting/search`. A spec is therefore meaningful only for the platform it was built against, except the portable fields (`countries`, `ageMin`/`ageMax`, `gender`, `incomeTier`, `languages`) which carry across platforms. Fields a platform cannot honour are rejected at create time with `INVALID_FIELD_VALUE` naming the offending field (not silently dropped).
      - `countries` string[] — ISO 3166-1 alpha-2 country codes (e.g. ['US']).
      - `regions` object[] — Region/state targeting. `key` is the platform location ID from /v1/ads/targeting/search?dimension=geo&geoType=region.
        - `key` string, required
        - `name` string
      - `cities` object[] — City targeting. Optional `radius` + `distanceUnit` extend beyond the city limits; both must be set together or both omitted. `radius` is only honoured on platforms whose capability map allows city radius (Meta).
        - `key` string, required
        - `name` string
        - `radius` number — Radius around the city. Requires distanceUnit. Meta enforces a minimum city radius (~17 km / 10 mi); smaller values resolve to a 0-size audience and the ad fails at launch. For a tighter catchment use customLocations (lat/lng), which allows a smaller radius.
        - `distanceUnit` 'mile' | 'kilometer' — Required if radius is set.
      - `zips` object[] — Postal/ZIP targeting. `key` is the platform's postal location ID (e.g. Meta `US:94304`). Supported on Meta, Google, TikTok, Pinterest, X.
        - `key` string, required
        - `name` string
      - `metros` object[] — DMA / metro-area targeting. `key` is the platform's metro ID (e.g. Meta `DMA:807`).
        - `key` string, required
        - `name` string
      - `customLocations` object[] — Point-radius (lat/lng) targeting (Meta custom_locations / Google proximity). Honoured only where the capability map allows radius (Meta).
        - `latitude` number, required
        - `longitude` number, required
        - `radius` number, required — Positive radius around the point.
        - `distanceUnit` 'mile' | 'kilometer', required
        - `name` string
        - `address` string — Optional label, sent to Meta as `address_string`. latitude/longitude take precedence for the pin location.
      - `excludedLocations` object — Geo to exclude from the audience. Mirrors the inclusion geo shape: excluded cities can carry a radius catchment and excluded custom (lat/lng) pins are supported, both on Meta (excluded_geo_locations).
        - `countries` string[]
        - `regions` object[]
          - `key` string, required
          - `name` string
        - `cities` object[] — Cities to exclude. Optional `radius` + `distanceUnit` exclude a catchment around the city (both must be set together or both omitted); Meta honours the radius on excluded cities.
          - `key` string, required
          - `radius` number — Radius around the excluded city. Requires distanceUnit.
          - `distanceUnit` 'mile' | 'kilometer' — Required if radius is set.
        - `zips` object[]
          - `key` string, required
          - `name` string
        - `places` object[] — Named points of interest to exclude. `key` from /v1/ads/targeting/search.
          - `key` string, required
        - `neighborhoods` object[] — Named neighbourhood areas to exclude. `key` from /v1/ads/targeting/search.
          - `key` string, required
        - `customLocations` object[] — Point-radius (lat/lng) pins to exclude (Meta excluded_geo_locations.custom_locations). Mirrors the inclusion customLocations shape.
          - `latitude` number, required
          - `longitude` number, required
          - `radius` number, required — Positive radius around the point.
          - `distanceUnit` 'mile' | 'kilometer', required
          - `name` string
          - `address` string — Optional label, sent to Meta as `address_string`. latitude/longitude take precedence for the pin location.
      - `ageMin` integer
      - `ageMax` integer
      - `gender` 'all' | 'male' | 'female' — Restrict by gender. 'all' (default) targets everyone. Applied on Meta, TikTok and Pinterest. Ignored on Google, LinkedIn and X.
      - `incomeTier` 'top_5' | 'top_10' | 'top_10_25' | 'top_25_50' — Normalized household-income tier (ZIP/percentile based). Meta and TikTok express all four. Google maps only `top_10` (its INCOME_RANGE_90_UP); other tiers on Google, and any income tier on LinkedIn / X / Pinterest, are rejected. On Meta, income/zip targeting requires the relevant `specialAdCategories` to be unset (housing/employment/credit ads cannot use it).
      - `languages` string[] — Language codes restricting the audience by language. On Meta, ISO 639-1 codes (e.g. ['en']); a bare code targets all regional variants ("en" = all English), or use a region-qualified code ("en_GB", "pt_BR") for a specific one. Unknown codes are rejected.
      - `interests` object[] — Interest entities from /v1/ads/targeting/search?dimension=interest. Each carries the platform's opaque id.
        - `id` string, required
        - `name` string
      - `behaviors` object[] — Behaviour entities from /v1/ads/targeting/search?dimension=behavior. Supported on Meta and TikTok.
        - `id` string, required
        - `name` string
      - `industries` string[] — LinkedIn B2B only. Industry URN id fragments.
      - `companySizes` string[] — LinkedIn B2B only.
      - `seniorities` string[] — LinkedIn B2B only.
      - `jobFunctions` string[] — LinkedIn B2B only.
      - `audienceInclude` string[] — Platform audience IDs to include.
      - `audienceExclude` string[] — Platform audience IDs to exclude.

## Response `201`

Audience created

- object
  - `audience` object
  - `message` string

## Other responses

- `400` — Missing required fields
- `401` — Unauthorized
- `403` — Ads access required. Legacy plans need the Ads add-on; included by default on usage-based plans.

---

[API](https://skmtc.net/zernio/apis/zernio-api.md) · [All operations](https://skmtc.net/zernio/apis/zernio-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/zernio/zernio-api/revisions/19fd2b70854e/schema)
