Overview

Overview of CloudX iOS SDK setup and core features

Requires iOS 13.0+, Xcode 16.0+, and Swift 6.0+.

Installation

CocoaPods

Podfile
platform :ios, '15.0'

target 'YourApp' do
  use_frameworks!

  # Core SDK
  pod 'CloudXCore', '~> 3.9.1'

  # Adapters for ad networks (add as needed).
  # Each adapter versions independently as <network-sdk-version>.<adapter-revision>
  # and is built against that exact network SDK version.
  pod 'CloudXAppLovinAdapter', '~> 13.6.3.0'        # AppLovinSDK 13.6.3
  pod 'CloudXMetaAdapter', '~> 6.22.0.0'            # FBAudienceNetwork 6.22.0
  pod 'CloudXVungleAdapter', '~> 7.7.6.0'           # VungleAds 7.7.6
  pod 'CloudXInMobiAdapter', '~> 11.4.1.0'          # InMobiSDK 11.4.1
  pod 'CloudXMintegralAdapter', '~> 8.1.6.0'        # MintegralAdSDK 8.1.6
  pod 'CloudXUnityAdsAdapter', '~> 4.20.1.0'        # UnityAds 4.20.1
  pod 'CloudXMagniteAdapterV2', '~> 1.0.0.2'        # MagniteSDK 1.0.0
  pod 'CloudXMobileFuseAdapter', '~> 1.11.0.1'      # MobileFuseSDK 1.11.0
  pod 'CloudXMolocoAdapter', '~> 4.10.0.0'           # MolocoSDKiOS 4.10.0
  pod 'CloudXVerveAdapter', '~> 3.9.0.0'            # HyBid 3.9.0
  pod 'CloudXDigitalTurbineAdapter', '~> 8.4.10.0'  # Fyber Marketplace SDK 8.4.10
  pod 'CloudXGoogleWaterfallAdapter', '~> 13.9.0.0' # Google Mobile Ads SDK 13.9.0
  pod 'CloudXPangleAdapter', '~> 8.3.0.6.0'         # Ads-Global (Pangle / ByteDance) 8.3.0.6
  pod 'CloudXTaurusXAdapter', '~> 1.18.2.0'         # TaurusxAdsSDK 1.18.2
end
pod install --repo-update

Initialization

#import <CloudXCore/CloudXCore.h>

CLXInitializationConfiguration *config =
    [CLXInitializationConfiguration configurationWithAppKey:@"your-app-key-here"];

[[CloudXCore shared] initializeWithConfiguration:config completion:^(CLXSdkConfiguration *sdkConfig, CLXError * _Nullable error) {
    if (sdkConfig) {
        NSLog(@"CloudX SDK initialized successfully");
    } else {
        NSLog(@"Failed to initialize CloudX SDK: %@", error.localizedDescription);
    }
}];

Ad Formats

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

Ad Information (CLXAd)

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

PropertyTypeDescription
adFormatCLXAdFormatAd format (banner, MREC, interstitial, rewarded, native)
adUnitIdNSString?The ad unit ID
adUnitNameNSString?The ad unit name
networkNameNSString?Name of the winning ad network
networkPlacementNSString?Network-specific placement ID
placementNSString?Custom placement set via placement property
revenueNSNumber?Impression-level revenue in USD
revenuePrecisionNSString?Revenue precision, when provided by the winning network
creativeIdentifierNSString?Creative identifier for creative-level issue reporting
requestLatencyNSTimeIntervalTime in seconds from ad request to ad response
nativeAdCLXNativeAd?Native ad asset container for native ads; nil for non-native formats
adValuesNSDictionary<NSString *, NSString *>SDK-defined metadata for the loaded ad; values may be absent by format or network
- (void)didLoadAd:(CLXAd *)ad {
    NSLog(@"Ad format: %ld", (long)ad.adFormat);
    NSLog(@"Network: %@", ad.networkName);
    NSLog(@"Revenue: %@", ad.revenue);
}

Error Handling

All SDK errors are returned as CLXError objects in delegate callbacks:

PropertyTypeDescription
codeCLXErrorCodeError category
localizedDescriptionNSStringHuman-readable description
underlyingErrorNSError?Optional underlying error

Error Code Categories

RangeCategoryCommon Codes
0GeneralCLXErrorCodeInternalError
100-199NetworkCLXErrorCodeNetworkError, CLXErrorCodeNetworkTimeout, CLXErrorCodeServerError, CLXErrorCodeNoConnection
200-299InitializationCLXErrorCodeNotInitialized, CLXErrorCodeSDKDisabled, CLXErrorCodeNoAdaptersFound, CLXErrorCodeInvalidAppKey
300-399Ad LoadingCLXErrorCodeNoFill, CLXErrorCodeInvalidAdUnit, CLXErrorCodeAdsDisabled
400-499DisplayCLXErrorCodeAdNotReady, CLXErrorCodeAdAlreadyShowing
600-699AdapterCLXErrorCodeAdapterNoFill, CLXErrorCodeAdapterTimeout, CLXErrorCodeAdapterLoadTimeout, CLXErrorCodeAdapterInitializationError

Advanced Features

Debug Logging

[CloudXCore setMinLogLevel:CLXLogLevelDebug];  // Enable debug logging
[CloudXCore setMinLogLevel:CLXLogLevelNone];   // Disable all logging

Log Levels: verbose < debug < info < warn < error < none

Impression-Level Revenue Tracking

Set a revenueDelegate on any ad format to receive impression-level revenue (ILR) callbacks. The CLXAd object contains the revenue value in USD and the winning network name.

self.bannerAd.revenueDelegate = self;

- (void)didPayRevenueForAd:(CLXAd *)ad {
    NSLog(@"Revenue: %@ from %@", ad.revenue, ad.networkName);
}

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

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
platformYesCLXRevenuePlatformAdMob, CLXRevenuePlatformInMobi, CLXRevenuePlatformTopOn, or CLXRevenuePlatformCustom(@"MyProvider") in Objective-C; .adMob, .inMobi, .topOn, or .custom("MyProvider") in Swift
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
precisionNoexact, estimated, publisherDefined, 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 iOS AdValue.value is already in the supplied currency units, so pass it directly to CloudX. Do not divide it by 1_000_000.0.

static CLXRevenuePrecision *CLXRevenuePrecisionFromGAD(GADAdValuePrecision precision) {
    switch (precision) {
        case GADAdValuePrecisionPrecise: return CLXRevenuePrecision.exact;
        case GADAdValuePrecisionEstimated: return CLXRevenuePrecision.estimated;
        case GADAdValuePrecisionPublisherProvided: return CLXRevenuePrecision.publisherDefined;
        case GADAdValuePrecisionUnknown: return CLXRevenuePrecision.undefined;
    }
    return CLXRevenuePrecision.undefined;
}

- (BOOL)reportAdMobPaidEventWithAdValue:(GADAdValue *)adValue
                               adFormat:(NSString *)adFormat
                                adUnitId:(NSString *)adUnitId
                            responseInfo:(GADResponseInfo *)responseInfo {
    GADAdNetworkResponseInfo *servedBy = responseInfo.loadedAdNetworkResponseInfo;
    CLXRevenueData *data =
        [CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformAdMob
                                        revenue:adValue.value.doubleValue
                                       adFormat:adFormat
                                   builderBlock:^(CLXRevenueDataBuilder *builder) {
            builder.currencyCode = adValue.currencyCode;
            builder.precision = CLXRevenuePrecisionFromGAD(adValue.precision);
            builder.networkName = servedBy.adSourceName;
            builder.adUnitId = adUnitId;
            builder.thirdPartyAdPlacementId = servedBy.adSourceInstanceName;
        }];

    return [[CloudXCore shared] reportRevenueData:data];
}

__weak GADBannerView *weakBannerView = bannerView;
bannerView.paidEventHandler = ^(GADAdValue *adValue) {
    [self reportAdMobPaidEventWithAdValue:adValue
                                 adFormat:@"banner"
                                  adUnitId:adUnitId
                              responseInfo:weakBannerView.responseInfo];
};

InMobi impression events

For InMobi, save the IMAdMetaInfo object from banner(_:didReceiveWithMetaInfo:). When bannerAdImpressed(_:) fires, send the saved metaInfo.getBid() to CloudX, then clear the saved value.

@property (nonatomic, strong, nullable) IMAdMetaInfo *latestInMobiMetaInfo;

- (void)banner:(IMBanner *)banner didReceiveWithMetaInfo:(IMAdMetaInfo *)info {
    self.latestInMobiMetaInfo = info;
}

- (void)bannerAdImpressed:(IMBanner *)banner {
    if (!self.latestInMobiMetaInfo) {
        return;
    }

    [self reportInMobiImpressionWithMetaInfo:self.latestInMobiMetaInfo
                                    adFormat:@"banner"
                                 placementId:inMobiPlacementId];
    self.latestInMobiMetaInfo = nil;
}

- (BOOL)reportInMobiImpressionWithMetaInfo:(IMAdMetaInfo *)metaInfo
                                  adFormat:(NSString *)adFormat
                               placementId:(NSString *)placementId {
    CLXRevenueData *data =
        [CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformInMobi
                                        revenue:[metaInfo getBid]
                                       adFormat:adFormat
                                   builderBlock:^(CLXRevenueDataBuilder *builder) {
            builder.precision = CLXRevenuePrecision.estimated;
            builder.thirdPartyAdPlacementId = placementId;
            builder.creativeId = metaInfo.creativeID;
        }];

    return [[CloudXCore shared] reportRevenueData:data];
}

For interstitial and rewarded InMobi ads, use the same pattern with IMInterstitialDelegate: save IMAdMetaInfo in interstitial(_:didReceiveWithMetaInfo:), then report it in interstitialAdImpressed(_:).

TopOn revenue events

TopOn iOS reports revenue in didRevenueForPlacementID:extra:. Use publisher_revenue from extra as one-impression revenue and currency as the currency code.

static NSString *CLXTopOnStringValue(NSDictionary *extra, NSString *key) {
    id value = extra[key];
    return [value isKindOfClass:NSString.class] ? value : nil;
}

static CLXRevenuePrecision *CLXRevenuePrecisionFromTopOn(NSString *precision) {
    if ([precision isEqualToString:@"exact"]) return CLXRevenuePrecision.exact;
    if ([precision isEqualToString:@"estimated"]) return CLXRevenuePrecision.estimated;
    if ([precision isEqualToString:@"publisher_defined"]) return CLXRevenuePrecision.publisherDefined;
    return CLXRevenuePrecision.undefined;
}

- (BOOL)reportTopOnRevenueForPlacementID:(NSString *)placementID
                                   extra:(NSDictionary *)extra
                                adFormat:(NSString *)adFormat {
    NSNumber *revenue = extra[@"publisher_revenue"];
    if (![revenue isKindOfClass:NSNumber.class]) {
        return NO;
    }

    CLXRevenueData *data =
        [CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformTopOn
                                        revenue:revenue.doubleValue
                                       adFormat:adFormat
                                   builderBlock:^(CLXRevenueDataBuilder *builder) {
            builder.currencyCode = CLXTopOnStringValue(extra, @"currency");
            builder.precision = CLXRevenuePrecisionFromTopOn(CLXTopOnStringValue(extra, @"precision"));
            builder.networkName = CLXTopOnStringValue(extra, @"network_name");
            builder.adUnitId = placementID;
            builder.thirdPartyAdPlacementId = CLXTopOnStringValue(extra, @"network_placement_id");
            builder.networkPlacement = CLXTopOnStringValue(extra, @"adsource_id");
            builder.countryCode = CLXTopOnStringValue(extra, @"country");
        }];

    return [[CloudXCore shared] reportRevenueData:data];
}

- (void)didRevenueForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
    [self reportTopOnRevenueForPlacementID:placementID
                                     extra:extra
                                  adFormat:@"banner"];
}

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 CLXRevenuePlatformCustom(@"TradPlus") in Objective-C or CLXRevenuePlatform.custom("TradPlus") in Swift. 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 TradPlus, revenue 0.01, and currency code USD. If your source reports CPM/eCPM, divide by 1_000.0 first.

- (BOOL)reportCustomRevenueEventWithProviderName:(NSString *)providerName
                                         revenue:(double)revenue
                                        adFormat:(NSString *)adFormat
                                    currencyCode:(NSString *)currencyCode
                                        adUnitId:(NSString *)adUnitId
                                     placementId:(NSString *)placementId {
    CLXRevenueData *data =
        [CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformCustom(providerName)
                                        revenue:revenue
                                       adFormat:adFormat
                                   builderBlock:^(CLXRevenueDataBuilder *builder) {
            builder.currencyCode = currencyCode;
            builder.precision = CLXRevenuePrecision.publisherDefined;
            builder.adUnitId = adUnitId;
            builder.thirdPartyAdPlacementId = placementId;
        }];

    return [[CloudXCore shared] reportRevenueData:data];
}

Delegate Threading

Publisher delegate callbacks are delivered on the main queue and may fire inline relative to the SDK call that triggered them. Keep delegate handlers re-entrant-safe if they call back into the SDK.

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 the logs for your device IFA:

    [CloudX][INFO] Device IFA for test whitelisting: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
    
  2. Copy the IFA 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

Privacy Compliance

The CloudX SDK supports GDPR and CCPA privacy compliance by reading standard IAB privacy strings from NSUserDefaults. 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 (IDFA) 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”)

App Tracking Transparency (ATT)

On iOS 14.5+, you must request App Tracking Transparency authorization before the SDK can access the IDFA. Request ATT permission before initializing the CloudX SDK:

#import <AppTrackingTransparency/AppTrackingTransparency.h>

if (@available(iOS 14.5, *)) {
    [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
        // Initialize CloudX SDK after ATT response
        [self initializeCloudX];
    }];
} else {
    [self initializeCloudX];
}

Add the NSUserTrackingUsageDescription key to your Info.plist with a description of why you need tracking permission.

Manual Privacy API

If you manage user consent yourself (without a CMP), you can set GDPR and CCPA privacy status directly. Call these before initializing the SDK — some ad network SDKs require privacy settings at initialization time and will not apply values set after init.

// Set privacy BEFORE initializing the SDK
[CloudXCore setHasUserConsent:@YES];
[CloudXCore setDoNotSell:@NO];

[[CloudXCore shared] initializeWithConfiguration:config completion:completion];

User Targeting

// Set hashed user ID for targeting
[[CloudXCore shared] setHashedUserID:@"hashed-user-id"];

// Set custom user key-value pairs (cleared by privacy regulations)
[[CloudXCore shared] setUserKeyValue:@"age" value:@"25"];
[[CloudXCore shared] setUserKeyValue:@"gender" value:@"male"];
[[CloudXCore shared] setUserKeyValue:@"location" value:@"US"];

// Set custom app key-value pairs for request targeting
[[CloudXCore shared] setAppKeyValue:@"app_version" value:@"1.0.0"];
[[CloudXCore shared] setAppKeyValue:@"user_level" value:@"premium"];

// Clear all custom key-values
[[CloudXCore shared] 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.

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

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

banner?.setExtraParameter("requestId", value: "request-456")
banner?.setExtraParameter("impressionKey", value: "impression-789")
banner?.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