Skip to main content
A property list is a named list of typed identifiers (websites, mobile apps, CTV apps) owned by one advertiser. Its 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_any applies only to products on those channels.
  • Include lists go to sellers that declare property-list support in their capabilities, as targeting_overlay.property_list on every package of a media buy (and as property_list on 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.
The seller resolves the reference at 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

Pass domains (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):
The file is xlsx, xls or csv, up to 10 MB. Semicola reads the first column of the first sheet and ignores other columns. A first row whose first cell is 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 the externalId and 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_audiences call 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_include or audience_exclude.
Matching happens at the sellers, so 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

Pass pushNotificationConfig 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.