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#
- Floxis auction. Before each AdMob load, the SDK runs one auction for the placement (1.5 s deadline by default).
- AdMob load. If Floxis wins, the price tier (for example
hb_pb = 0.70) is added to the AdMob request. - 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#
| Item | Requirement |
|---|---|
| iOS | 15.0 or newer |
| Xcode | 26.2 or newer (needed by Google Mobile Ads 13.4 and later), Swift 5.9 or newer |
| Package manager | Swift Package Manager. CocoaPods and a manual XCFramework are not offered. |
| Google Mobile Ads | 13.8.0 or newer, below 14. The package requires it. |
| Prebid Mobile | 3.4.0, pinned by the package. Prebid's AdMob adapters come with it. |
| Account | A 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#
| Key | What to put there |
|---|---|
GADApplicationIdentifier | Your AdMob app ID (ca-app-pub-XXXX~YYYY). Google Mobile Ads needs it. |
SKAdNetworkItems | The SKAdNetwork IDs of the networks that buy your inventory (below). |
NSUserTrackingUsageDescription | Only 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), andlmt = 0. - Not authorized, or not asked: no usable IDFA (iOS reports it as all zeros), the vendor ID (IDFV) in
device.ext.ifv, andlmt = 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:
| Option | What it does |
|---|---|
timeoutMs | The longest the SDK waits for Floxis before AdMob loads without it (200–10,000 ms, default 1,500). |
storeURL | Your App Store URL, sent to buyers as app.storeurl. Must be https. |
debug | Writes Floxis FLX-xxx lines to the Xcode console. It never changes what is sent, so it is safe in a release build. |
testMode | Marks 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. |
shareLocation | Default 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:enabledisfalse.
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:
In AdMob, open Mediation, create an iOS mediation group for the format (or open your existing one), and add the ad unit.
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:
Format Custom Event class Banner PrebidAdMobBannerAdapterInterstitial PrebidAdMobInterstitialAdapterVideo interstitial PrebidAdMobVideoInterstitialAdapterRewarded PrebidAdMobRewardedAdapterFor example, the row for $1.25 has the parameter
{"hb_pb":"1.25"}and the eCPM1.25.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.
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.
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.
Banner#
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.additionalSizesbeforeload. - 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 logsFLX-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;
onDismissis a good place to do it. - A loaded ad expires one hour after it loads.
onShowFailurereceives Google's error if the ad fails to present.isFloxisWinnertells you whether the Floxis Custom Event served the loaded ad.destroy()drops the ad; no callback arrives afterwards.
show(from:) returns a FloxisShowResult:
| Result | Meaning |
|---|---|
.shown | The ad is on screen. |
.notReady | Nothing is loaded yet. |
.alreadyShown | This ad was already shown. Load a new one. |
.destroyed | destroy() was called. |
.expired | The ad is older than one hour. Load a new one. |
.disabled | Floxis 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
}7. Consent and privacy#
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_*) toUserDefaults. 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 oldertagForChildDirectedTreatmentas true or false. Floxis passes the flag to the auction asregs.coppa, and a child-directed request carries no device location. Set Google's setting as you already do:MobileAds.shared.requestConfiguration.ageRestrictedTreatment = .childIf 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 addsnpa=1to 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:
shareLocationis 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:
UserDefaultsaccess with reasonCA92.1, to read your CMP's consent keys;NSPrivacyTrackingset 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 type | When it is sent | Linked | Tracking |
|---|---|---|---|
| Identifiers › Device ID | IDFA after ATT authorisation; otherwise IDFV | Yes | Yes |
| Location › Precise Location | Only under the location rules above | Yes | Yes |
| Location › Coarse Location | Derived from the request IP address | Yes | No |
| Usage Data › Advertising Data | Every auction (the ad requested and the ad shown) | Yes | No |
| Usage Data › Product Interaction | Impression depth and session length, under the signal rules above | Yes | No |
| Other Data | Battery, Low Power Mode, dark mode, volume muted, under the signal rules above | Yes | No |
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#
- In the Simulator or a development-signed build, set
debug: trueandtestMode: true. - Watch the Xcode console for
Floxis FLX-lines. You should seeFLX-101at start-up. Bids and no-bids log nothing; a skipped auction logsFLX-201and a failed oneFLX-203. - Read each load's
FloxisAuctionOutcomein its completion:status,priceBucket(the winninghb_pb),latencyMsanddetail. - 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.
- 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:
| Code | Meaning |
|---|---|
FLX-101 | Initialisation finished |
FLX-103 | Initialisation failed |
FLX-105 | testMode ignored: not a development build |
FLX-201 | Auction skipped (disabled, personalised ads off, or not initialised) |
FLX-202 | A Floxis ad expired before show |
FLX-203 | Auction timed out or failed |
FLX-400 | Show of a Floxis ad blocked after a disable or consent change |
FLX-500 | The 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.