Overview

Overview of CloudX Android SDK setup and core features

Maven Central

Installation

Requires Android API 23+, Java 8+, and Kotlin 2.1 or newer in the host app. The Magnite adapter needs Kotlin 2.3 or newer; see the note below the dependencies.

The CloudX SDK and its adapters are published to Maven Central (https://repo1.maven.org/maven2/). Make sure your project resolves from it in settings.gradle.kts:

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()   // https://repo1.maven.org/maven2/
    }
}

Then add the CloudX SDK to your app’s build.gradle:

dependencies {
    implementation("io.cloudx:sdk:4.12.0")

    // Adapters for ad networks
    implementation("io.cloudx:adapter-bidmachine:3.8.0.0")         // BidMachine SDK 3.8.0
    implementation("io.cloudx:adapter-bigo:6.1.0.0")               // BIGO Ads SDK 6.1.0
    implementation("io.cloudx:adapter-digitalturbine:8.5.0.0")     // Digital Turbine Marketplace SDK 8.5.0
    implementation("io.cloudx:adapter-googlewaterfall:25.5.0.0")   // Google Mobile Ads SDK 25.5.0
    implementation("io.cloudx:adapter-inmobi:11.5.1.0")            // InMobi SDK 11.5.1
    implementation("io.cloudx:adapter-magnite:1.0.2.1")            // Magnite Ads SDK 1.0.2
    implementation("io.cloudx:adapter-meta:6.22.0.1")              // Meta Audience Network 6.22.0
    implementation("io.cloudx:adapter-mintegral:17.1.91.0")        // Mintegral SDK 17.1.91
    implementation("io.cloudx:adapter-mobilefuse:1.13.0.0")        // MobileFuse SDK 1.13.0
    implementation("io.cloudx:adapter-moloco:4.13.0.2")            // Moloco SDK 4.13.0
    implementation("io.cloudx:adapter-pangle:8.4.0.5.0")           // Pangle SDK 8.4.0.5
    implementation("io.cloudx:adapter-taurusx:1.19.1.1")           // TaurusX SDK 1.19.1
    implementation("io.cloudx:adapter-unityads:4.21.0.2")          // Unity Ads SDK 4.21.0
    implementation("io.cloudx:adapter-verve:3.9.3.0")              // Verve HyBid SDK 3.9.3
    implementation("io.cloudx:adapter-vungle:7.7.9.1")             // Vungle SDK 7.7.9
}

Initialization

// Initialize with app key
CloudX.initialize(
    configuration = CloudXInitializationConfiguration.builder("your-app-key-here")
        .build(),
    listener = object : CloudXInitializationListener {
        override fun onInitialized(configuration: CloudXSdkConfiguration) {
            Log.d("CloudX", "CloudX SDK initialized successfully")
        }

        override fun onInitializationFailed(cloudXError: CloudXError) {
            Log.e("CloudX", "Failed to initialize CloudX SDK: ${cloudXError.message}")
        }
    }
)

App Startup Warm-Up

Starting with SDK 4.7.0, the SDK includes an AndroidX App Startup initializer that prepares some of its work before you call CloudX.initialize(). This is a performance optimization: it lets the SDK initialize and load its first ad faster, without slowing your app’s startup.

The warm-up does no work on the main thread at process start and waits until your app is in the foreground and idle before doing anything more than lightweight background setup. Processes that never come to the foreground, such as those started for push messages or background jobs, do only that lightweight setup.

This does not initialize the SDK. CloudX.initialize() is still required, and nothing about when or how you call it changes. The warm-up is best-effort: if it has not finished, the SDK does the work when it needs it, and it cannot cause your app’s startup to fail.

To turn the warm-up off, remove the initializer in your app manifest:

AndroidManifest.xml
<provider
    xmlns:tools="http://schemas.android.com/tools"
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data
        android:name="io.cloudx.sdk.CloudXStartupInitializer"
        tools:node="remove" />
</provider>

The xmlns:tools declaration on the provider lets you paste the block in as-is. If your <manifest> element already declares the prefix, you can drop it.

Behavior is the same either way. With the initializer removed, the SDK does this preparation when you call initialize(), so your first initialization and first ad load are noticeably slower.

Ad Formats

CloudX supports banner, MREC, interstitial, rewarded, native, and App Open ad integrations. Use the format-specific guides for implementation details:

Ad Information (CloudXAd)

The CloudXAd object is passed to listener callbacks and contains information about the loaded/displayed ad:

PropertyTypeDescription
adFormatCloudXAdFormatAd format (BANNER, MREC, INTERSTITIAL, REWARDED, NATIVE, APP_OPEN)
adUnitIdStringThe ad unit ID
networkNameStringName of the winning ad network
networkPlacementString?Network-specific placement ID
mediatorNameStringMediator that served the ad; cloudx for CloudX-won ads
mediatorAdUnitIdString?The mediator’s ad unit ID; equals adUnitId for CloudX-won ads
creativeIdString?Creative identifier for creative-level issue reporting
placementString?Custom placement set via setPlacement()
revenueDoubleRevenue in USD: the bid-time estimate for CloudX bids, or the realized amount when the ad source reports impression-level revenue
revenuePrecisionStringPrecision of revenue: exact, estimated, publisher_defined, or undefined
adValuesMap<String, String>SDK-provided ad metadata, used by features such as Trusted Arbiter

creativeId is null when the demand source does not report a creative id. It is distinct from the creativeId you pass in CloudXRevenueData, which describes an impression won outside the CloudX ad flow.

override fun onAdLoaded(cloudXAd: CloudXAd) {
    Log.d("CloudX", "Ad format: ${cloudXAd.adFormat}")
    Log.d("CloudX", "Network: ${cloudXAd.networkName}")
    Log.d("CloudX", "Bid-time revenue: ${cloudXAd.revenue}")
}

Error Handling

All SDK errors are returned as CloudXError objects in listener callbacks:

PropertyTypeDescription
codeCloudXErrorCodeError category
messageStringHuman-readable description
causeThrowable?Optional underlying exception
formattedMessageStringPre-formatted message including code and description

Error Code Categories

RangeCategoryCommon Codes
0GeneralINTERNAL_ERROR
100-199NetworkNETWORK_ERROR, NETWORK_TIMEOUT, NETWORK_SERVER_ERROR, NETWORK_NO_CONNECTION
200-299InitializationNOT_INITIALIZED, SDK_DISABLED, NO_ADAPTERS_FOUND, INVALID_APP_KEY
300-399Ad LoadingNO_FILL, INVALID_AD_UNIT, ADS_DISABLED
400-499DisplayAD_NOT_READY, AD_ALREADY_SHOWING
600-699AdapterADAPTER_NO_FILL, ADAPTER_TIMEOUT, ADAPTER_LOAD_TIMEOUT, ADAPTER_INITIALIZATION_ERROR

Advanced Features

Debug Logging

CloudX.setMinLogLevel(CloudXLogLevel.DEBUG)  // Enable debug logging
CloudX.setMinLogLevel(CloudXLogLevel.NONE)   // Disable all logging

Log Levels: VERBOSE < DEBUG < INFO < WARN < ERROR < NONE

Filter logcat with tag CloudX to see SDK logs.

Mediation Debugger

Open the Mediation Debugger to check an integration on the device: every installed adapter with its version and status (completed, incomplete, missing, or not configured), the SDK configuration, privacy state, and your ad units. You can also load and show a test ad per ad unit and share the whole report as text.

CloudX.showMediationDebugger(context)

Call it after the initialize callback reports success. Before that, it returns false and opens nothing. The screen updates live while open. Pass an Activity context where possible. It is meant for development and QA builds; wire it to a debug menu or a gesture.

Note: A test mode request from the debugger takes effect on the next initialization, after you restart the app. The server decides whether that session runs in test mode.

Publisher-Reported Revenue Data

If your app receives impression-level revenue callbacks or bid metadata from AdMob, InMobi, TopOn, or another mediation platform outside the CloudX ad flow, forward those events to CloudX after initialization:

FieldRequiredDescription
platformYesCloudXRevenuePlatform.ADMOB, CloudXRevenuePlatform.INMOBI, CloudXRevenuePlatform.TOPON, or CloudXRevenuePlatform.custom("MyProvider")
revenueYesRevenue for one impression in the supplied currency, not CPM/eCPM
adFormatYesAd format string such as banner, mrec, interstitial, rewarded, native, or app_open
currencyCodeNoISO 4217 currency code, if known
precisionNoCloudXRevenuePrecision.EXACT, ESTIMATED, PUBLISHER_DEFINED, or UNDEFINED
networkNameNoWinning ad network name, if known
adUnitIdNoMediation-platform ad unit ID
thirdPartyAdPlacementIdNoNetwork-side ad unit or placement ID
creativeIdNoCreative ID from the ad network
networkPlacementNoNetwork placement identifier
countryCodeNoUser country code, if known
userSegmentNoUser segment, if known

reportRevenueData() returns true when the event is accepted into the CloudX revenue pipeline. It returns false if the SDK is not initialized, server-side revenue tracking is disabled, or the platform name is blank. Acceptance is not a delivery guarantee.

AdMob paid events

AdMob Android AdValue.valueMicros is reported in micro-units of the supplied currency, so divide it by 1_000_000.0 before passing it to CloudX.

private fun Int.toCloudXRevenuePrecision(): CloudXRevenuePrecision = when (this) {
    AdValue.PrecisionType.PRECISE -> CloudXRevenuePrecision.EXACT
    AdValue.PrecisionType.ESTIMATED -> CloudXRevenuePrecision.ESTIMATED
    AdValue.PrecisionType.PUBLISHER_PROVIDED -> CloudXRevenuePrecision.PUBLISHER_DEFINED
    else -> CloudXRevenuePrecision.UNDEFINED
}

private fun reportAdMobPaidEvent(
    adValue: AdValue,
    adFormat: String,
    adUnitId: String,
    responseInfo: ResponseInfo?,
): Boolean {
    val servedBy = responseInfo?.loadedAdapterResponseInfo

    return CloudX.reportRevenueData(
        CloudXRevenueData.builder(
            platform = CloudXRevenuePlatform.ADMOB,
            revenue = adValue.valueMicros / 1_000_000.0,
            adFormat = adFormat,
        )
            .currencyCode(adValue.currencyCode)
            .precision(adValue.precisionType.toCloudXRevenuePrecision())
            .networkName(servedBy?.adSourceName)
            .adUnitId(adUnitId)
            .thirdPartyAdPlacementId(servedBy?.adSourceInstanceName)
            .build(),
    )
}

bannerView.setOnPaidEventListener { adValue ->
    reportAdMobPaidEvent(
        adValue = adValue,
        adFormat = "banner",
        adUnitId = adUnitId,
        responseInfo = bannerView.responseInfo,
    )
}

InMobi impression events

For InMobi, save the AdMetaInfo object from onAdFetchSuccessful. When onAdImpression fires, send the saved metaInfo.bid to CloudX, then clear the saved value.

private var latestInMobiMetaInfo: AdMetaInfo? = null

override fun onAdFetchSuccessful(ad: InMobiBanner, info: AdMetaInfo) {
    latestInMobiMetaInfo = info
}

override fun onAdImpression(ad: InMobiBanner) {
    latestInMobiMetaInfo?.let { metaInfo ->
        reportInMobiImpression(
            metaInfo = metaInfo,
            adFormat = "banner",
            placementId = inMobiPlacementId.toString(),
        )
    }
    latestInMobiMetaInfo = null
}

private fun reportInMobiImpression(
    metaInfo: AdMetaInfo,
    adFormat: String,
    placementId: String,
): Boolean =
    CloudX.reportRevenueData(
        CloudXRevenueData.builder(
            platform = CloudXRevenuePlatform.INMOBI,
            revenue = metaInfo.bid,
            adFormat = adFormat,
        )
            .precision(CloudXRevenuePrecision.ESTIMATED)
            .thirdPartyAdPlacementId(placementId)
            .creativeId(metaInfo.creativeID)
            .build(),
    )

For interstitial and rewarded InMobi ads, use the same pattern with InterstitialAdEventListener: save AdMetaInfo in onAdFetchSuccessful, then report it in onAdImpression.

TopOn revenue events

TopOn Android reports revenue in onAdRevenuePaid(ATAdInfo). Set an ATAdRevenueListener on the ad object and pass adInfo.getPublisherRevenue() directly to CloudX. Do not divide this value like AdMob micros or CPM.

private fun reportTopOnRevenue(
    adInfo: ATAdInfo,
    adFormat: String,
): Boolean =
    CloudX.reportRevenueData(
        CloudXRevenueData.builder(
            platform = CloudXRevenuePlatform.TOPON,
            revenue = adInfo.getPublisherRevenue(),
            adFormat = adFormat,
        )
            .currencyCode(adInfo.getCurrency())
            .networkName(adInfo.getNetworkName())
            .adUnitId(adInfo.getPlacementId())
            .thirdPartyAdPlacementId(adInfo.getNetworkPlacementId())
            .networkPlacement(adInfo.getAdsourceId())
            .build(),
    )

mBannerView.setAdRevenueListener(object : ATAdRevenueListener {
    override fun onAdRevenuePaid(adInfo: ATAdInfo) {
        reportTopOnRevenue(
            adInfo = adInfo,
            adFormat = "banner",
        )
    }
})

For other TopOn ad formats, set the same listener on the loaded ad object and pass the matching CloudX adFormat.

Custom platform events

Use custom for providers that do not have a CloudX SDK constant like AdMob, InMobi, and TopOn do. The value is only the provider name, such as CloudXRevenuePlatform.custom("TradPlus") or CloudXRevenuePlatform.custom("Nimbus"). Keep the name stable so CloudX can group that provider’s revenue consistently.

Do not include the amount or currency in the provider name. For a TradPlus impression worth USD 0.01, send platform = CloudXRevenuePlatform.custom("TradPlus"), revenue = 0.01, and .currencyCode("USD"). If your source reports CPM/eCPM, divide by 1_000.0 first.

private fun reportCustomRevenueEvent(
    providerName: String,
    revenue: Double,
    adFormat: String,
    currencyCode: String,
    adUnitId: String,
    placementId: String,
): Boolean =
    CloudX.reportRevenueData(
        CloudXRevenueData.builder(
            platform = CloudXRevenuePlatform.custom(providerName),
            revenue = revenue,
            adFormat = adFormat,
        )
            .currencyCode(currencyCode)
            .precision(CloudXRevenuePrecision.PUBLISHER_DEFINED)
            .adUnitId(adUnitId)
            .thirdPartyAdPlacementId(placementId)
            .build(),
    )

Impression-Level Revenue Tracking

Set a revenueListener on any ad format to receive revenue callbacks. CloudXAd.revenue contains the bid-time USD estimate.

bannerAd.revenueListener = object : CloudXAdRevenueListener {
    override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
        Log.d("CloudX", "Bid-time revenue: ${cloudXAd.revenue} from ${cloudXAd.networkName}")
    }
}

Works with all ad formats (banner, MREC, interstitial, rewarded).

Test Mode

Test mode is server-controlled via device whitelisting. This provides better security and control over which devices receive test ads.

To enable test mode:

  1. Initialize the SDK and check logcat for your device advertising ID:

    [CloudX][AdvertisingIdProvider] Device IFA for test whitelisting: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX (LAT: false)
    
  2. Copy the advertising ID and add it to your device whitelist on the CloudX server dashboard

  3. The SDK will automatically configure adapters for test mode and include the test flag in bid requests

Note: Test mode is determined by the server, so you don’t need to change any code between development and production builds.

Privacy Compliance

The CloudX SDK supports GDPR and CCPA privacy compliance by reading standard IAB privacy strings from SharedPreferences. These values are typically set automatically by your Consent Management Platform (CMP) such as Google UMP, OneTrust, or Sourcepoint.

How It Works

The SDK automatically detects user location and reads consent signals:

  1. EU Users (GDPR): Checks TCF v2 consent for purposes 1 and 2 per the IAB Global Vendor List and vendor consent (CloudX Vendor ID: 1510)
  2. US Users (CCPA): Checks for sale/sharing opt-out signals
  3. Other Regions: No restrictions applied

When consent is denied or user opts out, the SDK removes PII from ad requests:

  • Advertising ID (GAID) is cleared
  • Geo coordinates (lat/lon) are removed
  • User key-values are not sent
  • Hashed user ID is removed from bidder-bound requests and suppressed from impression reporting

Supported Privacy Keys

KeyStandardDescription
IABGPP_HDR_GppStringGPPGlobal Privacy Platform string (modern)
IABGPP_GppSIDGPPSection IDs (e.g., “2” for EU, “7” for US-National, “8” for US-CA)
IABTCF_TCStringTCF v2GDPR consent string (legacy)
IABTCF_gdprAppliesTCF v2Whether GDPR applies (1 = yes, 0 = no)
IABUSPrivacy_StringUS PrivacyCCPA privacy string (legacy, e.g., “1YNN”)

Note: The SDK prioritizes GPP (modern standard) over legacy TCF/US Privacy strings when both are available.

Manual Privacy API

If you manage user consent yourself (without a CMP), you can set GDPR and CCPA privacy status directly. These must be called prior to SDK initialization.

// GDPR consent: true, false, or null to defer to CMP
CloudX.setHasUserConsent(true)

// CCPA do-not-sell: true, false, or null to defer to CMP
CloudX.setDoNotSell(true)

User Targeting

// Set hashed user ID for targeting
CloudX.setHashedUserId("hashed-user-id")

// Set custom user key-value pairs
CloudX.setUserKeyValue("age", "25")
CloudX.setUserKeyValue("gender", "male")
CloudX.setUserKeyValue("location", "US")

// Set custom app key-value pairs
CloudX.setAppKeyValue("app_version", "1.0.0")
CloudX.setAppKeyValue("user_level", "premium")

// Clear all custom key-values
CloudX.clearAllKeyValues()

The hashed user ID is a publisher-provided pseudonymous identifier. When applicable privacy signals allow it, CloudX captures the value at auction time and returns it as hashed_user_id in the request and impression activity exports. Use auction_id to join the exports. Use the ID to join CloudX activity to cohorts in your own user data. The export value is empty when no ID was set, persistence was suppressed by privacy signals, or the value exceeded 128 characters. Do not pass raw personal data.

User and app key-values are also available in the request activity export. Use setUserKeyValue for session-wide user attributes and setAppKeyValue for session-wide app attributes. The SDK sends current values with each later auction request.

Identity passthrough

Pass UID 2.0, EUID, LiveRamp, ID5, and Intent IQ with setUserKeyValue. Set each value when you have it, and again when it refreshes.

KeyPass this
uidapi.comUID2 advertising token, not the refresh token. Do not decrypt it. UID2 docs
euid.euEUID advertising token, not the refresh token. Do not decrypt it. EUID docs
liveramp.comLiveRamp ATS envelope, not a RampID. LiveRamp docs
id5-sync.comID5 universal UID. Do not pass 0. ID5 docs
intentiq.comIntent IQ ID (IIQ ID) from your Intent IQ integration. Intent IQ docs

CloudX does not generate these IDs. Create them with UID2, EUID, LiveRamp ATS, ID5, or Intent IQ, then pass the string. This is separate from hashed user ID.

CloudX.setUserKeyValue("uidapi.com", uid2Token)
CloudX.setUserKeyValue("euid.eu", euidToken)
CloudX.setUserKeyValue("liveramp.com", liveRampEnvelope)
CloudX.setUserKeyValue("id5-sync.com", id5Id)
CloudX.setUserKeyValue("intentiq.com", intentIQId)

For metadata that belongs to one ad object or load, call setExtraParameter before load:

adView.setExtraParameter("requestId", "request-456")
adView.setExtraParameter("impressionKey", "impression-789")
adView.load()

Extra parameters remain on that ad object until changed or cleared. The SDK snapshots them for each load. CloudX returns the complete bag in the extra_parameters request-export column. The reserved tags key also controls tag-based routing; use another key for correlation metadata.

CloudX stores each of the three compact JSON bags only when the entire bag is at most 256 UTF-8 bytes. The limit is independent for each export column and includes all combined keys, values, quotes, separators, and braces—not 256 bytes per K/V pair. When privacy rules prohibit publisher-data persistence, or a bag is malformed or too large, its export cell is empty. Do not include raw personal data, secrets, or consent strings.

Support

For support, contact support@cloudx.io