> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cloudx.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Trusted Arbiter

> Compare CloudX bids with supported third-party bids in Android apps

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. CloudX SDK 4.2.0 and later also supports custom publisher-supplied bid inputs.

<Info>
  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.
</Info>

## Basic API

Create bid candidates from loaded ads, then pass them to `CloudX.arbiter()`.

```kotlin theme={null}
// 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).

<Tip>
  Kotlin callers can use the suspending overload instead of a listener. It returns the same `CloudXArbiterResult`:

  ```kotlin theme={null}
  val result = CloudX.arbiter(configuration)
  Log.d("CloudX", "Selected platform: ${result.platform.name}")
  ```
</Tip>

## Custom Bid Inputs

Use `CloudXArbiterBid.custom()` when you want Trusted Arbiter to compare CloudX with a third-party platform that does not have a dedicated bid helper.

```kotlin theme={null}
val customBid = CloudXArbiterBid.custom(
    platformName = "my_mediation_platform",
    networkName = "winning_demand_source",
    revenuePerImpressionUSD = 0.00125,
    precision = CloudXArbiterPrecision.EXACT,
    extras = mapOf("ad_unit" to "third-party-ad-unit-id"),
)

val configuration = CloudXArbiterConfiguration.builder(
    listOf(CloudXArbiterBid.cloudX(cloudXAd), customBid)
).build()
```

When a custom bid wins, `result.platform` is `CloudXArbiterPlatform.CUSTOM` and `result.platformName` contains the `platformName` supplied on the bid. Pass `revenuePerImpressionUSD` as revenue for one impression in USD, not CPM. Use `CloudXArbiterPrecision.EXACT`, `ESTIMATED`, `PUBLISHER_DEFINED`, or `UNDEFINED` to describe that revenue value.

## 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.

<Steps>
  <Step title="Load both candidates">
    Create the CloudX and LevelPlay interstitials, attach listeners, and start a load on each platform.

    ```kotlin theme={null}
    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()
    ```
  </Step>

  <Step title="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.

    ```kotlin theme={null}
    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
    }
    ```
  </Step>

  <Step title="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` field | Type     | `CloudXArbiterBid.levelPlay` parameter |
    | ----------------------- | -------- | -------------------------------------- |
    | `adNetwork`             | `String` | `networkName`                          |
    | `revenue`               | `Double` | `revenue`                              |
    | `precision`             | `String` | `precision`                            |

    ```kotlin theme={null}
    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
            )
        },
    )
    ```
  </Step>

  <Step title="Run the arbiter">
    Wrap the bids in a `CloudXArbiterConfiguration` and pass it to `CloudX.arbiter()` with a listener. `onCompleted()` runs on the main thread.

    ```kotlin theme={null}
    val configuration = CloudXArbiterConfiguration.builder(bids).build()
    CloudX.arbiter(configuration, object : CloudXArbiterListener {
        override fun onCompleted(result: CloudXArbiterResult) {
            showWinner(result)
        }
    })
    ```
  </Step>

  <Step title="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.

    ```kotlin theme={null}
    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
        }
    }
    ```
  </Step>
</Steps>

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.

```kotlin ArbiterInterstitialController.kt theme={null}
/**
 * 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.
