> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
> Use this file to discover all available pages before exploring further.

# TMP Specification

> Authoritative message type definitions, field tables, privacy requirements, and conformance levels for the Trusted Match Protocol.

# Trusted Match Protocol Specification

<Note>
  **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](/docs/reference/experimental-status) for the full contract. Fields on this surface are not subject to deprecation cycles until 3.0.0 GA.
</Note>

This is the authoritative reference for the Trusted Match Protocol (TMP). For conceptual introductions, see the [overview](/docs/trusted-match/) and [core concepts](/docs/trusted-match/context-and-identity).

Specific areas expected to evolve include TMPX exposure tokens, country-partitioned identity, and Offer macros — see the [3.1.0 roadmap](https://github.com/adcontextprotocol/adcp/issues/2201) for planned changes.

## Definitions

| Term                       | Definition                                                                                                                                                                                                                                  |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Context Match**          | TMP operation that evaluates available packages against content context. Carries no user identity.                                                                                                                                          |
| **Identity Match**         | TMP operation that evaluates user eligibility against package criteria. Carries no page context.                                                                                                                                            |
| **TMP Router**             | Infrastructure that fans out TMP requests to buyer agents and merges responses. A single binary that handles both context and identity requests, with structurally separate code paths.                                                     |
| **Offer**                  | A buyer's response to a context match request. Ranges from simple activation (package\_id only) to rich proposals with brand, price, summary, and creative manifest.                                                                        |
| **Available package**      | A package from an active media buy that is eligible for evaluation on a given placement. Package metadata — including the originating seller agent — is synced at media buy time. See [Package Sync](#package-sync).                        |
| **Seller agent**           | The buyer-side agent that sold the package into a publisher. Identified by the agent URL declared in the publisher's `adagents.json` `authorized_agents[].url`. Every `AvailablePackage` is bound to exactly one seller agent at sync time. |
| **Eligibility**            | List of eligible package IDs returned by Identity Match, plus a serve-window throttle. The buyer computes eligibility from frequency caps, audience membership, and other signals; the reasons are opaque to the publisher.                 |
| **Artifact**               | A typed content reference associated with a publisher property (article URL, episode EIDR, show Gracenote ID, music ISRC, product GTIN, conversation turn). Each artifact has a `type` and `value`. Referenced in context match requests.   |
| **Temporal decorrelation** | Random delay and random ordering between Context Match and Identity Match requests, preventing timing- and order-based correlation.                                                                                                         |

## Message Types

All TMP message types include a `type` field that identifies the message for deserialization. Routers and agents use this field to select the correct schema for parsing the JSON body.

| Message                 | `type` value              |
| ----------------------- | ------------------------- |
| Context Match request   | `context_match_request`   |
| Context Match response  | `context_match_response`  |
| Identity Match request  | `identity_match_request`  |
| Identity Match response | `identity_match_response` |
| Error response          | `error`                   |

### ContextMatchRequest

Sent by the publisher (via router) to buyer agents. Contains content context. MUST NOT contain user identity.

| Field              | Type               | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ------------------ | ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`             | string             | Yes      | `"context_match_request"`. Message type discriminator for deserialization.                                                                                                                                                                                                                                                                                                                                                                           |
| `protocol_version` | string             | No       | TMP protocol version. Default: `1.0`. Allows receivers to handle semantic differences across versions.                                                                                                                                                                                                                                                                                                                                               |
| `request_id`       | string             | Yes      | Unique request identifier for logging. MUST NOT correlate with any Identity Match request\_id.                                                                                                                                                                                                                                                                                                                                                       |
| `property_rid`     | UUID               | Yes      | Property catalog UUID (v7). Globally unique, stable.                                                                                                                                                                                                                                                                                                                                                                                                 |
| `property_id`      | string             | No       | Publisher's human-readable slug. Optional when `property_rid` is present.                                                                                                                                                                                                                                                                                                                                                                            |
| `property_type`    | enum               | Yes      | One of: `website`, `mobile_app`, `ctv_app`, `desktop_app`, `dooh`, `podcast`, `radio`, `linear_tv`, `streaming_audio`, `ai_assistant`. See `property-type` enum.                                                                                                                                                                                                                                                                                     |
| `placement_id`     | string             | Yes      | Placement identifier from the publisher's placement registry in `adagents.json`. One placement per request.                                                                                                                                                                                                                                                                                                                                          |
| `seller_agent_url` | string (URI)       | Yes      | API endpoint URL of the seller agent issuing this request. The provider resolves the active package set it has synced for this seller against it; an unsynced seller MUST yield an empty offer set, never a fall-back to another seller's set. A single per-placement value carrying no user identity. Compared with AdCP URL canonicalization. Consistent with `seller_agent_url` on the Identity Match request and `agent_url` in `adagents.json`. |
| `artifact`         | Artifact           | No       | Full content artifact adjacent to this ad opportunity. Same schema as content standards evaluation. The publisher sends the full artifact when they want the buyer to evaluate the actual content. Contractual protections govern buyer use. TEE deployment upgrades contractual trust to cryptographic verification.                                                                                                                                |
| `artifact_refs`    | List\<ArtifactRef> | No       | Public content references the buyer can resolve independently. Each has a `type` (one of: `url`, `url_hash`, `eidr`, `gracenote`, `isrc`, `gtin`, `rss_guid`, `isbn`, `custom`) and a `value`. For URL-addressable content, the buyer may have pre-classified these. Use `url_hash` when the publisher prefers not to reveal the URL (contextual clean room).                                                                                        |
| `context_signals`  | ContextSignals     | No       | Pre-computed classifier outputs for the content environment. Use when content is ephemeral (conversation turns, search queries) or to supplement artifact-based matching. Can replace `artifact_refs` entirely. Raw content MUST NOT be included — only classified outputs. The publisher is the classifier boundary.                                                                                                                                |
| `geo`              | Geo                | No       | Coarse geographic location of the viewer. Publisher controls granularity — country for regulatory compliance, region/metro for campaign targeting and valuation. No postcode or coordinates — coarsened to prevent user identification.                                                                                                                                                                                                              |
| `package_ids`      | List\<string>      | No       | Restrict evaluation to specific packages. When omitted, the provider evaluates all eligible packages for this placement (the common case). Package metadata (formats, catalogs) is synced at media buy time — not sent per request.                                                                                                                                                                                                                  |

#### 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.

| Field              | Type          | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ------------------ | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `topics`           | List\<string> | No       | Content topic identifiers. Use IAB Content Taxonomy 3.0 IDs when `taxonomy_id` is 7 (default), or human-readable strings for custom taxonomies.                                                                                                                                                                                                                                                                                                                                                                                                              |
| `taxonomy_source`  | enum          | No       | Organization that defines the topic taxonomy. Default: `iab`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `taxonomy_id`      | integer       | No       | Taxonomy version within the source. For IAB, follows the AdCOM cattax enum: `7` = Content Taxonomy 3.0 (CC-BY-3.0). Default: `7`.                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `sentiment`        | enum          | No       | Content sentiment: `positive`, `negative`, `neutral`, `mixed`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `keywords`         | List\<string> | No       | Content keywords extracted by the publisher's classifier.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `language`         | string        | No       | ISO 639-1 language code.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `content_policies` | List\<string> | No       | Policy IDs from the [AdCP Policy Registry](/docs/governance/policy-registry) that this content satisfies. Routers populate this from the publisher's property governance configuration or content metadata. Buyers filter on policies they require via `required_policies` on packages. This is a **pre-filtering optimization** — contexts missing required policies are excluded before reaching downstream governance. Definitive enforcement happens at the governance layer via [`check_governance`](/docs/governance/campaign/tasks/check_governance). |
| `summary`          | string        | No       | Natural language summary for relevance judgment. Useful for LLM-native buyers that evaluate semantically.                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `embedding`        | string        | No       | Content embedding as base64-encoded int8 vector. Captures semantic content beyond topics and keywords.                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `embedding_model`  | string        | No       | Embedding model identifier (e.g., `nomic-embed-text-v1.5`). Required when `embedding` is present.                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `embedding_dims`   | integer       | No       | Number of dimensions in the embedding vector. Required when `embedding` is present.                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |

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 parse `artifact_refs` strings by pattern. The following conventions are normative:

| Type          | Pattern                 | Example                                                |
| ------------- | ----------------------- | ------------------------------------------------------ |
| URL           | Starts with `https://`  | `https://oakwood.example/articles/sustainable-kitchen` |
| URL hash      | 44-char base64 (Blake3) | `k7Xp9mQ2vL8nR3wY5tB1aH6jK0pZ4xC9dF2eG7iMqw==`         |
| EIDR          | Starts with `eidr:`     | `eidr:10.5240/XXXX-XXXX-XXXX-XXXX-XXXX-C`              |
| Gracenote TMS | Starts with `tms:`      | `tms:SH012345670000`                                   |
| RSS + GUID    | Starts with `rss:`      | `rss:https://feed.example/rss+guid:ep-2026-03-15`      |
| GTIN          | 8-14 digit numeric      | `00012345600012`                                       |

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.

| Field   | Type   | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`  | enum   | Yes      | One of: `url`, `url_hash`, `eidr`, `gracenote`, `isrc`, `gtin`, `rss_guid`, `isbn`, `custom`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `value` | string | Yes      | The identifier value. For `url`: canonical content URL (MUST NOT contain user-specific paths or query params; use `url_hash` to avoid revealing URLs). For `url_hash`: base64-encoded Blake3 hash (canonicalization: strip scheme, strip [www./m./amp](http://www./m./amp). prefixes, lowercase, strip trailing slash, strip query params and fragments). For `eidr`: EIDR DOI (e.g., `10.5240/xxxx`). For `gracenote`: Gracenote TMS ID (e.g., `SH032541890000`). For `isrc`: ISRC code (e.g., `USRC17607839`). For `gtin`: GTIN (e.g., `00012345678905`). For `rss_guid`: episode GUID from RSS feed. For `isbn`: ISBN (e.g., `978-0-123456-78-9`). For `custom`: publisher-defined string. |

#### Geo

Geographic context for the impression opportunity. Publisher controls granularity.

| Field     | Type   | Required | Description                                            |
| --------- | ------ | -------- | ------------------------------------------------------ |
| `country` | string | No       | ISO 3166-1 alpha-2 country code (e.g., `US`, `GB`).    |
| `region`  | string | No       | ISO 3166-2 subdivision code (e.g., `US-CA`, `GB-SCT`). |
| `metro`   | Metro  | No       | Metro area using AdCP's metro classification systems.  |

### ContextMatchResponse

Returned by the buyer agent. Contains offers for matched packages and optional response-level targeting signals.

| Field        | Type         | Required | Description                                                                                                                                                                                                                                                                                                  |
| ------------ | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`       | string       | Yes      | `"context_match_response"`. Message type discriminator for deserialization.                                                                                                                                                                                                                                  |
| `request_id` | string       | Yes      | Echo of the request's `request_id`.                                                                                                                                                                                                                                                                          |
| `offers`     | List\<Offer> | Yes      | Offers from the buyer, one per activated package. Empty list means no packages matched.                                                                                                                                                                                                                      |
| `cache_ttl`  | integer      | No       | Provider override (in seconds) for the router's default Context Match response cache TTL. When present, routers MUST use this value instead of their default. `0` disables caching (e.g., when targeting configuration has just changed); schema-enforced maximum is 86400 seconds. See [Caching](#caching). |
| `signals`    | Signals      | No       | Response-level targeting signals for ad server pass-through. Not per-offer — applies to the response as a whole. In the GAM case, these carry the key-value pairs that trigger line items.                                                                                                                   |

#### Offer

A buyer's response for a single package.

| Field               | Type                 | Required | Description                                                                                                                                                                                                                                                                                       |
| ------------------- | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package_id`        | string               | Yes      | Package identifier from the media buy.                                                                                                                                                                                                                                                            |
| `seller_agent`      | SellerAgentRef       | No       | Optional echo of the package's seller agent for publisher-side observability. Non-authoritative — the binding on the cached AvailablePackage is source of truth. Routers MAY stamp this field from the cached package→seller map when omitted by the provider. See [Package Sync](#package-sync). |
| `brand`             | BrandRef             | No       | Brand for this offer. Required when the product allows dynamic brands. For single-brand packages, already known from the media buy.                                                                                                                                                               |
| `price`             | OfferPrice           | No       | Variable price for this offer. Only present when the product supports variable pricing.                                                                                                                                                                                                           |
| `summary`           | string               | No       | Buyer-generated description of the offer for the publisher to judge relevance. E.g., "50% off Goldenfield mayo — recipe integration".                                                                                                                                                             |
| `creative_manifest` | CreativeManifest     | No       | Full creative details, inline. When present, the publisher has everything needed to render. For large creatives (VAST, video), the manifest references external assets via URLs.                                                                                                                  |
| `macros`            | Map\<string, string> | No       | Key-value pairs for dynamic creative rendering or attribution tracking. In the GAM case, these flow as macro values. Distinct from the Identity Match `tmpx` field which carries an encrypted exposure token for frequency tracking.                                                              |

#### OfferPrice

| Field      | Type   | Required | Description                                  |
| ---------- | ------ | -------- | -------------------------------------------- |
| `amount`   | number | Yes      | Price amount in the specified currency.      |
| `currency` | string | No       | ISO 4217 currency code. Default: `USD`.      |
| `model`    | enum   | Yes      | One of: `cpm`, `cpc`, `cpcv`, `cpa`, `flat`. |

#### Signals

Response-level targeting signals for ad server pass-through.

| Field           | Type                | Required | Description                              |
| --------------- | ------------------- | -------- | ---------------------------------------- |
| `segments`      | List\<string>       | No       | Audience or contextual segment IDs.      |
| `targeting_kvs` | List\<KeyValuePair> | No       | Key-value pairs for ad server targeting. |

#### KeyValuePair

| Field   | Type   | Required | Description      |
| ------- | ------ | -------- | ---------------- |
| `key`   | string | Yes      | Targeting key.   |
| `value` | string | Yes      | Targeting value. |

### 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.

| Field                | Type                    | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| -------------------- | ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`               | string                  | Yes      | `"identity_match_request"`. Message type discriminator for deserialization.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `protocol_version`   | string                  | No       | TMP protocol version. Default: `1.0`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `request_id`         | string                  | Yes      | Unique request identifier. MUST NOT correlate with any Context Match request\_id.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `seller_agent_url`   | string (URI)            | Yes      | API endpoint URL of the seller agent issuing this request. The buyer's identity-match service uses this to resolve the active package set it has registered for this seller; when `package_ids` is omitted, evaluation occurs against that full set. Compared using the [AdCP URL canonicalization rules](/docs/reference/url-canonicalization), not byte-equality. Consistent with `seller_agent.agent_url` on `AvailablePackage` and `agent_url` in `adagents.json`.                                                                                                                                                     |
| `identities`         | List\<Identity>         | Yes      | One or more identity tokens for the user. Publishers SHOULD include every token they have available — the buyer resolves on whichever graph matches, maximizing match rate. Each entry is an independent identifier for the same user; buyers MUST NOT treat the combination as a new correlated identity.                                                                                                                                                                                                                                                                                                                 |
| `consent`            | Consent                 | No       | Privacy consent signals. Buyers in regulated jurisdictions MUST NOT process identity tokens without consent information.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `package_ids`        | List\<string>           | No       | When omitted, the buyer evaluates eligibility against the full set of active packages it has registered for `seller_agent_url`. When provided, composition MUST be statistically independent of the current placement. Two acceptable modes: **all-active** (every active package for this buyer at this publisher) or **fuzzed** (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 page-specific subset is forbidden — it would let the buyer correlate with Context Match by comparing package sets. |
| `country`            | string                  | No       | ISO 3166-1 alpha-2 country code. Routing directive — the router uses this to select the correct regional provider. The router MUST strip this field before forwarding to the buyer agent. Not an identity signal.                                                                                                                                                                                                                                                                                                                                                                                                          |
| `sealed_credentials` | List\<SealedCredential> | No       | **Experimental (`trusted_match.verified_identity`).** HPKE-sealed verified-identity credentials addressed to specific audiences — the network-as-RP carrier. Opaque pass-through for the publisher. See [Verified Identity Attestation](#verified-identity-attestation).                                                                                                                                                                                                                                                                                                                                                   |

Each entry in `identities` is a `{user_token, uid_type, attestation?}` triple:

| Field         | Type        | Required | Description                                                                                                                                                                                                                                                                                               |
| ------------- | ----------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user_token`  | string      | Yes      | Opaque token from an identity provider (ID5, LiveRamp, UID2) or publisher-generated. Buyer may map to internal identity graph but cannot reverse to PII.                                                                                                                                                  |
| `uid_type`    | enum        | Yes      | Type of user identifier: `uid2`, `rampid`, `id5`, `euid`, `pairid`, `maid`, `hashed_email`, `publisher_first_party`, `world_id_nullifier`, `other`. Tells the buyer which identity graph to resolve against. See `uid-type` enum.                                                                         |
| `attestation` | Attestation | No       | **Experimental (`trusted_match.verified_identity`).** Verifiable proof *about* this identity (proof-of-personhood and/or age). The receiver MUST verify it and treat an unverifiable attestation as absent — never as asserted-true. See [Verified Identity Attestation](#verified-identity-attestation). |

### IdentityMatchResponse

Returned by the buyer agent. A list of eligible package IDs with a serve-window throttle.

| Field                  | Type                                        | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ---------------------- | ------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`                 | string                                      | Yes      | `"identity_match_response"`. Message type discriminator for deserialization.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `request_id`           | string                                      | Yes      | Echo of the request's `request_id`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `eligible_package_ids` | List\<string>                               | Yes      | Package IDs the user is eligible for. Packages not listed are ineligible.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `serve_window_sec`     | integer                                     | Yes      | Per-package single-shot fcap window, in seconds. Range: 1–300. Default: 60. After serving the user one impression on each eligible package within this window, the publisher MUST re-query Identity Match before serving from those packages again. This is **not** a router response cache TTL — it is a buyer-asserted serve throttle. Multi-impression frequency caps are handled separately by the buyer's impression tracker, which writes cap-fire events to the IdentityMatch cap-state store at the boundary regardless of this window — see [Frequency-Cap Data Flow](/docs/trusted-match/identity-match-implementation).                                                                                                                                                                                                                               |
| `tmpx_macros`          | List\<TmpxMacro>                            | No       | Provider-emitted (identity-agent-side field). The agent's ordered TMPX chunks paired with the ad-server macro name each chunk fills. Names MUST be drawn from the provider's registered `tmpx_macros` list (see [Provider Registration](#provider-registration)) in the same order. Capped at 2 entries in v1. Each `value` is an opaque URL-safe wire string the publisher substitutes verbatim — publishers MUST NOT parse, decode, transform, or choose an encoding. Consumers reading the router's merged response SHOULD consume `tmpx_providers` and ignore `tmpx_macros` at the root.                                                                                                                                                                                                                                                                     |
| `tmpx_providers`       | Map\<string, \{ macros: List\<TmpxMacro> }> | No       | Router-populated. TMPX macro/value pairs grouped by the originating identity provider's `provider_id`, so the publisher fires each provider's tokens through that provider's specific ad-server macros (e.g. `PIN_TMPX_1`, `PIN_TMPX_2` for one provider; `NOVA_TMPX_1` for another — configured per provider in GAM / VAST URL / DOOH play log). Required by router conformance when any identity provider emitted TMPX in this request; collapsing per-provider tokens into a single string loses attribution and breaks per-provider impression accounting. Macro names MUST come from each provider's registered `tmpx_macros` — publishers MUST NOT derive macro names from `provider_id` at runtime. SHAPE CHANGE from the experimental v1 surface that shipped in #5689 (which used `Map<provider_id, string>`); sanctioned by the experimental contract. |
| `tmpx`                 | string                                      | No       | DEPRECATED in favor of `tmpx_providers`. Single HPKE-encrypted exposure token. Routers MAY continue to populate this field for back-compat with consumers that only know the single-token shape; when both fields are present, `tmpx_providers` is authoritative. Wire format: `kid.base64url_nopad(ciphertext)` (unpadded, no `=` characters). Removed in 4.0.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |

#### TmpxMacro

| Field   | Type   | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`  | string | Yes      | Ad-server macro name as configured in the publisher's ad server (e.g. `PIN_TMPX_1`). MUST appear in the emitting provider's registered `tmpx_macros` list. Pattern: `^[A-Z][A-Z0-9_]*$`. Provider-namespaced so the publisher can target distinct slots per provider.                                                                                                                                                                                                           |
| `value` | string | Yes      | Opaque, URL-safe wire string the publisher substitutes verbatim into the named macro slot. Length capped at 1024 characters — comfortably above the 255-char GAM key-value limit and large enough for HPKE-overhead + chunked payloads. Publishers MUST NOT parse, decode, or transform this value. The protocol fixes the wire format so platforms interoperate; a platform that can carry raw bytes MAY optimize privately but the wire contract remains the URL-safe string. |

The response includes eligible package IDs, a serve-window throttle, and the per-provider TMPX shape (`tmpx_providers` after router merge, or `tmpx_macros` at the per-provider level). By convention, provider→router payloads populate `tmpx_macros` and leave `tmpx_providers` absent; router→publisher payloads populate `tmpx_providers` (built by collecting each provider's `tmpx_macros`). A router response MUST NOT carry `tmpx_macros` at the root — leaking an upstream provider's root array alongside `tmpx_providers` would give the publisher no schema signal for which to read and risks double-firing the same value into one slot, corrupting the per-provider accounting the map exists to protect. The schema cannot enforce this (the same schema serves both hops) so it lives as a router-conformance invariant; routers MUST drop `tmpx_macros` from outbound responses. Each TMPX value 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.

**TMPX macro trafficking.** Macro names are part of operational setup, not protocol-synthesized identifiers. Each identity provider registers its stable, provider-namespaced macro names in `tmpx_macros` on its provider-registration entry (e.g. Pinnacle registers `["PIN_TMPX_1", "PIN_TMPX_2"]`; Nova registers `["NOVA_TMPX_1"]`). Publishers configure those exact names in their ad server (GAM key-values, VAST URL macros, DOOH play-log fields). When the response arrives, the publisher fires each provider's `macros[].value` into the matching `macros[].name` slot — the contract is "substitute this exact string into this exact macro." Ordered multi-chunk support lets a single TMPX exceed one macro slot (capped at 2 chunks per provider in v1 and MAY be raised without a shape change). Deriving macro names from `provider_id` at runtime would break this trafficking model because GAM/ad-server line items are configured against the registered names ahead of time, not against runtime-synthesized strings.

`tmpx_providers` keys macro/value pairs by `provider_id` so the router preserves attribution when its fan-out reaches multiple identity providers. The legacy `tmpx` field remains supported through 3.x for consumers that haven't migrated; when both fields are present, `tmpx_providers` is authoritative and the singular field SHOULD reflect one provider's first-slot value solely as a transitional convenience.

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 compute `eligible_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:

1. **Audience eligibility.** Either the package has no audience requirement, OR there exists at least one audience identifier `a` such that `a` is in the package's required audience set AND `a` is in the audience-membership of at least one identity `i ∈ request.identities` (the union across the user's resolved identities intersects the package's required audiences).
2. **Frequency cap eligibility.** No `(identity, package)` cap-state entry exists for any identity `i ∈ request.identities` against 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](/docs/trusted-match/identity-match-implementation) for the boundary contract.
3. **Active state.** Packages or policies marked inactive MUST be treated as if absent.
4. **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).
5. **Age eligibility** (experimental — applies only when `trusted_match.verified_identity` is in effect). Either the package requires no age policy, OR some identity `i ∈ request.identities` carries a **verified** `attestation` (per the [Verified Identity Attestation](#verified-identity-attestation) conformance rules) with an age claim ≥ the threshold resolved from `(the package's required age policy, request geo)`. An unverified or absent attestation does not satisfy this clause. When the feature is not in effect this clause is vacuously true, so core conformance is unchanged.

The TMPX returned with the response MUST encode the resolved identities so the out-of-band impression tracker can update fcap policy state and signal cap-fire events to the IdentityMatch cap-state store — see § TMPX tokens and [Frequency-Cap Data Flow](/docs/trusted-match/identity-match-implementation).

Storage backend (valkey, Aerospike, DynamoDB, in-memory, anything) is implementation. Two services with different storage backends that satisfy these invariants for the same inputs MUST return the same eligibility output.

#### 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.

| Field         | Type   | Required | Description                                                                 |
| ------------- | ------ | -------- | --------------------------------------------------------------------------- |
| `gdpr`        | bool   | No       | Whether GDPR applies to this request.                                       |
| `tcf_consent` | string | No       | IAB TCF v2.2 consent string. Present when `gdpr` is true.                   |
| `gpp`         | string | No       | IAB Global Privacy Platform string.                                         |
| `us_privacy`  | string | No       | US Privacy string (CCPA). Deprecated in favor of GPP but still widely used. |

### Verified Identity Attestation

**Experimental — declare `trusted_match.verified_identity` in `experimental_features`** (separate from `trusted_match.core`, so buyers can detect support independently). Lets a publisher — or a network/issuer acting as the relying party — carry a **verifiable** proof *about* a user (proof-of-personhood and/or age) so the buyer verifies the claim cryptographically rather than trusting an assertion. Issuer-agnostic: World ID is the first scheme; mDL / VC-style issuers use the same shape. Design rationale: `specs/tmp-verified-identity-attestation.md`.

This widens `identity-match-request.json`, which is otherwise `additionalProperties: false`. The widening is deliberate: an attestation is proof on the **identity** side of the privacy boundary — not page context — so it does not breach the boundary the strict schema protects.

#### Topologies

| Topology                     | Relying party                   | Carrier                                                            |
| ---------------------------- | ------------------------------- | ------------------------------------------------------------------ |
| Publisher-as-RP              | Publisher                       | `attestation` on an `identities[]` entry (per-publisher pseudonym) |
| Network-as-RP / issuer-as-RP | A network, or the issuer itself | `sealed_credentials[]` (HPKE-sealed to the audience's key)         |

#### Attestation

Optional object on each `identities[]` entry.

| Field                | Type               | Required | Description                                                                                                                                                                                                                                              |
| -------------------- | ------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `issuer`             | BrandRef           | Yes      | Identity issuer / attestation authority as a vendor BrandRef (e.g. `{"domain": "world.org"}`) — the same shape AdCP uses for measurement/signals vendors. Domain is the anchor; `brand.json` hosting is optional. `scheme` selects the verifier version. |
| `scheme`             | string             | Yes      | Proof scheme and version, e.g. `world_id_v4`.                                                                                                                                                                                                            |
| `relying_party_id`   | string             | No       | Relying-party id the proof was minted for. Checked against the `relying_party_id` owner's published `identity_relying_parties[]` in `brand.json` (provenance).                                                                                           |
| `action`             | string             | No       | Issuer action/scope the proof was bound to.                                                                                                                                                                                                              |
| `claims`             | List\<enum>        | Yes      | Closed, issuer-agnostic set: `unique_human`, `age_over_13`, `age_over_16`, `age_over_18`, `age_over_21`. See `attestation-claim` enum.                                                                                                                   |
| `verification_level` | enum               | No       | `orb` \| `device` \| `document`. Credential strength.                                                                                                                                                                                                    |
| `signal_binding`     | string             | No       | Hash of the signal the proof commits to (replay defense).                                                                                                                                                                                                |
| `proof`              | object             | Yes      | Scheme-specific verifiable proof material; opaque to this schema.                                                                                                                                                                                        |
| `expires_at`         | string (date-time) | No       | Validity horizon; receivers MUST reject when past.                                                                                                                                                                                                       |

#### SealedCredential

Entry in the top-level `sealed_credentials[]`.

| Field          | Type   | Required | Description                                                                                                                   |
| -------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `audience_kid` | string | Yes      | Key id of the recipient (network / relying party) whose HPKE private key opens `payload`.                                     |
| `payload`      | string | Yes      | HPKE-sealed attestation in the TMPX envelope format `kid.base64url_nopad(ciphertext)`. Opaque pass-through for the publisher. |

#### Conformance invariants (verified attestation)

A receiver that accepts attestations MUST:

1. **Verify before trust.** Verify the proof for every `scheme` it accepts. An attestation that fails verification — or `signal_binding`, or `relying_party_id` provenance, or `expires_at` — MUST be treated as **absent** (no attestation), never as an asserted-true claim.
2. **No silent upgrade.** Never treat an unverified or unverifiable attestation as `true`.
3. **relying\_party\_id provenance.** Confirm the attestation's `relying_party_id` belongs to the entity claiming the traffic before trusting it. The owner's `brand.json` `identity_relying_parties[]` is the v1 discovery surface, but it is self-published over HTTPS — the authoritative root is the issuer's own relying-party registry (e.g. World ID's on-chain registry), against which `brand.json` is a cross-check. A receiver MUST NOT trust a `relying_party_id` solely because some `brand.json` lists it absent an issuer-side anchor; the bidirectional issuer-metadata cross-check is a tracked open item.
4. **Sealed credentials.** Decrypt only `sealed_credentials[]` entries whose `audience_kid` the receiver holds a key for; ignore the rest.
5. **Bound resources.** Bound attestation and sealed-credential count and size to prevent DoS.

**Replay (v1 limitation).** Verifying a proof establishes that *a* credential holder produced it, not that it was produced for *this* impression. Without an enforced `signal_binding` freshness window and nullifier-reuse tracking — both left to the verifier in v1 (the freshness policy is WG-open) — this surface provides at most daily-epoch replay resistance (via `request_id` dedup). Receivers SHOULD bind `signal_binding` to a fresh, receiver-checkable value and track nullifier reuse, and MUST NOT over-trust an attestation as a per-impression liveness signal until they do.

#### Router handling of `sealed_credentials[]`

The router MUST forward each `sealed_credentials[]` entry only to the provider that owns its `audience_kid` (route-by-audience, not broadcast). The tamper-evidence and cache-partitioning rules are defined canonically in [Identity Match signed fields](#identity-match-signed-fields) and [Caching](#caching): `sealed_credentials` is folded into the per-provider re-signature canonical bytes (so an injected or swapped blob breaks the request signature) and `sealed_credentials_hash` is part of the dedup cache key (so a network-credential change repartitions the cache rather than serving a stale response). The same canonical-identities bytes already cover any per-identity `attestation`.

#### Age as eligibility

A verified age claim (`age_over_N`) resolves to `eligible_package_ids` — it is **never** carried as a cleartext age or date of birth. The verifying party maps `(required age policy, geo) → required threshold claim` and includes a package only when the attestation carries a claim ≥ the required threshold. The jurisdiction → threshold tables are maintained in the [AdCP Policy Registry](/docs/governance/policy-registry) (a package requires an age policy via `required_policies`); sub-country (e.g. US state) resolution happens where finer geo is available, since the Identity Match `country` field is coarse and stripped before forwarding.

### Error Response

Returned by a provider or router when a request cannot be processed. Distinct from an empty result — an empty `offers` array or empty `eligible_package_ids` list is a valid response meaning no matches, not an error.

| Field        | Type   | Required | Description                                                                                                                                                                                                                                                                                                                                           |
| ------------ | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`       | string | Yes      | `"error"`. Message type discriminator for deserialization.                                                                                                                                                                                                                                                                                            |
| `request_id` | string | Yes      | Echo of the original request's `request_id`.                                                                                                                                                                                                                                                                                                          |
| `code`       | enum   | Yes      | Machine-readable error code: `invalid_request`, `unknown_package`, `seller_not_authorized`, `rate_limited`, `timeout`, `internal_error`, `provider_unavailable`. `seller_not_authorized` is returned at [package sync](#package-sync) time when an AvailablePackage declares a `seller_agent.agent_url` not present in the publisher's adagents.json. |
| `message`    | string | No       | Human-readable error description for debugging.                                                                                                                                                                                                                                                                                                       |

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/trusted-match/provider-registration.json`).

| Setting          | Type          | Required    | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| ---------------- | ------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider_id`    | string        | Yes         | Stable identifier for this provider registration. Used in logs, metrics, cache keys, and as the key in `tmpx_providers` on the identity-match response so the publisher can route each provider's TMPX `macros[]` to that provider's pre-configured ad-server slots (registered in `tmpx_macros` — macro names MUST NOT be derived from `provider_id` at runtime). Pattern: `^[A-Za-z0-9_]+$`, max length 64 — a safe alphanumeric/underscore charset so the value can appear in operational surfaces (logs, metrics, dashboards) without quoting.                                                                                                                                                                                                                                                                                                    |
| `endpoint`       | URL           | Yes         | Provider's base URL. The router appends `/context` or `/identity` when dispatching.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `context_match`  | bool          | No          | Provider handles Context Match requests. At least one of `context_match` or `identity_match` must be true.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `identity_match` | bool          | No          | Provider handles Identity Match requests. At least one of `context_match` or `identity_match` must be true.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `countries`      | List\<string> | Conditional | ISO 3166-1 alpha-2 country codes this provider serves. MUST be present and non-empty when `identity_match` is true.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `uid_types`      | List\<string> | Conditional | Identity types this provider can resolve (from `uid-type` enum). The router selects providers whose `uid_types` overlaps with any `uid_type` in the request's `identities` array, and filters the forwarded `identities` to the intersection — providers MUST NOT receive tokens for types they did not declare. MUST be present and non-empty when `identity_match` is true.                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `properties`     | List\<UUID>   | No          | Property RIDs this provider serves. When absent, the provider serves all properties.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `timeout_ms`     | integer       | No          | Per-provider timeout in milliseconds. Must be ≤ the router's overall `latency_budget_ms`. Default: 50.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `priority`       | integer       | No          | Provider ordering for merge conflict resolution. Lower values = higher priority. Default: 0.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `tmpx_macros`    | List\<string> | No          | Stable, provider-namespaced ad-server macro names this provider's TMPX response fills, ordered (e.g. `["PIN_TMPX_1", "PIN_TMPX_2"]`). Publishers traffic these exact names in their ad server; the router places each provider's TMPX chunks into the matching slots on the identity-match response. Names MUST match pattern `^[A-Z][A-Z0-9_]*$`. Capped at 2 entries in v1; the cap MAY rise without a shape change. A provider that emits TMPX (i.e. populates `tmpx_macros` on its identity-match response) MUST also register this list, otherwise the router has no slot names to forward into `tmpx_providers`; the schema cannot enforce this because "emits TMPX" is not a schema-visible predicate. Macro names MUST NOT be derived from `provider_id` at runtime — trafficking is configured against these registered names ahead of time. |
| `status`         | enum          | No          | Provider lifecycle status: `active`, `inactive`, or `draining`. Default: `active`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |

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.

State transitions are immediate in static configuration (reload the config) and take effect within one refresh cycle in dynamic registration.

### Provider registration security

**Endpoint URL validation (SSRF).** Both static configuration and dynamic registration MUST validate provider `endpoint` URLs against the canonical [Webhook URL validation (SSRF)](/docs/building/implementation/security#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](#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 the `trusted_match` field. Buyers see this on `get_products` and know what TMP capabilities are available.

| Field            | Type                 | Required | Description                                                                                                                                                                                                                                                                                                                                         |
| ---------------- | -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `context_match`  | bool                 | Yes      | Product supports Context Match requests.                                                                                                                                                                                                                                                                                                            |
| `identity_match` | bool                 | No       | Product supports Identity Match requests. Default: false.                                                                                                                                                                                                                                                                                           |
| `response_types` | List\<string>        | No       | What the publisher can accept back: `activation` (default), `catalog_items`, `creative`, `deal`.                                                                                                                                                                                                                                                    |
| `dynamic_brands` | bool                 | No       | Whether the buyer can select a brand at match time. When false (default), brand must be on the media buy. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements.                                                                                                  |
| `providers`      | List\<ProviderEntry> | No       | TMP providers integrated with this product's inventory. Each entry identifies a provider by `agent_url` (from the registry) and declares what match types it supports. The product-level `context_match` and `identity_match` booleans declare overall support; per-provider booleans declare which provider handles each. Enables buyer discovery. |

#### ProviderEntry

| Field            | Type          | Required | Description                                                                                                                                                                                                                                                                                                                                              |
| ---------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent_url`      | string (URI)  | Yes      | Provider's agent URL from the registry. Canonical identifier for this TMP provider. Compared against the router's provider registry using the [AdCP URL canonicalization rules](/docs/reference/url-canonicalization), not byte-equality.                                                                                                                |
| `context_match`  | bool          | No       | Whether this provider handles context match for this product. Default: false.                                                                                                                                                                                                                                                                            |
| `identity_match` | bool          | No       | Whether this provider handles identity match for this product. Default: false.                                                                                                                                                                                                                                                                           |
| `countries`      | List\<string> | No       | ISO 3166-1 alpha-2 country codes this provider serves. The router filters providers by the request's `country` field. Required when `identity_match` is true.                                                                                                                                                                                            |
| `uid_types`      | List\<string> | No       | Identity types this provider can resolve (from `uid-type` enum). The router selects providers whose `uid_types` overlaps with any `uid_type` in the request's `identities` array, and filters the forwarded `identities` to the intersection — providers MUST NOT receive tokens for types they did not declare. Required when `identity_match` is true. |

## 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 the `AvailablePackage` 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.

<Note>
  `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](/docs/reference/experimental-status) for the 3.x-to-3.x evolution policy.
</Note>

### AvailablePackage

| Field          | Type            | Required | Description                                                                                                                                                                                                                           |
| -------------- | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package_id`   | string          | Yes      | Unique identifier for the package.                                                                                                                                                                                                    |
| `media_buy_id` | string          | Yes      | Media buy this package belongs to.                                                                                                                                                                                                    |
| `seller_agent` | SellerAgentRef  | Yes      | Seller agent that owns this package. `agent_url` MUST match one of `authorized_agents[].url` in the adagents.json authoritative for every property this package may serve. See [Seller Agent Attribution](#seller-agent-attribution). |
| `format_ids`   | List\<FormatId> | No       | Creative format identifiers eligible for this package. Uses the standard `{agent_url, id}` shape.                                                                                                                                     |
| `catalogs`     | List\<Catalog>  | No       | Buyer catalogs attached to this package, with selectors scoping which items are in play. Referenced by `catalog_id` against separately-synced catalog data.                                                                           |

#### SellerAgentRef

| Field       | Type         | Required | Description                                                                                                                                                                |
| ----------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent_url` | string (URI) | Yes      | The seller agent's API endpoint URL, exactly as declared in the property publisher's adagents.json `authorized_agents[].url`. HTTPS in production.                         |
| `id`        | string       | No       | Reserved for a future registry-assigned stable seller identifier. Not used today; included so the reference can absorb an opaque ID layer later without a breaking rename. |

### Seller Agent Attribution

TMP providers cache packages from many seller agents against many publishers. The `seller_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](https://adcontextprotocol.org/schemas/v3/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](#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 validate `seller_agent.agent_url` against the property publisher's adagents.json at sync time:

1. For each property the package may serve, fetch `/.well-known/adagents.json` on the property domain. If the file contains an `authoritative_location` pointer, follow it at most one hop to a same-scheme HTTPS URL; do not chain further. Both the initial fetch and the `authoritative_location` hop MUST apply the [Webhook URL validation (SSRF)](/docs/building/implementation/security#webhook-url-validation-ssrf) rules — HTTPS-only, 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 transparent redirects, and the TCP connection pinned to the validated IP. The inbound-fetch rules referenced for [Provider registration security](#provider-registration-security) apply equally to this outbound fetch.
2. Confirm that `seller_agent.agent_url` appears in `authorized_agents[].url` and that any `property_ids`, `collections`, `placement_ids`, `placement_tags`, `countries`, `effective_from`, and `effective_until` constraints on that entry permit the package's scope. The URL comparison uses the [AdCP URL canonicalization rules](/docs/reference/url-canonicalization) — canonicalize both values before matching, never byte-equality. Reject `seller_agent.agent_url` values that do not use the `https://` scheme with `seller_not_authorized`; non-HTTPS seller URLs have no transport-integrity guarantee and cannot be trusted as an authorization key.
3. On mismatch, reject the sync operation for that `AvailablePackage` with an `error` response using `code: seller_not_authorized`. Other packages in the same sync batch are unaffected. The exact wire shape of the sync error is deployment-specific; `code` is the machine-readable reason.

**Cache and re-validation.** The validation result SHOULD be cached for at most 5 minutes, matching the recommended adagents.json cache window. Providers MUST re-validate on cache expiry and SHOULD surface sustained `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

| Actor            | Sync time                                                                                                                                                                                                                       | Request time                                                                                                                                                                                                                                                                                                       |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Seller agent** | Include its own adagents-registered `agent_url` as `seller_agent.agent_url` on every `AvailablePackage` it syncs. MUST be the URL the publisher has attested in `authorized_agents[].url`.                                      | No new behavior. The offer MAY echo `seller_agent`; omitting it is fine — the router can stamp from cache.                                                                                                                                                                                                         |
| **Publisher**    | Keep `authorized_agents` in adagents.json accurate, including scope constraints (`property_ids`, `placement_ids`, `countries`, effective windows).                                                                              | No new behavior.                                                                                                                                                                                                                                                                                                   |
| **Router**       | No new behavior.                                                                                                                                                                                                                | MAY stamp `seller_agent` on offers that arrive without it, using its cached package→seller binding. Both `context_match_request` and `identity_match_request` carry `seller_agent_url` directly per their schemas; the router MUST forward it unchanged and MUST NOT use it to filter or re-route the request.     |
| **Provider**     | Validate `seller_agent.agent_url` against the property's adagents.json per [Sync-Time Validation](#sync-time-validation). Reject unauthorized packages with `seller_not_authorized`. Store the binding with the cached package. | Echo `seller_agent` on offers it produces. The cached binding is immutable for an in-flight decision, but providers MUST treat packages whose authorization has expired (removal from `authorized_agents` or `effective_until` passed) as `unknown_package` on subsequent requests until the package is re-synced. |

### What This Is Not

* **Not a per-user filter.** `seller_agent_url` selects 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](#package-set-decorrelation) exists to prevent. The package-side `seller_agent` attribution 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_id` and TAG-IDs serve a distinct financial-audit identity space. They remain on `adagents.json` `contact` — 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 `SellerAgentRef` through the reserved `id` slot or an `ext` field without breaking changes. When a future release populates both `agent_url` and `id`, `agent_url` remains authoritative and `id` is 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_ids` from Identity Match and let the buyer evaluate against the full active set it has registered for `seller_agent_url`. When `package_ids` IS 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_url` and `package_ids` are present, the buyer evaluates against the intersection of its registered active set and `package_ids`; unknown IDs in `package_ids` MUST 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_url` is 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 `consent` is 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 signed `provider_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.

Signatures also bind to `seller_agent_url`, the asking seller's agent URL. `seller_agent_url` selects which seller's registered active package set the receiver evaluates against (see [Seller Agent Attribution](#seller-agent-attribution)) and is the lookup key receivers use to resolve the publisher's signing keys for verification. Including it in the signed bytes prevents a captured signature from being replayed under a different seller's identity to read another seller's offers or active package set.

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.

| Header             | Value                                                    |
| ------------------ | -------------------------------------------------------- |
| `X-AdCP-Signature` | Base64-encoded (URL-safe, no padding) Ed25519 signature  |
| `X-AdCP-Key-Id`    | Key identifier from the agent's `agent-signing-key.json` |

#### Context Match signed fields

Concatenated in this order, UTF-8, newline-separated:

| Field                   | Source                                                                                                                                                                                                                                     |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`                  | `context_match_request`                                                                                                                                                                                                                    |
| `seller_agent_url`      | From the request body, compared using the [AdCP URL canonicalization rules](/docs/reference/url-canonicalization). Binds the signature to the asking seller — reusing a signature under a different `seller_agent_url` fails verification. |
| `property_rid`          | From the request body                                                                                                                                                                                                                      |
| `placement_id`          | From the request body                                                                                                                                                                                                                      |
| `package_ids`           | Sorted, comma-separated list of active package IDs                                                                                                                                                                                         |
| `provider_endpoint_url` | Provider's registered endpoint URL (exact string match with provider registration, no trailing slash)                                                                                                                                      |
| `daily_epoch`           | `floor(unix_timestamp / 86400)`                                                                                                                                                                                                            |

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 seller per placement per provider, the same signature can be cached and reused for all requests to the same `(seller_agent_url, placement_id, provider_endpoint_url)` triple within a 24-hour epoch. Cache keys MUST include `seller_agent_url` and `provider_endpoint_url` — reusing a signature across sellers or 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](https://datatracker.ietf.org/doc/html/rfc8785) serialization of the following canonical object. Using JCS eliminates delimiter-injection risk — raw `tcf_consent` / `gpp` / `us_privacy` / `package_id` values may contain any byte, but JSON string escaping in JCS renders any framing byte harmless.

| Field                     | Source                                                                                                                                                                                                                                     |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`                    | `"identity_match_request"`                                                                                                                                                                                                                 |
| `request_id`              | From the request body                                                                                                                                                                                                                      |
| `seller_agent_url`        | From the request body, compared using the [AdCP URL canonicalization rules](/docs/reference/url-canonicalization). Binds the signature to the asking seller — reusing a signature under a different `seller_agent_url` fails verification. |
| `identities_hash`         | Hex-encoded SHA-256 of the canonical `identities` bytes (see below)                                                                                                                                                                        |
| `consent`                 | The request's `consent` object verbatim, or `null` when absent                                                                                                                                                                             |
| `package_ids`             | The request's `package_ids` sorted lexicographically by UTF-8 byte order                                                                                                                                                                   |
| `sealed_credentials_hash` | Hex-encoded SHA-256 of the canonical `sealed_credentials` bytes (see below), or `null` when absent. **Experimental (`trusted_match.verified_identity`).**                                                                                  |
| `provider_endpoint_url`   | Provider's registered endpoint URL (exact string match with provider registration, no trailing slash). Binds the signature to a specific provider — reusing a signature across providers fails verification.                               |
| `daily_epoch`             | `floor(unix_timestamp / 86400)` as a JSON integer                                                                                                                                                                                          |

**Canonical `identities` bytes:** deduplicate on `(uid_type, user_token)` using byte-exact match (no case folding, no trimming) — this collapses duplicate tokens only — sort entries by `uid_type` (UTF-8 byte order), then by `user_token` (UTF-8 byte order), then serialize the resulting array of **complete identity objects** as [RFC 8785 JCS](https://datatracker.ietf.org/doc/html/rfc8785). Each object is serialized in full, **including any `attestation`** — so the signature covers the attestation, and a stripped, swapped, or injected per-identity attestation breaks signature verification (the dedup key on `(uid_type, user_token)` is for collapsing duplicate tokens, not a statement that `attestation` is excluded from the hash). SHA-256 the UTF-8 bytes; hex-encode for the signed input, use raw bytes for cache keys (both conventions hash the same preimage).

**Canonical `sealed_credentials` bytes** (experimental, `trusted_match.verified_identity`): sort entries by `audience_kid` (UTF-8 byte order), serialize the resulting array as [RFC 8785 JCS](https://datatracker.ietf.org/doc/html/rfc8785), SHA-256 the UTF-8 bytes. When the request carries no `sealed_credentials`, `sealed_credentials_hash` is `null`. Because `sealed_credentials` is part of the signed input, an injected or swapped sealed blob breaks signature verification; because `sealed_credentials_hash` is part of the dedup cache key (see [Caching](#caching)), a network-credential change repartitions the cache so stale eligibility is not served. The router re-signs over each provider's forwarded set after [route-by-`audience_kid`](#verified-identity-attestation) filtering.

The router filters `identities` per provider before forwarding (see [Identity Match fan-out](/docs/trusted-match/router-architecture#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](#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 via `agent-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 setting `revoked_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_at` is present and the signing epoch falls at or after the revocation timestamp.
* Re-fetch `agent-signing-key.json` on 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.

Keys may be removed from the trust anchor entirely once the cache TTL (recommended: 5 minutes) has elapsed across all verifiers. Removing the key earlier can produce a window where verifiers with a cached copy accept signatures that a fresh fetch would reject — keep the `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 a `country` 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) with `mode_base`:

| Parameter | Value                                                  |
| --------- | ------------------------------------------------------ |
| Mode      | `mode_base` — encrypt with recipient's public key only |
| KEM       | DHKEM(X25519, HKDF-SHA256)                             |
| KDF       | HKDF-SHA256                                            |
| AEAD      | ChaCha20-Poly1305                                      |

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):**

| Field     | Size    | Description                                         |
| --------- | ------- | --------------------------------------------------- |
| Version   | 1 byte  | Format version (`0x01`)                             |
| Timestamp | 4 bytes | uint32 Unix seconds — token creation time           |
| Country   | 2 bytes | ISO 3166-1 alpha-2, ASCII — identity data residency |
| Nonce     | 8 bytes | Random — replay deduplication at the master         |
| Count     | 1 byte  | Number of identity entries                          |

**Entries (repeated, buyer-configured priority order):**

| Field   | Size    | Description                                            |
| ------- | ------- | ------------------------------------------------------ |
| Type ID | 1 byte  | Fixed integer, defines token binary size               |
| Token   | N bytes | Raw binary identity token (size determined by Type ID) |

**Type ID registry:**

| ID | Token Type              | Binary size                                                 |
| -- | ----------------------- | ----------------------------------------------------------- |
| 1  | uid2                    | 32 bytes                                                    |
| 2  | euid                    | 32 bytes                                                    |
| 3  | id5                     | 32 bytes                                                    |
| 4  | rampid                  | 32 bytes                                                    |
| 5  | rampid\_derived         | 48 bytes                                                    |
| 6  | maid                    | 16 bytes                                                    |
| 7  | pairid                  | 32 bytes                                                    |
| 8  | hashed\_email           | 32 bytes                                                    |
| 9  | publisher\_first\_party | 32 bytes                                                    |
| 10 | world\_id\_nullifier    | 48 bytes (16-byte relying-party digest + 32-byte nullifier) |

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.

`world_id_nullifier` is **relying-party-scoped**: its token is the 16-byte SHA-256 digest of the proof's `relying_party_id` followed by the 32-byte nullifier. This entry is produced only from a **verified** attestation — the verifier-derived nullifier under the receiver-confirmed `rp_id` (see [Verified Identity Attestation](#verified-identity-attestation)). A sender-asserted `world_id_nullifier` in `identities[]` carries no trust and is never resolved or sealed; it cannot even form this token, since it lacks a verified `rp_id`. Unlike `uid2`/`id5`/`rampid`, the entry is not a graph identifier the buyer resolves — it is a proof-of-personhood pseudonym, valid only with an accompanying verified attestation. A World ID nullifier is meaningful only within the `rp_id` it was minted for, and that `rp_id` rides the request-side `attestation`, which does not round-trip into the token. Carrying the `rp_id` digest in the token lets the out-of-band impression tracker attribute the nullifier to its relying party — by matching the digest against the relying parties it accepts — and key frequency state on the `(rp_id, nullifier)` pair, so a nullifier under one relying party never shares cap state with another. The token carries no `rp_id` cleartext, and the digest width is a working-group open item.

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_keys` on agent authorization entries in `adagents.json`.
* Read replicas use the public key to encrypt. No per-replica key management.

Key rotation follows the same pattern as TMP signing keys: 5-minute cache TTL, kid prefix for versioning, 30-day grace period for old master keys.

### 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 the `serve_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

For each terminal surface the publisher trafficks the macro names each identity provider declared in its `tmpx_macros` registration entry (e.g. `PIN_TMPX_1`, `NOVA_TMPX_1`) — those names are the operational contract the ad-server line items target. At serve time, the publisher walks `tmpx_providers[provider_id].macros[]` from the response and substitutes each entry's `value` verbatim into the slot named by its `name`. The legacy `{TMPX}` macro (from the deprecated singular `tmpx` field) remains supported for consumers that haven't migrated.

* **Web, mobile, CTV (SSAI), audio (DAI):** Standard impression pixel — substitute each `tmpx_providers[*].macros[].value` into the matching ad-server macro slot.
* **CTV (client-side VAST):** Publisher performs the same per-macro substitution before the VAST document reaches the player.
* **DOOH:** Play-log-based — TMPX values are logged by the DOOH player and included in play log records rather than pixel URLs. Per-provider macro/value pairs MUST be logged with their `provider_id` so the buyer can route to the correct decryption master.

## Transport

* JSON over HTTP/2 POST. All implementations MUST use this transport.
* Each provider exposes two path-based endpoints under its base URL: `POST /context` for Context Match and `POST /identity` for Identity Match. The router dispatches requests by path, not by inspecting the message body.
* Each provider SHOULD expose `GET /health` at its base URL. The endpoint returns HTTP `200` with a JSON body `{"status": "ok"}` when the provider is ready to accept requests. Any non-`200` response 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 `type` field 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 the `type` field matches the endpoint: `context_match_request` on `/context`, `identity_match_request` on `/identity`. A mismatch MUST be rejected with HTTP `400`.
* Connections SHOULD be reused via HTTP/2 multiplexing.
* The router SHOULD maintain a connection pool to each buyer agent.
* The [adcp-go](https://github.com/adcontextprotocol/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 HTTP `200`:

| HTTP Status | Meaning                                                                                        |
| ----------- | ---------------------------------------------------------------------------------------------- |
| `200`       | Request processed. Body contains the TMP response (success, empty result, or TMP error).       |
| `400`       | Malformed request (invalid JSON, missing `type` field). Not a TMP response — no body to parse. |
| `503`       | Provider temporarily unavailable. The router should retry or skip.                             |

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_ttl` field (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, sealed_credentials_hash}`, where `identities_hash` is the SHA-256 of the canonical `identities` bytes defined in [Identity Match signed fields](#identity-match-signed-fields) (computed over the per-provider filtered subset); `package_ids_hash` is SHA-256 over the JCS serialization of the sorted `package_ids` array; `consent_hash` is SHA-256 over the JCS serialization of the request's `consent` object (or JCS `null` when the field is absent — this distinguishes "consent unknown" from an explicit-empty consent object); and `sealed_credentials_hash` (experimental, `trusted_match.verified_identity`) is the SHA-256 of the canonical `sealed_credentials` bytes defined in [Identity Match signed fields](#identity-match-signed-fields), or `null` when absent — so a network-credential change that shifts eligibility repartitions the cache rather than serving a stale response. JCS framing prevents delimiter-injection: raw consent strings or package IDs containing `|`, `,`, or `\n` cannot 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_sec` is 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_match` capability
* 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_match` capability
* 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
