A bid request can allow EUR responses while an impression's floor is still denominated in USD. If an integration reads the response currency list and silently applies it to the floor, its arithmetic can look correct while comparing different units.
The problem is easy to miss because both values are ordinary JSON numbers. Their meaning depends on the currency attached to each field.
This walkthrough uses OpenRTB 2.6's June 2026 specification snapshot. The examples focus on currency handling and are not complete bid requests or production pricing logic.
Follow the field that owns the currency
The request's cur field describes allowed bid response currencies. The impression's bidfloorcur describes the currency of that impression's floor. In the referenced snapshot, the latter defaults to USD when omitted.
Those are separate contracts. Setting cur to ["EUR"] does not rewrite an omitted bidfloorcur to EUR.
Consider this fragment:
{
"cur": ["EUR"],
"imp": [
{ "id": "video-1", "bidfloor": 3.20 }
]
}
For the impression floor, the omitted currency resolves to USD under the cited specification. A bid price expressed in EUR needs an explicit conversion before a numerical floor comparison makes sense.
There is another scope to review when deals are involved. A deal has its own floor fields. Inspect the applicable deal and exchange contract rather than applying an impression-level assumption everywhere.
Preserve the amount and currency together
Avoid passing a bare number called floor through several functions. Use a structure that carries the amount, currency and source. That makes it harder for a later step to lose the unit or confuse an explicit value with a default.
The following complete Node.js example performs a narrow comparison. The exchange rate is synthetic and is supplied by the caller:
import assert from 'node:assert/strict';
function impressionFloor(imp) {
const amount = imp.bidfloor === undefined ? 0 : imp.bidfloor;
const currency = imp.bidfloorcur === undefined ? 'USD' : imp.bidfloorcur;
if (!Number.isFinite(amount) || amount < 0) {
throw new TypeError('invalid floor amount');
}
if (!['USD', 'EUR'].includes(currency)) {
throw new TypeError('unsupported currency in this example');
}
return {
amount, currency,
currencySource: imp.bidfloorcur === undefined ? 'default' : 'explicit'
};
}
function convertFloor(floor, target, rates) {
if (floor.currency === target) return floor.amount;
const rate = rates[`${floor.currency}:${target}`];
if (!Number.isFinite(rate) || rate <= 0) {
throw new Error('usable conversion rate missing');
}
return floor.amount * rate;
}
const request = { cur: ['EUR'], imp: [{ id: 'video-1', bidfloor: 3.2 }] };
const floor = impressionFloor(request.imp[0]);
const rates = { 'USD:EUR': 0.9 };
const floorInEUR = convertFloor(floor, 'EUR', rates);
assert.equal(floor.currency, 'USD');
assert.equal(floor.currencySource, 'default');
assert.ok(Math.abs(floorInEUR - 2.88) < 1e-12);
assert.equal(3.0 >= floorInEUR, true);
assert.equal(3.0 >= floor.amount, false);
assert.throws(() => convertFloor(floor, 'EUR', {}));
assert.throws(() => impressionFloor({ bidfloor: '3.20' }));
assert.throws(() => impressionFloor({ bidfloor: -1 }));
assert.equal(impressionFloor({ bidfloor: 0 }).amount, 0);
The two comparisons deliberately produce different answers. With this invented rate, a EUR 3.00 bid exceeds the converted EUR 2.88 floor. Comparing the same bid directly with the unconverted USD 3.20 number would reject it.
The example supports only two currencies so that unsupported inputs fail visibly. A production implementation needs a maintained currency set, a rate source and an explicit rounding policy.
Define the direction and age of the rate
A rate named 0.9 is incomplete. State that it means EUR per USD, record when it became effective and identify the source. Multiplying by an inverse rate can produce plausible values without immediately revealing the error.
Attach that metadata to the pricing decision or a retrievable rate snapshot. A later replay should use the rate policy that applied to the original decision, rather than whichever rate happens to be current when a developer investigates it.
Do not treat a missing rate as one-to-one conversion. Decide whether the application should skip the impression, stop the calculation or route the case for review. That decision is business policy and should appear in the diagnostic result.
Likewise, do not round a floor for display and reuse the displayed number in auction logic. Define the precision and comparison behavior required by your integration. Binary floating point, exchange rounding rules and boundary prices need deliberate treatment beyond this small example.
Test defaults and explicit values separately
An omitted currency and an explicit USD currency can lead to the same effective value while carrying different diagnostic meaning. Preserve which path was taken so you can find producers that rely on defaults unexpectedly.
Include fixtures for a missing floor, a zero floor, an explicit non-USD currency and a malformed numeric string. Zero must survive as zero. A truthiness check such as amount || fallback can replace a legitimate value.
Then add a request with multiple impressions and different floor currencies. This catches helpers that compute one request-wide currency and apply it to every impression.
For deal traffic, keep dedicated cases with the deal identifier and applicable floor fields. The test should establish which floor the integration selected before it tests the comparison arithmetic.
Debug a rejected bid with the normalized record
When a bid appears to clear a floor but is rejected, collect the matched impression or deal, raw floor amount, effective floor currency, response currency, conversion snapshot and final comparison value.
Check the units as well as the currency. OpenRTB pricing uses CPM conventions in these fields. An amount interpreted as a per-impression charge introduces a separate scale error that currency conversion cannot fix.
Finally, distinguish your local eligibility calculation from the exchange's auction result. Clearing this one floor check does not establish that the bid is otherwise valid or that it should win.
I maintain RTBlint. Its OpenRTB mistakes reference is useful for reviewing the surrounding request and response contract. Keep the currency decision in your own replay fixture so that a pricing investigation can reproduce the exact comparison.
Sources
Technical sources checked October 25, 2026. Drafted with AI; examples are synthetic unless explicitly stated otherwise.
Top comments (0)