purpose is include (only buy on these) or exclude (never buy on
these).
How lists are enforced
Enforcement happens when products are selected: at discovery and when a campaign goes live.- Exclude lists are enforced by Semicola. Products whose publisher website domain (or a
subdomain of it) is on a list leave discovery, and a campaign that still has such a product
staged won’t go live; the error names each product and the list. App and CTV properties without
a website domain aren’t matched. A list with
filters.channels_anyapplies only to products on those channels. - Include lists go to sellers that declare property-list support in their capabilities, as
targeting_overlay.property_liston every package of a media buy (and asproperty_liston discovery once the seller is known to support it). Sellers that don’t declare support never receive it. Creating an include list, replacing its identifiers, or attaching it to a campaign re-sends it to the advertiser’s live media buys (cascadeSummary); a failed buy is logged and doesn’t undo the change.
GET /lists/{listId} on the API origin, with the bearer
token carried in the reference. Storefronts hosted on Semicola declare property-list support: they
offer only products that cover a listed property (answering property_list_applied: true), and
refuse a package whose product covers none of the list. The answer is the ADCP GetPropertyListResponse (paginated with
max_results and cursor; cache it for 24 hours).
Identifiers and resolution
Passdomains (shorthand for type: "domain"), typed identifiers, or both: 1 to 100,000 per
request, canonicalized and deduplicated. Website domains always resolve. App and CTV identifiers
resolve when they appear on products this account has been offered; the rest come back in
unresolvedIdentifiers and won’t target. Only resolved identifiers are stored, so the
unresolved list appears on the create or update response and not on later reads. Always check
resolutionSummary.
PUT replaces the whole identifier set (there is no incremental add or remove). DELETE
archives the list.
Upload a large list
For lists in the thousands, upload a file instead of batching JSON calls (separate create calls make separate lists):domain, identifier, url, host or app
is treated as a header and skipped. Blank rows are dropped, duplicates are removed (the first one
is kept), and at most 100,000 identifiers are accepted. Each value is resolved as a website domain.
The 201 response is the created list plus an upload block (filename, sizeBytes, totalRows,
skippedHeader, parsedIdentifiers); compare parsedIdentifiers with your file and check
resolutionSummary.
Check before you commit
POST /api/v2/buyer/property-lists/check sorts candidates into ok, modify (canonicalized,
for example www. removed), remove (duplicates) and assess (manual review). Every app and
CTV identifier lands in assess. Semicola isn’t connected to the AgenticAdvertising.org property
registry yet, so domains can’t be registry-confirmed or registry-blocked: clean domains land in
assess too. Checks that include a domain return a reportId, readable for 7 days.
Audiences
POST /api/v2/buyer/advertisers/{advertiserId}/audiences/sync adds members (an externalId plus
an email, a phone with its + country code, their SHA-256 hashes, or universal ids), removes
members by externalId, or deletes an audience. Raw email and phone are normalized and hashed
before anything is stored. The call returns 202 with a taskId for GET /tasks/{taskId}.
uploadedCount is the stored member count.
A campaign’s audienceConfig (targetAudienceIds, suppressAudienceIds) adds audiences; with
deleteMissing: true it replaces the set. They reach sellers that declare audience targeting as
audience_include and audience_exclude.
How audiences reach sellers
- At launch. When a buy goes to a seller that declares audience targeting, Semicola first sends
that seller the campaign’s audiences with AdCP
sync_audiences: every current member, as theexternalIdand hashed identifiers only. This shares the buy’s 5-second budget with event-source registration and never holds the buy back. An audience the seller already holds isn’t sent again; one that failed is retried on the next buy. - Later changes. Each
sync_audiencescall afterwards sends the same change (members added, members removed with their hashed identifiers, or the audience deleted) to every seller already holding the audience. - Audiences off. With Audiences off for a seller in
Connections, nothing is sent to it, at launch or later, and its
buys carry no
audience_includeoraudience_exclude.
GET /api/v2/buyer/advertisers/{advertiserId}/audiences and
list_audiences report what they answered:
A seller’s answer is read when Semicola sends it the audience or a change, so a seller that finishes
matching later shows up after the next change.
Semicola’s hosted storefronts take audiences but have no identity graph to match against: they count
members and answer
processing, never a match count, so an audience sent only to hosted storefronts
stays PROCESSING.
To share an audience with a chosen agent before any campaign buys there, use
Syndication.
The sync callback
PasspushNotificationConfig on an audience sync to have Semicola POST the outcome to your URL
when the sync finishes (Semicola’s own sync, not a seller’s matching):
The body is the AdCP task payload:
idempotency_key, operation_id, task_id,
task_type: "sync_audiences", status (completed or failed), timestamp, message, your
token, and on success result.audiences[] (audienceId, action, uploadedCount). Credentials
are used for that one call and never stored. It’s one attempt with a 10-second timeout, and it
doesn’t hold up the sync’s answer. The same outcome is also raised as an audience.synced or
audience.sync_failed notification.