Floxis Maven/Documentation

Floxis iOS SDK guide#

The Floxis iOS SDK adds Floxis demand to an iOS app that already monetises with AdMob. It is a thin layer over the open-source Prebid Mobile SDK, installed as a Swift package.

Availability: the iOS SDK is not yet a public release. It will be available from the next release; contact your Floxis account manager for early access. Your account manager gives you the package URL and version to use in step 1.

Before you start, set up an endpoint with the integration type Floxis SDK for the app on the Supply page. When you open the saved endpoint, its integration panel shows:

  • the seat and region for this app. The panel shows them in its Android setup code; use the same two values on iOS;
  • the AdMob setup sheet: one AdMob Custom Event per price tier, to copy or download as CSV. Pick iOS as the platform.

How it works#

  1. Floxis auction. Before each AdMob load, the SDK runs one auction for the placement (1.5 s deadline by default).
  2. AdMob load. If Floxis wins, the price tier (for example hb_pb = 0.70) is added to the AdMob request.
  3. Custom Event renders. AdMob's waterfall reaches the Custom Event for that price tier, and the Prebid adapter shows the Floxis ad.

If Floxis doesn't bid, or doesn't answer in time, AdMob loads exactly as it does today. Floxis takes part in your AdMob waterfall at the eCPM of each Custom Event. It does not take part in AdMob bidding.

Supported formats: banner, interstitial (display, video, or both in one auction) and rewarded video. Native, app open and rewarded interstitial ads are not supported. On iOS the SDK works with AdMob only; the Google Ad Manager, AppLovin MAX and Unity LevelPlay integrations are Android-only for now.

Requirements#

ItemRequirement
iOS15.0 or newer
Xcode26.2 or newer (needed by Google Mobile Ads 13.4 and later), Swift 5.9 or newer
Package managerSwift Package Manager. CocoaPods and a manual XCFramework are not offered.
Google Mobile Ads13.8.0 or newer, below 14. The package requires it.
Prebid Mobile3.4.0, pinned by the package. Prebid's AdMob adapters come with it.
AccountA Floxis SDK endpoint on the Supply page, and AdMob ad units for each placement.

1. Add the Swift package#

In Xcode, choose File › Add Package Dependencies…, enter the package URL from your account manager, and add these products to your app target:

  • FloxisSDK: the auction and the AdMob wrappers;
  • FloxisSDKCore: configuration and result types.

The package brings in Prebid Mobile 3.4.0 and Google Mobile Ads 13.8.0 or a newer 13.x. To set your App Store ID (step 4), your code also imports PrebidMobile. Add Prebid's package as well, at exactly 3.4.0, and link its PrebidMobile product:

  • URL: https://github.com/prebid/prebid-mobile-ios.git
  • Dependency rule: Exact Version 3.4.0

If you manage dependencies in a Package.swift, the same setup reads:

dependencies: [
    .package(url: "<package URL from your account manager>", exact: "<version>"),
    .package(url: "https://github.com/prebid/prebid-mobile-ios.git", exact: "3.4.0"),
],
targets: [
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "FloxisSDK", package: "<package name>"),
            .product(name: "FloxisSDKCore", package: "<package name>"),
            .product(name: "PrebidMobile", package: "prebid-mobile-ios"),
        ]
    ),
]

No linker flags or build settings are needed. The package bundles its privacy manifest and the MRAID and Open Measurement scripts that Prebid uses, so Prebid does not download those scripts at run time.

2. Update Info.plist#

KeyWhat to put there
GADApplicationIdentifierYour AdMob app ID (ca-app-pub-XXXX~YYYY). Google Mobile Ads needs it.
SKAdNetworkItemsThe SKAdNetwork IDs of the networks that buy your inventory (below).
NSUserTrackingUsageDescriptionOnly if your app shows the App Tracking Transparency prompt (step 3).

SKAdNetwork. Floxis has no SKAdNetwork ID of its own. Add Google's ID and the third-party buyer IDs that Google publishes for AdMob, in lower case. Prebid copies every ID in your list, with your App Store ID, into each Floxis bid request (imp.ext.skadn), so buyers can see which ad networks your app supports.

<key>SKAdNetworkItems</key>
<array>
  <dict>
    <key>SKAdNetworkIdentifier</key>
    <string>cstr6suwn9.skadnetwork</string>
  </dict>
  <!-- add Google's third-party buyer IDs here -->
</array>

When the list is present, also set your numeric App Store ID as Targeting.shared.sourceapp (step 4). Without it, Prebid logs "Info.plist contains SKAdNetwork but sourceapp is nil!" and leaves imp.ext.skadn out of the request, so buyers see no SKAdNetwork support at all.

3. App Tracking Transparency (optional)#

The SDK never shows the ATT prompt. If your app asks for tracking permission, ask before you start ads and wait for the answer, so the first auctions already carry it. iOS shows the prompt only while the app is active, so call this from your first screen (after your consent screen), not from didFinishLaunchingWithOptions:

import AppTrackingTransparency

Task { @MainActor in
    if ATTrackingManager.trackingAuthorizationStatus == .notDetermined {
        _ = await ATTrackingManager.requestTrackingAuthorization()
    }
    startAds()          // step 4
}

What Prebid sends for each answer:

  • Authorized: the advertising ID (IDFA) in device.ifa, only when TCF allows device access (Purpose 1 is granted, or no Purpose 1 choice is recorded and GDPR does not apply), and lmt = 0.
  • Not authorized, or not asked: no usable IDFA (iOS reports it as all zeros), the vendor ID (IDFV) in device.ext.ifv, and lmt = 1. The exchange also removes user IDs, the full IP address and the device and session signals from these requests.
  • In both cases, the ATT status in device.ext.atts.

4. Initialise after MobileAds.shared.start#

Run your consent management platform (CMP) first, so its consent keys are in place before the first auction. Then start Google Mobile Ads and initialise Floxis on the main thread, with the seat and region from your endpoint's integration panel. If you don't use ATT, call startAds() once your CMP is done.

import FloxisSDK
import FloxisSDKCore
import GoogleMobileAds
import PrebidMobile

@MainActor
func startAds() {
    Targeting.shared.sourceapp = "1234567890"          // your numeric App Store ID
    GoogleMobileAds.MobileAds.shared.start(completionHandler: nil)
    do {
        let configuration = try FloxisConfiguration(
            seat: "YOUR_SEAT",
            region: "us-e",
            timeoutMs: 1_500,
            storeURL: URL(string: "https://apps.apple.com/app/id1234567890")
        )
        Floxis.shared.initialize(configuration) { status, detail in
            print("Floxis init: \(status) \(detail ?? "")")
        }
    } catch {
        print("Floxis configuration: \(error)")
    }
}

FloxisConfiguration throws a FloxisConfigurationError (invalidSeat, invalidRegion, invalidTimeout or invalidStoreURL) when a value is malformed. The other options:

OptionWhat it does
timeoutMsThe longest the SDK waits for Floxis before AdMob loads without it (200–10,000 ms, default 1,500).
storeURLYour App Store URL, sent to buyers as app.storeurl. Must be https.
debugWrites Floxis FLX-xxx lines to the Xcode console. It never changes what is sent, so it is safe in a release build.
testModeMarks auctions as test traffic. Only works in the Simulator or a development-signed build (see step 9).
enabled / Floxis.shared.setEnabled(_:)Kill switch. false stops all Floxis auctions at once, for example from your remote config.
personalizedAds / Floxis.shared.setPersonalizedAds(_:)false skips Floxis bidding and adds npa=1 to each Floxis wrapper's AdMob request. Update it when consent changes.
shareLocationDefault true: lets Prebid send the device location when the app already has permission and consent allows it.

The completion reports one of these statuses:

  • .succeeded: ready to bid.
  • .serverStatusWarning: initialised; auctions still run.
  • .failed: loads skip Floxis and AdMob loads on its own. A later load retries, at most once every 30 seconds. Initialising again with a different seat or region also returns .failed.
  • .disabled: enabled is false.

Ads loaded before you call initialize skip Floxis with skippedDisabled, and ads loaded while it is still starting skip it with skippedNotInitialized. Both still load from AdMob.

5. Set up AdMob mediation#

For each ad unit that should get Floxis demand:

  1. In AdMob, open Mediation, create an iOS mediation group for the format (or open your existing one), and add the ad unit.

  2. Add one Custom Event per row of your endpoint's AdMob setup sheet. Pick iOS and the ad format in the panel, then copy the rows or download them as CSV. Each row gives the label, class name, parameter and eCPM:

    FormatCustom Event class
    BannerPrebidAdMobBannerAdapter
    InterstitialPrebidAdMobInterstitialAdapter
    Video interstitialPrebidAdMobVideoInterstitialAdapter
    RewardedPrebidAdMobRewardedAdapter

    For example, the row for $1.25 has the parameter {"hb_pb":"1.25"} and the eCPM 1.25.

  3. Create every row of the sheet. AdMob matches the parameter exactly, so a missing tier means a winning Floxis bid at that price can't show. When you change the endpoint's price tiers, export the sheet again and update AdMob to match.

  4. Interstitial ad units that take both display and video (the SDK default): add the Interstitial and the Video interstitial rows to the same mediation group.

  5. Banner ad units: turn off AdMob's automatic refresh. The Floxis banner refreshes itself and runs a new auction each time.

6. Load ads#

configID is your name for the placement (for example home_banner, level_end_interstitial), up to 128 characters. Floxis reports by placement, so give each placement its own name, keep it the same across app versions, and never put user data in it.

Call the SDK from the main thread; its callbacks arrive on the main thread. Keep a strong reference to each wrapper until its ad has been shown or its screen is gone. The examples write Google's types in full (GoogleMobileAds.BannerView), because Prebid has types with the same names.

import FloxisSDK
import FloxisSDKCore
import GoogleMobileAds
import UIKit

final class HomeViewController: UIViewController, GoogleMobileAds.BannerViewDelegate {
    private let bannerView = GoogleMobileAds.BannerView(adSize: GoogleMobileAds.AdSizeBanner)
    private var banner: FloxisBannerAd?

    override func viewDidLoad() {
        super.viewDidLoad()
        bannerView.adUnitID = "ca-app-pub-XXXX/BANNER"
        bannerView.rootViewController = self
        bannerView.delegate = self
        view.addSubview(bannerView)                     // lay it out with your own constraints

        let banner = FloxisBannerAd(
            configID: "home_banner",
            size: CGSize(width: 320, height: 50),
            bannerView: bannerView,
            refreshIntervalSeconds: 60                  // 30...120, 0 turns refresh off
        )
        banner.load { outcome in
            print("Floxis banner: \(outcome.status) \(outcome.priceBucket ?? "-")")
        }
        self.banner = banner
    }

    override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated); banner?.resume() }
    override func viewDidDisappear(_ animated: Bool) { banner?.pause(); super.viewDidDisappear(animated) }

    func bannerView(_ bannerView: GoogleMobileAds.BannerView, didFailToReceiveAdWithError error: Error) {
        banner?.adMobDidFailToLoad(error)
    }
}
  • Extra sizes for the same slot go in banner.additionalSizes before load.
  • Refresh also pauses while the app is in the background, and waits while the banner is not in a window.
  • Call banner.destroy() when the screen is closed for good. Refresh stops, and no callback arrives afterwards.
  • Forwarding AdMob's banner failure with adMobDidFailToLoad(_:) is optional. It logs FLX-500.

Interstitial#

The full-screen examples run inside a view controller, with the same imports as the banner.

private var interstitial: FloxisInterstitialAd?

func loadInterstitial() {
    let ad = FloxisInterstitialAd(configID: "level_end_interstitial", adMobUnitID: "ca-app-pub-XXXX/INTERSTITIAL")
    ad.onDismiss = { [weak self] in self?.loadInterstitial() }
    ad.load { outcome, error in
        print("Floxis interstitial: \(outcome.status) \(error?.localizedDescription ?? "loaded")")
    }
    interstitial = ad
}

func showInterstitial() {
    guard let interstitial else { return }
    switch interstitial.show(from: self) {
    case .expired, .disabled: loadInterstitial()
    default: break
    }
}

By default each interstitial auction asks for display and video together, and the higher bid wins. For one kind only, pass format: .display (or .video). Video length and skipping can be limited:

let ad = FloxisInterstitialAd(
    configID: "level_end_interstitial",
    adMobUnitID: "ca-app-pub-XXXX/INTERSTITIAL",
    format: .video,
    videoOptions: FloxisVideoOptions(maxDurationSeconds: 30, skippable: true)
)

Rewarded#

private var rewarded: FloxisRewardedAd?

func loadRewarded() {
    let ad = FloxisRewardedAd(
        configID: "rewarded_main",
        adMobUnitID: "ca-app-pub-XXXX/REWARDED",
        videoOptions: FloxisVideoOptions(maxDurationSeconds: 30)
    )
    ad.onDismiss = { [weak self] in self?.loadRewarded() }
    ad.load { outcome, error in print("Floxis rewarded: \(outcome.status)") }
    rewarded = ad
}

func showRewarded() {
    guard let rewarded else { return }
    switch rewarded.show(from: self, userDidEarnReward: { [weak self] in self?.grantReward() }) {
    case .expired, .disabled: loadRewarded()
    default: break
    }
}

The reward closure runs only when Google reports that the user earned the reward. Loading or opening the ad grants nothing.

Full-screen ad rules#

  • Each loaded ad shows once. Load a new one for the next show; onDismiss is a good place to do it.
  • A loaded ad expires one hour after it loads.
  • onShowFailure receives Google's error if the ad fails to present.
  • isFloxisWinner tells you whether the Floxis Custom Event served the loaded ad.
  • destroy() drops the ad; no callback arrives afterwards.

show(from:) returns a FloxisShowResult:

ResultMeaning
.shownThe ad is on screen.
.notReadyNothing is loaded yet.
.alreadyShownThis ad was already shown. Load a new one.
.destroyeddestroy() was called.
.expiredThe ad is older than one hour. Load a new one.
.disabledFloxis won, but the kill switch or setPersonalizedAds(false) was applied after load.

Custom request settings#

Each wrapper has a requestFactory. Return a new GoogleMobileAds.Request on every call; the SDK adds its own extras to it:

ad.requestFactory = {
    let request = GoogleMobileAds.Request()
    request.contentURL = "https://example.com/article"
    return request
}

You are responsible for collecting consent and for your app's privacy disclosures. What the SDK does:

  • TCF v2, US Privacy and GPP. Your CMP writes the standard IAB keys (IABTCF_*, IABUSPrivacy_String, IABGPP_*) to UserDefaults. Prebid reads them and forwards them to the auction.

  • Children (COPPA). The SDK does not decide whether a user is a child, and it does not block child-directed traffic. Before each auction it copies Google Mobile Ads' child-directed setting into Prebid's COPPA flag: ageRestrictedTreatment = .child, or the older tagForChildDirectedTreatment as true or false. Floxis passes the flag to the auction as regs.coppa, and a child-directed request carries no device location. Set Google's setting as you already do:

    MobileAds.shared.requestConfiguration.ageRestrictedTreatment = .child

    If Google's setting is unspecified, the SDK keeps whatever you set on Prebid with Targeting.shared.subjectToCOPPA = true.

  • Personalised ads. setPersonalizedAds(false) skips Floxis bidding. Each Floxis wrapper adds npa=1 to its own AdMob request; the SDK never changes your app-wide Google request configuration.

  • Device and session signals. Battery level, charging, Low Power Mode, dark mode, whether the system volume is at zero (as video playbackmethod), impression depth and session length are sent only when all of these are true:

    • personalised ads are on;
    • GDPR does not apply, or the TC string grants Purpose 1;
    • the US Privacy string does not record an opt-out;
    • no applicable GPP US section records an opt-out of sale, sharing or targeted advertising.

    Battery fields also need your app to have turned on UIDevice.current.isBatteryMonitoringEnabled.

  • Location. The SDK never asks for location permission. It sends a location only when all of these are true:

    • shareLocation is on;
    • the app already holds When In Use or Always authorisation;
    • the request is not child-directed;
    • under GDPR, the user opted in to precise geolocation (TCF special feature 1);
    • neither the US Privacy string nor an applicable GPP US section records an opt-out.

Privacy manifest. FloxisSDK ships a PrivacyInfo.xcprivacy, and Xcode includes it in your app's privacy report (Product › Archive, then Generate Privacy Report in the Organizer). It declares:

  • UserDefaults access with reason CA92.1, to read your CMP's consent keys;
  • NSPrivacyTracking set to true, because the SDK can pass the IDFA to buyers, which Apple counts as tracking. Prebid sends the IDFA only after the user authorises tracking (step 3);
  • no tracking domains. iOS blocks a listed tracking domain until the user authorises tracking, and the Floxis auction also serves users who have not, so auctions run whatever the ATT answer. A separate hostname for tracking traffic is planned; when it is in place, the manifest will list it;
  • the data types below, all for third-party advertising.

Prebid Mobile 3.4.0 has no privacy manifest for its own code; only the Open Measurement framework it bundles ships one. Make sure your App Store answers also cover what Prebid and Google Mobile Ads collect.

App Store privacy labels. These are the data types the SDK's manifest declares. Use them as a starting point for your answers in App Store Connect:

App Store data typeWhen it is sentLinkedTracking
Identifiers › Device IDIDFA after ATT authorisation; otherwise IDFVYesYes
Location › Precise LocationOnly under the location rules aboveYesYes
Location › Coarse LocationDerived from the request IP addressYesNo
Usage Data › Advertising DataEvery auction (the ad requested and the ad shown)YesNo
Usage Data › Product InteractionImpression depth and session length, under the signal rules aboveYesNo
Other DataBattery, Low Power Mode, dark mode, volume muted, under the signal rules aboveYesNo

All data goes to the auction over HTTPS. For the full rules, see the SDK privacy and consent guide.

8. Add the app-ads.txt line#

Buyers that check app-ads.txt will not bid on the app without the Floxis line. Add it to app-ads.txt at the root of the developer website on the app's App Store listing. Ask the dashboard assistant "what is my ads.txt line for Floxis" for the exact line for your account. The **Floxis ads.txt, app-ads.txt and sellers.json Guide** explains where the file lives and how crawlers find it.

9. Test before release#

  1. In the Simulator or a development-signed build, set debug: true and testMode: true.
  2. Watch the Xcode console for Floxis FLX- lines. You should see FLX-101 at start-up. Bids and no-bids log nothing; a skipped auction logs FLX-201 and a failed one FLX-203.
  3. Read each load's FloxisAuctionOutcome in its completion: status, priceBucket (the winning hb_pb), latencyMs and detail.
  4. Check that an ad Floxis won shows through the AdMob Custom Event. Google's test ad units have no Floxis Custom Events, so use your own ad units. Some buyers don't bid on test traffic, so a test build may see few Floxis wins.
  5. Tell your account manager your placement names and the date of the release build, so the first live traffic can be checked with you.

testMode only takes effect in the Simulator or when the app's provisioning profile allows debugging (development signing). App Store, TestFlight, ad hoc and enterprise builds ignore it, send normal auctions, and report FLX-105. Floxis.shared.testAuctions shows whether test mode is in effect.

Set Floxis.shared.onDiagnostic to receive the same codes in your own logging, even with debug: false:

CodeMeaning
FLX-101Initialisation finished
FLX-103Initialisation failed
FLX-105testMode ignored: not a development build
FLX-201Auction skipped (disabled, personalised ads off, or not initialised)
FLX-202A Floxis ad expired before show
FLX-203Auction timed out or failed
FLX-400Show of a Floxis ad blocked after a disable or consent change
FLX-500The AdMob load failed

For no-fill causes, missing Custom Events and other common issues, see the **SDK testing and troubleshooting** guide. Version changes are in the SDK release notes, and every public type is in the SDK API reference.

Licence and sample app#

The SDK is proprietary, all rights reserved. It depends on Prebid Mobile, under the Apache 2.0 licence, and bundles unmodified copies of Prebid's MRAID and Open Measurement scripts. Google Mobile Ads comes under Google's own terms. Floxis.sdkVersion returns the SDK version. Your account manager can share a sample app that loads each ad format on your seat.