Trusted Match Protocol Specification
Experimental. The Trusted Match Protocol is part of AdCP 3.0 as an experimental surface — it may change between 3.x releases with at least 6 weeks’ notice. Sellers implementing TMP MUST declare
trusted_match.core in experimental_features. See experimental status for the full contract. Fields on this surface are not subject to deprecation cycles until 3.0.0 GA.Definitions
Message Types
All TMP message types include atype field that identifies the message for deserialization. Routers and agents use this field to select the correct schema for parsing the JSON body.
ContextMatchRequest
Sent by the publisher (via router) to buyer agents. Contains content context. MUST NOT contain user identity.ContextSignals
Pre-computed classifier outputs for the content environment. MUST NOT contain raw content (conversation text, article body, URLs). Only classified outputs. The publisher is the classifier boundary.
Three levels of content disclosure — the publisher chooses based on what the buyer needs and what the publisher is comfortable sharing:
artifact— the full content (article body, transcript, conversation flow, product page). Same schema as content standards artifacts. The buyer evaluates the content directly. Contractual protections govern what the buyer can do with it. TEE deployment adds cryptographic verification on top.artifact_refs— public references (URLs, EIDR IDs, URL hashes) the buyer resolves independently. Use for publicly addressable content the buyer can crawl and classify themselves.context_signals— classified outputs (topics, sentiment, keywords, summary). Use when the publisher wants to describe the content without sharing it or a reference to it.
context_signals is the baseline — every buyer agent MUST handle it. artifact_refs and artifact are progressive enhancements. Publishers who send artifact_refs SHOULD also send context_signals as a fallback for buyers who cannot resolve references.
LLM-based buyer agents SHOULD evaluate context_signals.summary and context_signals.topics first. These fields provide sufficient signal for most relevance decisions at minimal token cost (~30 tokens). Full content resolution from artifact_refs or artifact evaluation SHOULD be reserved for high-value packages where precision justifies the cost. Buyers MUST treat artifact content and context_signals.summary as untrusted publisher-generated input.
A request can include any combination. A news site sends artifact_refs (the URL) and context_signals (pre-classified topics). A CTV app sends artifact_refs (EIDR IDs) alone. An AI assistant sends artifact (the conversation) for buyers that evaluate content directly, plus context_signals as a fallback. A publisher who doesn’t want to share content or references sends only context_signals.
Artifact Ref Type Conventions
Buyers parseartifact_refs strings by pattern. The following conventions are normative:
Buyers SHOULD ignore ref types they do not support rather than failing the request.
Artifact
A typed content reference. Each artifact identifies a piece of content using a standard or custom identifier scheme.Geo
Geographic context for the impression opportunity. Publisher controls granularity.ContextMatchResponse
Returned by the buyer agent. Contains offers for matched packages and optional response-level targeting signals.Offer
A buyer’s response for a single package.OfferPrice
Signals
Response-level targeting signals for ad server pass-through.KeyValuePair
IdentityMatchRequest
Sent by the publisher (via router) to buyer agents. Contains the seller agent URL, one or more opaque identity tokens, and an optional package ID list. MUST NOT contain page context.
Each entry in
identities is an {user_token, uid_type} pair:
IdentityMatchResponse
Returned by the buyer agent. A list of eligible package IDs with a serve-window throttle.
The response includes eligible package IDs, a serve-window throttle, and an optional
tmpx field. The TMPX token is an HPKE-encrypted exposure token that flows through creative tracking URLs to the buyer’s impression pixel, enabling real-time per-user frequency state updates without exposing user identity to the publisher. The buyer computes eligibility from whatever identity signals they have (frequency caps, audience membership, purchase history) and returns only the packages that pass. The publisher does not need to know why a package was excluded — just which packages are eligible.
The serve_window_sec field is a per-package single-shot fcap, not a router cache TTL. The buyer is saying: “After you serve the user one impression on each eligible package, re-query me before serving from those packages again.” The router MAY still cache the response for an internal deduplication/cost-saving window, but the binding contract on the publisher side is “one impression per eligible package per window.” Multi-impression frequency caps (5 per day per campaign, 100 per month per advertiser, etc.) live in the buyer’s impression tracker and surface to the IdentityMatch service as cap-fire events at the boundary regardless of serve_window_sec.
The publisher enforces allocation rules (competitive separation, pod composition) using the eligibility list as input. This eliminates the need for pod-specific or batch-specific protocol semantics — the publisher allocates across whatever placements exist during the serve window (a CTV ad pod, a web page with 20 slots, a single pre-roll), honoring the one-impression-per-package contract.
Conformance invariants for IdentityMatch eligibility
A conformant IdentityMatch service MUST computeeligible_package_ids such that, for each package_id ∈ request.package_ids, the package is included in eligible_package_ids if and only if all of the following hold:
- Audience eligibility. Either the package has no audience requirement, OR there exists at least one audience identifier
asuch thatais in the package’s required audience set ANDais in the audience-membership of at least one identityi ∈ request.identities(the union across the user’s resolved identities intersects the package’s required audiences). - Frequency cap eligibility. No
(identity, package)cap-state entry exists for any identityi ∈ request.identitiesagainst the package. Cap-state entries are written by the buyer’s impression tracker when it determines an impression has exhausted a cap and carry an expiration timestamp; an entry is “present” until that timestamp. The protocol does not constrain how the impression tracker counts impressions, evaluates windows, or decides when a cap fires — only the boundary contract (cap-fire entries flow into the cap-state store; the IdentityMatch service checks presence at query time). See Frequency-Cap Data Flow for the boundary contract. - Active state. Packages or policies marked inactive MUST be treated as if absent.
- Audience freshness. If the buyer’s audience pipeline publishes a freshness deadline and the current time is past it, that audience-membership entry MUST NOT contribute to (1).
Consent
Privacy consent signals for the identity match. Publishers MUST include consent information when operating in regulated jurisdictions (EU/EEA, California, etc.). Buyers MUST NOT process user tokens without consent information when required by applicable law.Error Response
Returned by a provider or router when a request cannot be processed. Distinct from an empty result — an emptyoffers array or empty eligible_package_ids list is a valid response meaning no matches, not an error.
The router SHOULD exclude providers that return errors from the merged response for that request. The router MAY track error rates per provider and preemptively skip providers with sustained errors.
Provider Registration
TMP providers are registered with the router via publisher configuration. The publisher specifies which providers the router should call, along with each provider’s endpoint and supported capabilities. This is an operational relationship — the publisher trusts the provider to run code in their ad decisioning path. The standard registration path is static configuration — the publisher declares providers in their Prebid module config, router YAML, or equivalent surface-specific configuration. Dynamic registration (API-driven, database-backed) is an equally valid variant for publishers who manage many providers or need runtime updates. Both approaches use the same provider registration schema (/schemas/tmp/provider-registration.json).
At least one of
context_match or identity_match must be true — a provider that handles neither operation is invalid. When identity_match is true, countries and uid_types are required — the router cannot perform country-partitioned identity routing without them. The schema enforces both constraints.
Providers MAY support any combination of context_match and identity_match. A provider that supports only context_match is a pure enrichment or contextual targeting provider. A provider that supports only identity_match is a frequency capping provider — the publisher evaluates context locally from the media buy’s targeting rules and calls the buyer only for identity checks.
Provider lifecycle
Providers have three lifecycle states:- Active: Provider receives requests normally.
- Draining: Provider stops receiving new requests. In-flight requests complete normally. Use this when taking a provider offline for maintenance — the router finishes current work without starting new fan-outs to this provider.
- Inactive: Provider is skipped entirely. Use this to disable a provider without removing its configuration.
Provider registration security
Endpoint URL validation (SSRF). Both static configuration and dynamic registration MUST validate providerendpoint URLs against the canonical Webhook URL validation (SSRF) rules — HTTPS only in production, reserved IPv4 and IPv6 ranges rejected (including ::ffff:0:0/96 IPv4-mapped bypasses and the 169.254.169.254 / fd00:ec2::254 cloud metadata addresses), no redirects. Because a router calls the provider on every request, DNS rebinding is the primary risk: routers MUST either pin the TCP connection to the IP that passed validation or re-validate the socket’s post-handshake peer address before sending the request body. Re-resolving DNS without pinning is not sufficient.
Dynamic registration authentication. The dynamic registration API is a privileged surface; unauthenticated registration lets an attacker point publisher traffic at arbitrary HTTPS endpoints. Routers exposing dynamic registration MUST authenticate callers (mTLS or short-lived OAuth 2.0 tokens; static API keys only with IP allow-listing) and SHOULD apply per-agent rate limits on mutations and a cap on total registered providers per publisher to bound registration-storm abuse.
Router-to-provider auth. The existing “deployment-specific (mTLS, API key, etc.)” language in Request Authentication sets the mechanism; the minimum bar is that production providers MUST NOT accept anonymous calls. Static bearer tokens MAY be used only with IP allow-listing.
/health endpoint. The /health endpoint providers are encouraged to expose for router liveness MAY be unauthenticated, but the response MUST NOT leak internal state. Providers SHOULD return 200 with body {"status": "ok"} when ready and 503 when not ready; other status codes are bugs. Providers MUST NOT differentiate status codes or response bodies by internal subsystem (for example, a distinct code when the database is down versus when the identity cache is down is a side-channel that maps external probing onto internal topology). Version strings, build hashes, internal hostnames, and dependency statuses MUST NOT appear in the body. Rate-limit the endpoint (recommended: 1 req/sec per source IP) so it doesn’t become a DoS amplifier.
Product Integration
Publishers declare TMP support on their products via thetrusted_match field. Buyers see this on get_products and know what TMP capabilities are available.
ProviderEntry
Package Sync
Package metadata is synced from seller agents to TMP providers at media buy creation time and whenever the media buy materially changes. Providers cache theAvailablePackage set per placement and use it at request time — no package metadata flows through context_match_request or identity_match_request. The sync transport, authentication, and batch-error shape are deployment-specific; this section defines the payload contract and the obligations each participant inherits.
seller_agent on AvailablePackage is required under the experimental trusted_match.core surface. Sellers running TMP in existing deployments must update their sync payloads to populate it — see the experimental feature contract for the 3.x-to-3.x evolution policy.AvailablePackage
SellerAgentRef
Seller Agent Attribution
TMP providers cache packages from many seller agents against many publishers. Theseller_agent field makes that provenance explicit on each cached AvailablePackage so providers — which have no access to a media-buy store — can attribute offers, apply per-seller observability, and resolve disputes without out-of-band lookups.
The canonical seller identity in AdCP is the agent URL declared in the property publisher’s adagents.json authorized_agents[].url entry. TMP reuses that URL directly rather than introducing a parallel identifier space. The id slot on SellerAgentRef is reserved for a future registry-assigned opaque identifier and is not used today.
Placement rationale. Both context_match_request and identity_match_request carry seller_agent_url. It is the key the receiving agent uses to resolve the active package set it has registered for the asking seller: the provider on the context path, the buyer agent on the identity path. When package_ids is omitted, evaluation runs against that seller’s full active set; a seller_agent_url the receiver has not synced packages for MUST yield an empty result rather than a fall-back to another seller’s set.
seller_agent also lives on the cached AvailablePackage (sync time) for attribution — that binding is the source of truth for which seller owns a package, and the offer echo below reads from it. The two carry different jobs: the request-side seller_agent_url selects which seller’s set to evaluate; the package-side seller_agent attributes an individual package. Sync time remains when a provider first learns about a package; that binding is established once and reused for every subsequent evaluation.
seller_agent_url does not interact with the Package set decorrelation guarantee. That guarantee constrains data that varies per user — chiefly package_ids, whose composition MUST be independent of the current placement. seller_agent_url is a single stable value identifying the asking seller; it is identical for every user on a given placement and carries no user identity, so it adds no per-user signal on which context and identity requests could be correlated. This is receiver-side active-set scoping, not a per-user filter.
Offer echo. seller_agent MAY appear on offer.json as an echo from the cached package, for publisher-side log pipelines that want one-hop attribution without rejoining to the media-buy store. The echo is non-authoritative — the cached AvailablePackage binding is source of truth. Providers and routers MUST overwrite a disagreeing echo with the cached binding before logging, forwarding, or emitting the offer downstream, and MUST NOT use the echo value in any field consumed for billing, reporting, or dispute resolution. A disagreement SHOULD be counted to a seller_agent_echo_mismatch metric for anomaly detection. Routers MAY stamp seller_agent on merge when providers omit it.
Sync-Time Validation
Providers SHOULD validateseller_agent.agent_url against the property publisher’s adagents.json at sync time:
- For each property the package may serve, fetch
/.well-known/adagents.jsonon the property domain. If the file contains anauthoritative_locationpointer, follow it at most one hop to a same-scheme HTTPS URL; do not chain further. Both the initial fetch and theauthoritative_locationhop MUST apply the Webhook URL validation (SSRF) rules — HTTPS-only, reserved IPv4 and IPv6 ranges rejected (including::ffff:0:0/96IPv4-mapped bypasses and the169.254.169.254/fd00:ec2::254cloud metadata addresses), no transparent redirects, and the TCP connection pinned to the validated IP. The inbound-fetch rules referenced for Provider registration security apply equally to this outbound fetch. - Confirm that
seller_agent.agent_urlappears inauthorized_agents[].urland that anyproperty_ids,collections,placement_ids,placement_tags,countries,effective_from, andeffective_untilconstraints on that entry permit the package’s scope. The URL comparison uses the AdCP URL canonicalization rules — canonicalize both values before matching, never byte-equality. Rejectseller_agent.agent_urlvalues that do not use thehttps://scheme withseller_not_authorized; non-HTTPS seller URLs have no transport-integrity guarantee and cannot be trusted as an authorization key. - On mismatch, reject the sync operation for that
AvailablePackagewith anerrorresponse usingcode: seller_not_authorized. Other packages in the same sync batch are unaffected. The exact wire shape of the sync error is deployment-specific;codeis the machine-readable reason.
seller_not_authorized rejections to publisher operations — repeated failures usually indicate the publisher’s adagents.json and the seller’s sync pipeline have diverged. When an authorization’s effective_until passes or the seller is removed from authorized_agents, providers MUST treat previously-cached packages from that seller as unknown_package at request time until they are re-synced and re-validated.
Fetch failures. When validation cannot complete (fetch error, timeout, cert failure, malformed file), providers SHOULD reject the sync with seller_not_authorized rather than cache an unvalidated binding. Providers that fail-open MUST bound the unvalidated window to the 5-minute cache TTL and re-validate at the next opportunity; a binding that has never successfully validated MUST NOT remain cached past that window.
Bypass for pre-attested relationships. Providers MAY skip the adagents.json check only when they can verify the same agent_url → authorized_agents[].url binding through an out-of-band onboarding process (e.g., a mutually authenticated provider-seller enrollment that carries the publisher’s attestation), and SHOULD publish their enforcement mode (enforcing / advisory) in their conformance self-report so publishers can gate onboarding. This escape hatch is permitted for trusted_match.core v1 and will be removed in the first non-experimental TMP release, at which point the validation becomes MUST.
Participant Responsibilities
What This Is Not
- Not a per-user filter.
seller_agent_urlselects which seller’s registered active set the receiver evaluates — it is the same value for every user on a placement and carries no user identity. Publishers, routers, and providers MUST NOT use it as a per-user or per-request filter, nor to re-route the request; doing so would reintroduce the per-user variation Package set decorrelation exists to prevent. The package-sideseller_agentattribution likewise MUST NOT be used to scope or filter a request — it exists for offer attribution only. - Not a sellers.json bridge. IAB sellers.json
seller_idand TAG-IDs serve a distinct financial-audit identity space. They remain onadagents.jsoncontact— TMP does not duplicate them. - Not a cryptographic attestation. The binding is publisher-attested via adagents.json over HTTPS. Signed TMP seller claims are a future enhancement and can layer onto
SellerAgentRefthrough the reservedidslot or anextfield without breaking changes. When a future release populates bothagent_urlandid,agent_urlremains authoritative andidis advisory — a disagreement between the two MUST NOT be used to upgrade trust above what the URL’s adagents.json binding provides.
Privacy Requirements
The following requirements use RFC 2119 keywords (MUST, SHOULD, MAY).Structural separation
- Context Match requests MUST NOT contain user identity data (user tokens, device IDs, IP addresses, session tokens, or any data that could identify a specific user).
- Identity Match requests MUST NOT contain page context data (URLs, content hashes, topic IDs, content signals, or any data that could identify what the user is viewing).
- The TMP Router MUST process Context Match and Identity Match in structurally separate code paths with no shared state.
- Context Match and Identity Match request IDs MUST NOT be correlated or derivable from each other.
Package set decorrelation
- Context Match MUST NOT be filtered by user identity or audience. The provider evaluates its synced package set for the placement — the same packages for every user. No per-request package list is sent, so the publisher cannot accidentally leak identity through package filtering.
- The publisher SHOULD omit
package_idsfrom Identity Match and let the buyer evaluate against the full active set it has registered forseller_agent_url. Whenpackage_idsIS provided, its composition MUST be statistically independent of the current placement — sending only the page-specific subset would allow a buyer to correlate Identity Match with Context Match by comparing package sets. Two acceptable modes:- All-active. Include every active package this buyer has at this publisher.
- Fuzzed. Include a random sample of active packages, optionally padded with synthetic non-existent IDs, drawn from a distribution that does not depend on the current placement. The silent-drop rule below makes synthetic-ID padding safe — unknown IDs do not affect response shape and cannot leak registry membership.
- When both
seller_agent_urlandpackage_idsare present, the buyer evaluates against the intersection of its registered active set andpackage_ids; unknown IDs inpackage_idsMUST be silently ignored (not error-surfaced) so the response does not leak registry membership back to the publisher. - A publisher that maintains a cached list of all active package IDs per buyer MAY send the full set on every Identity Match request as a defense in depth, but the buyer-side resolution from
seller_agent_urlis the primary mechanism. - The publisher performs the intersection of context match offers and identity match eligibility locally, after both responses arrive.
Temporal decorrelation
- Publishers SHOULD introduce a random delay between Context Match and Identity Match requests. Recommended: 100-2000ms, uniformly distributed.
- Publishers SHOULD also randomize the order of Context Match and Identity Match: each opportunity SHOULD have a roughly equal probability of Context Match being sent first or Identity Match being sent first. A fixed order — e.g., Identity Match always after Context Match — leaks the pairing through ordering even when the delay is randomized.
- Publishers MAY batch Identity Match requests across multiple page views.
- Publishers MAY route Context Match and Identity Match through different network paths.
TEE attestation
- The TMP Router SHOULD provide TEE attestation when available, proving the deployed binary matches the published source.
- Attestation documents SHOULD be available on request to publishers and auditors.
- Attestation SHOULD include measurements confirming service code integrity and isolation.
Consent handling
- When
consentis omitted from an Identity Match request, buyers MUST treat this as “consent status unknown” rather than “consent not required.” - Buyers in jurisdictions where consent is required MUST reject Identity Match requests that omit
consent, not assume consent.
User token requirements
- User tokens MUST be opaque to buyer agents. Tokens may originate from identity providers (ID5, LiveRamp, UID2) or be publisher-generated.
- User tokens MUST NOT contain PII or be reversible to PII by the buyer agent.
Request Authentication
TMP requests carry a signature to prove the request originated from an authorized router. This prevents unauthorized parties from sending forged requests to providers to probe targeting logic, extract sponsored content, or manipulate frequency state.Signing model
The router signs all requests using Ed25519. Both Context Match and Identity Match requests are signed, but with different signed fields reflecting their different contents and caching characteristics. Signatures bind the request to a specific provider. The router signs a separate signature per fan-out target using the provider’s endpoint URL from the router’s provider registration. Providers MUST verify that the signedprovider_endpoint_url matches their own advertised endpoint and reject the request otherwise. This prevents a captured signature from being replayed against a different provider in the registry within the epoch.
The daily epoch provides replay protection — a captured signature is valid for at most ~48 hours (current + previous epoch accepted by verifiers).
Signature envelope
The signature is transmitted via HTTP headers alongside the JSON body.Context Match signed fields
Concatenated in this order, UTF-8, newline-separated:
When
package_ids is absent from the request, the signature MUST use an empty string for that field in the signed payload.
Because the signed fields are static per placement per provider, the same signature can be cached and reused for all requests to the same (placement_id, provider_endpoint_url) pair within a 24-hour epoch. Cache keys MUST include provider_endpoint_url — reusing a signature across providers violates the binding and will fail verification.
Identity Match signed fields
The signed input is the hex-encoded SHA-256 of the RFC 8785 JCS serialization of the following canonical object. Using JCS eliminates delimiter-injection risk — rawtcf_consent / gpp / us_privacy / package_id values may contain any byte, but JSON string escaping in JCS renders any framing byte harmless.
Canonical
identities bytes: deduplicate on (uid_type, user_token) using byte-exact match (no case folding, no trimming), sort entries by uid_type (UTF-8 byte order), then by user_token (UTF-8 byte order), then serialize the resulting array as RFC 8785 JCS. SHA-256 the UTF-8 bytes; hex-encode for the signed input, use raw bytes for cache keys (both conventions hash the same preimage).
The router filters identities per provider before forwarding (see Identity Match fan-out). The signature is computed over each provider’s filtered identities set — the router re-signs per outbound forward. The same filtered identities_hash value is used in the cache key (see Caching), so each provider has its own cache partition keyed on the subset it actually received.
Identity Match signatures include request_id and identities_hash, so they are unique per request and cannot be cached. This is intentional — Identity Match responses affect buyer-side frequency state and must be idempotent. Buyers MUST deduplicate Identity Match requests by request_id within the daily epoch window. A repeated request_id MUST return the same response without updating frequency state. Buyers SHOULD dedupe by hash(request_id) rather than retaining the raw identifier.
Signature verification
The router MUST authenticate incoming requests from the publisher before signing and fanning out. The mechanism for publisher-to-router authentication is deployment-specific (mTLS, API key, etc.) and outside the scope of TMP signing, but MUST be enforced. This prevents a compromised publisher-side component from laundering unauthenticated requests through the router’s signature. The router signs requests before fanning out. Providers verify signatures using the publisher’s public key, obtained from the property registry. This proves the request originated from an authorized router — not a third party probing the provider’s targeting logic. Providers SHOULD sample-verify rather than verify every request — Ed25519 verification adds ~30μs per request, which is small compared to the full pipeline but adds up at volume. A 5% sample rate detects fraudulent publishers within seconds while adding negligible overhead. On verification failure, the provider SHOULD suppress the property for a configurable period (recommended: 24 hours) and alert operations.Key rotation
Agents publish new signing keys viaagent-signing-key.json at their well-known agent URL. Routers SHOULD cache keys with a 5-minute TTL. When a signature fails verification, the router SHOULD re-fetch the key before rejecting — the agent may have rotated.
Key revocation
Rotation replaces a key; revocation kills one. Revocation is for the compromise case where a private key is known or suspected to have leaked. Publishers mark a compromised key by settingrevoked_at (ISO 8601 timestamp) on the key entry in agent-signing-key.json and leaving the key in the trust anchor during a grace period so stale caches still find it. Verifiers MUST:
- Reject any signature produced with a key where
revoked_atis present and the signing epoch falls at or after the revocation timestamp. - Re-fetch
agent-signing-key.jsonon verification failure before rejecting the request, to pick up a revocation that propagated after the last cache refresh. - Treat revocation as non-retryable for the offending request — there is no “maybe it’ll work on retry” state.
revoked_at marker in place until cache propagation completes.
Revocation limits. Revocation propagates within 5 minutes (cache TTL) but does not retroactively invalidate signatures already accepted by providers before the revocation was observed. Captured signatures with a signing epoch before revoked_at remain verifiable for the remainder of the 48-hour replay window. Operators who suspect compromise SHOULD:
- Rotate proactively on any suspicion, not only on confirmed leak.
- Keep signing keys in HSM or KMS rather than on disk to minimize compromise likelihood in the first place.
- Accept that a leaked key grants up to ~48 hours of forgery capability until daily-epoch rollover retires the signed payloads that contained it. Shorter custom epoch windows are a deployment option when this window is unacceptable.
Key distribution
Publisher public keys are distributed via the property registry. Each property record includes the publisher’s Ed25519 public key. Providers download the registry at startup and keep it current via incremental sync.Wire Format
Content type:application/json
All TMP messages use JSON encoding. Field names and types follow the JSON Schema definitions in this specification. Implementations MUST support JSON.
TMP messages are small (200-600 bytes per request/response). At these sizes, serialization format is less than 1% of total latency — the protocol’s performance comes from smaller messages and structural separation, not encoding efficiency. JSON is universal, debuggable, and supported by every language and tool.
Country-Partitioned Identity
Identity data is logically partitioned by country (ISO 3166-1 alpha-2). The protocol carries the country code on every Identity Match request and in every TMPX token (the buyer’s read replica includes the country in the encrypted plaintext for data residency routing — this is buyer-internal and not visible to the publisher or router). How countries are physically grouped into database clusters is a deployment decision — the protocol does not constrain it. Publishers include acountry field on Identity Match requests as a routing directive. The router uses it to select providers whose countries list includes that country code, then strips the field before forwarding. The buyer agent never sees the country — it is not an identity signal.
Each provider entry declares which countries and identity types it serves via countries and uid_types fields. A multi-country buyer operates separate provider entries per cluster (e.g., one for US, one for EU countries). This enables buyers to comply with data residency requirements, subscribe publishers to only the countries they serve, and move countries between clusters without protocol changes.
TMPX Exposure Tokens
TMP uses encrypted exposure tokens (TMPX) to close the frequency capping loop. The Identity Match read replica encrypts resolved identity tokens into an opaque TMPX macro that flows through creative tracking URLs. The buyer’s impression pixel decrypts the token and logs per-user exposures.Encryption
TMPX uses HPKE (RFC 9180) withmode_base:
The buyer’s cluster master holds the recipient private key. Read replicas encrypt using the master’s public key. Only the master can decrypt. Forging a TMPX token requires both the master’s public key (published) and realistic identity tokens from identity providers (UID2, ID5, RampID) — fabricating these is harder than the fraud itself. Systematic frequency manipulation is detected at the IVT (Invalid Traffic) layer, not the encryption layer.
Binary format
The TMPX plaintext is a compact binary structure. The type ID implicitly defines the token length — no length prefix is needed. Header (16 bytes):
Entries (repeated, buyer-configured priority order):
Type ID registry:
Type IDs are stable — new types append, existing IDs never change. Tokens are stored in binary (UUIDs as 16 bytes, base64-encoded tokens decoded). RampID has two entries because maintained (XY, 32 bytes) and derived (Xi, 48 bytes) forms differ in size.
If a parser encounters an unknown Type ID, it MUST stop parsing and treat the remaining entries as absent. The header Count field indicates total entries, but implementations MUST NOT assume all entries are parseable — forward compatibility requires graceful degradation when newer Type IDs are present.
Wire format
The TMPX macro value uses the format<kid>.<base64url_ciphertext>. The ciphertext MUST use unpadded base64url encoding (RFC 4648 section 5, no = padding characters). Padding characters break URL query parameters where = is the key-value delimiter. The kid (key identifier, max 8 characters) is opaque — it MUST NOT encode geographic or deployment information. It maps to a cluster master private key internally.
Size budget (255-char GAM macro limit): After HPKE overhead (48 bytes) and header (16 bytes), ~120 bytes remain for identity entries. Three 32-byte tokens = 99 bytes — fits comfortably. When the buyer resolves more identities than fit the budget, the TMPX plaintext is truncated to the highest-priority entries according to buyer deployment configuration. The priority order is a buyer-side configuration concern (not protocol-level), typically ranking deterministic graphs (UID2, RampID) above probabilistic or publisher-scoped identifiers. Buyers MUST configure an explicit priority list — the default implementation MUST NOT truncate arbitrarily — and the list SHOULD be documented in the buyer’s operational runbook.
Key management
One X25519 keypair per cluster:- The cluster master holds the private key for decryption.
- The public key is published in
encryption_keyson agent authorization entries inadagents.json. - Read replicas use the public key to encrypt. No per-replica key management.
Replay protection
The 8-byte random nonce enables deduplication at the master. The master stores nonces for a configurable window (recommended: 7 days) and rejects duplicates. The nonce is inside the AEAD-protected ciphertext — intermediaries cannot observe it.Caching behavior
The TMPX token is generated once per Identity Match evaluation and accompanies the eligibility response for theserve_window_sec window. All impressions on eligible packages within that window share the same TMPX value (same nonce, same tokens).
The buyer’s master MUST NOT deduplicate by TMPX value or nonce within a serve window — each pixel fire is one impression. Multiple ads served to the same user in a CTV pod or a web page with multiple ad units all produce distinct pixel fires with the same TMPX token. The nonce deduplication only prevents replay of the same TMPX token after the serve window expires — if the same nonce appears outside its original window, it is a replay and MUST be rejected.
Publisher obligations
Publishers MUST NOT parse, decrypt, or make decisions based on TMPX values. The token is opaque pass-through data substituted into creative tracking URLs exactly like other macros. For DOOH inventory, the player MAY include the opaque TMPX value in play log records transmitted to the buyer for exposure reconciliation — the publisher MUST NOT retain TMPX values beyond the transmission window.Inventory-specific behavior
- Web, mobile, CTV (SSAI), audio (DAI): Standard impression pixel —
{TMPX}substituted at serve time. - CTV (client-side VAST): Publisher substitutes
{TMPX}before the VAST document reaches the player. - DOOH: Play-log-based — the TMPX token is logged by the DOOH player and included in play log records rather than pixel URLs.
Transport
- JSON over HTTP/2 POST. All implementations MUST use this transport.
- Each provider exposes two path-based endpoints under its base URL:
POST /contextfor Context Match andPOST /identityfor Identity Match. The router dispatches requests by path, not by inspecting the message body. - Each provider SHOULD expose
GET /healthat its base URL. The endpoint returns HTTP200with a JSON body{"status": "ok"}when the provider is ready to accept requests. Any non-200response or connection failure means the provider is not ready. Routers and operators use this for pre-flight checks and monitoring — it is not called in the request hot path. - The
typefield in each message identifies the message for deserialization — routers and agents use it to select the correct schema, not for routing. Agents MUST validate that thetypefield matches the endpoint:context_match_requeston/context,identity_match_requeston/identity. A mismatch MUST be rejected with HTTP400. - Connections SHOULD be reused via HTTP/2 multiplexing.
- The router SHOULD maintain a connection pool to each buyer agent.
- The adcp-go SDK provides the reference client and server implementation. Conformance tests validate compatibility — implementations in other languages MUST pass the same test suite.
HTTP Status Codes
TMP uses HTTP status codes for transport-level errors only. Application-level results (including TMP error responses) are always returned with HTTP200:
This means a
200 with an empty offers array is a valid “no matches” response, and a 200 with a TMP error body ("type": "error") is a valid application error. The router handles one response format regardless of outcome.
Latency
- TMP targets sub-50ms end-to-end latency (publisher → router → agents → router → publisher).
- The router SHOULD apply adaptive per-agent timeouts based on observed latency percentiles.
- Agents that exceed the timeout are excluded from the merged response for that request.
- The router MAY preemptively skip agents whose p95 latency consistently exceeds the budget.
Caching
Context Match responses are cacheable because the same packages are evaluated for every user on a given placement. The recommended cache key is{property_rid, placement_id, provider_id}.
- Routers SHOULD cache Context Match responses with a TTL of 5 minutes.
- Providers MAY include a
cache_ttlfield (integer, seconds) in Context Match responses to override the default. Routers MUST respect this value when present. - Identity Match responses are bound by
serve_window_sec(per-package single-shot fcap, max 300s, default 60s). Routers MAY apply an internal deduplication cache keyed on{identities_hash, provider_id, package_ids_hash, consent_hash}, whereidentities_hashis the SHA-256 of the canonicalidentitiesbytes defined in Identity Match signed fields (computed over the per-provider filtered subset);package_ids_hashis SHA-256 over the JCS serialization of the sortedpackage_idsarray;consent_hashis SHA-256 over the JCS serialization of the request’sconsentobject (or JCSnullwhen the field is absent — this distinguishes “consent unknown” from an explicit-empty consent object). JCS framing prevents delimiter-injection: raw consent strings or package IDs containing|,,, or\ncannot collide two distinct inputs. Including the identity set ensures that adding or removing tokens produces a distinct cache entry. Including the package list hash ensures cached responses are invalidated when the active package set changes (e.g., a new media buy activates). Including the consent hash prevents eligibility decisions taken under one consent state from being served under another. The publisher’s binding contract is the serve-window throttle, not the router’s internal cache window. - When a provider’s targeting configuration changes (new packages, updated targeting rules), the provider SHOULD return
"cache_ttl": 0(Context Match) or"serve_window_sec": 1(Identity Match) until the change has propagated, then resume normal values. cache_ttl(Context Match) has a schema-enforced maximum of 86400 seconds.serve_window_secis bounded at 300 seconds — longer windows make per-package fcap too coarse for typical campaigns, shorter than the IdentityMatch round-trip wastes the throttle.
Conformance Levels
TMP Buyer Agent (Basic)
- Supports
context_matchcapability - Responds to ContextMatchRequest with valid ContextMatchResponse
- Meets latency budget (p95 < 30ms for agent-side processing)
- Respects privacy constraints (does not log or correlate requests with identity data from other sources)
TMP Buyer Agent (Full)
All of Basic, plus:- Supports
identity_matchcapability - Responds to IdentityMatchRequest with valid IdentityMatchResponse (eligible package IDs + TTL)
- Supports rich offers (brand, price, summary, creative manifest) when the product declares matching
response_types
TMP Router (Basic)
- Configured with provider endpoints
- Fans out Context Match and Identity Match to authorized providers
- Merges responses
- Meets end-to-end latency budget (p95 < 50ms)
TMP Router (Trusted)
All of Basic, plus:- Runs in a TEE-attested environment (e.g., AWS Nitro Enclaves)
- Provides attestation documents on request, proving the deployed binary matches the published source
- Structural separation between context and identity code paths is verifiable via attestation measurements