Overview
A property list is a named, advertiser-scoped collection of typed identifiers (websites, mobile apps, CTV apps) used to shape where a brand’s media runs. Lists carry apurpose of either include (only buy on these
properties) or exclude (never buy on these properties). Once created, lists
are linked to the brand’s targeting profile and enforced when you buy:
include lists can travel to supporting sales agents during discovery or as
buy-time targeting. Apostra also filters conflicting inventory at selection
time.
Property lists are a buyer-curated concept. They are owned by your
advertiser and validated against the AAO property registry. Supporting sales
agents receive list references that they resolve on demand. Apostra enforces
exclusions on the buyer’s side as a backstop.
Include lists
Restrict targeting to a hand-picked allow-list (e.g., a curated PMP of
premium publishers).
Exclude lists
Block specific domains across all of an advertiser’s campaigns (e.g., a
brand-safety blocklist).
How lists are enforced
Enforcement happens at selection time: when products are discovered and when a campaign is executed. Delivery-grain enforcement at the seller (dropping individual impressions inside an already-running buy) is not yet available.- Include lists are sent as
filters.property_listduring discovery when a sales agent declares eithermedia_buy.features.property_list_filtering: trueormedia_buy.execution.targeting.property_list. The feature declaration authorizes discovery filtering only. Atargeting_overlay.property_listreference is sent on media-buy packages only when the seller declares the execution-targeting dimension. Sellers without the relevant declaration do not receive the reference. - Exclude lists are not sent in
get_products: AdCP discovery filters do not defineproperty_list_exclude. They remain enforced on the buyer side during discovery and are sent astargeting_overlay.property_list_excludeon media-buy packages only when a sales agent declaresmedia_buy.execution.targeting.property_list_excludesupport. When an advertiser links multiple exclusion catalogs, the seller receives one reference that resolves to their combined, deduplicated identifiers. Apostra also enforces the list on the buyer’s side: products whose stored property identifiers match the advertiser’s exclusion list are filtered out of product discovery results, and execution rejects conflicting selections. Discovery reports the number removed inexcludedProductCount, per-storefront counts inexcludedProductCountsByStorefront, and bounded product details inexcludedProductsByStorefront; each detail contains the product ID, name, and matching domains plus typed identifiers. Canonicalget_productsreports the same total asext.interchange.excluded_product_countand each seller row carriesexcluded_product_countplus boundedexcluded_products. The domains are the intersection of that seller product and the buyer’s own list, not the buyer’s complete list. When an exclusion empties a completed result, the response guidance says how many products from how many sellers were removed because they included excluded properties instead of calling the result a lack of matching inventory. A legacy candidate without seller identity is included in the total but not assigned to a seller row, so the guidance reports the removed-product count without inventing a seller count. Executing a campaign fails with a clear error naming the conflicting products when a selected product’s publisher is on the list. Remove those products from the buy, or remove the exclusion, to proceed. Matching uses the typed identifiers the current property-membership model stores:domain,subdomain,ios_bundle,android_package,apple_tv_bundle,bundle_id,apple_app_store_id,google_play_id,roku_store_id,roku_channel_id,fire_tv_asin,fire_tv_app_id,amazon_app_store_id,samsung_app_id,lg_channel_id, andvizio_app_id. Identifier values are exact after normalisation: domains, iOS bundles, and Apple TV bundles are lowercased; Android packages, generic bundles, store IDs, and channel IDs keep their case. Products can declare every AdCP identifier type, but DOOH venue and podcast list entries are not stored by this property-list model yet, so they do not match an exclusion list at selection time. - Products sold through an Apostra storefront take the declaration of the
sales agent behind the storefront, not the storefront’s own. The storefront
passes both references to that sales agent unchanged. Apostra reads the
agent’s declaration from the AdCP capabilities it last refreshed for it, so a
seller declares
property_listorproperty_list_excludein its own sales agent’s capabilities. Managed sales agents and platform adapters publish no such declaration, so they don’t receive references. - A list reference you set on a package yourself (
property_list,property_list_exclude,collection_listorcollection_list_excludein a package’stargetingOverlay) is sent only to a seller that declares that exact field undermedia_buy.execution.targeting. A collection-list reference also needs the product to allow collection targeting (collection_targeting_allowed: true); AdCP treats a product without it as a bundle. If the seller does not declare the field, executing the media buy, or updating a media buy the seller already holds, fails with a validation error that names the package, the field and the seller, and nothing is sent. A seller that does not declare a field is not obliged to apply the list, so sending it anyway could let the buy run without it. Remove the reference from the package, or choose products whose seller declares the field. If Apostra cannot read the seller’s declaration, the request fails with a retryable error instead.
Concept
Each list stores a set of typed identifiers that are normalized, deduped, and resolved through two systems:- AAO registry — the cross-publisher Ad Context registry used to confirm a property is a real, identified entry.
- Local property catalog — Apostra’s mapping of identifiers to targetable
Propertyrecords used by sales agents.
resolutionSummary:
A single list can hold up to 100,000 identifiers per request. The service
chunks large inputs server-side against the AAO registry (which itself caps at
10,000 domains per call) and returns a single resource with the full
resolution summary.
Identifier types
Property lists accept AdCP-aligned typed identifiers. Pass a typed array viaidentifiers: [{type, value}], or use the domains: string[] shorthand
when every entry is a website domain.
A single mobile app can appear in a property list under multiple identifier
types (e.g.
ios_bundle AND apple_app_store_id); each resolves independently
against the AAO registry / local catalog.
domains: ["example-times.com"] is exactly equivalent to
identifiers: [{ "type": "domain", "value": "example-times.com" }]. You can pass
both fields in the same request — they’re concatenated and deduplicated.Read-side normalization.
apple_tv_bundle and ios_bundle share the
same backing column (App.bundle with appStore=APPLE_APP_STORE), so an
apple_tv_bundle write reads back as ios_bundle on subsequent GET/list
responses. The generic bundle_id fallback likewise normalizes to the
resolved app row’s store-typed form (ios_bundle or android_package).
Submit the type that best matches the AdCP property registry; expect the
response to carry the canonical store-typed form.Use with a V3 MCP agent
Buyer agents use one V3 write call for a property list’s lifecycle:save_property_list. Create a list with its advertiser, name, purpose, and
identifiers; update its name or identifier set with its returned ID; or archive
it with isArchived: true. Archiving is currently one-way.
To validate identifiers without saving a list, use the same call with
check: true, then pass its returned report ID to
get(kind: "property_list", include: ["report"], reportId: "..."); this
does not require a saved-list ID. get returns one bounded identifier page at
a time. Use its identifierPage.nextOffset with identifierOffset to continue,
and set identifierCategory to unresolved or registered when you need a
resolution bucket. Each page item is an AdCP identifier (type, value). A
value longer than 512 bytes is cut and flagged with value_truncated: true; a
cut value no longer matches a property, so do not copy it into another request.
search returns compact list summaries rather than identifier arrays. Use
search(kind: "property_list", filter: { advertiserId: "123" }) to find an
advertiser’s lists and get for a single list. The advertiser ID is required
because property lists are advertiser-scoped.
Every list belongs to the advertiser it was created for. By default a new
list becomes the advertiser’s all-campaigns list for its purpose: it replaces
the previous one (the response names it in replacedPropertyListIds; the
replaced list stays saved but applies to no campaign), applies to every
campaign, and is pushed to active media buys. Create it with appliesToAllCampaigns: false to save it for
the advertiser without applying it to any campaign; the advertiser’s lists and active
media buys are left unchanged. The choice is fixed at creation, and get and
search return it as appliesToAllCampaigns. If the advertiser’s brand has no list
slot for the list’s channels, a default create cannot apply the list anywhere:
it is saved for campaign use only and returned with
appliesToAllCampaigns: false.
Campaign lists
Every campaign inherits the advertiser’s lists. A campaign can also add one include list and one exclude list of its own, stored on the campaign:campaignPropertyListIdsets the campaign’s own include list.campaignPropertyListExcludeIdsets the campaign’s own exclude list.
save_campaign and on the v2 API’s
POST /campaigns/{campaignId}/property-lists.
propertyListId is deprecated on both; use campaignPropertyListId. Until
2026-12-31 a non-null propertyListId keeps its old behaviour: it applies the
named include list, which must already be configured for the advertiser, to
the campaign’s active media buys, combined with the campaign’s own include
list, without storing it as the campaign’s own list, and the response carries a deprecationNotice naming the replacement.
propertyListId: null is refused with a validation error, on save_campaign
and on the v2 endpoint alike. It used to remove property-list targeting from
the campaign’s live buys, which would leave them running without the
advertiser’s lists. To clear the campaign’s own list, send
campaignPropertyListId: null; the advertiser’s lists keep applying. After
2026-12-31 propertyListId is refused.
Each must be a list of the right purpose that belongs to the campaign’s
advertiser. A list saved with appliesToAllCampaigns: false is the usual
choice, because it applies only where a campaign uses it. Set either field to
null to clear the campaign’s own list; that never removes an advertiser list.
Both fields need an existing campaign (campaignId).
The lists combine the way you would expect:
- Exclusions add up. A property is excluded if the advertiser’s exclude lists or the campaign’s exclude list contain it. A campaign cannot remove an advertiser exclusion.
- Inclusions narrow. When both the advertiser and the campaign have an include list, a property must be on both.
GET /lists/:listId endpoint as any other list.
Saving a campaign’s lists pushes the lists in effect to the campaign’s active
media buys, and every buy created afterwards carries them from its first
dispatch. Archiving a list clears it from any campaign that uses it.
Read the campaign with get(kind: "campaign", include: ["propertyLists"]) (or
GET /api/v2/buyer/campaigns/:campaignId/property-lists). The response has:
The same combination applies when a campaign is executed: products whose
properties are on the advertiser’s or the campaign’s exclude lists are
rejected. Product discovery filters by the advertiser’s lists only; a
campaign’s own lists take effect when its media buys are executed.
Endpoints
All buyer endpoints below are mounted underhttps://api.apostra.com/api/v2/buyer.
The GET /lists/:listId resolution endpoint is mounted at the app root
(no /api/v2/buyer or /api/v2/storefront prefix) per ADCP convention.
All
propertyList-returning responses use the { propertyList } wrapper.
List endpoints return { propertyLists, total }.
How to use
Create an include list
Domains-only shorthand:identifiers— typed{type, value}[]actually resolved to local Property rows. Persisted as catalog membership.unresolvedIdentifiers— submitted identifiers with no matching local Property record. Transient — see persistence note below.registeredIdentifiers— identifiers found in the AAO registry but not yet locally targetable. Today onlydomainentries can land here. Transient.domains/unresolvedDomains/registeredDomains— convenience views of the above filtered totype: "domain". App identifiers are not in these.resolutionSummary— counts andresolutionRate(0..1) over the deduplicated, normalized input.
unresolvedIdentifiers and registeredIdentifiers are transient output of
the resolution call, not persisted state. They appear on create/update
responses, then drop on subsequent GETs and on name-only PUTs. Only
identifiers (the resolved set) is persisted on the list. Re-submit the
identifier set on a PUT to re-surface them.Update a list
APUT replaces the full identifier set. Active media buys that reference
this list are then notified — see Cascade behavior.
You can pass domains, identifiers, or both:
resolutionSummary and cascadeSummary whenever
identifiers change. A name-only PUT returns neither.
Validate before you commit
UsePOST /api/v2/buyer/property-lists/check to lint a candidate identifier set
against curation rules without creating anything:
Each bucket entry carries an
identifier: { type, value } field that mirrors
the input — use it to disambiguate types.
reportId (plus reportIds[] when domain input is chunked) is returned only
when at least one domain was submitted. Bundles-only and store-IDs-only
requests return no report IDs since no AAO call was made.
Cascade behavior
When youPUT a list with domains or identifiers, every active media buy that
uses the list is notified via ADCP update_media_buy so the sales agent can
refresh its cached property set. This covers include and exclude lists alike,
whether the list is one of the advertiser’s lists or a campaign’s own list.
Each campaign’s media buys receive the combined reference in effect for that
campaign (see Property-list briefs for sellers).
Each reference goes only to a sales agent that declares its targeting
dimension (property_list for include, property_list_exclude for exclude),
the same rule as a new buy. A buy whose sales agent doesn’t declare it is
updated without that reference, and the skipped change is logged. The update
response surfaces this with a cascadeSummary:
Resolution endpoint
Sales agents resolve aPropertyListReference by calling
GET https://api.apostra.com/lists/:listId with an HMAC bearer token
that Apostra mints when it embeds the reference in a request. This endpoint is
mounted at the app root (no /api/v2/buyer or /api/v2/storefront prefix) per ADCP convention. The
endpoint returns ADCP GetPropertyListResponse shape (not the standard
{ data, error, meta } envelope):
cache_valid_until (24 h).
Best practices
- Always inspect
resolutionSummary. Any non-zerounresolvedCountindicates identifiers that will not target — surface them to the user before going live. - Submit multiple identifier types for the same app. A mobile app is more
reliably resolved when you include both its
ios_bundleandandroid_package(and, where available,apple_app_store_id/google_play_id). Each is resolved independently against the AAO registry. - Prefer one large list over many small ones. A 100k-identifier list is
cheaper than 100 lists of 1k identifiers because each list creates its own
SmartPropertyListlink. - Use
filters.channels_anyto scope a list. Without it, the list applies across all of the brand’s targeting profiles. With it, you can keep display/OLV separate from CTV. - Treat updates as full replacements.
PUTreplaces the entire identifier set; there is no incremental add/remove. Re-send the union you want stored. - Validate before bulk import. Run
POST /api/v2/buyer/property-lists/checkon the raw input first, especially when assembling a list from spreadsheets or third-party feeds. - Avoid touching lists during high-traffic windows. Cascading notifies every active media buy that uses the list; schedule large updates for off-peak times when possible.
Limits
Related
Property-list briefs for sellers
What a seller’s agent receives and resolves when a buyer campaign carries a
property list.
Publisher properties and coverage
How seller coverage appears in
get_products.