From you
A server that can POST OpenRTB 2.5/2.6 JSON to our endpoint and parse a VAST 4.x response in the bid body.
Connect your CTV / video supply directly to Vuukle’s programmatic demand stack. This page is the complete spec — your engineering team should be able to ship the integration in a day.
From you
A server that can POST OpenRTB 2.5/2.6 JSON to our endpoint and parse a VAST 4.x response in the bid body.
From us
Your unique Publisher ID — the same seller ID we list in vuukle.com/sellers.json. Provided as part of onboarding.
POST https://rtb.vuukle.com/openrtb2/video?pub=<YOUR_PUBLISHER_ID>Content-Type: application/json| Setting | Value |
|---|---|
| Method | POST |
| Content-Type | application/json |
| Protocol | OpenRTB 2.5 or 2.6 |
| Recommended client timeout | 1200 ms (we cap server-side at 1500 ms) |
| Auction model | First-price (at: 1) |
This is the smallest valid request that will produce bids. Replace <...> placeholders with real values. Your Publisher ID goes in the URL only — you don’t need to repeat it inside the body.
{ "id": "<your_unique_request_id>", "tmax": 1000, "at": 1, "cur": ["USD"],
"imp": [{ "id": "1", "bidfloor": 10.0, "bidfloorcur": "USD", "secure": 1, "video": { "mimes": ["video/mp4", "video/webm", "application/javascript"], "w": 1920, "h": 1080, "minduration": 5, "maxduration": 30, "protocols": [2, 3, 5, 6, 7, 8], "linearity": 1, "placement": 1, "plcmt": 1, "startdelay": 0, "skip": 0, "api": [2], "playbackmethod": [2] } }],
"app": { "bundle": "roku.popcornflix", "name": "Popcornflix", "storeurl": "https://channelstore.roku.com/details/6119/popcornflix" },
"device": { "ua": "<player_user_agent>", "ip": "<viewer_ipv4>", "ifa": "<roku_rida_or_amazon_afai_or_etc>", "devicetype": 3, "make": "Roku", "os": "Roku OS", "geo": { "country": "USA", "region": "CA" } },
"user": { "id": "<viewer_id_or_ifa>" }, "regs": { "coppa": 0, "gdpr": 0, "ext": { "us_privacy": "1---" } }}{ "id": "<your_request_id>", "cur": "USD", "seatbid": [{ "seat": "pubmatic", "bid": [{ "id": "<bid_id>", "impid": "1", "price": 12.50, "adm": "<VAST>...</VAST>", "nurl": "https://...", "w": 1920, "h": 1080, "dur": 15 }] }]}The adm field contains VAST 4.x XML (inline or wrapper). Render it in your player as-is. Tracking pixels and event firing are embedded inside the VAST.
No body. Fall through to your next waterfall step. This is the most common response for any new integration during the first 24 hours of bidder learning.
{ "error": "missing publisher (set ?pub= or site.publisher.id / app.publisher.id)" }Common reasons:
| Reason | Fix |
|---|---|
missing publisher (...) | Add ?pub=... to URL or include app.publisher.id |
invalid json | Body wasn’t valid JSON |
bundle not authorized | Bundle isn’t on your allow-list — email adops@vuukle.com |
5xx responses indicate our service issue. Treat as no-bid and fall through. Do not retry within the same impression opportunity.
| Field | Notes |
|---|---|
id | Trace ID — keep stable across retries |
at | 1 (first-price) — required for CTV demand to bid correctly |
tmax | Auction timeout in ms — we recommend 1000 |
imp[0].bidfloor + bidfloorcur | Floor CPM in USD. Bidders must bid ≥ this or no-bid. |
imp[0].video.mimes | At minimum ["video/mp4"]. Adding video/webm, application/javascript (VPAID) widens demand. |
imp[0].video.w / h | Player dimensions |
imp[0].video.protocols | [2, 3, 5, 6, 7, 8] covers VAST 2/3/4 inline and wrapper |
imp[0].video.placement + plcmt | 1 for in-stream preroll |
app.bundle | The most important field. Drives our IPD detection, ad-slot mapping, and floor routing. Use the canonical store ID (e.g. roku.popcornflix, B07DBBTDBS). |
device.ua, device.ip, device.ifa, device.devicetype, device.make, device.os | Used by every bidder for inventory recognition |
These materially improve bid rate and CPM:
| Field | Why |
|---|---|
device.geo.country / region / zip / lat / lon | Required for any geo-targeted demand. Most CTV deals are US-only or US/CA. |
app.content.cat (IAB taxonomy), channel, genre, language, rating, len | Premium-content signals raise CPMs significantly |
app.name, app.storeurl | Helps bidders recognize the inventory in their reporting |
imp.video.skip, skipafter, skipmin | Non-skip inventory bids meaningfully higher |
regs.gdpr, user.consent, regs.ext.us_privacy | Required for compliant bidding in EU/CA |
bcat, badv | Category and advertiser blocklists — honored end-to-end |
Vuukle runs first-price auctions (at: 1). The CPM in imp.bidfloor is the minimum bidders must beat — if a bidder bids $12 against your $10 floor, you receive $12 (not a Vickrey-discounted second-price amount).
For most publishers, imp.bidfloor is hard — bidders must bid ≥ it or no-bid:
"imp": [{ "id": "1", "bidfloor": 10.0, "bidfloorcur": "USD"}]Sending different floors per inventory tier is just changing imp.bidfloor per request:
$6 → kids tier (mapped to PubMatic ad slot A)$10 → adult non-premium (mapped to PubMatic ad slot B)$15 → adult premium (mapped to PubMatic ad slot C)You don’t need to know which ad slot each bidder uses — just send the right floor and we route correctly.
For private marketplace deals at a contracted CPM with a specific buyer, use imp.pmp.deals[]:
"imp": [{ "id": "1", "bidfloor": 0.01, "pmp": { "private_auction": 1, "deals": [{ "id": "<deal_id>", "bidfloor": 18.0 }] }}]Contact your Vuukle account manager to set up a PMP deal.
You don’t need to construct schain — we build it for you. For transparency, here’s what we send downstream:
"source": { "fd": 0, "schain": { "complete": 1, "ver": "1.0", "nodes": [ { "asi": "<your_domain>", "sid": "<your_pub_id>", "hp": 1 }, { "asi": "vuukle.com", "sid": "<your_pub_id>", "hp": 1 } ] }}If you already construct your own schain, send it in the request — we append the vuukle.com node without disturbing your upstream nodes.
vuukle.com/sellers.json declares every publisher we have authorized — your entry is verifiable there.
| Device | device.devicetype | device.ifa value |
|---|---|---|
| Roku | 3 (Connected TV) | RIDA |
| Amazon Fire TV | 3 or 7 | AFAI |
| Apple TV | 3 | IDFA |
| Samsung Smart TV | 7 | TIFA |
| LG Smart TV | 7 | LGUDID |
| Android Mobile | 1 | AAID |
| iOS Mobile | 1 | IDFA |
| Desktop Web | 2 | (none — use IP-based) |
POST your BidRequest to https://rtb.vuukle.com/openrtb2/video?pub=<YOUR_PUBLISHER_ID>.
Set Content-Type: application/json.
Set "at": 1 (first-price) in the body.
Set client-side timeout to 1200 ms.
On HTTP 200: parse seatbid[0].bid[0].adm, render the VAST in your player. Tracking pixels and event firing happen automatically from inside the VAST.
On HTTP 204: no ad available, fall through to your next waterfall step.
On HTTP 5xx or timeout: treat as no-bid; do not retry within the same impression opportunity.
After your first 100 requests, email adops@vuukle.com so we can run a parse-confirmation diagnostic and confirm fill is flowing.
For transparency, every request you send goes through this pipeline:
Cloudflare edge — bot management, geo and ASN tagging, DDoS protection.
Vuukle edge — resolve your Publisher ID from ?pub=, load your config, validate request shape.
Enrichment — set app.ext.inventorypartnerdomain from app.bundle, append the vuukle.com node to schain, map floor tier to bidder ad slot.
Caps & IVT check — enforce daily per-tag impression caps, per-device frequency caps, daily revenue ceiling. Tag every request with an IVT signal (CF bot score, UA class, IFA presence).
Auction — fan out to all configured demand partners via Prebid Server. First-price selection across returned bids.
Response — return winning VAST or 204 to you.
Logging — every request is logged compactly to ClickHouse (publisher / bundle / IFA / country / IVT signals / outcome). Suspicious requests get full-body archival for forensic review.
You see all of this in your publisher dashboard.
To protect you against misconfigured floors, bot exploitation, and runaway financial exposure, every newly-onboarded publisher starts with conservative caps. They auto-loosen after a 7-day warm-up and can be adjusted any time by your Vuukle account manager.
| Cap | Default | Warm-up week |
|---|---|---|
| Impressions per tag per day | 50,000 | 10,000 |
| Impressions per IFA per day (freq cap) | 5 | 5 |
| Revenue per publisher per day | $500 | $100 |
If a cap is hit, we return HTTP 204 with response header X-Vuukle-Block-Reason: <reason> so your engineering team can monitor cap pressure.
Your domain needs an ads.txt (or app-ads.txt for in-app supply) that lists vuukle.com as an authorized seller, with your seller ID. We provide the exact line to add during onboarding.
We forward whatever you send in user.ext.eids — identity signals are not stripped. For premium demand, the more identity you include, the higher the bid prices.
No — both are at https://rtb.vuukle.com/openrtb2/video?pub=<YOUR_PUBLISHER_ID>. Same URL, same payload shape. Until you’ve completed onboarding, your Publisher ID is flagged “test mode” server-side: we log everything and run real auctions but don’t reconcile billing. Promotion to production happens after a successful 24-hour test run.
DigitalOcean NYC3. Typical round-trip latency from US-East CTV players is under 50 ms.
Your publisher dashboard at https://publishers.vuukle.com/<your_publisher_id> shows live: requests, bid rate, fill rate, CPM, revenue, IVT signals, and per-bundle breakdowns. Login is provisioned during onboarding.
Email adops@vuukle.com with the bundle ID, store URL, and IFA type. Most bundles are live within a business day. Until then, requests from unknown bundles return 204.
Email support is monitored Monday–Friday during business hours globally (we have offices in 4 time zones). For urgent integration issues during a test, we set up a shared Slack channel for sub-hour response.
| Onboarding & technical | adops@vuukle.com |
| Account / commercial | Your Vuukle account manager |
| Urgent integration issues | The shared Slack channel set up during onboarding |