Skip to content

Audience.create

Turn your waitlist or signups into an Audience on your ad account.

Action class
None
Approval
Runs on its own
Emits
Audience.created
Sites
7 sites
Price
Free
POST https://api.sb/{your-domain}/audiences/create

Matches a list from your records, such as your waitlist, to an Audience on your ad account, every email hashed first. This Verb is a sketch on the hub’s Audience Noun.

Inputs

NameTypeDefaultMeaning
querystringRequiredThe saved query that picks the people, in the where grammar, such as source = 'waitlist'. It stays live, so the Audience follows your records.
venuestringRequiredWhere to match it, by id, such as facebook, linkedin or google-search.
lookalikenumber | nullnullA lookalike of the matched list, from 1, the closest, to 10, as a percent of each country’s people. Unset: the list itself.
countriesstring[]['US']The countries a lookalike is drawn from, as ISO 3166 codes.
namestringthe queryThe name the ad account shows.
refresh'daily' | 'never''daily'daily sends each day’s removals, then its additions, so someone who opts out leaves the Audience within a day.

Outputs

FieldTypeWhat null means
audienceAudienceNever null.
audience.sizeinteger, in peopleNever null: how many people the query picks today.
audience.outsidejsonNull in try mode and until the ad account returns its own id for the Audience.
audience.matchedinteger | null, in peopleNull until the ad account reports how many it matched, which can take hours.

Effects

Action class

None: no approval rule names it, so it runs within its seat’s Permissions and Limits

Modes
  • try: Counts who the query picks and checks each against Suppressions and opt-outs; nothing is uploaded.
  • send: Returns a pending Action; Audience.created fires when the ad account reports how many it matched.
  • do: Holds the call for the Mode’s hold time, then returns 202 with the pending Action if the match is still running.
Idempotency

Only with a key: the same Idempotency-Key and input return the first Action, and the same key with other input returns 409. An Audience already on the account for the same query is updated, never uploaded twice: outside holds its id there.

Saved or shared

The Audience keeps its query. Addresses reach the ad account hashed, and only the matched count comes back. Nothing is shared with other startups.

Emits

Audience.created with { audience, venue, size, matched }

Performed by

Paid Media Manager

Price

Free on your own account: the platform charges nothing for the call.

On each Venue

Through Google Ads.

Google Search: Responsive search ad YouTube: Video

Google Analytics

Through Google Analytics.

Our fieldGoogle Analytics’s field
Audience.queryaudience filter clauses
  • A retargeting audience. Fills only from visits after it is built, so build it before the traffic you want to reach.

LinkedIn

Through LinkedIn Ads.

Our fieldLinkedIn Ads’s field
Audience.querydmpSegment list upload (A company list by name and domain, or contacts with each email hashed before it leaves your records)
  • Matched audiences. Run from 300 matched members; under that the campaign refuses with venue-rejected.
Feed post Single image ad, 1200 × 628, 1200 × 1200 or 720 × 900 Document ad

Facebook and Instagram

Through Meta Ads.

Our fieldMeta Ads’s field
Audience.querycustomaudience (subtype CUSTOM) (Emails are hashed before they leave your records)
Audience.lookalikelookalike_spec.ratio (1 is 0.01)
Facebook: Feed image ad, 1440 × 1800, 4:5 Instagram: Feed image, 320 to 1,440 pixels wide, 4:5 to 1.91:1 Instagram: Carousel Instagram: Reel, 9:16 Instagram: Story, 9:16

What try does

AccountWhat try does
Google AdsSends every change with validate_only set: Google Ads checks the whole build and returns any error, and creates nothing.
Google AnalyticsReads run for real, because a report changes nothing; try stores no rows, builds no audience and marks no event.
LinkedIn AdsReads your ad account, the matched count of each audience and the targeting facets, and checks the campaign against LinkedIn’s budget minimums; LinkedIn has no validate-only call, so nothing is sent to it and nothing is created.
Meta AdsSends the whole build with Meta’s validate_only execution option: Meta runs every check and creates nothing, and the response names any field it would reject.

Refuses when

CheckErrorFixThen call
An Audience upload with one suppressed or opted-out member, or any check that errorsaudience-guardRemove the rows the Guard lists, then upload again; nothing serves until then.Audience.update
A member from a matched, licensed or bought sourcefirst-partyUse only your own Visitors and Contacts.Audience.update
A tag or a list upload for a browser with an opt-out signal or no Consentopt-outNothing to do: tags stop and the member leaves every list.Audience.update

Every call through an account can also meet the shared account refusals: grant-missing, needs-reauth, account-not-connected, over-mandate, quota-reached, venue-rejected.

The audience-guard payload
JSON
{
  "type": "https://startupbuilder.dev/errors/audience-guard",
  "title": "Upload has a suppressed member",
  "status": 422,
  "detail": "An Audience upload with one suppressed or opted-out member, or any check that errors.",
  "fix": "Remove the rows the Guard lists, then upload again; nothing serves until then.",
  "next": {
    "verb": "Audience.update",
    "input": {}
  },
  "instance": "https://api.sb/leaseline.app/actions/action_1"
}

When it waits

FactValue
send returnsA pending Action, reason: 'awaitingSource', until the ad account reports its match count.
Delivered byAudience.created

Example

Text
Make a 1% lookalike of my waitlist on Facebook and Instagram, leaving
out anyone suppressed.

To retry, send the same Idempotency-Key: the retry returns the first answer for $0. A new key reads again.

Test values

A sk_test_ key answers these the same way every time, at $0 (every test value).

Test valueAlways returnsWhat it is for
query: 'test = suppressed-member'Refuses with audience-guardOne of its three test members sits on a Suppression, so nothing uploads until that member leaves the query.
maxCost: 0.0001Refuses with max-costAny priced call with a maxCost of 0.0001 is refused before it spends: the price is above the cap.
rate-limited.testRefuses with rate-limitedAny call naming this hostname answers 429 with Retry-After, so your retry logic runs.
sources-busy.testRefuses with sources-busyAny call naming this hostname answers 503 as if every route were busy, and nothing is spent.

Used by