Skip to main content
Trusted Arbiter compares a loaded CloudX bid with supported third-party bids and returns the selected platform. CloudX SDK versions 4.1.0 and later support CloudX, Unity LevelPlay, and PubMatic bid inputs.
Call CloudX.arbiter() after CloudX.initialize() has completed. Until the arbiter service is available, the SDK falls back to selecting the highest comparable USD bid from the supplied inputs.

Basic API

Create bid candidates from loaded ads, then pass them to CloudX.arbiter().
// cloudXAd is the CloudXAd from a CloudX onAdLoaded callback.
// levelPlayAdInfo is the Unity LevelPlay ad info object.
// pobBid is the PubMatic/OpenWrap bid object.
val bids = listOf(
    CloudXArbiterBid.cloudX(cloudXAd),
    CloudXArbiterBid.levelPlay(
        networkName = levelPlayAdInfo.adNetwork,
        revenue = levelPlayAdInfo.revenue,
        precision = levelPlayAdInfo.precision,
    ),
    CloudXArbiterBid.pubmatic(
        price = pobBid.price,
        partnerName = pobBid.partnerName,
    )
)

val configuration = CloudXArbiterConfiguration.builder(bids).build()

CloudX.arbiter(configuration, object : CloudXArbiterListener {
    override fun onCompleted(result: CloudXArbiterResult) {
        Log.d("CloudX", "Selected platform: ${result.platform.name}")
    }
})
CloudXArbiterBid.cloudX() accepts the CloudXAd object from a CloudX load callback. levelPlay() accepts Unity LevelPlay ad info values. pubmatic() accepts a PubMatic OpenWrap bid price and optional partner name. partnerName and the extras map are optional on the bid factories. onCompleted() runs on the main thread, so you can show an ad or update UI directly from it. result.platform is CloudXArbiterPlatform.CLOUDX, LEVELPLAY, or PUBMATIC for the selected platform, or CloudXArbiterPlatform.NONE when no winner could be selected (for example, no bids were supplied).
Kotlin callers can use the suspending overload instead of a listener. It returns the same CloudXArbiterResult:
val result = CloudX.arbiter(configuration)
Log.d("CloudX", "Selected platform: ${result.platform.name}")

Step-by-step: arbitrate CloudX and LevelPlay

This walkthrough shows exactly which Unity LevelPlay callback to read and which values to pass into CloudX.arbiter(). It uses an interstitial, but the same field mapping applies to any format.
1

Load both candidates

Create the CloudX and LevelPlay interstitials, attach listeners, and start a load on each platform.
val cloudXInterstitial = CloudX.createInterstitial(context, "YOUR_CLOUDX_AD_UNIT_ID")
cloudXInterstitial.listener = cloudXListener
cloudXInterstitial.load()

val levelPlayInterstitial = LevelPlayInterstitialAd("YOUR_LEVELPLAY_AD_UNIT_ID")
levelPlayInterstitial.setListener(levelPlayListener)
levelPlayInterstitial.loadAd()
2

Capture each platform's loaded ad

LevelPlay delivers a LevelPlayAdInfo in its onAdLoaded callback; CloudX delivers a CloudXAd in onAdLoaded. Hold onto both — you read the arbiter inputs from them in the next step.
private var cloudXAd: CloudXAd? = null
private var levelPlayInfo: LevelPlayAdInfo? = null

// CloudXInterstitialListener
override fun onAdLoaded(cloudXAd: CloudXAd) {
    this.cloudXAd = cloudXAd
}

// LevelPlayInterstitialAdListener
override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
    levelPlayInfo = levelPlayAdInfo
}
3

Map the values into bids

Read the LevelPlay fields off LevelPlayAdInfo and pass them to CloudXArbiterBid.levelPlay(). The CloudX bid takes the CloudXAd directly. listOfNotNull submits only the platforms that actually loaded.
LevelPlayAdInfo fieldTypeCloudXArbiterBid.levelPlay parameter
adNetworkStringnetworkName
revenueDoublerevenue
precisionStringprecision
val bids = listOfNotNull(
    cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
    levelPlayInfo?.let { info ->
        CloudXArbiterBid.levelPlay(
            networkName = info.adNetwork,   // LevelPlayAdInfo.adNetwork
            revenue = info.revenue,         // LevelPlayAdInfo.revenue
            precision = info.precision,     // LevelPlayAdInfo.precision
        )
    },
)
4

Run the arbiter

Wrap the bids in a CloudXArbiterConfiguration and pass it to CloudX.arbiter() with a listener. onCompleted() runs on the main thread.
val configuration = CloudXArbiterConfiguration.builder(bids).build()
CloudX.arbiter(configuration, object : CloudXArbiterListener {
    override fun onCompleted(result: CloudXArbiterResult) {
        showWinner(result)
    }
})
5

Show the winner

Switch on result.platform and show the winning platform’s ad. CloudXArbiterPlatform.NONE means no winner was selected — continue without showing an ad.
private fun showWinner(result: CloudXArbiterResult) {
    when (result.platform) {
        CloudXArbiterPlatform.CLOUDX -> cloudXInterstitial.show(activity)
        CloudXArbiterPlatform.LEVELPLAY -> levelPlayInterstitial.showAd(activity)
        else -> { } // CloudXArbiterPlatform.NONE — no winner; continue without an ad
    }
}
The ArbiterInterstitialController below packages these same steps into a reusable component that prepares a winner ahead of the placement.

Interstitial example

This interstitial example arbitrates between two platforms: CloudX and Unity LevelPlay. Prepare a winner before the placement is reached:
  1. Load CloudX and LevelPlay in parallel.
  2. Wait until both platforms have loaded or failed.
  3. Submit only loaded candidates to Trusted Arbiter.
  4. Cache the selected platform.
  5. At the placement, show the cached winner immediately.
If both platforms fail, start another load cycle. If the placement is reached before a winner is prepared, continue the app flow without showing an ad.
ArbiterInterstitialController.kt
/**
 * Prepares a Trusted Arbiter winner ahead of time so an interstitial can be shown
 * instantly when a placement is reached.
 *
 * Loads [cloudXInterstitial] and [levelPlayInterstitial] in parallel, waits until both
 * have finished loading or failing, submits the loaded candidates to [CloudX.arbiter],
 * and caches the selected [CloudXArbiterPlatform] in [nextWinner].
 */
class ArbiterInterstitialController(
    private val cloudXInterstitial: CloudXInterstitialAd,
    private val levelPlayInterstitial: LevelPlayInterstitialAd,
) {
    /** Receives the arbitration outcome once a winner has been prepared. */
    interface Listener {
        /** Called when the arbiter has selected [platform] for the next show. */
        fun onWinnerPrepared(platform: CloudXArbiterPlatform)
    }

    /** Set to observe [Listener.onWinnerPrepared] callbacks. */
    var listener: Listener? = null
    private var cloudXAd: CloudXAd? = null
    private var cloudXLoadDone = false
    private var levelPlayAdInfo: LevelPlayAdInfo? = null
    private var levelPlayLoadDone = false
    private var nextWinner: CloudXArbiterPlatform? = null

    init {
        cloudXInterstitial.listener = createCloudXListener()
        levelPlayInterstitial.setListener(createLevelPlayListener())
    }

    /** Starts a load for each platform that does not currently hold a cached ad. */
    fun loadMissingAds() {
        if (cloudXAd == null) cloudXInterstitial.load()
        if (levelPlayAdInfo == null) levelPlayInterstitial.loadAd()
    }

    /**
     * Shows the prepared winner on [activity], returning true only when a show call was made.
     *
     * Returns false when no winner is ready or the cached ad is no longer available, in which
     * case a fresh load cycle is started.
     */
    fun showAtPlacement(activity: Activity): Boolean {
        return when (nextWinner) {
            CloudXArbiterPlatform.CLOUDX -> showCloudX(activity)
            CloudXArbiterPlatform.LEVELPLAY -> showLevelPlay(activity)
            else -> false
        }
    }

    /**
     * Runs the arbiter once both platforms have settled, then caches the winning platform.
     *
     * Returns early until both loads complete. If neither platform loaded, it restarts the
     * load cycle; otherwise it submits the loaded candidates to [CloudX.arbiter].
     */
    private fun maybePrepareWinner() {
        if (!cloudXLoadDone || !levelPlayLoadDone) return

        if (cloudXAd == null && levelPlayAdInfo == null) {
            cloudXLoadDone = false
            levelPlayLoadDone = false
            loadMissingAds()
            return
        }

        val bids = listOfNotNull(
            cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
            levelPlayAdInfo?.let { adInfo ->
                CloudXArbiterBid.levelPlay(
                    networkName = adInfo.adNetwork,
                    revenue = adInfo.revenue,
                    precision = adInfo.precision,
                )
            }
        )

        CloudX.arbiter(
            configuration = CloudXArbiterConfiguration.builder(bids).build(),
            listener = object : CloudXArbiterListener {
                override fun onCompleted(result: CloudXArbiterResult) {
                    nextWinner = result.platform
                    listener?.onWinnerPrepared(result.platform)
                }
            }
        )
    }

    private fun showCloudX(activity: Activity): Boolean {
        if (cloudXInterstitial.isAdReady) {
            cloudXInterstitial.show(activity)
            return true
        }

        clearCloudXAndLoadMissingAds()
        return false
    }

    private fun showLevelPlay(activity: Activity): Boolean {
        if (levelPlayInterstitial.isAdReady) {
            levelPlayInterstitial.showAd(activity)
            return true
        }

        clearLevelPlayAndLoadMissingAds()
        return false
    }

    private fun clearCloudXAndLoadMissingAds() {
        cloudXAd = null
        cloudXLoadDone = false
        nextWinner = null
        loadMissingAds()
    }

    private fun clearLevelPlayAndLoadMissingAds() {
        levelPlayAdInfo = null
        levelPlayLoadDone = false
        nextWinner = null
        loadMissingAds()
    }

    private fun createCloudXListener() = object : CloudXInterstitialListener {
        override fun onAdLoaded(cloudXAd: CloudXAd) {
            this@ArbiterInterstitialController.cloudXAd = cloudXAd
            cloudXLoadDone = true
            maybePrepareWinner()
        }

        override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
            cloudXAd = null
            cloudXLoadDone = true
            maybePrepareWinner()
        }

        override fun onAdDisplayed(cloudXAd: CloudXAd) = Unit

        override fun onAdDisplayFailed(cloudXAd: CloudXAd, cloudXError: CloudXError) {
            clearCloudXAndLoadMissingAds()
        }

        override fun onAdHidden(cloudXAd: CloudXAd) {
            clearCloudXAndLoadMissingAds()
        }

        override fun onAdClicked(cloudXAd: CloudXAd) = Unit
    }

    private fun createLevelPlayListener() = object : LevelPlayInterstitialAdListener {
        override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
            this@ArbiterInterstitialController.levelPlayAdInfo = levelPlayAdInfo
            levelPlayLoadDone = true
            maybePrepareWinner()
        }

        override fun onAdLoadFailed(levelPlayAdError: LevelPlayAdError) {
            levelPlayAdInfo = null
            levelPlayLoadDone = true
            maybePrepareWinner()
        }

        override fun onAdInfoChanged(levelPlayAdInfo: LevelPlayAdInfo) {
            this@ArbiterInterstitialController.levelPlayAdInfo = levelPlayAdInfo
        }

        override fun onAdDisplayed(levelPlayAdInfo: LevelPlayAdInfo) = Unit

        override fun onAdDisplayFailed(
            levelPlayAdError: LevelPlayAdError,
            levelPlayAdInfo: LevelPlayAdInfo
        ) {
            clearLevelPlayAndLoadMissingAds()
        }

        override fun onAdClosed(levelPlayAdInfo: LevelPlayAdInfo) {
            clearLevelPlayAndLoadMissingAds()
        }

        override fun onAdClicked(levelPlayAdInfo: LevelPlayAdInfo) = Unit
    }
}
showAtPlacement() returns true only when an ad show call was made. onAdInfoChanged() keeps the cached LevelPlay candidate up to date while it remains loaded. For PubMatic OpenWrap, create a third-party bid with CloudXArbiterBid.pubmatic(price, partnerName). If the arbiter service is unavailable, the SDK falls back to the highest comparable USD bid among the supplied supported bid inputs.