SceneContext Labs / clearTag
bench · platforms · live traffic

API reference

Two ways in. Point an ad tag at /tag and clearTag cleans and decorates it in flight, or call /v1/enrich and place the values yourself. Both return the same signals for the same content.

Enrichment

GET/v1/enrich cacheable lookup

Content in, signals out. Every field is optional; send what you have, as your partner actually sends it — macros, mixed delimiters, wrong language and all.

curl "https://cleartag.scenecontext.io/v1/enrich?\
channel=WELT%20HD&genre=Nachrichten,%20Politik&title=Tagesschau"
Param
channelChannel or station name, in any spelling.
genreOne or more genres. Commas, pipes, slashes, "und" and "&" all separate.
title seriesProgramme and series names.
catIAB codes (IAB17, 17) or labels.
keywordsFree-form terms, comma separated.
idYour own content id, if you have a stable one.
urlPage or content URL.
countryISO alpha-2 (DE, CH). On POST it belongs at device.geo.country, where OpenRTB already puts it. Not a targeting value — it decides which market a channel name resolves to, so without it a German channel can match a same-named channel elsewhere.
livestreamOptional hint for linear or FAST inventory. Send it only where it is true — omitted means unknown, not video-on-demand, and a mixed catalogue is the normal case. Resolution does not depend on it.

Responses carry Cache-Control: public, max-age=300. The same content always resolves to the same signals, so put a CDN or edge cache in front of this and an SSAI stitcher can call it per ad break.

POST/v1/enrich when you have a whole tag
curl -X POST https://cleartag.scenecontext.io/v1/enrich \
  -d '{"content":{"channel":"WELT HD","genre":"Nachrichten, Politik"},
       "tag":"https://securepubads.g.doubleclick.net/gampad/ads?iu=..."}'

Same fields, nested under content, plus two extras:

tagAn ad tag URL. Supply it and the response also carries decorated_tag, ready to fire.
junk"strip" removes publisher keys whose value is only a macro or placeholder. Off by default, which keeps every key exactly as sent.
Why two verbs. Enrichment is a pure lookup, so GET is both the honest verb and the cacheable one — and caching is what makes this viable at ad-request latency. POST earns its place for tags: an ad tag URL runs to thousands of characters, nests its own query string inside cust_params, and carries device ids and consent strings. Passing one as a query parameter means triple encoding and writes personal data into every access log and proxy on the way. Use GET to look up signals, POST to hand over a tag.

Response

{
  "ok": true,
  "enriched": true,
  "id": "SC5fMRINbiJdcBaL-0",
  "content":  { "content_channel": "welt", "content_genre": "news" },
  "emitted":  { "channel": "welt", "genre": "news",
                "id": "SC5fMRINbiJdcBaL-0", "seg": "current-events,news" },
  "segments": [ { "slug": "news", "score": 0.666 } ],
  "openrtb":  { "content": { ... }, "key_val": "channel=welt|genre=news|..." }
}
Field
emittedThe key-values to place. Identical for every ad server, so the same values work whichever transport your partner uses.
idThe content id. Stable for the same content across tags and ad servers — this is what makes an impression identifiable downstream.
openrtbThe same enrichment as a bid-request content object, plus PubMatic-style key_val.
enrichedfalse with a reason when nothing usable survived. Never an error — see below.
Fail-open. When nothing usable arrives you get enriched: false and a reason, not a 4xx. On the proxy path the original tag is redirected to unchanged. An ad request is never lost because enrichment failed.

Proxy

GET/tag 302 to your ad server
/tag?tag=<url-encoded ad tag>[&channel=…&genre=…&junk=strip]

Cleans, resolves and decorates, then redirects. A 302 rather than a fetch, so your ad server still sees the real device IP and user agent — targeting, frequency capping and fraud signals are unchanged. Explicit params override whatever the tag carries.

GET/preview same pipeline, JSON instead of a redirect

Identical parameters to /tag. Returns what would be sent, including received_keys classifying each key you sent as read, junk or ignored. This is what the bench renders. Never logged.

What a deployment emits

Some signals are an ad-ops decision rather than a technical one. These live in data/policy.json and apply on POST /reload — no release:

emit.titleContent title as ct_ti and OpenRTB content.title. Off if a programme name is competitively sensitive, or simply should not leave your ad server.
emit.seriesSeries name. Same consideration.
emit.keywordsProgramme keywords in the content object. Never in cust_params — keywords are unbounded and the reportable key-value ceiling is what publishers hit first.
emit.sc_segmentsSceneContext segment ids in the content object. Useful to a buyer onboarded onto that vocabulary, noise to everyone else.
ppsj is never affected. No emission switch can disable the IAB taxonomy: it is the standardised, buyer-facing path, and losing it silently is the problem this exists to solve. The taxonomy version is set by data/pps.json, so a different IAB release is a data change rather than a code one.
Titles on live channels. A live channel with no title in the tag gets no title back: the schedule data carries the air window, keywords, mood, audience and segments, but no programme name. fill.title_from_resolver exists for when that changes; it will never invent one from keywords.

Limits

/tagToken bucket per client address. Over the limit it degrades to an unenriched passthrough rather than failing — a viewer never loses an ad because we were busy.
/v1/enrichIts own bucket, sized for server-side integration rather than for someone refreshing a dashboard. Ask for a partner key and we raise it for you specifically.
Body64 KB on POST.
Authentication. The public bench is open, which is right for a coordinated test and wrong for production traffic. Partner deployments issue a key, sent as X-API-Key: <key> or Authorization: Bearer <key>. Quota is then counted per key rather than per address — an exchange calls from many hosts, and a shared NAT would otherwise let one caller exhaust everyone else's.