The ZeroGPU Real Time Data module enriches an incoming OpenRTB request with IAB content categories derived from the publisher’s domain.
On each auction the module resolves the domain from the bid request, classifies
it with ZeroGPU’s zlm-v1-iab-domain-classifier model, and appends the
resulting categories to {site,app,dooh}.content.data as Seller-Defined
contextual segments under ext.segtax: 6 (IAB Content Taxonomy 2.2).
Because the segments are written to standard First Party Data fields, every bidder in the auction can read them. No bidder-specific integration is required.
The auction never waits on ZeroGPU. The hook reads an in-process cache and nothing else. When a domain is not yet cached, the auction proceeds unenriched and the classification is fetched in the background, so subsequent auctions on that domain are enriched from memory. Measured against the live API, the hook costs ~15µs whether the domain is cached or not, while the classification call it avoids takes ~0.9s.
Every failure mode is fail-open: if the ZeroGPU API is slow, unreachable, or returns an error, auctions simply go unenriched. The module never rejects a request, never delays one, and never creates bids.
This module is available in PBS-Go.
A ZeroGPU API key is required. Sign in at platform.zerogpu.ai/dashboard, or start from zerogpu.ai and click Start Building.
The classification model is documented at:
The module runs at the processed_auction_request stage. api_key is the only
required parameter.
hooks:
enabled: true
modules:
zerogpu:
rtd:
enabled: true
api_key: ${ZEROGPU_API_KEY}
host_execution_plan: >
{
"endpoints": {
"/openrtb2/auction": {
"stages": {
"processed_auction_request": {
"groups": [{
"timeout": 10,
"hook_sequence": [{
"module_code": "zerogpu.rtd",
"hook_impl_code": "zerogpu-rtd-processed-auction-request"
}]
}]
}
}
}
}
}
The equivalent JSON configuration:
{
"hooks": {
"enabled": true,
"modules": {
"zerogpu": {
"rtd": {
"enabled": true,
"api_key": "<your ZeroGPU API key>",
"timeout_ms": 2000,
"cache_ttl_seconds": 86400,
"min_score": 0.5
}
}
},
"host_execution_plan": {
"endpoints": {
"/openrtb2/auction": {
"stages": {
"processed_auction_request": {
"groups": [
{
"timeout": 10,
"hook_sequence": [
{
"module_code": "zerogpu.rtd",
"hook_impl_code": "zerogpu-rtd-processed-auction-request"
}
]
}
]
}
}
}
}
}
}
}
The group timeout only has to cover an in-memory cache read, so a small value
is correct. It is unrelated to timeout_ms, which bounds the background
warm-up and never applies to the auction path.
To run the module only for specific accounts, either place the execution plan in
the account config, or keep it host-level and restrict it with
account_filter.allow_list:
hooks:
modules:
zerogpu:
rtd:
enabled: true
api_key: ${ZEROGPU_API_KEY}
account_filter:
allow_list: ["1001", "1002"]
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enabled |
boolean | yes | false |
Enables the module. |
api_key |
string | yes | - | ZeroGPU API key, sent as the x-api-key header. |
endpoint |
string | no | https://api.zerogpu.ai/v1/responses |
Classification endpoint. Overridable for a different region or a proxy; the Responses API request and response shape is assumed. |
model |
string | no | zlm-v1-iab-domain-classifier |
Model to classify with. |
timeout_ms |
int | no | 2000 |
HTTP timeout for a background classification call. Never applies to the auction path. |
cache_ttl_seconds |
int | no | 86400 |
How long a successful classification is cached. |
negative_cache_ttl_seconds |
int | no | 300 |
How long an empty result or a stable failure (400/401/403/420) is cached. |
retry_cache_ttl_seconds |
int | no | 30 |
How long a transient failure (timeout, 5xx) is cached before retrying. |
cache_size |
int | no | 10485760 |
Cache size in bytes. Minimum 524288. |
min_score |
float | no | 0.5 |
Minimum confidence a category must have to be emitted. |
max_segments |
int | no | 0 |
Maximum segments per taxonomy. 0 means unlimited. |
data_provider_name |
string | no | zerogpu.ai |
Value written to the name field of each injected data object. |
enrich_content_1_0 |
boolean | no | false |
Also emit IAB Content Taxonomy 1.0 codes under ext.segtax: 1. |
enrich_user_audience |
boolean | no | false |
Also emit IAB Audience Taxonomy 1.1 segments to user.data under ext.segtax: 4. See Privacy. |
account_filter.allow_list |
list of strings | no | [] |
Account IDs permitted to use the module. Empty means all accounts. |
On each auction the module looks the domain up in its local cache:
cache_ttl_seconds (24h by default).Concurrent auctions for the same uncached domain collapse onto a single outbound request, so a burst of traffic on a new domain does not produce a burst of API calls.
The practical cost of this design is that the first impressions on a newly-seen domain go unenriched - roughly the duration of one classification call. After that the domain is cached for 24 hours.
The domain is taken from the first usable value of:
site.domainsite.page (the hostname is parsed out of the URL)site.publisher.domainapp.domainapp.bundleapp.publisher.domaindooh.domaindooh.publisher.domainValues carrying no domain signal - localhost, bare IP addresses, numeric iOS
store IDs - are skipped. If nothing resolves, the module does nothing.
The value is then normalized so that every spelling of a site shares one cache
entry: the scheme, path, query, port and any trailing dot are dropped, case is
folded, and a leading www., m. or amp. is removed. So
https://AMP.Example.com/article?x=1 and example.com are one entry, not two.
Other subdomains are preserved - blog.example.com is classified separately
from example.com, because they host different content. A variant prefix is
only stripped when at least two labels remain, so amp.dev stays amp.dev.
Given a classification of coursera.com, the module appends:
{
"site": {
"content": {
"data": [{
"name": "zerogpu.ai",
"ext": { "segtax": 6 },
"segment": [{ "id": "132" }, { "id": "148" }]
}]
}
}
}
Existing content.data entries are preserved. Enrichment is idempotent: if a
data object from the same provider already exists for a taxonomy, nothing is
appended.
Taxonomy identifiers follow the IAB segtax registry and Prebid’s Segments and Taxonomy table:
| segtax | Taxonomy | ORTB location | Enabled by |
|---|---|---|---|
| 6 | IAB Content Taxonomy 2.2 | {site,app,dooh}.content.data |
on by default |
| 1 | IAB Content Taxonomy 1.0 (deprecated) | {site,app,dooh}.content.data |
enrich_content_1_0 |
| 4 | IAB Audience Taxonomy 1.1 | user.data |
enrich_user_audience |
If the publisher sets ext.prebid.data.bidders, Prebid Server’s First Party
Data processing removes site.content.data and user.data from the global
request and redistributes them to the listed bidders only. This is core
behaviour, not module behaviour, but it determines which bidders see the
injected segments.
The default configuration sends only a domain to ZeroGPU and writes only contextual data. No user identifiers, device data, or geographic information leave Prebid Server, and nothing is written to user-scoped ORTB fields. Under the default configuration the module has no user privacy implications, so no TCF-EU, CCPA, USNat, or TCF-CA handling is required.
enrich_user_audience is off by default and should stay off unless the host has
made a deliberate decision. When enabled, IAB Audience Taxonomy segments are
written to user.data. Two caveats:
user.data presents contextual inference as audience
data.enrichUfpd
Activity Control. PBS-Go
does not currently expose activity controls to modules, so the module cannot
perform that check on the host’s behalf.The module does not create bids and does not add pixels to creatives.
Each invocation emits one activity named zerogpu-rtd-domain-classification:
| Outcome | Activity status | Result status | Values |
|---|---|---|---|
| Segments injected | success |
modify |
domain, content_2_2_count, content_1_0_count, audience_count |
| Nothing to inject | success |
allow |
reason |
Because classification happens off the auction path, a failed warm-up is not
attributable to any one auction. Warm-up failures are logged instead: warn for
a rejected domain, error for an authentication or quota problem, and info
for transient failures.
Send an auction request with "test": 1 and ext.prebid.trace: "verbose" and
inspect ext.prebid.modules in the response.
| Symptom | Likely cause |
|---|---|
Analytics reason is no domain available on the request |
Neither site, app, nor dooh carried a usable domain. |
Analytics reason is domain not yet cached; warming in the background |
Expected on the first auctions for a domain. If it persists, the warm-up is failing - check the server log. |
Analytics reason is no categories available for this domain |
Either the classifier returned nothing above min_score, or a warm-up failure is being suppressed by the negative cache. |
| Log reports status 401 or 403 | The api_key is invalid or lacks access to the model. |
| Log reports status 420 | The ZeroGPU quota is exhausted. |
| A domain never becomes enriched | Warm-ups are failing. Failures are negative-cached, so check the log rather than the auction trace. |