Request & response templates

Exactly what travels on the wire, in both directions and in every format we support — OpenRTB, XML and JSON, plus Prebid.js for browser banner requests. Use the configuration for your integration below.

Overview

As a supply partner you call us and we answer with a price and a click URL. As a demand partner we call you and read a price and a destination out of your response.

DirectionProtocolRouteBodies
Supply → usFeedGET /feed/bidderQuery in · XML or JSON out
Supply → usOpenRTB 2.5POST /rtb/bidderJSON in · JSON out
Us → demandFeedGET your URLQuery out · XML or JSON back
Us → demandOpenRTB 2.5POST your URLJSON out · JSON back

Feed and OpenRTB host: https://bid.searchplan.co. Each zone on these endpoints is identified by zid and authorised by ckey, both issued by us.

For browser banner auctions, see Prebid integration at prebid.searchplan.co, using the zone parameter.

What we support

Three things describe a placement, and they are independent: what the slot is, where it is rendered, and what kind of audience it carries.

Formats #

The format is the imp object you send. It also decides what travels in adm when we bid.

  • popimp.banner + instl— Popunder or interstitial. adm carries a destination URL, a popunder_xml document, or an <a href> snippet — tell us which one your tag expects.
  • bannerimp.banner— Display. adm carries the buyer's HTML markup passed through unmodified, or a destination URL.
  • videoimp.video— Instream video. adm carries the buyer's VAST document, passed through unmodified, so their impression and quartile trackers stay intact. We do not wrap or re-generate it.
  • nativeimp.native— Native placement. adm carries the buyer's native JSON response, passed through unmodified.

On the feed endpoint, type accepts aliases: popup for pop, and ctv, vast or instream for video. You do not have to change what you already send us.

Environments #

Where the ad is rendered. This is a separate axis from the format: there is banner on web and banner in-app, and video in all three. What tells them apart in OpenRTB is the container, not imp.

  • website— Send site.domain and site.page with the full URL.
  • in-appapp— Send app.bundle, app.name and app.storeurl.
  • CTVapp + devicetype— As in-app, plus device.devicetype 3 (Connected TV) or 7 (Set-Top Box).

Buyers filter hard on this. A request that says site but carries no usable domain, or one that omits app.bundle on in-app traffic, gets dropped downstream before it is ever priced — and it will look to you like a silent no-bid.

Categories #

Every zone is either MS (mainstream) or AD (adult). We never cross them: adult creatives are not served on mainstream inventory and the other way round. Tell us which one a zone carries when you set it up.

Price floors #

Send imp.bidfloor and imp.bidfloorcur whenever you have a floor. We forward it to buyers unchanged. Without it they bid blind, and the usual result is a lower clearing price rather than a higher one. A 0 is read as "no floor declared", not as a floor of zero.

Feed supply → us

Endpoint URL #

Use the endpoint URL we provide. The example below carries a placeholder zone and key — it will not serve traffic.

https://bid.searchplan.co/feed/bidder?zid=YOUR_ZID&ckey=YOUR_KEY&type=popup&ip=82.131.34.62&referrer=http%3A%2F%2Fexample.com&ua=Mozilla%2F5.0+%28Macintosh%3B+Intel+Mac+OS+X+10_14_2%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F71.0.3578.98+Safari%2F537.36

Endpoint GET-parameters #

  • zidRequired— Zone ID. Issued by us.
  • ckeyRequired— Zone key. Issued by us; a wrong key returns 403.
  • typeRequired— Feed type: popup, banner, video, native. pop, ctv, vast and instream are accepted as aliases — see What we support.
  • ipRequired— IP address of the visitor.
  • uaRequired— User-Agent header from the visitor's HTTP request. Must be in URL-encoded format.
  • pageOptional— URL of the page where the ad will be displayed. Must be in URL-encoded format.
  • referrerOptional— URL of the page the visitor came from. Must be in URL-encoded format.
  • refidOptional— Reference ID (subid). Passed through to reporting. subid and uid are accepted as aliases.
  • countryOptional but highly recommended— Visitor country, ISO-2. Without it we resolve the country from the connection, which on server-to-server calls is your server's location, not the visitor's.
  • qOptional— Search term, when the traffic carries one. Some zones are configured to require it.
  • langOptional— Language of the visitor, two-letter code.
  • w, hRequired for banner— Slot size in pixels. Forwarded to demand and echoed back as width/height. Without it the demand gets no size and the response echoes empty values.
  • pmodelOptional— Pricing model. 1 — CPM, 2 — CPC. Defaults to the zone setting.
  • curOptional— Overrides bid currency. ISO-4217 alpha code. Default USD.
  • formatOptional— Response format:
    • xml — default
    • json

The response shape follows type. A popup answers with a clickUrl; a banner answers with ad, width and height. Ask for the type your zone is set up for — a banner zone called with type=popup finds no matching demand and returns 204.

Popup XML-response example #

<?xml version="1.0" encoding="UTF-8"?>
<result>
    <clickUrl>https://bid.searchplan.co/c/eyJkIjoiNTgwOTkxMCIsInMi</clickUrl>
    <idad>486213905</idad>
    <bid>0.138540</bid>
    <cur>USD</cur>
</result>

Popup JSON-response example #

{
    "clickUrl": "https://bid.searchplan.co/c/eyJkIjoiNTgwOTkxMCIsInMi",
    "idad": 486213905,
    "bid": 0.138540,
    "cur": "USD"
}

Banner XML-response example #

<?xml version="1.0" encoding="UTF-8"?>
<result>
    <ad><![CDATA[<a href="https://bid.searchplan.co/c/<token>" target="_blank" rel="noopener sponsored"></a>]]></ad>
    <width>300</width>
    <height>250</height>
    <idad>486213905</idad>
    <bid>0.138540</bid>
    <cur>USD</cur>
    <clickUrl>https://bid.searchplan.co/c/<token></clickUrl>
</result>

Banner JSON-response example #

{
    "ad": "<a href=\"https://bid.searchplan.co/c/<token>\" target=\"_blank\" rel=\"noopener sponsored\"></a>",
    "width": "300",
    "height": "250",
    "idad": 486213905,
    "bid": 0.138540,
    "cur": "USD",
    "clickUrl": "https://bid.searchplan.co/c/<token>"
}

The banner markup is a link, not a creative. What wins our auction is a destination, so ad is an anchor pointing at the click URL — wrap it in your own creative, or ignore it and use clickUrl directly, which we send alongside for exactly that. width and height echo the w/h you sent; send them, because they are also what we forward to demand.

Response fields #

  • clickUrl— Where to send the visitor. Redirects to the landing page and settles the win on its own. Present in both types.
  • adbanner— Markup for the slot: an anchor to clickUrl.
  • width, heightbanner— Size served, echoed from your request.
  • idad— Identifier of the served ad, stable for that response.
  • bid— Price in the unit your zone is paid in: CPC by default, CPM if agreed or if pmodel=1.
  • cur— Currency of bid.

Response HTTP-codes #

  • 200— Successful response
  • 204— Empty response. No ads were found
  • 400— Required parameters are missing or incorrect
  • 403— Wrong zone key
  • 404— Zone is unknown or inactive

No bid is a 204 with an empty body — not a zero-bid payload and not an error.

OpenRTB supply → us

OpenRTB 2.5 over POST, credentials in the query string: POST /rtb/bidder?zid=YOUR_ZID&ckey=YOUR_KEY with Content-Type: application/json. We honour tmax as the deadline and never bid below imp.bidfloor.

BidRequest you send
{
  "id": "a3d9-supply-req-001",
  "at": 1,
  "cur": ["USD"],
  "tmax": 400,
  "imp": [{
    "id": "1",
    "banner": { "w": 300, "h": 250 },   // or "instl": 1 for pop
    "bidfloor": 0.01,
    "bidfloorcur": "USD",
    "tagid": "your-placement"
  }],
  "site": {
    "domain": "example-publisher.com",
    "page":   "https://example-publisher.com/article"
  },
  "device": {
    "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) …",
    "ip": "88.20.14.7",
    "geo": { "country": "USA" }
  },
  "user": { "id": "end-user-id" }
}
BidResponse 200 · x-openrtb-version: 2.5
{
  "id": "a3d9-supply-req-001",
  "cur": "USD",
  "seatbid": [{
    "seat": "bidtags",
    "bid": [{
      "id": "b0f1c2d3-4e5a-6b7c-8d9e-0f1a2b3c4d5e",
      "impid": "1",
      "price": 0.13854,
      "adm": "https://bid.searchplan.co/c/eyJkIjoiNTgwOTkxMCIsInMi",
      "crid": "5809910",
      "nurl": "https://bid.searchplan.co/n/eyJkIjoiNTgwOTkxMCIsInMi",
      "adomain": ["advertiser.com"],
      "w": 300,
      "h": 250
    }]
  }]
}

price is always CPM here. w and h are echoed from your imp.banner. Billing depends on the contract configured for your zone. For BURL-enabled Pop zones, call bid.burl when the pop opens; nurl only reports the auction win. See billable impressions.

adm payload #

What travels in adm is set per zone. Tell us which shape your tag expects:

url default
https://bid.searchplan.co/c/TOKEN
popunder_xml
<?xml version="1.0"?>
<ad><popunderAd><url><![CDATA[https://bid.searchplan.co/c/TOKEN]]></url></popunderAd></ad>
html
<a href="https://bid.searchplan.co/c/TOKEN" target="_blank"></a>

We return a destination, not creative markup. The bid tells you where to send the user, so this fits formats that open a landing page rather than render a creative we supply.

Request examples per format #

Same envelope in all four cases. What changes is the imp object and the container. Fields shown are the ones that matter; the rest of OpenRTB 2.5 applies.

Banner — web #

{
  "id": "req-001", "at": 1, "cur": ["USD"], "tmax": 400,
  "imp": [{
    "id": "1",
    "banner": { "w": 300, "h": 250 },
    "bidfloor": 0.35, "bidfloorcur": "USD",
    "tagid": "your-placement"
  }],
  "site": {
    "domain": "example.com",
    "page":   "https://example.com/article"
  },
  "device": {
    "ua": "Mozilla/5.0 ...", "ip": "88.20.14.7",
    "devicetype": 2,
    "geo": { "country": "USA" }
  },
  "user": { "id": "end-user-id" }
}

Banner — in-app #

Same as above, replacing site with:

"app": {
  "bundle":   "com.example.app",
  "name":     "Example App",
  "storeurl": "https://play.google.com/store/apps/details?id=com.example.app"
},
"device": { "devicetype": 4, "ifa": "38400000-8cf0-11bd-b23e-10b96e40000d" }

Video — instream #

app or site depending on the environment. For CTV, use app and devicetype 3.

"imp": [{
  "id": "1",
  "video": {
    "mimes": ["video/mp4"],
    "protocols": [2, 3, 5, 6],
    "w": 1920, "h": 1080,
    "minduration": 5, "maxduration": 30,
    "linearity": 1, "placement": 1,
    "skip": 1
  },
  "bidfloor": 0.50, "bidfloorcur": "USD"
}],
"device": { "devicetype": 3 }

protocols is worth getting right: it declares which VAST versions your player accepts, and buyers use it to decide whether to bid at all.

Native #

native.request travels as a string, not as an object — that is what the spec says and what buyers parse.

"imp": [{
  "id": "1",
  "native": {
    "request": "{\"native\":{\"ver\":\"1.2\",\"context\":1,\"plcmtcnt\":1,\"assets\":[
                  {\"id\":1,\"required\":1,\"title\":{\"len\":100}},
                  {\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":250}},
                  {\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":200}}]}}",
    "ver": "1.2"
  },
  "bidfloor": 0.20, "bidfloorcur": "USD"
}]

Response — the same shape for every format #

{
  "id": "req-001", "cur": "USD",
  "seatbid": [{ "seat": "bidtags", "bid": [{
    "id": "b0f1c2d3-...", "impid": "1",
    "price": 0.138540,
    "adm": "<depends on the format, see above>",
    "crid": "creative-id",
    "nurl": "https://bid.searchplan.co/n/TOKEN",
    "adomain": ["advertiser.com"],
    "w": 300, "h": 250
  }]}]
}

price is always CPM, whatever the format. No bid returns HTTP 204 with an empty body — that is normal, not an error. w and h are echoed back from imp.banner.

Billable impressions #

Pop OpenRTB: BURL is available by agreement, enabled per zone. When enabled, our bid includes a signed burl such as https://bid.searchplan.co/b/TOKEN. Call the exact returned URL when the pop actually opens, according to the agreed billable event.

  • burlopt-in Pop— Confirms the CPM impression and publisher payout. Do not call it just because the auction was won.
  • nurlwin notice— On BURL-enabled zones it reports the auction win only; it does not bill an impression.
  • /c/TOKENredirect— Opens the destination. It does not replace BURL for CPM billing on BURL-enabled zones. CPC click accounting remains a separate contract.
  • /i/TOKENpixel— Impression signal for integrations configured to bill on the markup impression event.

No automatic NURL fallback on BURL-enabled zones. If BURL is not received and accepted, the CPM impression and payout are not recorded through NURL or the redirect. Confirm support before enabling this option. Feed integrations and zones without BURL retain their existing billing contract.

BURL callbacks return 204 when handled, including duplicate callbacks; a 204 alone does not prove a new billable impression. Invalid tokens return 403; expired tokens return 410. Retry deduplication has operational limits; do not intentionally replay callbacks.

Agree on the event and validate real traffic and partner reports. Our supply-side BURL and any demand-side BURL are separate notifications; receiving one does not prove that the other was sent or that the buyer confirmed revenue.

Prebid · Banner web Browser → Bidtags #

Add Bidtags to your website’s Prebid.js auction using bidder code bidtags and your assigned zone. Requests go directly to https://prebid.searchplan.co/prebid/bid. No server, ckey or API token is needed in the bidder parameters.

This endpoint supports banner web. Video, native and app inventory are not enabled.

Get the adapter #

Download the integration package: adapter source, instructions and a Prebid.js 11.34.0 build containing Bidtags only. The adapter is distributed directly by Bidtags; this integration does not require an ASO adapter or server.

For an existing Prebid integration, add both supplied source files to your Prebid.js modules/ directory and include bidtagsBidAdapter in your build alongside your existing bidders and modules. Keep your consent configuration and ad-server setup. The supplied standalone build is for isolated validation; it does not contain your other bidders or consent modules.

Standalone buildFor an isolated integration page
<script async src="https://prebid.searchplan.co/prebid.js"></script>

Bidder parameters #

Bid configurationAdd to your ad unit’s bids array
{
  bidder: 'bidtags',
  params: {
    zone: 123456
  }
}
  • zoneRequired— Zone ID issued by Bidtags. 123456 is an example only; replace it with the ID assigned to your website.
  • mediaTypes.banner.sizesRequired— Supported sizes: 300×250, 728×90, 320×50 and 300×600.
Banner ad unitUse your actual slot code
var adUnit = {
  code: 'banner-slot',
  mediaTypes: {
    banner: { sizes: [[300, 250]] }
  },
  bids: [{ bidder: 'bidtags', params: { zone: 123456 } }]
};

pbjs.que.push(function () {
  pbjs.addAdUnits([adUnit]);
  pbjs.requestBids({
    timeout: 1500,
    bidsBackHandler: function () {
      // Continue your existing ad-server / auction flow here.
    }
  });
});

Use a real HTTPS page. The browser’s Origin and the page URL supplied by Prebid must match. Run validation directly on the page, rather than inside a cross-origin iframe. During request validation, an empty zone domain list accepts HTTPS origins; any configured domain allowlist is enforced.

Verify requests #

Open your browser’s Network panel, run the auction and filter for /prebid/bid. The adapter sends one JSON request per banner opportunity using POST and Content-Type: text/plain, without cross-origin credentials.

Expected requestSent automatically by the adapter
POST https://prebid.searchplan.co/prebid/bid?zid=123456
Content-Type: text/plain
Origin: https://your-website.example
No-bid responseAccepted, no buyer connected
HTTP 204 No Content
X-BidTags-Status: received-no-demand

An empty Bidtags result is expected when no demand is connected. Continue the auction normally for your other bidders. Accepted requests are recorded under the assigned zone; reporting follows the normal aggregation schedule and may not appear immediately. Preflight OPTIONS requests are not counted as bid requests.

CodeMeaning on the Prebid endpointWhat to check
204Accepted request, no bid; or a valid preflight.For POST, check X-BidTags-Status: received-no-demand.
400Invalid request or unsupported banner format.Page URL, matching Origin, banner sizes and request body.
403Origin or domain not allowed.HTTPS page and the zone’s allowed domains.
404Unknown or disabled zone.Assigned zone ID and activation status.
405Unsupported method.Use the adapter’s POST request.
413Request body exceeds 64 KiB.Reduce unnecessary request data.

For troubleshooting, send your account manager the website URL, zone ID, UTC request time and HTTP status. Keep your existing consent modules configured: the adapter forwards available privacy signals but does not obtain consent itself.

Feed us → demand

Give us a URL template with macros and we call it with GET. We read two values out of your response — the price and the destination — so your existing shape works unchanged.

Template what you give us
https://your-endpoint.com/search
  ?q={q}&ip={ip}&ua={ua}&country={country}
  &subid={supply}&url={url}&device={device}
Call GET · macros resolved
https://your-endpoint.com/search
  ?q=car%20insurance&ip=88.20.14.7&ua=Mozilla%2F5.0%20(Windows%20NT%2010.0…)
  &country=US&subid=164759
  &url=https%3A%2F%2Fexample-publisher.com%2Farticle&device=desktop

Response — XML #

Either attributes or text nodes; CDATA is unwrapped for you.

Attributes you return
<?xml version="1.0"?>
<result>
  <listing bid="0.4200" url="https://land.example.com/o?c=1"/>
</result>
Paths we read
bid_path = result.0.listing.0.@bid    // → 0.4200
url_path = result.0.listing.0.@url    // → https://land.example.com/o?c=1
Text nodes you return
<response>
  <ad>
    <price>0.42</price>
    <link><![CDATA[https://land.example.com/o?c=1&x=2]]></link>
  </ad>
</response>
Paths we read
bid_path = response.0.ad.0.price.0    // → 0.42
url_path = response.0.ad.0.link.0     // → https://land.example.com/o?c=1&x=2

How XML paths are built. Every element becomes a list, because a tag can repeat — so each step carries an index: result.0.listing.0. Attributes are prefixed with @, and a bare text node is read straight from the element. You don't have to work this out yourself: send us one sample response and we set the paths.

Response — JSON #

You return
{
  "listings": [
    { "bid": 0.42, "url": "https://land.example.com/o?c=1" }
  ]
}
Paths we read
bid_path = listings.0.bid
url_path = listings.0.url

Response fields we read #

  • bidRequired— Your price. Tell us whether it is cpc or cpm; zero or less counts as no bid.
  • urlRequired— Destination we send the visitor to. Must be an absolute http or https URL.

Both are located by path, so the field names above are only a convention — yours can be called anything, at any depth.

Response HTTP-codes we accept #

  • 200— Parsed for a bid. An empty body, a zero bid or a missing destination is treated as no bid.
  • 204— No bid. The expected way to say "nothing this time".
  • Anything else— Logged as an error for that request and skipped. It never blocks the auction.

OpenRTB us → demand

We POST a BidRequest built to your spec. The two templates below are the ones we send today — banner and pop — generated by the bidder itself, so this is literally what arrives at your endpoint.

BidRequest banner
{
  "id": "6f2a1c94-3d17-4b8e-9c02-1a5f7e0d4b33",
  "imp": [{
    "id": "1",
    "banner": { "w": 300, "h": 250 },
    "tagid": "pub-4471"
  }],
  "at": 1,
  "cur": ["USD"],
  "tmax": 370,
  "device": {
    "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 …",
    "ip": "88.20.14.7",
    "devicetype": 2,
    "geo": { "country": "USA", "type": 2 },
    "language": "en",
    "os": "Windows",
    "dnt": 0,
    "lmt": 0
  },
  "user": { "id": "ccf3cd95b1e10060d75d1a233fdde8f63ca5be81" },
  "site": {
    "id": "example-publisher.com",
    "domain": "example-publisher.com",
    "page": "https://example-publisher.com/article",
    "ref": "https://google.com/",
    "keywords": "car insurance",
    "publisher": { "id": "pub-4471" },
    "cat": ["IAB24"]
  },
  "source": {},
  "ext": { "sub": "164759" }
}
BidRequest pop — same shape, interstitial imp
  "imp": [{
    "id": "1",
    "instl": 1,          // no banner object for pop
    "tagid": "pub-4471"
  }],
BidResponse what we expect back
{
  "id": "6f2a1c94-3d17-4b8e-9c02-1a5f7e0d4b33",
  "cur": "USD",
  "seatbid": [{
    "bid": [{
      "id": "1",
      "impid": "1",
      "price": 0.55,
      "adm": "https://land.example.com/o?c=1",
      "nurl": "https://your-endpoint.com/win?p=${AUCTION_PRICE}",
      "adomain": ["advertiser.com"]
    }]
  }]
}

We read the first bid of the first seat. The destination comes from adm by default — a bare URL, or extracted from CDATA or an href if your adm is markup. If it lives in nurl instead, say so and we read it there.

Demand billing notices #

If your integration bills through bid.burl, include it in the bid response and agree its activation and billable event with us. Support is configured per demand or supply/demand connection; returning a BURL does not enable it automatically.

For Pop configured with pop_open, we call your BURL after the accepted opening signal on our redirect flow. For integrations configured with adm, the trigger is our markup impression signal. These are separate from the supply's call to our BURL. VAST impression trackers and feed destination URLs are not BURL.

${AUCTION_PRICE} is replaced with the buyer-side clearing price in the agreed price unit, not the publisher payout. Notifications are best effort: validate a real callback and reconcile it against your reporting API before considering billing confirmed.

SettingValue
Formatpop, banner or ctv — we only send what you are set up for.
Timeout50–2000 ms, default 250. tmax reserves processing time and is also bounded by the time remaining in the auction. The configured timeout is not a guarantee of that much time for every request.
GeoISO-2 allowlist. Empty means every country.
DevicesAny of desktop, mobile, tablet.
Pricecpc or cpm.
QPSAgreed per partner, enforced on our side.

Answer within the timeout. One deadline covers every partner in the auction — a late bid is discarded even if it would have won. ${AUCTION_PRICE}, ${AUCTION_CURRENCY} and ${AUCTION_IMP_ID} are filled in when we fire your nurl; any other ${AUCTION_*} macro is emptied rather than sent unresolved.

Reporting API supply → us

Revenue fields apply to JSON and CSV, including country and sub-ID breakdowns. Current-day and partially reconciled rows can have an estimate before any confirmed payout. Confirmation may arrive later and may be revised. Historical publisher payouts through 5 September 2026 are preserved. Country/sub-ID confirmed amounts are allocated from the confirmed zone payout; they are not independent source-level partner validation.

Pull your own delivery and revenue numbers. The token goes in the URL, so the call works from a scheduled fetch, a spreadsheet or a browser — no headers, no login.

The token identifies you and scopes the data. It only ever returns your own zones; asking for a zone that is not yours returns no rows rather than an error. Treat it as a credential — anyone holding the URL can read your numbers. Ask us to rotate it if it leaks.

Endpoint URL #

https://api.searchplan.co/api/rtb/report?token=YOUR_TOKEN&from=2026-08-01&to=2026-08-20

Endpoint GET-parameters #

  • tokenRequired— Your reporting token. Issued by us.
  • from, toOptional— Date range, YYYY-MM-DD, inclusive, UTC. Defaults to the last 7 days. Maximum span is 92 days.
  • groupOptional— How rows are aggregated:
    • day — one row per day, all zones summed. Default.
    • zone — one row per zone, whole range summed.
    • day_zone — one row per day and zone.
  • zoneOptional— Limit to one zone or several, comma-separated: zone=165101,165103.
  • zone_nameOptional— Limit to zones whose name contains this text, case-insensitive: zone_name=banner. Handy when you do not want to keep a list of IDs.
  • min_requestsOptional— Drop rows below this many requests. Useful to hide the long tail of near-empty days.
  • breakdownOptional— Add one of these dimensions to every row:
    • country — ISO-2 country.
    • subid — the sub-id received from your integration.
    • subid_country — sub-id and country together.
    Breakdown queries cover bidder traffic only, have a maximum range of 31 days, and can be combined with group and the zone filters above.
  • limit, offsetBreakdowns only— Page through high-cardinality results. limit defaults to 10,000 and cannot exceed 10,000; offset defaults to 0.
  • formatOptional— Response format:
    • json — default
    • csv — same columns, one header row

Country and sub-id breakdowns #

Use a breakdown when you need to compare the quality and earnings of traffic segments, rather than only totals by zone. For example:

https://api.searchplan.co/api/rtb/report?token=YOUR_TOKEN&from=2026-08-30&to=2026-08-31&group=day_zone&breakdown=subid_country&limit=1000&offset=0
  • country— Returns the full funnel: requests, responses, fill_rate, no-bid diagnostics, impressions, clicks and revenue.
  • subid, subid_country— Return requests, requests_estimated, impressions, clicks and revenue. Response, fill and no-bid fields are deliberately omitted because they are not recorded at sub-id granularity.

Coverage is bidder-only. Direct advertiser zones are excluded because their reports do not provide country or sub-id. Breakdown history starts on 30 August 2026. Sub-id request counts start at 2026-09-01T20:00:00Z; earlier sub-id rows can still contain delivery and revenue, but requests cannot be reconstructed.

Breakdown JSON and pagination #

{
 "rows": [
  {
   "date": "2026-09-02",
   "zone": "165104",
   "zone_name": "Example - WW - Pop",
   "subid": "campaign-42",
   "country": "US",
   "requests": 1250,
   "requests_estimated": true,
   "impressions": 41,
   "clicks": 2,
   "revenue": 0.1842,
   "estimated_revenue": 0.2010
  }
 ],
 "meta": {
  "breakdown": "subid_country",
  "coverage": "bidder_only",
  "limit": 1000,
  "offset": 0,
  "returned": 1,
  "truncated": false,
  "results_available_from": "2026-08-30",
  "requests_available_from": "2026-09-01T20:00:00Z"
 }
}

If meta.truncated is true, repeat the request with offset increased by the previous limit. Continue until it is false. CSV uses the same filters and paging parameters; add &format=csv.

JSON-response example #

{
 "rows": [
  {
   "date": "2026-08-21",
   "zone": "165104",
   "zone_name": "Example - WW - Pop",
   "requests": 1848,
   "responses": 1254,
   "fill_rate": 0.6786,
   "no_bid": 3994,
   "timeouts": 7558,
   "errors": 3002,
   "filtered": 0,
   "impressions": 1254,
   "clicks": 1,
   "revenue": 0.031922,
   "estimated_revenue": 0.035000
  }
 ]
}

Response fields #

  • dategroup=day, day_zone— Day of the row, UTC.
  • zone, zone_namegroup=zone, day_zone— Zone the row belongs to.
  • requests— Requests you sent us that reached the auction.
  • responses— Requests we answered with a bid.
  • fill_rate— responses / requests, as a fraction. 0.6786 is 67.86%.
  • impressions— Ads served. On feed integrations every answered request is an impression, because you request the ad in order to display it. On OpenRTB it counts the win notices you fire, since there you may award the impression to another bidder.
  • clicks— Clicks registered through the clickUrl we returned.
  • revenue— Your confirmed publisher payout for the row, in USD. The existing field name is unchanged. Unconfirmed amounts are excluded; zero may mean not yet confirmed, rather than a final zero.
  • estimated_revenue— Your provisional publisher payout in USD, before partner validation. This is a separate estimate of the same traffic, not an amount to add to revenue. It may differ from the final confirmed amount.

Why a request did not get a bid #

These four fields add up to the requests that produced no bid, and they say where each one stopped. They are there so you can fix an integration without asking us to read the logs.

  • no_bid— We asked demand and nobody bid. Normal background level; a zone sitting at 100% no-bid usually means the traffic does not match what the connected demand buys.
  • timeouts— Demand did not answer in time. Ours to chase, not yours.
  • errors— Demand answered with an HTTP error.
  • filtered— Dropped before we asked anyone, so it never had a chance to earn. This is the one worth watching: a high number points at your request, not at demand. The usual causes are unresolved macros sent literally (ip={ip} instead of the value), a missing or empty ua or ip, no resolvable country, or a type the zone does not serve.

Standard, country and sub-id figures are aggregated hourly, so the current hour can read low until the next roll-up. The combined subid_country breakdown is built daily and is final after the next daily roll-up.

Response HTTP-codes #

  • 200— Successful response. An empty rows array is a valid 200.
  • 400— Bad date format, from after to, range over 92 days (31 with a breakdown), unknown group/breakdown, or invalid paging values.
  • 403— Invalid token, or a token that is not enabled for RTB reporting.

Macros

For demand feed templates. A macro we don't recognise, or one with no value for that request, resolves to an empty string — a template never goes out with {something} still in it. {ua}, {url} and {ref} are URL-encoded for you; the rest are substituted raw.

MacroValueAlso accepted as
{q}Search term{query} {keyword} {kw} {search} {term}
{ip}End user IP{user_ip} {clientip} {client_ip} {remote_ip} {visitor_ip}
{ua}User agent{useragent} {user_agent}
{url}Page URL{page} {page_url} {pageurl}
{ref}Referrer{referer} {referrer}
{subid}Sub-id from the supply{sub} {sub1} {subid1} {pubid} {refid}
{country}ISO-2 country{geo} {cc} {country_code}
{lang}Two-letter language{language} {accept_lang} {accept_language}
{device}desktop · mobile · tablet{device_type} {devicetype}
{os}OS from the user agent—
{supply}Our id for the traffic source—
{w} {h}Slot size, when sent—

{count}, {num}, {limit} and {results} always resolve to 1 — we take a single listing per request.

Status codes

CodeMeaningWhat to do
204No bid.Nothing — no demand won this auction.
400Missing parameter or invalid JSON body.The message names it, e.g. ip parameter is required.
403Wrong ckey.Check the key issued for that zone.
404Unknown or paused zone.Confirm the zid and that the zone is live.
405OpenRTB called without POST.Use POST.
410Click URL older than one hour.Expected for stale clicks.

Feed errors carry a body in the zone's format — <result><error>…</error></result> or {"error": "…"}. OpenRTB errors return the status with an empty body.