From 4ac0f4f9f271dadc08e1d9edb398d7282dadbb16 Mon Sep 17 00:00:00 2001 From: Pier-Luc Caron St-Pierre Date: Thu, 13 Aug 2026 09:21:23 -0400 Subject: [PATCH 1/3] docs(demos): document keyword classifications in ctxSegments demo Follow-up to #313, which rendered keyword classifications but left the page prose describing a categories-only response. Update the vanilla ctx_segments demos (both the default and nocookies copies): - Intro now covers both classification methods (taxonomy categories and free-form keywords), not just categories. - Response-shape section leads with the ContextualSegmentsResponse signature block, then a Field/Description table per classification kind (categories, keywords) instead of the cramped inline shape. - Add a note that ctxTargetingKeyValues() derives GAM key-values from classifications.categories only; the keywords array is not part of the map. The SDK ContextualSegmentsResponse type and ctxTargetingKeyValues() keyword handling remain unchanged, tracked separately in PRODUCT-3852. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../nocookies/targeting/ctx_segments.html.tpl | 55 +++++++++++++++++-- demos/vanilla/targeting/ctx_segments.html.tpl | 55 +++++++++++++++++-- 2 files changed, 98 insertions(+), 12 deletions(-) diff --git a/demos/vanilla/nocookies/targeting/ctx_segments.html.tpl b/demos/vanilla/nocookies/targeting/ctx_segments.html.tpl index 3bfd06f..e8bab53 100644 --- a/demos/vanilla/nocookies/targeting/ctx_segments.html.tpl +++ b/demos/vanilla/nocookies/targeting/ctx_segments.html.tpl @@ -70,20 +70,58 @@

Example: contextual segments API

- Shows how to call ctxSegments() to classify a page URL against one or more contextual - taxonomies (e.g. the - IAB Content Taxonomy) and inspect the - categories the DCN returns for it. + Shows how to call ctxSegments() to classify a page URL and inspect the classifications the DCN + returns for it: taxonomy categories (e.g. against the + IAB Content Taxonomy) and/or free-form + keywords, depending on which classifiers the DCN has enabled.

// Classify the URL of the current page (defaults to window.location.href):
 optable.instance.ctxSegments();
 
 // Or classify an explicit URL:
 optable.instance.ctxSegments("https://optable.co/");
+

The response is a ContextualSegmentsResponse:

+
{
+  classifications: {
+    categories: [{ id, name, score, taxonomy }],
+    keywords: [{ keyword, prominence }],
+  },
+}

- The response is a ContextualSegmentsResponse of the form - { classifications: { categories: [{ id, name, score, taxonomy }] } }. + The classifications object groups results by classification method, and the DCN includes only + the methods it has enabled. Two methods exist today:

+
categories: taxonomy classifications
+ + + + + + + + + + + + + +
FieldDescription
idCategory id within its taxonomy (e.g. an IAB category id).
nameHuman-readable category name.
scoreRelevance score from 0 to 1.
taxonomyId of the taxonomy the category belongs to (e.g. iab_ct_3_1).
+
keywords: free-form terms extracted from the page
+ + + + + + + + + + + +
FieldDescription
keywordThe extracted keyword text.
prominence + Per-page ordinal rank (1 = most prominent), not a score, so prominences are not comparable across + pages. +

Alternatively, configure the SDK with initContextual set to a callback. The SDK will automatically call ctxSegments() for the URL of the current page on initialization, and @@ -196,6 +234,11 @@ optable.instance.ctxSegments("https://optable.co/");

// Emit only the "iab_ct_3_1" taxonomy, under the GAM key "ctx_iab":
 loadGAM(optable.instance.ctxTargetingKeyValues({ iab_ct_3_1: "ctx_iab" }));
+

+ Note: ctxTargetingKeyValues() currently derives key-values from + classifications.categories only. The classifications.keywords array is not part of + the map, so keyword classifications are not emitted as GAM key-values. +

diff --git a/demos/vanilla/targeting/ctx_segments.html.tpl b/demos/vanilla/targeting/ctx_segments.html.tpl index 7f19472..29e1a1e 100644 --- a/demos/vanilla/targeting/ctx_segments.html.tpl +++ b/demos/vanilla/targeting/ctx_segments.html.tpl @@ -69,20 +69,58 @@

Example: contextual segments API

- Shows how to call ctxSegments() to classify a page URL against one or more contextual - taxonomies (e.g. the - IAB Content Taxonomy) and inspect the - categories the DCN returns for it. + Shows how to call ctxSegments() to classify a page URL and inspect the classifications the DCN + returns for it: taxonomy categories (e.g. against the + IAB Content Taxonomy) and/or free-form + keywords, depending on which classifiers the DCN has enabled.

// Classify the URL of the current page (defaults to window.location.href):
 optable.instance.ctxSegments();
 
 // Or classify an explicit URL:
 optable.instance.ctxSegments("https://optable.co/");
+

The response is a ContextualSegmentsResponse:

+
{
+  classifications: {
+    categories: [{ id, name, score, taxonomy }],
+    keywords: [{ keyword, prominence }],
+  },
+}

- The response is a ContextualSegmentsResponse of the form - { classifications: { categories: [{ id, name, score, taxonomy }] } }. + The classifications object groups results by classification method, and the DCN includes only + the methods it has enabled. Two methods exist today:

+
categories: taxonomy classifications
+ + + + + + + + + + + + + +
FieldDescription
idCategory id within its taxonomy (e.g. an IAB category id).
nameHuman-readable category name.
scoreRelevance score from 0 to 1.
taxonomyId of the taxonomy the category belongs to (e.g. iab_ct_3_1).
+
keywords: free-form terms extracted from the page
+ + + + + + + + + + + +
FieldDescription
keywordThe extracted keyword text.
prominence + Per-page ordinal rank (1 = most prominent), not a score, so prominences are not comparable across + pages. +

Alternatively, configure the SDK with initContextual set to a callback. The SDK will automatically call ctxSegments() for the URL of the current page on initialization, and @@ -193,6 +231,11 @@ optable.instance.ctxSegments("https://optable.co/");

// Emit only the "iab_ct_3_1" taxonomy, under the GAM key "ctx_iab":
 loadGAM(optable.instance.ctxTargetingKeyValues({ iab_ct_3_1: "ctx_iab" }));
+

+ Note: ctxTargetingKeyValues() currently derives key-values from + classifications.categories only. The classifications.keywords array is not part of + the map, so keyword classifications are not emitted as GAM key-values. +

From 1c5d80fc5e7838474261d466e5cc62e86e8a306c Mon Sep 17 00:00:00 2001 From: Pier-Luc Caron St-Pierre Date: Fri, 14 Aug 2026 12:25:13 -0400 Subject: [PATCH 2/3] feat(sdk): emit keyword classifications in ctxTargetingKeyValues() The contextual segments API returns keyword classifications in classifications.keywords alongside classifications.categories, but ctxTargetingKeyValues() only emitted category ids grouped by taxonomy. Emit keywords as GAM targeting key-values too. By default they are emitted under the key "ctx_kw"; a new options argument lets callers rename the key (keywordKey), cap the count (maxKeywords, default 10), or opt out entirely (keywordKey: ""). Keyword values are ordered by prominence (most prominent first) and sanitized to GAM's value rules: lowercased, reserved characters stripped, and truncated to the 40-character value limit, which keeps output within GAM's 61,440-char ad-request URL budget. Add the ContextualKeyword type and keywords field on ContextualClassifications, and unit tests for default emission, key rename, opt-out, prominence sort, maxKeywords cap, sanitization, case-insensitive dedupe, and empty-key omission. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/edge/contextual_segments.test.ts | 119 +++++++++++++++++++++++++++ lib/edge/contextual_segments.ts | 101 ++++++++++++++++++++++- lib/sdk.ts | 8 +- 3 files changed, 224 insertions(+), 4 deletions(-) create mode 100644 lib/edge/contextual_segments.test.ts diff --git a/lib/edge/contextual_segments.test.ts b/lib/edge/contextual_segments.test.ts new file mode 100644 index 0000000..d9abe04 --- /dev/null +++ b/lib/edge/contextual_segments.test.ts @@ -0,0 +1,119 @@ +import { ContextualTargetingKeyValues } from "./contextual_segments"; +import type { ContextualSegmentsResponse } from "./contextual_segments"; + +// Helper to build a response with the given categories/keywords. Accepts a +// partial so tests can model responses where the DCN omitted a key entirely. +function response(classifications: Partial): ContextualSegmentsResponse { + return { classifications: classifications as ContextualSegmentsResponse["classifications"] }; +} + +describe("ContextualTargetingKeyValues keyword emission", () => { + const withKeywords = response({ + categories: [{ id: "53", name: "Business", score: 0.9, taxonomy: "iab_ct_3_1" }], + keywords: [ + { keyword: "startup", prominence: 2 }, + { keyword: "earnings", prominence: 1 }, + { keyword: "entrepreneur", prominence: 3 }, + ], + }); + + test("emits keywords by default under ctx_kw, sorted by prominence (1 = most prominent first)", () => { + expect(ContextualTargetingKeyValues(withKeywords)).toEqual({ + iab_ct_3_1: ["53"], + ctx_kw: ["earnings", "startup", "entrepreneur"], + }); + // Renaming taxonomy keys does not affect the default keyword key. + expect(ContextualTargetingKeyValues(withKeywords, { iab_ct_3_1: "ctx_iab" })).toEqual({ + ctx_iab: ["53"], + ctx_kw: ["earnings", "startup", "entrepreneur"], + }); + }); + + test("emits keywords under a caller-provided key when keywordKey is set", () => { + expect(ContextualTargetingKeyValues(withKeywords, undefined, { keywordKey: "kw" })).toEqual({ + iab_ct_3_1: ["53"], + kw: ["earnings", "startup", "entrepreneur"], + }); + }); + + test("opts out of keyword emission when keywordKey is an empty string", () => { + expect(ContextualTargetingKeyValues(withKeywords, { iab_ct_3_1: "ctx_iab" }, { keywordKey: "" })).toEqual({ + ctx_iab: ["53"], + }); + }); + + test("caps to maxKeywords by prominence", () => { + expect(ContextualTargetingKeyValues(withKeywords, undefined, { keywordKey: "ctx_kw", maxKeywords: 2 })).toEqual({ + iab_ct_3_1: ["53"], + ctx_kw: ["earnings", "startup"], + }); + }); + + test("defaults to the top 10 keywords when maxKeywords is not given", () => { + const many = response({ + categories: [], + keywords: Array.from({ length: 15 }, (_, i) => ({ keyword: `kw${i}`, prominence: i + 1 })), + }); + const result = ContextualTargetingKeyValues(many, undefined, { keywordKey: "ctx_kw" }); + expect(result.ctx_kw).toHaveLength(10); + expect(result.ctx_kw).toEqual(["kw0", "kw1", "kw2", "kw3", "kw4", "kw5", "kw6", "kw7", "kw8", "kw9"]); + }); + + test("sanitizes values to GAM rules: lowercases, strips disallowed chars, truncates to 40, drops empties", () => { + const dirty = response({ + categories: [], + keywords: [ + { keyword: "Q3 Earnings", prominence: 1 }, // uppercase + space (space allowed) + { keyword: "R&D,budgets", prominence: 2 }, // & and , are disallowed + { keyword: "!!!", prominence: 3 }, // becomes empty -> dropped + { keyword: "a".repeat(50), prominence: 4 }, // too long -> truncated to 40 + ], + }); + const result = ContextualTargetingKeyValues(dirty, undefined, { keywordKey: "ctx_kw" }); + expect(result.ctx_kw).toEqual(["q3 earnings", "rdbudgets", "a".repeat(40)]); + }); + + test("dedupes case-insensitively, preserving prominence order", () => { + const dupes = response({ + categories: [], + keywords: [ + { keyword: "B2B", prominence: 1 }, + { keyword: "b2b", prominence: 2 }, + { keyword: "pricing", prominence: 3 }, + ], + }); + expect(ContextualTargetingKeyValues(dupes, undefined, { keywordKey: "ctx_kw" }).ctx_kw).toEqual(["b2b", "pricing"]); + }); + + test("omits the keyword key entirely when there are no usable keywords", () => { + expect(ContextualTargetingKeyValues(response({ categories: [] }), undefined, { keywordKey: "ctx_kw" })).toEqual({}); + expect( + ContextualTargetingKeyValues(response({ categories: [], keywords: [] }), undefined, { keywordKey: "ctx_kw" }) + ).toEqual({}); + expect( + ContextualTargetingKeyValues( + response({ categories: [], keywords: [{ keyword: "***", prominence: 1 }] }), + undefined, + { + keywordKey: "ctx_kw", + } + ) + ).toEqual({}); + }); + + test("orders keywords with missing/invalid prominence last", () => { + const mixed = response({ + categories: [], + keywords: [ + { keyword: "second", prominence: 5 }, + { keyword: "last", prominence: undefined as unknown as number }, + { keyword: "first", prominence: 1 }, + ], + }); + expect(ContextualTargetingKeyValues(mixed, undefined, { keywordKey: "ctx_kw" }).ctx_kw).toEqual([ + "first", + "second", + "last", + ]); + }); +}); diff --git a/lib/edge/contextual_segments.ts b/lib/edge/contextual_segments.ts index e3c4af2..9e76d55 100644 --- a/lib/edge/contextual_segments.ts +++ b/lib/edge/contextual_segments.ts @@ -12,8 +12,15 @@ type ContextualCategory = { taxonomy: string; }; +type ContextualKeyword = { + keyword: string; + // Per-page ordinal rank (1 = most prominent), not a comparable score. + prominence: number; +}; + type ContextualClassifications = { categories: ContextualCategory[]; + keywords: ContextualKeyword[]; }; type ContextualSegmentsResponse = { @@ -40,6 +47,45 @@ async function ContextualSegments(config: ResolvedConfig, url: string): Promise< // passing to ad servers such as GAM via googletag.pubads().setTargeting(key, values). type ContextualTargetingKeyValues = Record; +// Options for including keyword classifications in the targeting key-values. +type ContextualTargetingKeyValuesOptions = { + // GAM key under which keyword classifications are emitted. Defaults to + // DEFAULT_KEYWORD_KEY when omitted, so keywords are emitted by default. Pass + // an empty string to opt out of keyword emission entirely. + keywordKey?: string; + // Maximum number of keyword values to emit, keeping the most prominent. GAM + // limits the whole ad request URL to 61,440 characters, so keyword output is + // bounded rather than dumping every keyword. Defaults to DEFAULT_MAX_KEYWORDS. + maxKeywords?: number; +}; + +// Default GAM key under which keyword values are emitted. +const DEFAULT_KEYWORD_KEY = "ctx_kw"; + +// Default number of keyword values emitted when maxKeywords is not provided. +const DEFAULT_MAX_KEYWORDS = 10; + +// Characters GAM reserves in custom targeting keys and values, stripped from +// keyword values before emitting. See "Valid key-value entry" in the GAM docs. +const GAM_RESERVED_CHARS = /["'=!+#*~^()<>[\],;&]/g; + +// GAM custom targeting values are capped at 40 characters. +const GAM_MAX_VALUE_LENGTH = 40; + +// Normalizes a keyword into a GAM-safe custom targeting value: lowercases (values +// are case-insensitive), strips GAM-reserved characters, collapses whitespace, +// and truncates to the 40-character value limit. Returns "" if nothing usable +// remains, so the caller can drop it. +function sanitizeGamValue(keyword: string): string { + return keyword + .toLowerCase() + .replace(GAM_RESERVED_CHARS, "") + .replace(/\s+/g, " ") + .trim() + .slice(0, GAM_MAX_VALUE_LENGTH) + .trim(); +} + // Builds GAM-style targeting key-values from a contextual segments response by // grouping category ids under a key derived from each category's taxonomy. // @@ -49,9 +95,21 @@ type ContextualTargetingKeyValues = Record; // With taxonomyKeys, only taxonomies present in the map are emitted, renamed to // the mapped key (filter + rename): // ContextualTargetingKeyValues(resp, { iab_ct_3_1: "foo" }) => { "foo": ["53", ...] } +// +// Keyword classifications are additionally emitted by default under +// DEFAULT_KEYWORD_KEY, sorted by prominence (1 = most prominent), sanitized to +// GAM's value rules, and capped to options.maxKeywords (default +// DEFAULT_MAX_KEYWORDS): +// ContextualTargetingKeyValues(resp) +// => { "iab_ct_3_1": ["53", ...], "ctx_kw": ["nba", "playoffs", ...] } +// +// Pass options.keywordKey to emit keywords under a different key, or an empty +// string to opt out of keyword emission entirely: +// ContextualTargetingKeyValues(resp, undefined, { keywordKey: "" }) // no keywords function ContextualTargetingKeyValues( response: ContextualSegmentsResponse | null, - taxonomyKeys?: Record + taxonomyKeys?: Record, + options?: ContextualTargetingKeyValuesOptions ): ContextualTargetingKeyValues { const result: ContextualTargetingKeyValues = {}; const categories = response?.classifications?.categories ?? []; @@ -82,9 +140,48 @@ function ContextualTargetingKeyValues( } } + // Default the keyword key so keywords are emitted without opting in; an + // explicit empty string opts out. + const keywordKey = options?.keywordKey ?? DEFAULT_KEYWORD_KEY; + if (keywordKey) { + const maxKeywords = options?.maxKeywords ?? DEFAULT_MAX_KEYWORDS; + const keywords = response?.classifications?.keywords ?? []; + // Sort by prominence (1 = most prominent); missing/invalid prominence sorts last. + const prominenceOf = (k: ContextualKeyword): number => + typeof k?.prominence === "number" ? k.prominence : Number.POSITIVE_INFINITY; + + const values: string[] = []; + const seen = new Set(); + for (const keyword of [...keywords].sort((a, b) => prominenceOf(a) - prominenceOf(b))) { + if (values.length >= maxKeywords) { + break; + } + if (typeof keyword?.keyword !== "string") { + continue; + } + const value = sanitizeGamValue(keyword.keyword); + // Drop empties and dedupe (values are case-insensitive to GAM). + if (value.length === 0 || seen.has(value)) { + continue; + } + seen.add(value); + values.push(value); + } + + if (values.length > 0) { + result[keywordKey] = values; + } + } + return result; } export { ContextualSegments, ContextualTargetingKeyValues }; export default ContextualSegments; -export type { ContextualCategory, ContextualClassifications, ContextualSegmentsResponse }; +export type { + ContextualCategory, + ContextualKeyword, + ContextualClassifications, + ContextualSegmentsResponse, + ContextualTargetingKeyValuesOptions, +}; diff --git a/lib/sdk.ts b/lib/sdk.ts index 3107fea..426606c 100644 --- a/lib/sdk.ts +++ b/lib/sdk.ts @@ -25,6 +25,7 @@ import { ContextualSegments, ContextualSegmentsResponse, ContextualTargetingKeyValues, + ContextualTargetingKeyValuesOptions, } from "./edge/contextual_segments"; import { sha256 } from "js-sha256"; import { Tokenize, TokenizeResponse } from "./edge/tokenize"; @@ -181,8 +182,11 @@ class OptableSDK { return response; } - ctxTargetingKeyValues(taxonomyKeys?: Record): ContextualTargetingKeyValues { - return ContextualTargetingKeyValues(this.contextualResponse, taxonomyKeys); + ctxTargetingKeyValues( + taxonomyKeys?: Record, + options?: ContextualTargetingKeyValuesOptions + ): ContextualTargetingKeyValues { + return ContextualTargetingKeyValues(this.contextualResponse, taxonomyKeys, options); } async tokenize(id: string): Promise { From 4f22af1e395e4184e5b803498215194d26d845cd Mon Sep 17 00:00:00 2001 From: Pier-Luc Caron St-Pierre Date: Fri, 14 Aug 2026 12:25:13 -0400 Subject: [PATCH 3/3] docs: document keyword key-values in ctxTargetingKeyValues() Reflect the default-on keyword emission in the README and the vanilla ctxSegments demos: - README: add keywords to the ContextualSegmentsResponse shape, describe the classifications container, and document the default ctx_kw key plus the keywordKey/maxKeywords options and opt-out. - demos: rewrite the GAM key-values section to describe default keyword emission (previously stated keywords were not emitted), and add an example of the default map returned by ctxTargetingKeyValues(). Example category ids and keywords are kept consistent with IAB Content Taxonomy 3.1 (Business/Advertising/Technology). Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 30 ++++++++++++++----- .../nocookies/targeting/ctx_segments.html.tpl | 22 ++++++++++++-- demos/vanilla/targeting/ctx_segments.html.tpl | 22 ++++++++++++-- 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c92ad2e..60f955a 100644 --- a/README.md +++ b/README.md @@ -426,30 +426,46 @@ The response has the shape: type ContextualSegmentsResponse = { classifications: { categories: { id: string; name: string; score: number; taxonomy: string }[]; + keywords: { keyword: string; prominence: number }[]; }; }; ``` +`classifications` groups results by classification method; the DCN populates only the methods it has enabled. `categories` are taxonomy classifications (each carrying its own `taxonomy`); `keywords` are free-form terms extracted from the page. A category's `score` is a relevance score from 0 to 1, whereas a keyword's `prominence` is a per-page ordinal rank (1 = most prominent), not a comparable score. + Each call to `ctxSegments()` caches its response on the SDK instance (calling it again refreshes the cache). When `initContextual: true`, the SDK calls `ctxSegments()` for you during initialization, so the cache is populated automatically. -> **Note:** The requested URL must already have been classified by the DCN. If the DCN has no classification for the URL, the response will contain an empty `categories` array. +> **Note:** The requested URL must already have been classified by the DCN. If the DCN has no classification for the URL, the response will contain empty `categories` and `keywords` arrays. #### Contextual targeting key-values -`ctxTargetingKeyValues(taxonomyKeys?)` reads the cached `ctxSegments()` response and builds a `Record` of category ids grouped by taxonomy, ready to pass to an ad server such as Google Ad Manager via `googletag.pubads().setTargeting()`. - -Without arguments, each taxonomy value is used as the key: +`ctxTargetingKeyValues(taxonomyKeys?, options?)` reads the cached `ctxSegments()` response and builds a `Record`, ready to pass to an ad server such as Google Ad Manager via `googletag.pubads().setTargeting()`. It emits category ids grouped by taxonomy, plus, by default, the page's keywords under the key `ctx_kw`: ```javascript sdk.ctxTargetingKeyValues(); -// => { "iab_ct_3_1": ["53", "91", "58", "115", "90", "52"] } +// => { +// "iab_ct_3_1": ["53", "91", "58", "115", "90", "52"], +// "ctx_kw": ["advertising", "programmatic", "ad tech"] +// } ``` -Pass a `taxonomyKeys` map to rename keys. Only taxonomies present in the map are emitted (filter + rename), which is useful when you only want to set keys you have configured in your ad server: +Pass a `taxonomyKeys` map to rename category keys. Only taxonomies present in the map are emitted (filter + rename), which is useful when you only want to set keys you have configured in your ad server: ```javascript sdk.ctxTargetingKeyValues({ iab_ct_3_1: "foo" }); -// => { "foo": ["53", "91", "58", "115", "90", "52"] } +// => { "foo": ["53", "91", "58", "115", "90", "52"], "ctx_kw": ["advertising", "programmatic", "ad tech"] } +``` + +Keyword values are ordered by `prominence` (most prominent first), capped to the top 10, and sanitized to GAM's value rules (lowercased, [reserved characters](https://support.google.com/admanager/answer/10020177) stripped, truncated to the 40-character value limit). Use the `options` argument to change the keyword key (`keywordKey`), change the cap (`maxKeywords`), or opt out of keyword key-values by passing an empty `keywordKey`: + +```javascript +// Rename the keyword key and emit only the top 5 keywords: +sdk.ctxTargetingKeyValues({ iab_ct_3_1: "foo" }, { keywordKey: "kw", maxKeywords: 5 }); +// => { "foo": ["53", ...], "kw": ["advertising", "programmatic", "ad tech", "marketing", "audience targeting"] } + +// Opt out of keyword key-values entirely: +sdk.ctxTargetingKeyValues(undefined, { keywordKey: "" }); +// => { "iab_ct_3_1": ["53", "91", "58", "115", "90", "52"] } ``` A typical Google Ad Manager activation uses a `loadGAM()` helper: diff --git a/demos/vanilla/nocookies/targeting/ctx_segments.html.tpl b/demos/vanilla/nocookies/targeting/ctx_segments.html.tpl index e8bab53..63fef96 100644 --- a/demos/vanilla/nocookies/targeting/ctx_segments.html.tpl +++ b/demos/vanilla/nocookies/targeting/ctx_segments.html.tpl @@ -199,6 +199,15 @@ optable.instance.ctxSegments("https://optable.co/"); runs:

loadGAM(optable.instance.ctxTargetingKeyValues());
+

+ By default the returned map has one key per taxonomy the DCN classified into (keyed by the raw taxonomy + value) plus the page's keywords under ctx_kw. For example, + ctxTargetingKeyValues() might return: +

+
{
+  "iab_ct_3_1": ["53", "91", "58", "115", "90", "52"],
+  "ctx_kw": ["advertising", "programmatic", "ad tech"]
+}

If you want loadGAM() to run as soon as the contextual segments arrive — without making a second ctxSegments() call — pass a callback to initContextual. The SDK fires the @@ -235,10 +244,17 @@ optable.instance.ctxSegments("https://optable.co/");

// Emit only the "iab_ct_3_1" taxonomy, under the GAM key "ctx_iab":
 loadGAM(optable.instance.ctxTargetingKeyValues({ iab_ct_3_1: "ctx_iab" }));

- Note: ctxTargetingKeyValues() currently derives key-values from - classifications.categories only. The classifications.keywords array is not part of - the map, so keyword classifications are not emitted as GAM key-values. + Keyword classifications are also emitted, by default under the GAM key ctx_kw. The values are + the page's keywords ordered by prominence (most prominent first), capped to the top 10, and + sanitized to GAM's value rules (lowercased, reserved characters stripped, truncated to 40 characters). Pass + keywordKey to rename the key or maxKeywords to change the cap, or set + keywordKey to an empty string to opt out of keyword key-values entirely:

+
// Rename the keyword key and emit only the top 5 keywords:
+loadGAM(optable.instance.ctxTargetingKeyValues({ iab_ct_3_1: "ctx_iab" }, { keywordKey: "kw", maxKeywords: 5 }));
+
+// Opt out of keyword key-values:
+loadGAM(optable.instance.ctxTargetingKeyValues(undefined, { keywordKey: "" }));
diff --git a/demos/vanilla/targeting/ctx_segments.html.tpl b/demos/vanilla/targeting/ctx_segments.html.tpl index 29e1a1e..745c0fe 100644 --- a/demos/vanilla/targeting/ctx_segments.html.tpl +++ b/demos/vanilla/targeting/ctx_segments.html.tpl @@ -197,6 +197,15 @@ optable.instance.ctxSegments("https://optable.co/"); runs:

loadGAM(optable.instance.ctxTargetingKeyValues());
+

+ By default the returned map has one key per taxonomy the DCN classified into (keyed by the raw taxonomy + value) plus the page's keywords under ctx_kw. For example, + ctxTargetingKeyValues() might return: +

+
{
+  "iab_ct_3_1": ["53", "91", "58", "115", "90", "52"],
+  "ctx_kw": ["advertising", "programmatic", "ad tech"]
+}

If you want loadGAM() to run as soon as the contextual segments arrive — without making a second ctxSegments() call — pass a callback to initContextual. The SDK fires the @@ -232,10 +241,17 @@ optable.instance.ctxSegments("https://optable.co/");

// Emit only the "iab_ct_3_1" taxonomy, under the GAM key "ctx_iab":
 loadGAM(optable.instance.ctxTargetingKeyValues({ iab_ct_3_1: "ctx_iab" }));

- Note: ctxTargetingKeyValues() currently derives key-values from - classifications.categories only. The classifications.keywords array is not part of - the map, so keyword classifications are not emitted as GAM key-values. + Keyword classifications are also emitted, by default under the GAM key ctx_kw. The values are + the page's keywords ordered by prominence (most prominent first), capped to the top 10, and + sanitized to GAM's value rules (lowercased, reserved characters stripped, truncated to 40 characters). Pass + keywordKey to rename the key or maxKeywords to change the cap, or set + keywordKey to an empty string to opt out of keyword key-values entirely:

+
// Rename the keyword key and emit only the top 5 keywords:
+loadGAM(optable.instance.ctxTargetingKeyValues({ iab_ct_3_1: "ctx_iab" }, { keywordKey: "kw", maxKeywords: 5 }));
+
+// Opt out of keyword key-values:
+loadGAM(optable.instance.ctxTargetingKeyValues(undefined, { keywordKey: "" }));