Adverteks OpenRTB Integration Guide
Adverteks is a real-time bidding exchange supporting OpenRTB 2.5. This guide covers everything external DSPs and SSPs need to connect, submit bids, receive win notices, and track ad events.
Base URL
Authentication
All partner management endpoints require an API key passed in the Authorization: Bearer {api_key} header. RTB bid endpoints are public — register as a partner first to receive your partner_id and api_key.
/api/rtb/bid, /api/rtb/win, /api/rtb/event) are public — use your partner_id inside request payloads as the identifier.
Rate Limits
| Endpoint | Limit | Window |
|---|---|---|
| POST /api/rtb/bid | 100 req/min | Per IP |
| GET /api/ad/serve/:zoneKey | 1,000 req/min | Per zone key |
| POST /api/auth/* | 3 req/hr | Per IP |
| General API | 30 req/min | Per IP |
Rate limit exceeded → HTTP 429 Too Many Requests with Retry-After header.
Bid Request
DSPs can submit bids directly to Adverteks via the inbound bid endpoint. Adverteks runs a unified second-price auction combining your bid with internal campaigns. Hard timeout: 200ms.
Submit an OpenRTB 2.5 bid request. Returns a full bid response on match, HTTP 204 on no-fill.
Bid Request Object
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Required | Unique auction ID. Echoed in response and used in win notice as ${AUCTION_ID}. |
| imp | array | Required | Impression objects. At least one required. |
| imp[].id | string | Required | Impression identifier within this request. |
| imp[].banner | object | Optional | Banner object with w and h. Supported: 728×90, 300×250, 320×50. |
| imp[].bidfloor | float | Optional | Minimum CPM in USD. Default: 0.0. Zone floor pricing may override. |
| site.id | string | Optional | Zone key. Used to route bid to the correct publisher inventory. |
| device.geo.lat/lon | float | Optional | User coordinates. Enables geofence bid multipliers (0.8×–2.0×). |
| device.ip | string | Optional | User IP. Used for geo fallback and fraud detection. |
| at | integer | Optional | Auction type. 2 = Second price (always used). |
| tmax | integer | Optional | Timeout in ms. Adverteks enforces 200ms hard limit regardless of this value. |
Bid Response Object
| Field | Type | Description |
|---|---|---|
| id | string | Echoes bid request id. |
| seatbid[].bid[].id | string | Unique bid ID — use as ${AUCTION_BID_ID} in win notice. |
| seatbid[].bid[].price | float | Clearing price (CPM USD). What you'll be charged on win. |
| seatbid[].bid[].adm | string | Ad markup HTML with impression tracker and click URL. |
| seatbid[].bid[].nurl | string | Win notice URL with macro placeholders. Fire on win. |
| seatbid[].seat | string | Seat ID — use as ${AUCTION_SEAT_ID}. |
| cur | string | Always "USD". |
{
"id": "8a3f5d2b-1c47-4e89-b0a2-6f9e7d4c1b83",
"at": 2,
"tmax": 150,
"imp": [
{
"id": "1",
"banner": { "w": 300, "h": 250, "pos": 1 },
"bidfloor": 0.50,
"bidfloorcur": "USD",
"secure": 1
}
],
"site": {
"id": "zone_abc123",
"page": "https://example.com/article/sports",
"domain": "example.com",
"cat": ["IAB17"]
},
"device": {
"ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)...",
"ip": "203.0.113.42",
"geo": { "lat": 40.7128, "lon": -74.0060, "country": "USA" },
"devicetype": 4
},
"user": { "id": "usr_9c2e7f1a" }
}
{
"id": "8a3f5d2b-1c47-4e89-b0a2-6f9e7d4c1b83",
"cur": "USD",
"seatbid": [
{
"seat": "seat_adverteks_001",
"bid": [
{
"id": "bid_7e2a9f4d",
"impid": "1",
"price": 1.82,
"adid": "creative_445",
"adm": "<div class=\"ad-unit\">...</div>",
"nurl": "https://adverteks.polsia.app/api/rtb/win?auction_id=${AUCTION_ID}&price=${AUCTION_PRICE}&bid_id=${AUCTION_BID_ID}",
"w": 300,
"h": 250
}
]
}
]
}
No-Bid Handling
When no campaign matches the zone or no bid exceeds the floor, Adverteks returns:
DSPs should treat 204 as a no-fill and serve a passback or house ad.
Win Notice
Fire the win notice URL from the bid response after winning an auction. This confirms the impression, triggers budget deduction, and credits the publisher's 70% revenue share.
Fire win notice via POST body or GET (pixel nURL). Must be fired within 30 seconds of auction. Late notices are rejected and the impression is not billed.
| Parameter | Type | Required | Description |
|---|---|---|---|
| auction_id | string | Required | Original bid request id. Substitute ${AUCTION_ID}. |
| price | float | Required | Clearing price CPM in USD. Substitute ${AUCTION_PRICE}. |
| bid_id | string | Optional | Bid ID from response. Substitute ${AUCTION_BID_ID}. |
| seat_id | string | Optional | Seat ID. Substitute ${AUCTION_SEAT_ID}. |
Macro Substitution
| Macro | Example Value | Description |
|---|---|---|
| ${AUCTION_PRICE} | 1.82 | Clearing CPM in USD. Use for budget deduction. |
| ${AUCTION_ID} | 8a3f5d2b-1c47-... | Auction/bid request ID. Links win to original request. |
| ${AUCTION_BID_ID} | bid_7e2a9f4d | Bid ID from response seatbid[].bid[].id. |
| ${AUCTION_SEAT_ID} | seat_001 | Seat ID for multi-seat DSP routing. |
| ${AUCTION_IMP_ID} | 1 | Impression ID from the bid request. |
| ${AUCTION_AD_ID} | creative_445 | Ad creative ID selected for this impression. |
# Before substitution (from bid response):
https://adverteks.polsia.app/api/rtb/win?auction_id=${AUCTION_ID}&price=${AUCTION_PRICE}&bid_id=${AUCTION_BID_ID}
# After macro substitution (what actually fires):
https://adverteks.polsia.app/api/rtb/win?auction_id=8a3f5d2b-1c47-4e89-b0a2-6f9e7d4c1b83&price=1.82&bid_id=bid_7e2a9f4d
Event Tracking
Report impression, click, and billing events. Events update publisher earnings, campaign performance dashboards, and fraud scoring.
Report an ad event. All event types share this endpoint — differentiate via event_type.
| Field | Type | Required | Description |
|---|---|---|---|
| event_type | string | Required | One of: impression, click, billing. |
| auction_id | string | Required | Original auction ID. Links event to impression and auction records. |
| imp_id | string | Optional | Impression ID. Required for click events. |
| price | float | Optional | Clearing price. Required for billing events. Must match win notice price. |
| timestamp | integer | Optional | Unix epoch milliseconds. Defaults to server time if omitted. |
// Impression event
{ "event_type": "impression", "auction_id": "8a3f5d2b-...", "imp_id": "1" }
// Click event
{ "event_type": "click", "auction_id": "8a3f5d2b-...", "imp_id": "1" }
// Billing confirmation
{ "event_type": "billing", "auction_id": "8a3f5d2b-...", "price": 1.82 }
Ad Serving API
Publisher ad tags call this endpoint to retrieve a winning ad. Adverteks runs the unified auction (internal + external DSP bids) and returns the winning creative in under 200ms.
Fetch a winning ad for the given publisher zone. Returns ad markup, impression tracker, and click URL on match. HTTP 204 on no-fill.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| zoneKey | string | Required | Publisher ad zone key. Found in Dashboard → Ad Zones. |
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| lat | float | User latitude. Enables geofence bid multipliers. |
| lon | float | User longitude. Paired with lat. |
| format | string | Force format: 728x90, 300x250, 320x50. |
Response Format
{
"success": true,
"ad": {
"html": "<div class=\"adverteks-ad\">...</div>",
"width": 300,
"height": 250,
"impression_url": "https://adverteks.polsia.app/api/rtb/event?event_type=impression&auction_id=...",
"click_url": "https://adverteks.polsia.app/api/ad/click/imp_8f3a2c1d",
"creative_id": "creative_445",
"cpm": 1.82
},
"auction_id": "8a3f5d2b-1c47-4e89-b0a2-6f9e7d4c1b83"
}
Click Tracking
Track a click on a served ad. HTTP 302 redirect to the advertiser's destination after logging the click. impressionId is returned in ad.click_url from the serve response.
Ad Tag Embed
<div id="adverteks-ad-zone_abc123"></div>
<script src="https://adverteks.polsia.app/ad.js"
data-zone="zone_abc123"
data-container="adverteks-ad-zone_abc123"
async>
</script>
DSP / SSP Registration
External partners must be registered before traffic is exchanged. Registration is managed via the SuperAdmin panel or Admin API.
Registration Steps
partner_id and api_key. Include partner_id in all bid request payloads.Test Mode
- ✓ Bids validated — Your requests are received and validated but excluded from live auctions.
- ✓ No billing — Zero budget deduction. Test impressions are flagged in bid logs.
- ✓ Full responses — Bid responses returned with all fields populated for integration testing.
- ✓ Win notice simulation — Fire win notice URLs; events are logged but not charged.
Performance Monitoring
Partner performance stats: win rate, average CPM, fill rate, total spend, error rate.
{
"partner_id": "dsp_acme_001",
"status": "active",
"period": "24h",
"stats": {
"bid_requests_sent": 45230,
"bids_received": 38970,
"bid_rate": 0.862,
"wins": 1204,
"win_rate": 0.031,
"avg_clearing_cpm": 1.94,
"total_spend_usd": 2337.76,
"impressions": 1204,
"clicks": 47,
"ctr": 0.039,
"errors": 12,
"avg_response_time_ms": 84
}
}
Detailed bid request/response logs for debugging. Full JSON payloads, response times, auction outcomes.
| Query Param | Type | Description |
|---|---|---|
| limit | integer | Max records. Default: 50, Max: 500. |
| since | string | ISO 8601 timestamp filter. |
| outcome | string | Filter: win, loss, nobid, error. |
Sending a Bid Request
Complete examples for submitting an OpenRTB 2.5 bid request to Adverteks.
curl -X POST https://adverteks.polsia.app/api/rtb/bid \
-H "Content-Type: application/json" \
-d '{
"id": "8a3f5d2b-1c47-4e89-b0a2-6f9e7d4c1b83",
"at": 2,
"tmax": 150,
"imp": [{"id":"1","banner":{"w":300,"h":250},"bidfloor":0.50}],
"site": {"id":"zone_abc123","page":"https://example.com/article"},
"device": {"ip":"203.0.113.42","geo":{"lat":40.7128,"lon":-74.0060}}
}'
const { randomUUID } = require('crypto');
async function sendBidRequest(zoneKey, geo = {}) {
const bidRequest = {
id: randomUUID(),
at: 2,
tmax: 150,
imp: [{ id: '1', banner: { w: 300, h: 250 }, bidfloor: 0.50, secure: 1 }],
site: { id: zoneKey, page: 'https://example.com/article' },
device: {
ip: geo.ip || '0.0.0.0',
geo: geo.lat ? { lat: geo.lat, lon: geo.lon, country: 'USA' } : undefined
}
};
const resp = await fetch('https://adverteks.polsia.app/api/rtb/bid', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(bidRequest),
signal: AbortSignal.timeout(200)
});
if (resp.status === 204) { console.log('No fill'); return null; }
const data = await resp.json();
const bid = data.seatbid?.[0]?.bid?.[0];
if (bid) {
console.log(`Won at $${bid.price} CPM`);
// Substitute macros and fire win notice
const winUrl = bid.nurl
.replace('${AUCTION_PRICE}', bid.price)
.replace('${AUCTION_ID}', bidRequest.id)
.replace('${AUCTION_BID_ID}', bid.id);
await fetch(winUrl, { method: 'POST' });
}
return data;
}
sendBidRequest('zone_abc123', { ip: '203.0.113.42', lat: 40.7128, lon: -74.0060 });
import uuid
import httpx # pip install httpx
BASE = "https://adverteks.polsia.app"
def send_bid_request(zone_key: str, geo: dict = None) -> dict | None:
payload = {
"id": str(uuid.uuid4()),
"at": 2, "tmax": 150,
"imp": [{"id": "1", "banner": {"w": 300, "h": 250}, "bidfloor": 0.50}],
"site": {"id": zone_key, "page": "https://example.com"},
}
if geo:
payload["device"] = {
"ip": geo.get("ip", "0.0.0.0"),
"geo": {"lat": geo["lat"], "lon": geo["lon"], "country": "USA"},
}
with httpx.Client(timeout=0.2) as c:
r = c.post(f"{BASE}/api/rtb/bid", json=payload)
if r.status_code == 204:
print("No fill")
return None
r.raise_for_status()
data = r.json()
bid = data.get("seatbid", [{}])[0].get("bid", [None])[0]
if bid:
price = bid["price"]
print(f"Won at ${price:.4f} CPM")
win_url = (bid["nurl"]
.replace("${AUCTION_PRICE}", str(price))
.replace("${AUCTION_ID}", payload["id"])
.replace("${AUCTION_BID_ID}", bid["id"]))
httpx.post(win_url)
return data
send_bid_request("zone_abc123", geo={"ip": "203.0.113.42", "lat": 40.7128, "lon": -74.0060})
Firing a Win Notice
Fire win notices immediately after auction wins to confirm impressions.
# Via GET (pixel / nURL)
curl "https://adverteks.polsia.app/api/rtb/win?auction_id=8a3f5d2b-1c47&price=1.82&bid_id=bid_7e2a9f4d"
# Via POST body
curl -X POST https://adverteks.polsia.app/api/rtb/win \
-H "Content-Type: application/json" \
-d '{"auction_id":"8a3f5d2b-1c47-4e89-b0a2-6f9e7d4c1b83","price":1.82,"bid_id":"bid_7e2a9f4d"}'
async function fireWinNotice({ auctionId, price, bidId, seatId }) {
const resp = await fetch('https://adverteks.polsia.app/api/rtb/win', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ auction_id: auctionId, price, bid_id: bidId, seat_id: seatId })
});
return resp.ok;
}
// Or substitute macros in the nURL from bid response:
async function fireNurl(nurlTemplate, { price, auctionId, bidId }) {
const url = nurlTemplate
.replace('${AUCTION_PRICE}', price)
.replace('${AUCTION_ID}', auctionId)
.replace('${AUCTION_BID_ID}', bidId);
await fetch(url); // GET or POST both work
}
import httpx
def fire_win_notice(auction_id: str, price: float, bid_id: str):
resp = httpx.post(
"https://adverteks.polsia.app/api/rtb/win",
json={"auction_id": auction_id, "price": price, "bid_id": bid_id},
)
if resp.status_code == 200:
print(f"Win recorded — auction {auction_id} at ${price:.4f}")
else:
print(f"Win notice failed: {resp.status_code}")
Ad Serving & Click Tracking
Fetch ads programmatically or embed the Adverteks tag on any publisher page.
# Serve an ad
curl "https://adverteks.polsia.app/api/ad/serve/zone_abc123?lat=40.7128&lon=-74.0060"
# Force 728x90 leaderboard
curl "https://adverteks.polsia.app/api/ad/serve/zone_abc123?format=728x90"
# Track a click (follow redirects)
curl -L "https://adverteks.polsia.app/api/ad/click/imp_8f3a2c1d"
async function loadAd(zoneKey, container, geo = {}) {
const params = new URLSearchParams(
Object.fromEntries(Object.entries(geo).filter(([,v]) => v != null))
);
const resp = await fetch(`https://adverteks.polsia.app/api/ad/serve/${zoneKey}?${params}`);
if (resp.status === 204) {
container.innerHTML = ''; // no fill — serve passback
return;
}
const { ad } = await resp.json();
container.innerHTML = ad.html;
// Fire impression pixel
new Image().src = ad.impression_url;
// Wrap clicks through Adverteks tracker
container.querySelectorAll('a').forEach(a => { a.href = ad.click_url; });
}
loadAd('zone_abc123', document.getElementById('ad-slot'), { lat: 40.7128, lon: -74.0060 });
import httpx
def fetch_ad(zone_key: str, lat: float = None, lon: float = None) -> dict | None:
params = {}
if lat is not None:
params.update({"lat": lat, "lon": lon})
r = httpx.get(f"https://adverteks.polsia.app/api/ad/serve/{zone_key}", params=params)
if r.status_code == 204:
print("No fill — serving passback")
return None
r.raise_for_status()
ad = r.json()["ad"]
print(f"Served {ad['width']}x{ad['height']} at ${ad['cpm']:.4f} CPM")
return ad
ad = fetch_ad("zone_abc123", lat=40.7128, lon=-74.0060)