Skip to main content

Identity Match Frequency-Cap Data Flow

This page describes how frequency-cap state reaches the Identity Match service and how Identity Match consumes it at eligibility time. It defines the data flow only — what crosses the boundary between the impression tracker and the Identity Match service. Internal mechanics (how the impression tracker counts impressions, where policies live, what storage layout the Identity Match service uses, how identities are deduplicated upstream) are buyer-internal concerns and are out of scope here. The wire spec lives in the TMP specification; the conformance invariants the Identity Match service must satisfy are also normative there. The reference implementation of the Identity Match cap-state store ships in adcp-go/targeting/fcap.

Roles

The split is deliberate: counting impressions, evaluating windows, and deciding when a cap fires are buyer-internal policy concerns that vary across buyers and across campaigns. The Identity Match service stays narrow — it answers “is this user currently capped on this package?” and nothing more. New cap dimensions (advertiser, campaign, creative — see extensions) plug into the same boundary contract without changing the service.

End-to-end flow

Steps 1, 2, and 6 cross the wire and are normatively defined in the TMP specification. Steps 3 and 5 cross the impression-tracker → cap-state-store boundary and are defined on this page. Step 4 is buyer-internal — the protocol does not constrain it.

The cap-fire event

When a buyer’s policy evaluation determines that an impression has exhausted a cap, the impression tracker writes a cap-fire entry to the Identity Match cap-state store. Each entry consists of: A single cap-fire event typically corresponds to one entry; a cap that fires on multiple resolved identities or multiple packages produces one entry per (identity, package) pair, all sharing the same expire_at if the buyer’s policy is the same. The cap-state store does not record per-impression counts, policy definitions, or window configurations. Its only job is to answer “is this (user_identity, package) currently capped?” The buyer’s policy logic — counting, windowing, choosing dimensions to cap on, deciding when to fire — lives entirely in the impression tracker.

The eligibility query

At query time, the Identity Match service receives a list of identities and a list of candidate packages. For each candidate package, it checks the cap-state store for any matching (identity, package) entry across the user’s identities. If any entry exists, the package is excluded from eligible_package_ids. This is a presence check, not a count. Cap state is one input to eligibility. The Identity Match service also evaluates audience membership, package active state, audience freshness, and any other inputs the buyer cares about — see the conformance invariants. The cap-state portion of that evaluation is the part this page defines.

Policy updates and cap-state re-evaluation

Cap-state entries are written under whatever fcap policy was in force at cap-fire time. When the buyer’s fcap policies change — a window shortens or lengthens, a max_count rises or falls, a policy is paused or removed, a package is reassigned to a different policy — the existing cap-state entries written under the old policy can become stale. Stale entries either suppress users who should now be eligible (over-suppression) or fail to suppress users who should now be capped (under-suppression). When a fcap rule changes, the buyer’s policy owner (typically the impression tracker or a service in its pipeline) MUST re-evaluate every cap-state entry the rule applied to and push the appropriate update to the IdentityMatch cap-state store. Two event shapes cover the cases: Re-evaluation runs over the buyer’s own counting state (where impression history lives), not over the cap-state store — the cap-state store doesn’t carry counts. The output is the set of delete-or-extend events to apply. The reference store in adcp-go/targeting/fcap implements extend natively (a second RecordCap for the same (user_identity, field) overwrites the prior expire_at via HSETEX). Delete is a future extension — today, the simplest workaround is to extend with an expire_at already in the past, which causes the entry to be treated as absent at the next query and to be reaped by the backend’s TTL machinery. Re-evaluation can be expensive when a policy applies to many users. Buyers typically run it asynchronously: enqueue the policy-change event, sweep the affected user population in batches, push delete/extend events incrementally. The protocol does not constrain the cadence — only the eventual consistency requirement that cap-state must converge to what the current policies imply.

Reference implementation

The cap-state store API in adcp-go/targeting/fcap is the reference shape. It exposes two operations:
— plus batch variants for both. Field is {SellerAgentURL, PackageID}. The reference store is backed by Valkey 9 hashes, hashed by user identity, with one hash field per (seller_agent_url, package_id) tuple and a TTL set to expire_at. Other backends (Aerospike, DynamoDB, in-memory, anything) are conformant if they satisfy the boundary contract above.

Future extensions

Today the cap-state store is keyed at (user_identity, seller_agent_url, package_id). Future protocol versions may extend the field to additional dimensions — advertiser, campaign, creative, line item — so a buyer can express caps that span multiple packages without writing N entries on every cap-fire. The boundary contract on this page is unchanged by such extensions: the impression tracker writes cap-fire entries; the Identity Match service checks presence at query time.

See also