# Welcome to Airflux

Here you can find all the documentation you need to understand Airflux, integrate it into your game, and maximize your ad revenue. We will guide you through every step of the journey, from initial integration to utilizing advanced features for your game’s growth.

***

## **1.** What’s Airflux?

Airflux is an AI-powered ad optimization platform designed for mobile games, especially in the casual and mid-core genres. With a focus on playing experience, Airflux optimizes ad timing, frequency, and formats using machine learning and real-time data analysis to uplift LTV while reducing churn. With automated A/B testing and reinforcement learning, Airflux continuously adapts to changing user behaviors to maximize ad revenue while keeping your players engaged.

## **2. Who needs Airflux?**

Airflux is the ideal solution for game studios seeking sustainable growth through ad optimization based on each player’s behavior. You will benefit from Airflux if you are:

* developing and running casual and mid-core genres mobile games
* looking for ways to optimize in-game monetization strategies
* seeking to increase ad revenue without compromising user experience
* interested in adopting AI-driven ad optimization solutions

## 3. How do I get started with Airflux?

Learn how to integrate your game and get the most out of Airflux by following the Airflux Tutorial.

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Airflux Integration: Unity</td><td><a href="/files/EiJJrt8k3roBq9bBBf6y">/files/EiJJrt8k3roBq9bBBf6y</a></td><td><a href="/pages/Sr9RLQxqnzKQL90t7tTo">/pages/Sr9RLQxqnzKQL90t7tTo</a></td></tr><tr><td>Airflux Integration: Android</td><td><a href="/files/EiJJrt8k3roBq9bBBf6y">/files/EiJJrt8k3roBq9bBBf6y</a></td><td><a href="/pages/mehUMrf5rZaWFjPEErD4">/pages/mehUMrf5rZaWFjPEErD4</a></td></tr><tr><td>Airflux Integration: iOS</td><td><a href="/files/EiJJrt8k3roBq9bBBf6y">/files/EiJJrt8k3roBq9bBBf6y</a></td><td><a href="/pages/94nsaSxa62SGDswPAj0w">/pages/94nsaSxa62SGDswPAj0w</a></td></tr></tbody></table>

## **4.** Where can I get more information about Airflux?

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-type="content-ref"></th><th data-hidden></th></tr></thead><tbody><tr><td>Airflux Webpage</td><td><a href="http://airflux.ai/">http://airflux.ai/</a></td><td><a href="https://www.airflux.ai/">https://www.airflux.ai/</a></td><td>Find the full-feature intro of Airflux and book a demo on the official website.</td></tr><tr><td>Airflux Blog</td><td><a href="http://airflux.ai/blog">http://airflux.ai/blog</a></td><td><a href="https://www.airflux.ai/blog">https://www.airflux.ai/blog</a></td><td>Find the latest news and Airflux use cases on the official blog.</td></tr></tbody></table>

## Frequently Asked Questions

<details>

<summary>What is the minimum DAU required to get meaningful results with Airflux?</summary>

Generally, a DAU of around 20 to 30k is sufficient sample size for repeated weekly A/B testing. Note that Airflux is dependent on multiple factors to produce meaning results:

* Expected performance uplift resulting from policy changes
* The amount of event and player attribute data that can be fed to the AI model within test period

</details>

<details>

<summary>What kind of ads can be optimized using Airflux? Can Airflux be used to optimize rewarded ads and in-app purchases?</summary>

Airflux supports optimization across various ad types. Currently, Airflux focuses primarily on optimizing interstitial ads. Other features such as optimizing rewarded ads, are available in a private beta. For inquiries, please contact us at <sales@airflux.ai>.&#x20;

</details>

<details>

<summary>We are already conducting A/B testing with Firebase. How is Airflux different?</summary>

Firebase A/B testing is useful for measuring the effects of specific variables and validating hypotheses, but it relies on manual definitions and analysis to verify policies for particular user groups.

Airflux enhances these capabilities through AI-driven automation. It automatically proposes, tests, and continuously refines policies across hundreds or thousands of fine user segments, focusing on maximizing overall user lifetime value (LTV).

</details>

<details>

<summary>Is there any potential conflict with other mediation platforms (e.g., MAX, AdMob)?</summary>

Airflux is designed to operate seamlessly without conflicts alongside existing mediation platforms.

</details>


# Airflux Integration (Unity)

This guide will walk you through the entire process of integrating the Airflux SDK into your application. It covers everything from installation and initialization to implementing core functionalities.

***

## **Guide Outline**

You can use the following outline to navigate this guide.

{% stepper %}
{% step %}
[Add your app to the dashboard](/airflux-onboarding/airflux-integration-unity/1.-add-your-app-to-the-dashboard)
{% endstep %}

{% step %}
[Install the Airflux SDK](/airflux-onboarding/airflux-integration-unity/2.-install-the-airflux-sdk)
{% endstep %}

{% step %}
[Send in-game event data](/airflux-onboarding/airflux-integration-unity/3.-send-in-game-event-data)
{% endstep %}

{% step %}
[Call the Inference API](/airflux-onboarding/airflux-integration-unity/4.-call-the-inference-api)
{% endstep %}
{% endstepper %}

***

## Understanding Data Collection

Airflux collects data through three primary methods to make the most optimal ad display decisions.

{% hint style="success" %}
Each type of data is collected at a different **time** and for a different **purpose**, so you must provide **all three** to maximize the performance of the AI model.
{% endhint %}

1. In-game event data

   Records user actions such as `ORDER_COMPLETED` and `ACHIEVE_LEVEL`.
2. Player Attribute Data

   Records the player's current status, such as their `level` or `currency` balance.
3. Inference Parameters

   Records contextual information at the time of an ad request, such as `adType` or `adPlacementId`.


# 1. Add your app to the dashboard

Before installing the Airflux SDK in your gaming app, you need to add your app to the Airbridge dashboard. This step will take approximately 5 minutes.

***

## 1. Create an account and add your app to Airbridge

{% hint style="info" %}
Why is this step necessary?

Airflux is built on the Airbridge platform. The **App Name** and **App SDK Token** you get from Airbridge are required for initializing the Airflux SDK.
{% endhint %}

1. Visit the [Airflux Dashboard](https://app.airflux.ai/) and create an account via the Airbridge Dashboard.
2. Select the **Growth Plan** on the Airbridge plan intro page.
3. Set the **Organization name**.
4. Choose **Production mode** as the app mode and add your app per platform.
5. Set the **App Name**. This must be unique and cannot be changed once the app is added.
6. Set the **Time zone** and **Standard Currency**. Standard Currency should be set to ‘USD’. Choose carefully, as they cannot be changed once the app is added to Airbridge.
7. Click **Submit** to finish registering your app.

If you have finished adding an app and want to add additional apps, refer to this Airbridge [article](https://help.airbridge.io/en/guides/register-a-new-app).

## 2. Find the information required for SDK setup

Once your gaming app is added to Airbridge, visit the [Airflux Dashboard](https://app.airflux.ai/), navigate to **\[Tokens]** from the sidebar to find the following information. You will need these to initialize the SDK.

* App Name
* App SDK Token

## Frequently Asked Questions

<details>

<summary>Why sign up via Airbridge?</summary>

Airflux and Airbridge share a single sign-on (SSO) system managed by AB180. Once you've registered on Airbridge, you're all set to use Airflux as well.

</details>

<details>

<summary>My app is not live yet. Can I still add it to the dashboard?</summary>

If your app is not live yet, choose `Production mode` as the app mode, skip adding the app per platform, and move on to the next steps. Once your app goes live, go to the Airbridge dashboard, select **\[Settings]>\[App Settings]**, and add the app by using search or entering the app store URL.

</details>

<details>

<summary>Can I change the app mode after the app is added to Airbridge?</summary>

No, the app mode cannot be changed later. If you want to change the app mode, you need to add the app as a new app and choose a different app mode.

</details>


# 2. Install the Airflux SDK

## 1. SDK Installation

Follow the steps below to add the Airbridge SDK package file to your project.

{% tabs %}
{% tab title="Unity" %}

1. Download the latest version of the[ Airflux Unity SDK package file](https://sdk-download.airflux.ai/unity/index.html?latest).
2. Import the package file by selecting the menu option **\[Assets]>\[Import Package]>\[Custom Package]**.
3. When the import is complete, the **\[Airflux]** tab will appear in the top menu bar of the Unity Editor.
   {% endtab %}
   {% endtabs %}

***

## 2. SDK Initialization

The Airflux Unity SDK is designed to initialize automatically upon app launch once the package is imported and configured. Unlike Native SDKs, it is not necessary to write a separate initialization code (e.g., `Airflux.Initialize()`) in your scripts.

**Configure SDK Settings**

After importing the package, configure the SDK options through the Unity Editor menu.

1. In the Unity top menu bar, go to **\[Airflux] > \[Settings]**.
2. Enter the required information and adjust options in the inspector window.

<table><thead><tr><th width="125.76171875">SDK Option</th><th width="118.22265625">Data Type</th><th width="295.0703125">Description</th><th>Required</th></tr></thead><tbody><tr><td>App Name</td><td><code>string</code></td><td>Input the App Name from the Airbridge dashboard.</td><td><strong>Required</strong></td></tr><tr><td>App Token</td><td><code>string</code></td><td>Input the App Token from the Airbridge dashboard.</td><td><strong>Required</strong></td></tr><tr><td>SDK Enabled</td><td><code>boolean</code></td><td><p>Set whether to enable the SDK upon initialization.</p><ul><li>true: The SDK is initialized in active mode.</li><li>false: The SDK is initialized in inactive mode and is enabled upon calling the <code>Airflux.EnableSDK()</code> function.</li></ul></td><td>Optional</td></tr><tr><td>Auto Start Tracking Enabled</td><td><code>boolean</code></td><td><p>Set whether to collect events automatically upon SDK initialization.</p><ul><li>true: Event collection starts automatically upon initialization.</li><li>false: Event collection starts upon calling the <code>Airflux.StartTracking()</code> function.</li></ul></td><td>Optional</td></tr><tr><td>Log Level</td><td><code>AirfluxLogLevel</code></td><td>Set the log level for the Airflux SDK. Choose from <code>debug</code>, <code>info</code>, <code>warning</code>, <code>error</code>, <code>fault</code> .</td><td>Optional</td></tr><tr><td>Session Timeout</td><td><code>double</code></td><td>The default value is 300 seconds. Modify if needed.</td><td>Optional</td></tr><tr><td>Allow Every Country Enabled</td><td><code>boolean</code></td><td>When set to true, all countries are allowed to follow Airflux's optimization policies. To use a specific list of countries, set it to false and provide a list using <code>setCountryAllowlist()</code>.</td><td>Optional</td></tr><tr><td>Country Allowlist</td><td><code>List&#x3C;String></code></td><td>Set the countries where calling the Airflux's inference API should be allowed. Use country codes following the ISO 3166-1 alpha-2 format (e.g., US, KR). You can set multiple countries using the Country Allowlist. Airflux identifies a country based on the value tied to the device.</td><td>Optional</td></tr></tbody></table>

***

## 3. Verification

#### **Unity Log check**

To view detailed log information for your app, use the Unity Log tool.

{% hint style="warning" %}
You can set the log output level using the `setLogLevel` function. \
Setting it to `AirfluxLogLevel.DEBUG` allows you to see all Airflux logs.
{% endhint %}

***

## 4. Frequently Asked Questions

{% hint style="info" %}
Opt-in policy compliance

If player consent is required to send in-game data, implement the necessary setup by following the FAQ section below.&#x20;

[How can I set up the Airflux SDK to comply with the opt-in policy?](https://docs.airflux.ai/airflux-onboarding/airflux-integration/2.-install-the-airflux-sdk#how-can-i-set-up-the-airflux-sdk-to-comply-with-the-opt-in-policy)
{% endhint %}

<details>

<summary>How can I set up the Airflux SDK to comply with the opt-in policy?</summary>

The opt-in policy requires user consent before collecting and using player data. To adhere to this policy, implement the following methods.

1. **SDK opt-in setup**

Upon initialization of the Airflux SDK, set the initialization option `SetAutoStartTrackingEnabled()`to `false` and call the `StartTracking()` function at the point where you have received user consent for data tracking. The Airflux SDK will collect data after the `startTracking()` function is called.

{% hint style="warning" %}
**Attention**

Although event data is not tracked before `StartTracking()` is triggered and after `StopTracking()` is triggered, player attribute data is aggregated and anonymized for transmission upon calling the Inference API.
{% endhint %}

```csharp
// After player provided consent to data tracking
Airflux.StartTracking()

// If player withdraws consent to data tracking
Airflux.StopTracking()
```

2. **Initializing the Airflux SDK in inactive mode**

{% hint style="warning" %}
**Attention**

If the SDK is not enabled immediately after the SDK initialization, the Install and Open events may not be collected.
{% endhint %}

Set the initialization option `SetSDKEnabled()` to `false` to initialize the SDK with all functions disabled until user consent for data tracking is obtained. Through this method, you can adhere to privacy policies to the highest level. Note that when the SDK is set in inactive mode, all features are disabled and no events and player attribute data is sent to Airflux.

</details>

<details>

<summary>How can I configure the Airflux SDK to call the Inference API only in specific countries</summary>

Set the initialization opeion `SetAllowEveryCountryEnabled()` to false and add the countries you want to allow to the "Country Allowlist". Country codes should follow the ISO 3166-1 alpha-2 format (e.g., "US", "KR"), and multiple countries can be specified by separating codes with commas. Country codes are not case-sensitive.

</details>

<details>

<summary>Can I use other mediation platforms, such as MAX and AdMob, with Airflux?</summary>

Yes, Airflux is designed to work alongside existing mediation platforms.

</details>

<details>

<summary>How does Airflux determine a user’s country?</summary>

Airflux distinguishes a country based on the value tied to the deivce.

</details>

<details>

<summary>Does Airflux collect the ADID (Advertising ID)?</summary>

No. The Airflux SDK operates without collecting or requiring the ADID.

</details>

## 5. Troubleshooting

<details>

<summary>[Android] A coroutine dependency error occurs during the build process. </summary>

#### Issue

A coroutine dependency error occurs during the build process with the following message.

```
java.lang.NoClassDefFoundError: kotlin/coroutines/AbstractCoroutineContextKey
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
  ...
```

#### Cause

If the kotlinx-coroutines-core library version is 1.3.5 or later, [the kotlin-stdlib library version must be at a certain level or later](https://github.com/Kotlin/kotlinx.coroutines/issues/1879).

#### Solution

Check whether the kotlin-stdlib library version is v.1.3.70 or later with the `gradlew dependencies` command. If the version is earlier than v.1.3.70, you need to update it.

</details>


# 3. Send in-game event data

To enable optimal ad display and model training, it is crucial to send sufficient in-game event and player attribute data to Airflux. This guide details the core principles and implementation steps for data collection using the Airflux SDK.

<figure><img src="/files/zol4mAdTLCVez07be1F6" alt=""><figcaption></figcaption></figure>

***

## Understanding Data Collection

Airflux collects data through three primary methods to make the most optimal ad display decisions.

{% hint style="success" %}
Each type of data is collected at a different **time** and for a different **purpose**, so you must provide **all three** to maximize the performance of the AI model.
{% endhint %}

1. In-game event data

   Records user actions such as `ORDER_COMPLETED` and `ACHIEVE_LEVEL`.
2. Player Attribute Data

   Records the player's current status, such as their `level` or `currency` balance.
3. Inference Parameters

   Records contextual information at the time of an ad request, such as `adType` or `adPlacementId`.

***

## 1. Send in-game event data

Use the `Airflux.TrackEvent()` function to record key player actions within your game. The collected event data plays a crucial role in enabling the Airflux AI model to learn player behavior patterns and make optimal decisions.

### Detailed Event Guide with Code Examples

The following events are essential for model training. Clearly understand the purpose and timing of collecting each event, and ensure proper implementation for data transmission.

<details>

<summary>Ad Impression</summary>

Track this event immediately after an in-app ad is shown to the user. Collect relevant data such as ad type, revenue, and placement details.

{% hint style="danger" %}
The in-app ad revenue data must be collected using the client-side SDK through mediation platform integrations and sent to Airflux.
{% endhint %}

<table><thead><tr><th>Name</th><th width="235.9296875">Description</th><th>Required</th><th>Sample Value</th></tr></thead><tbody><tr><td><code>value</code></td><td>The amount of ad revenue.</td><td><strong>Required</strong></td><td><code>1.99</code></td></tr><tr><td><code>currency</code></td><td>The currency code for the ad revenue (ISO 4217).</td><td><strong>Required</strong></td><td><code>“USD”</code></td></tr><tr><td><code>adType</code></td><td>The type of ad. You <strong>must</strong> use one of the pre-defined strings from the list below.<br>• <code>"interstitial_ad"</code><br>• <code>"rewarded_ad"</code><br>• <code>“other”</code></td><td><strong>Required</strong></td><td><code>“interstitial_ad”</code></td></tr><tr><td><code>adPlacementID</code></td><td>A unique identifier for the ad placement.</td><td><strong>Required</strong></td><td><code>“placement_3”</code></td></tr><tr><td><code>adPlacementType</code></td><td>The type of ad placement. You <strong>must</strong> use one of the pre-defined strings from the list below.<br>• <code>"static"</code><br>• <code>"dynamic"</code></td><td><strong>Required</strong></td><td><code>“static”</code></td></tr><tr><td><code>adPlacementPosition</code></td><td>The position of the ad placement within the game. You <strong>must</strong> use one of the pre-defined strings from the list below<br>• <code>"stage_start"</code><br>• <code>"stage_middle"</code><br>• <code>"stage_end"</code><br>• <code>"non_stage"</code></td><td>Nullable (if the placement is dynamic)</td><td><code>“stage_start”</code></td></tr><tr><td><code>level</code></td><td>The player's or character's current level.</td><td>Nullable<br>(only if no level system)</td><td><code>10</code></td></tr><tr><td><code>stage</code></td><td>The stage number the user has played. If not in a game, this should be the last known result.</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr><tr><td><code>stageType</code></td><td>The type of stage where the user has played. If not in a game, this should be the last known result. You <strong>must</strong> use one of the pre-defined strings from the list below.<br>• <code>"primary_stage"</code><br>• <code>"secondary_stage"</code><br>• <code>"extra_stage"</code></td><td>Nullable (if the game has no stages)</td><td><code>"primary_stage"</code></td></tr><tr><td><code>totalFinishedStage</code></td><td>The total number of stages a player has finished. For games without stages, this should be the total play count.</td><td>Nullable (if the information is not available)</td><td><code>10</code></td></tr><tr><td><code>stageResult</code></td><td>The result of the most recently completed stage.If the ad placement occurs at the end of a stage or game, this should reflect the result of that stage. If the placement is in the middle of a stage/game or the user is not currently in gameplay, provide the result of the last finished stage.<br>You <strong>must</strong> use one of the pre-defined strings from the list below.<br>• <code>"success"</code><br>• <code>"fail"</code><br>• <code>"giveup"</code><br>• <code>"retry"</code><br>• <code>"draw"</code><br>• <code>"exhausted"</code></td><td>Nullable (if the game has no stages)</td><td><code>"success"</code><br></td></tr><tr><td><code>adCooldownSeconds</code></td><td>When a cooldown is applied to an ad placement, this key-value map shows the trigger and duration of the cooldown. The key is omitted when no cooldown is applied.<br>• <code>interstitial_ad</code> : Cooldown calculated from the last interstitial ad<br>• <code>rewarded_ad</code> : Cooldown calculated from the last rewarded ad.<br>• <code>app_open</code> : Cooldown calculated from the app open event<br>• <code>install</code> : Cooldown calculated from the app install event.<br>• <code>other</code> : Cooldown calculated from other events<br>Example: If <code>{"rewarded_ad": 120}</code> is included, it means a 2-minute cooldown is applied from the last rewarded ad.</td><td>Nullable (if no cooldown is applied)</td><td><code>{"rewarded_ad":10,"interstitial_ad": 20}</code></td></tr><tr><td><code>rewardItems</code></td><td>A key-value map of items rewarded to the player. Only for rewarded ads; otherwise, <code>null</code>.</td><td>Nullable (if the ad has no rewards)</td><td><code>{"coin": 500, "gem": 10}</code></td></tr></tbody></table>

#### **Code Example**

{% code title="Unity: C#" overflow="wrap" %}

```csharp
Airflux.TrackEvent(
    category: AirfluxCategory.AD_IMPRESSION,
    semanticAttributes: new Dictionary<string, object>
    {
        { AirfluxAttribute.VALUE, 1.99 },
        { AirfluxAttribute.CURRENCY, "USD" },
        { AirfluxAttribute.AD_TYPE, "interstitial_ad" },
        { AirfluxAttribute.AD_PLACEMENT_ID, "placement_3" },
        { AirfluxAttribute.AD_PLACEMENT_TYPE, "static" },
        { AirfluxAttribute.AD_PLACEMENT_POSITION, "stage_start" },
        { AirfluxAttribute.LEVEL, 10 },
        { AirfluxAttribute.STAGE_TYPE, "primary_stage" },
        { AirfluxAttribute.STAGE, 10 },
        { AirfluxAttribute.TOTAL_FINISHED_STAGE, 10 },
        { AirfluxAttribute.STAGE_RESULT, "success" },
        {
            AirfluxAttribute.AD_COOLDOWN_SECONDS, new Dictionary<string, object>
            {
                { "rewarded_ad", 10 },
                { "interstitial_ad", 20 }
            }
        },
        {
            AirfluxAttribute.REWARD_ITEMS, new Dictionary<string, object>
            {
                { "coin", 500 },
                { "gem", 10 }
            }
        }
     }
);
```

{% endcode %}

</details>

<details>

<summary>Order Completed</summary>

Track this event when an in-app purchase is completed. Collect data such as transaction ID, purchase amount, currency, and product information.

{% hint style="danger" %}
The in-app purchase revenue data must be collected using the client-side SDK and sent to Airflux. There might be a slight gap between the data sent to Airflux and the revenue data provided by vendors.
{% endhint %}

| Name                      | Description                                                                                                                                                             | Required     | Sample Value              |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------- |
| `transactionID`           | A unique identifier for the transaction.                                                                                                                                | **Required** | `TXN-20250411-5F3C9A72B1` |
| `value`                   | The purchase amount.                                                                                                                                                    | **Required** | `1.99`                    |
| `currency`                | The currency code for the purchase amount.                                                                                                                              | **Required** | `"USD"`                   |
| `products`                | A list of the purchased products.                                                                                                                                       | **Required** | `[]`                      |
| `products[X].productID`   | The unique identifier of the purchased product.                                                                                                                         | **Required** | `1C569KY32P1`             |
| `products[X].productName` | The name of the purchased product.                                                                                                                                      | **Required** | `"welcome_pack"`          |
| `purchaseRoute`           | <p>The purchase path. You must use one of the pre-defined strings from the list below.<br>• <code>"shop"</code><br>• <code>"popup"</code><br>• <code>"other"</code></p> | **Required** | `"shop"`                  |
| `inAppPurchased`          | The status of in-app purchases                                                                                                                                          | **Required** | `True`                    |

#### **Code Example**

{% code title="Unity: C#" %}

```csharp

Airflux.TrackEvent(
    category: AirfluxCategory.ORDER_COMPLETED,
    semanticAttributes: new Dictionary<string, object>
    {
        { AirfluxAttribute.TRANSACTION_ID, "TXN-20250411-5F3C9A72B1" },
        { AirfluxAttribute.VALUE, 1.99 },
        { AirfluxAttribute.CURRENCY, "USD" },
        {
            AirfluxAttribute.PRODUCTS, new List<object>
            {
                new Dictionary<string, object>
                {
                    { AirfluxAttribute.PRODUCT_ID, "1C569KY32P1" },
                    { AirfluxAttribute.PRODUCT_NAME, "welcome_pack" }
                }
            }
        },
        { AirfluxAttribute.PURCHASE_ROUTE, "shop" }
    }
);

logMessageText.GetComponent<TMP_Text>().text = "Order Completed Event Tracked";
```

{% endcode %}

</details>

<details>

<summary>Start Stage</summary>

Track this event when a stage or a game session begins.

<table><thead><tr><th width="135.54296875">Name</th><th width="214.48828125">Description</th><th>Required</th><th>Sample Value</th></tr></thead><tbody><tr><td><code>stageType</code></td><td>The type of stage where the user has played.<br><br>You must use one of the pre-defined strings from the list below.<br>• <code>"primary_stage"</code><br>• <code>"secondary_stage"</code><br>• <code>"extra_stage"</code></td><td>Nullable (if the game has no stages)</td><td><code>"primary_stage"</code></td></tr><tr><td><code>stage</code></td><td>The stage number the user has played<br></td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr></tbody></table>

#### **Code Example**

{% code title="Unity: C#" %}

```csharp
Airflux.TrackEvent(
    category: AirfluxCategory.START_STAGE,
    semanticAttributes: new Dictionary<string, object>
    {
	{ AirfluxAttribute.STAGE_TYPE, "primary_stage" },
        { AirfluxAttribute.STAGE, 10 }
    }
);
```

{% endcode %}

</details>

<details>

<summary>Finish Stage</summary>

Track this event when a stage or game ends.

<table><thead><tr><th>Name</th><th width="192.68359375">Description</th><th>Required</th><th>Sample Value</th></tr></thead><tbody><tr><td><code>stageType</code></td><td>The type of stage where the user has played.<br><br>You must use one of the pre-defined strings from the list below.<br>• <code>"primary_stage"</code><br>• <code>"secondary_stage"</code><br>• <code>"extra_stage"</code></td><td>Nullable (if the game has no stages)</td><td><code>"primary_stage"</code></td></tr><tr><td><code>stage</code></td><td>The stage number the user has played</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr><tr><td><code>totalFinishedStage</code></td><td>The total number of stages a player has finished.<br>For games without stages, this should be the total play count.</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr><tr><td><code>stageResult</code></td><td>The result of the most recently completed stage.<br>If the ad placement occurs at the end of a stage or game, this should reflect the result of that stage. If the placement is in the middle of a stage/game or the user is not currently in gameplay, provide the result of the last finished stage.<br>You must use one of the pre-defined strings from the list below.<br>• <code>"success"</code><br>• <code>"fail"</code><br>• <code>"giveup"</code><br>• <code>"retry"</code><br>• <code>"draw"</code><br>• <code>"exhausted"</code></td><td>Nullable (if the game has no stages)</td><td><code>"success"</code></td></tr></tbody></table>

#### Code Example

{% code title="Unity: C#" %}

```csharp
Airflux.TrackEvent(
    category: AirfluxCategory.FINISH_STAGE,
    semanticAttributes: new Dictionary<string, object>
    {
        { AirfluxAttribute.STAGE_TYPE, "primary_stage" },
        { AirfluxAttribute.STAGE, 10 },
        { AirfluxAttribute.TOTAL_FINISHED_STAGE, 10 },
        { AirfluxAttribute.STAGE_RESULT, "success" }
    }
);
```

{% endcode %}

</details>

<details>

<summary>Achieve Level</summary>

Track this event when a player's or character's level changes, including when it decreases. This event can be omitted for games without a level system.

| Name    | Description                                | Required                                     | Sample Value |
| ------- | ------------------------------------------ | -------------------------------------------- | ------------ |
| `level` | The player's or character's current level. | <p>Nullable<br>(only if no level system)</p> | `10`         |

#### **Code Example**

{% code title="Unity: C#" %}

```csharp
Airflux.TrackEvent(
    category: AirfluxCategory.ACHIEVE_LEVEL,
    semanticAttributes: new Dictionary<string, object>
    {
        { AirfluxAttribute.LEVEL, 10 }
    }
);
```

{% endcode %}

</details>

### Verification

<details>

<summary>Using the App Real-Time Log</summary>

Trigger events based on your test scenarios and check the corresponding logs in the \[Raw Data] > \[App Real-time Log] menu. The event data will be displayed in JSON format, allowing you to confirm that the data type and structure of each field match the predefined format.

<table data-header-hidden><thead><tr><th width="221.7890625">Field</th><th>Validation Criteria</th></tr></thead><tbody><tr><td>eventData.goal.category</td><td>Verify that the event name exactly matches the string defined in the taxonomy.</td></tr><tr><td>semanticAttributes</td><td>• All keys must match those defined in the taxonomy. <br>• Value types must match the defined types (string, number, boolean). <br>• For revenue events (ad_impression, order_completed), values must be positive numbers.</td></tr><tr><td>originalCurrency</td><td>Must be a 3-letter uppercase code defined by ISO-4217 (e.g., USD, KRW)</td></tr></tbody></table>

</details>

***

## 2. Send player attribute data

Player attribute data provides a crucial snapshot of a player's status at a given time. This data is used to fine-tune player segmentation and personalize ad experiences. There are two primary functions for sending this data: `Airflux.SetUser()` and `Airflux.SetContext()`.

{% hint style="warning" %}
Player attribute data is only transmitted to the server when an event is tracked or an inference API is called. Ensure this data is set before making any inference API requests.
{% endhint %}

### Send User ID

Send the player's unique User ID when they sign up or sign in. This ensures all subsequent events and attributes are properly linked to that user. The User ID must be sent before the event data.

<details>

<summary>Send User ID </summary>

#### **When to trigger**

* When a player signs up or signs in.

| Name | Description                 | Example                   |
| ---- | --------------------------- | ------------------------- |
| `ID` | The player’s unique User ID | `"your_internal_user_id"` |

{% hint style="danger" %}
If the User ID is not sent before the event data, the User ID cannot be linked to the event data.
{% endhint %}

#### **Code Examples**

{% code title="Unity: C#" %}

```csharp
Airflux.SetUser(AirfluxUser.ID, "your_internal_user_id");
```

{% endcode %}

</details>

### Send Contextual Data

`Airflux.SetContext()` is used to pass player attributes that are not tied to a specific event. This is crucial for providing the AI model with a complete snapshot of the player's status, such as their current level or currency balance at app launch.

<details>

<summary>Level Attributes</summary>

#### **When to trigger**

* When the game app opens
* When the player logs in

| Name    | Description                                | Example | Skip Case                 |
| ------- | ------------------------------------------ | ------- | ------------------------- |
| `Level` | The player's or character's current level. | `10`    | If the game has no levels |

{% hint style="danger" %}
The player attribute data must be passed to the SDK before the inference API request.
{% endhint %}

#### **Code Example**

{% code title="Unity: C#" %}

```csharp
Airflux.SetContext(AirfluxContext.LEVEL, 10);
```

{% endcode %}

</details>

<details>

<summary>Stage Attributes</summary>

#### **When to Trigger**

* When the game app opens.
* When a player logs in.

| Name                   | Description                                                                                                      | Example | Skip Case                       |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------- |
| `START_STAGE`          | The last stage the player started.                                                                               | `10`    | If the game has no stages       |
| `FINISH_STAGE`         | The last stage the player finished.                                                                              | `10`    | If the game has no stages       |
| `TOTAL_FINISHED_STAGE` | The total number of stages a player has finished. For games without stages, this should be the total play count. | `10`    | If information is not available |

#### **Code Example**

{% code title="Unity: C#" %}

```csharp
Airflux.SetContext(AirfluxContext.START_STAGE, "primary_stage, 10);
Airflux.SetContext(AirfluxContext.FINISH_STAGE, "primary_stage, 10);
Airflux.SetContext(AirfluxContext.TOTAL_FINISHED_STAGE, "primary_stage, 10);
```

{% endcode %}

</details>

<details>

<summary>Other Attributes</summary>

#### **When to Trigger**

When other custom game attributes are updated.&#x20;

#### **Note**

* Attributes can have up to 100 key-value pairs.
* Keys must satisfy the regex ^\[a-zA-Z\_]\[a-zA-Z0-9\_]\*$.
* The maximum length of keys is 128 characters.
* Values type must be string, numeric, or boolean.
* The maximum length of string values is 1024 characters.

| Name        | Description                                                       | Example                   | Skip Case     |
| ----------- | ----------------------------------------------------------------- | ------------------------- | ------------- |
| `Attribute` | A key-value map for other custom attributes and game information. | `"battlePass", "premium”` | If not needed |

#### **Code Examples**

{% code title="Unity: C#" %}

```csharp
Airflux.SetContext(AirfluxContext.ATTRIBUTE, "battlePass", "premium");
```

{% endcode %}

</details>

***

## Frequently Asked Questions

<details>

<summary>How should I use the <code>category</code>, <code>semanticAttributes</code>, and <code>customAttributes</code> parameters for event data collection?</summary>

| Name                 | Type                         | Description                                                                                                                                                                                                                                                                                                          |
| -------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `category`           | `String`                     | <p><strong>The event's unique name</strong> (e.g., <code>AD\_IMPRESSION</code>).<br>Only underscores are permitted as special characters; colons and other special characters are not allowed. If the collected data exceeds the maximum limit of 128 characters, only the initial 128 characters will be saved.</p> |
| `semanticAttributes` | `Dictionary<string, object>` | <p><strong>Semantic attributes of the event</strong><br>Semantic attribute data collection is limited by type: up to 1024 characters for strings, and 64 bits for integers or floats.</p>                                                                                                                            |
| `customAttributes`   | `Dictionary<string, object>` | **Custom attributes of the event** Custom attribute data collection is limited to 2,048 characters; exceeding the limit results in ERROR\_MAX\_LENGTH\_EXCEEDED.                                                                                                                                                     |

</details>

<details>

<summary>When a player completes a stage and levels up at the same time, how should I track it?</summary>

Use the `TrackEvent()` function to track the player's action of completing a stage as the Achieve Level event, and use the `SetLevel()` function to track the player's updated level as the player attribute.

</details>

<details>

<summary>Can I use the Airflux SDK to collect and send game store payment data?</summary>

No. The game store payment data must be collected and sent using the client-side SDK.

</details>

<details>

<summary>After restarting the game, the Airflux SDK stops sending events. How can I fix this?</summary>

The Airflux SDK automatically tracks app lifecycle events, so you do not need to manually call `StartTracking()`. This issue typically occurs if the SDK is disabled, either by:

1. Setting `SDK Enabled` in the SDK initialization options.
2. Calling the `Airflux.DisableSDK()` function during runtime.

When the SDK is disabled, both event tracking and inference requests are halted. To resolve this, you must explicitly call `Airflux.EnableSDK()` to re-activate the SDK's features.

</details>


# 4. Call the Inference API

Configure the Airflux SDK to request an inference decision before displaying an interstitial ad. Based on the AI's response, you can decide whether to proceed with showing the ad. This process allows for smarter, more revenue-optimized ad delivery.

***

<figure><img src="/files/36MUXoSd2P6ZMABnOVjQ" alt=""><figcaption></figcaption></figure>

## 1. Set up the API Call and callbacks

Use the `Airflux.RequestInference()` function with the `SHOW_INTERSTITIAL_AD` method to request a real-time ad decision. To ensure the AI makes an accurate decision, you must provide detailed contextual parameters and implement the appropriate callbacks.

<details>

<summary>Step 1 : Set Inference Parameters</summary>

To make an accurate decision, Airflux requires detailed **contextual parameters** related to the player state, ad type, and placement. These must be passed into the function via the `parameters` object using `AirfluxParameter.*` keys.

{% hint style="danger" %}
**Important**

All required fields must always be collected, and nullable fields should also be collected whenever available. Failure to provide these values may reduce optimization performance and lead to skewed experimental results.
{% endhint %}

<table><thead><tr><th>Name</th><th width="255.56640625">Description</th><th>Type</th><th>Required</th><th>Sample Value</th></tr></thead><tbody><tr><td><code>currency</code></td><td>The currency code for the ad revenue.(ISO 4217)</td><td>string</td><td>Required</td><td><code>“USD”</code></td></tr><tr><td><code>adType</code></td><td>The type of ad. You must use one of the pre-defined strings from the list below.<br>• <code>"interstitial_ad"</code><br>• <code>"rewarded_ad"</code><br>• <code>“other”</code></td><td>string</td><td>Required</td><td><code>“interstitial_ad”</code></td></tr><tr><td><code>adPlacementID</code></td><td>A unique identifier for the ad placement.</td><td>string</td><td>Required</td><td><code>“placement_3”</code></td></tr><tr><td><code>adPlacementType</code></td><td>The type of ad placement. You must use one of the pre-defined strings from the list below.<br>• <code>"static"</code><br>• <code>"dynamic"</code></td><td>string</td><td>Required</td><td><code>“static”</code></td></tr><tr><td><code>adPlacementPosition</code></td><td>The position of the ad placement within the game. You must use one of the pre-defined strings from the list below<br>• <code>"stage_start"</code><br>• <code>"stage_middle"</code><br>• <code>"stage_end"</code><br>• <code>"non_stage"</code></td><td>string</td><td>Nullable (if the placement is dynamic)</td><td><code>“stage_start"</code></td></tr><tr><td><code>level</code></td><td>The player's or character's current level.</td><td>int</td><td>Nullable<br>(only if no level system)</td><td><code>10</code></td></tr><tr><td><code>stage</code></td><td>The stage number where the ad was presented.<br>If not in a game, this should be the last known result.</td><td>int</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr><tr><td><code>stageType</code></td><td>The type of stage where the ad was presented.<br>If not in a game, this should be the last known result.<br>You must use one of the pre-defined strings from the list below.<br>• <code>"primary_stage"</code><br>• <code>"secondary_stage"</code><br>• <code>"extra_stage"</code></td><td>string</td><td>Nullable (if the game has no stages)</td><td><code>"primary_stage"</code></td></tr><tr><td><code>totalFinishedStage</code></td><td>Number of stages played so far (or play count if the game has no stages)</td><td>int</td><td>Nullable (if the information is not available)</td><td><code>10</code></td></tr><tr><td><code>stageResult</code></td><td>The result of the most recently completed stage.<br>If the ad placement occurs at the end of a stage or game, this should reflect the result of that stage. If the placement is in the middle of a stage/game or the user is not currently in gameplay, provide the result of the last finished stage.<br>You must use one of the pre-defined strings from the list below.<br>• <code>"success"</code><br>• <code>"fail"</code><br>• <code>"giveup"</code><br>• <code>"retry"</code><br>• <code>"draw"</code><br>• <code>"exhausted"</code></td><td>string</td><td>Nullable (if the game has no stages)</td><td><code>"success"</code></td></tr><tr><td><code>adCooldownSeconds</code></td><td>When a cooldown is applied to an ad placement, this key-value map shows the trigger and duration of the cooldown. The key is omitted when no cooldown is applied.<br>• <code>interstitial_ad</code> : Cooldown calculated from the last interstitial ad<br>• <code>rewarded_ad</code> : Cooldown calculated from the last rewarded ad.<br>• <code>app_open</code> : Cooldown calculated from the app open event<br>• <code>install</code> : Cooldown calculated from the app install event.<br>• <code>other</code> : Cooldown calculated from other events<br>Example: If <code>{"rewarded_ad": 120}</code> is included, it means a 2-minute cooldown is applied from the last rewarded ad.</td><td>map&#x3C;string, int></td><td>Nullable (if no cooldown is applied)</td><td><code>{”rewarded_ad”: 10, “interstitial_ad”: 20”}</code></td></tr><tr><td><code>rewardItems</code></td><td>A key-value map of items rewarded to the player. Only for rewarded ads; otherwise, null.</td><td>map&#x3C;string, int></td><td>Nullable (if the ad has no rewards)</td><td><code>{ "coin": 500, "gem": 10 }</code></td></tr></tbody></table>

</details>

<details>

<summary>Step 2 : Implement the Ad Display Logic</summary>

Once the inference request is sent, the SDK will trigger one of the following callbacks. You must implement the appropriate logic in each case to ensure a smooth player experience and stable ad revenue.

| Name                | Description                                                                                                                                                                                                                       | What your app should do                                                                  | Required     |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------ |
| `onShowAd`          | Triggered when the API determines the ad must be shown.                                                                                                                                                                           | Display the interstitial ad.                                                             | **Required** |
| `onSkipAd`          | Triggered when the API determines the ad must be skipped. Continue gameplay without showing ads.                                                                                                                                  | Skip the ad and continue gameplay seamlessly.                                            | **Required** |
| `onDefaultAdPolicy` | Triggered when the ad serving should not be decided by the Airflux policy. You must implement your internal logic to decide whether to show or skip the ad.                                                                       | Apply your internal ad policy logic and proceed accordingly.                             | **Required** |
| `onFailure`         | <p>Triggered when the inference request fails. This may happen if:<br>• The player's country is not supported (not in countryAllowlist)<br>• The server returns a 4XX or 5XX error<br>• The request times out after 3 seconds</p> | Use your fallback logic to decide whether to show or skip the ad. Retry is not required. | **Required** |

{% hint style="warning" %}
**Timeout and Fallback Recommendation**

Failure to handle the inference request properly may lead to degraded play experience and loss of ad revenue. To minimize potential negative effects, it is recommended to set a **3-second (default) timeout** and **implement `onFailure` code** in case the API call fails. Retry attempts upon function call failure are not required. In this case, your internal ad display logic should be included within the `onFailure` block.
{% endhint %}

</details>

#### Code Example

{% tabs %}
{% tab title="Unity: C#" %}

```csharp
Airflux.RequestInference(
    AirfluxInference.SHOW_INTERSTITIAL_AD(
        parameters: new Dictionary<string, object>
        {
            { AirfluxParameter.AD_TYPE, "interstitial_ad" },
            { AirfluxParameter.AD_PLACEMENT_ID, "placement_3" },
            { AirfluxParameter.AD_PLACEMENT_TYPE, "static" },
            { AirfluxParameter.AD_PLACEMENT_POSITION, "stage_start" },
            { AirfluxParameter.STAGE, 10 },
            { AirfluxParameter.STAGE_TYPE, "primary_stage" },
            { AirfluxParameter.TOTAL_FINISHED_STAGE, 10 },
            { AirfluxParameter.STAGE_RESULT, "success" },
            {
                AirfluxAttribute.AD_COOLDOWN_SECONDS, new Dictionary<string, object>
                {
                    { "rewarded_ad", 10 },
                    { "interstitial_ad", 20 }
                }
            },
            {
                AirfluxAttribute.REWARD_ITEMS, new Dictionary<string, object>
                {
                    { "coin", 500 },
                    { "gem", 10 }
                }
            }
        },
        onShowAd: () => {
            // AI determined to show the ad. Implement your logic here.
            Debug.Log("AI decided to show ad.");
            // Your function to display the interstitial ad
        },
        onSkipAd: () => {
            // AI determined to skip the ad. Implement your logic here.
            Debug.Log("AI decided to skip ad.");
            // Your function to continue gameplay without showing ads
        },
        onDefaultAdPolicy: () => {
            // Implement your internal logic to decide whether to show or skip the ad.
            Debug.Log("AI returned default policy. Implementing internal logic.");
            // Your function for default ad decision logic
        },
        onFailure: (error) => {
            // Inference request failed. Implement your internal logic to handle the error
            // and decide whether to show or skip the ad based on your game's policy.
            Debug.Log($"Inference request failed: {error.Message}. Falling back to default policy.");
            // Your function for default ad decision logic on error
        }
    )
);
```

{% endtab %}
{% endtabs %}

#### **Verification**

<details>

<summary>Testing with Force responses</summary>

Ensure that your app correctly calls the inference API at each interstitial placement and follows the returned decision reliably.

**What to test**

1. The inference request includes all required parameters.
2. Exactly one of the decision callbacks is triggered per request.

**Testing with Force responses**

To simulate various responses during development or QA, you can use the `AirfluxParameter.FORCE_RESPONSE` parameter in your inference request:

{% hint style="info" %}
It’s **recommended to implement handling for all four cases** to ensure consistent ad delivery across various environments and edge cases.
{% endhint %}

| Simulated value     | Callback triggered    | Purpose                                      | Expected Behavior                   |
| ------------------- | --------------------- | -------------------------------------------- | ----------------------------------- |
| `“showAd”`          | `onShowAd()`          | Test ad display flow                         | Ad is displayed to the player       |
| `“skipAd”`          | `onSkipAd()`          | Test ad skip behavior                        | Ad is skipped and gameplay resumes  |
| `“defaultAdPolicy”` | `onDefaultAdPolicy()` | Test your ad decision logic                  | Your ad decision logic is executed  |
| `“failure”`         | `onFailure(error)`    | Test fallback handling for failure scenarios | Custom policy determines ad display |

{% hint style="info" %}
The `FORCE_RESPONSE` parameter is intended for testing purposes only and **must be removed from production builds**. If left active, it may cause ads to be **always shown or always skipped**, regardless of actual inference decisions.
{% endhint %}

**Code Example**

{% code title="Unity: C#" %}

```csharp
{
    AirfluxParameter.FORCE_RESPONSE, new Dictionary<string, object>
    {
        { "action", "showAd" },
        {
            "parameters", new Dictionary<string, object>
            {
            }
        }
    }
}
```

{% endcode %}

</details>

***

## 2. Deploy the app

After completing sufficient QA and crash testing, deploy your gaming app. To ensure proper integration with Airflux, make sure the deployment follows the timeline coordinated with the Growth Manager.

<details>

<summary>Where can I get guidance for the app store review?</summary>

Click [here](https://docs.airflux.ai/airflux-reference/preparing-for-the-app-store-review) for guidance on preparing for the Google Play and App Store reviews.

</details>

***

## Next steps

Congratulations! If you have completed the steps above, you are all set to optimize your in-game advertising with Airflux. Click [here](https://docs.airflux.ai/reporting) to learn how to receive your optimization results.

***

## Frequently Asked Questions

<details>

<summary>What is the difference between <code>onSkipAd()</code> and <code>onFailure()</code> callbacks?</summary>

* The `onSkipAd()` callback function is triggered when the API call is successful, and the inference result from the Airflux AI model indicates that an ad should not be displayed to enhance the play experience and maximize ad revenue.
* The `onFailure()` callback function is triggered when the API call fails. This includes situations such as the device's country not being in the allowlist (`countryAllowlist`), network issues, server errors, or request validation failures, where no response is received.

</details>

<details>

<summary>What is the response time of the API by country?</summary>

Airflux aims to deliver reliable service to users worldwide and typically maintains quick response times in most regions. However, minor delays may arise based on the network environment.

</details>


# Airflux Integration (Android)

This guide will walk you through the entire process of integrating the Airflux SDK into your application. It covers everything from installation and initialization to implementing core functionalities.

***

## **Guide Outline**

You can use the following outline to navigate this guide.

{% stepper %}
{% step %}
[Add your app to the dashboard](/airflux-onboarding/airflux-integration-android/1.-add-your-app-to-the-dashboard)
{% endstep %}

{% step %}
[Install the Airflux SDK](/airflux-onboarding/airflux-integration-android/2.-install-the-airflux-sdk)
{% endstep %}

{% step %}
[Send in-game event data](/airflux-onboarding/airflux-integration-android/3.-send-in-game-event-data)
{% endstep %}

{% step %}
[Call the Inference API](/airflux-onboarding/airflux-integration-android/4.-call-the-inference-api)
{% endstep %}
{% endstepper %}

***

## Understanding Data Collection

Airflux collects data through three primary methods to make the most optimal ad display decisions.

{% hint style="success" %}
Each type of data is collected at a different **time** and for a different **purpose**, so you must provide **all three** to maximize the performance of the AI model.
{% endhint %}

1. In-game event data

   Records user actions such as `ORDER_COMPLETED` and `ACHIEVE_LEVEL`.
2. Player Attribute Data

   Records the player's current status, such as their `level` or `currency` balance.
3. Inference Parameters

   Records contextual information at the time of an ad request, such as `adType` or `adPlacementId`.


# 1. Add your app to the dashboard

Before installing the Airflux SDK in your gaming app, you need to add your app to the Airbridge dashboard. This step will take approximately 5 minutes.

***

## 1. Create an account and add your app to Airbridge

{% hint style="info" %}
Why is this step necessary?

Airflux is built on the Airbridge platform. The **App Name** and **App SDK Token** you get from Airbridge are required for initializing the Airflux SDK.
{% endhint %}

1. Visit the [Airflux Dashboard](https://app.airflux.ai/) and create an account via the Airbridge Dashboard.
2. Select the **Growth Plan** on the Airbridge plan intro page.
3. Set the **Organization name**.
4. Choose **Production mode** as the app mode and add your app per platform.
5. Set the **App Name**. This must be unique and cannot be changed once the app is added.
6. Set the **Time zone** and **Standard Currency**. Standard Currency should be set to ‘USD’.  Choose carefully, as they cannot be changed once the app is added to Airbridge.
7. Click **Submit** to finish registering your app.

If you have finished adding an app and want to add additional apps, refer to this Airbridge [article](https://help.airbridge.io/en/guides/register-a-new-app).

## 2. Find the information required for SDK setup

Once your gaming app is added to Airbridge, visit the [Airflux Dashboard](https://app.airflux.ai/), navigate to **\[Tokens]** from the sidebar to find the following information. You will need these to initialize the SDK.

* App Name
* App SDK Token

## Frequently Asked Questions

<details>

<summary>Why sign up via Airbridge?</summary>

Airflux and Airbridge share a single sign-on (SSO) system managed by AB180. Once you've registered on Airbridge, you're all set to use Airflux as well.

</details>

<details>

<summary>My app is not live yet. Can I still add it to the dashboard?</summary>

If your app is not live yet, choose `Production mode` as the app mode, skip adding the app per platform, and move on to the next steps. Once your app goes live, go to the Airbridge dashboard, select **\[Settings]>\[App Settings]**, and add the app by using search or entering the app store URL.

</details>

<details>

<summary>Can I change the app mode after the app is added to Airbridge?</summary>

No, the app mode cannot be changed later. If you want to change the app mode, you need to add the app as a new app and choose a different app mode.

</details>


# 2. Install the Airflux SDK

## 1. SDK Installation

{% stepper %}
{% step %}

#### Declare the SDK Repository

To install the Airflux Android SDK, you need to declare the SDK repository to only one file `settings.gradle` or `build.gradle`.

**Case A**: If your project-level `settings.gradle` file contains `dependencyResolutionManagement` block, add the SDK repository to the `repositories` block in the file.

{% tabs %}
{% tab title="Groovy" %}

```groovy
dependencyResolutionManagement {
    repositories {
        maven { url = "https://sdk-download.airflux.ai/maven" }
    }
}
```

{% endtab %}

{% tab title="Kotlin DSL" %}

```kotlin
dependencyResolutionManagement {
    repositories {
        maven { url = uri("https://sdk-download.airflux.ai/maven") }
    }
}
```

{% endtab %}
{% endtabs %}

**Case B**: If your project-level `build.gradle` file contains `allprojects` block, add the SDK repository to the `repositories` block in the file.

{% tabs %}
{% tab title="Groovy" %}

```groovy
allprojects {
    repositories {
        maven { url = "https://sdk-download.airflux.ai/maven" }
    }
}
```

{% endtab %}

{% tab title="Kotlin DSL" %}

```kotlin
allprojects {
    repositories {
        maven { url = uri("https://sdk-download.airflux.ai/maven") }
    }
}
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

#### Add the SDK Package

Add the SDK as a dependency in your app-level `app/build.gradle` file. Replace `$HERE_LATEST_VERSION` with the latest SDK version, available on the [<mark style="color:blue;">Airflux SDK Versions page</mark>](https://sdk-download.airflux.ai/maven/ai/airflux/sdk-android/index.html).

{% tabs %}
{% tab title="Groovy" %}

```groovy
dependencies {
    // Replace $HERE_LATEST_VERSION with the latest version from the SDK Versions page.
    implementation "ai.airflux:sdk-android:$HERE_LATEST_VERSION"
}
```

{% endtab %}

{% tab title="Kotlin DSL" %}

```kotlin
dependencies {
    // Replace $HERE_LATEST_VERSION with the latest version from the SDK Versions page.
    implementation("ai.airflux:sdk-android:$HERE_LATEST_VERSION")
}
```

{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

***

## 2. SDK Initialization

{% stepper %}
{% step %}

#### Create a Custom MainApplication

To enable this, create a custom `MainApplication` class and register it in your `AndroidManifest.xml` file.

{% code title="AndroidManifest.xml" %}

```xml
<application
    android:name=".MainApplication"
    ...>
```

{% endcode %}
{% endstep %}

{% step %}

#### Initialize the SDK

Initialize the SDK within the `onCreate()` method of your `MainApplication` class. This ensures the SDK is ready as soon as the app launches.

{% hint style="info" %}
Your **YOUR\_APP\_NAME** and **YOUR\_APP\_SDK\_TOKEN** credentials are available in the Airbridge dashboard via \[Settings] > \[Token Management].
{% endhint %}

<table><thead><tr><th width="125.76171875">SDK Option</th><th width="215.71484375">Method</th><th width="118.22265625">Data Type</th><th width="295.0703125">Description</th><th>Required</th></tr></thead><tbody><tr><td>App Name</td><td><code>AirfluxOptionBuilder()</code></td><td><code>string</code></td><td>Input the App Name from the Airbridge dashboard.</td><td><strong>Required</strong></td></tr><tr><td>App Token</td><td><code>AirfluxOptionBuilder()</code></td><td><code>string</code></td><td>Input the App Token from the Airbridge dashboard.</td><td><strong>Required</strong></td></tr><tr><td>SDK Enabled</td><td><code>setSDKEnabled()</code></td><td><code>boolean</code></td><td><p>Set whether to enable the SDK upon initialization.</p><ul><li>true: The SDK is initialized in active mode.</li><li>false: The SDK is initialized in inactive mode and is enabled upon calling the <code>Airflux.EnableSDK()</code> function.</li></ul></td><td>Optional</td></tr><tr><td>Auto Start Tracking Enabled</td><td><code>setAutoStartTrackingEnabled()</code></td><td><code>boolean</code></td><td><p>Set whether to collect events automatically upon SDK initialization.</p><ul><li>true: Event collection starts automatically upon initialization.</li><li>false: Event collection starts upon calling the <code>Airflux.StartTracking()</code> function.</li></ul></td><td>Optional</td></tr><tr><td>Log Level</td><td><code>setLogLevel()</code></td><td><code>AirfluxLogLevel</code></td><td>Set the log level for the Airflux SDK. Choose from <code>debug</code>, <code>info</code>, <code>warning</code>, <code>error</code>, <code>fault</code> .</td><td>Optional</td></tr><tr><td>Session Timeout</td><td><code>setSessionTimeout()</code></td><td><code>double</code></td><td>The default value is 300 seconds. Modify if needed.</td><td>Optional</td></tr><tr><td>Allow Every Country Enabled</td><td><code>setAllowEveryCountryEnabled()</code></td><td><code>boolean</code></td><td>When set to true, all countries are allowed to follow Airflux's optimization policies. To use a specific list of countries, set it to false and provide a list using <code>setCountryAllowlist()</code>.</td><td>Optional</td></tr><tr><td>Country Allowlist</td><td><code>setCountryAllowlist()</code></td><td><code>List&#x3C;String></code></td><td>Set the countries where calling the Airflux's inference API should be allowed. Use country codes following the ISO 3166-1 alpha-2 format (e.g., US, KR). You can set multiple countries using the Country Allowlist. Airflux identifies a country based on the value tied to the device.</td><td>Optional</td></tr></tbody></table>

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
import ai.airflux.Airflux
import ai.airflux.AirfluxOptionBuilder
import ai.airflux.AirfluxLogLevel
import android.app.Application

class MainApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        // Creates the option for initializing the Airflux SDK.
        // Replace "YOUR_APP_NAME" and "YOUR_APP_TOKEN" with your actual credentials.
        val option = AirfluxOptionBuilder("YOUR_APP_NAME", "YOUR_APP_TOKEN")
            // Automatically enables the SDK on startup.
            .setSDKEnabled(true)
            // Automatically starts tracking events.
            .setAutoStartTrackingEnabled(true)
            // Sets the log level to debug for detailed logs.
            .setLogLevel(AirfluxLogLevel.DEBUG)
            // Sets the session timeout to 300 seconds (5 minutes).
            .setSessionTimeout(300)
            // Allows Airflux features in all countries.
            .setAllowEveryCountryEnabled(true)
            // Uncomment the line below to allow only specific countries.
            // .setCountryAllowlist(listOf("US", "KR", "JP"))
            .build()

        Airflux.initializeSDK(this, option)
    }
}
```

{% endtab %}

{% tab title="Java" %}

```java
import ai.airflux.Airflux;
import ai.airflux.AirfluxOption;
import ai.airflux.AirfluxOptionBuilder;
import ai.airflux.AirfluxLogLevel;
import android.app.Application;
import java.util.Arrays;
import java.util.List;

public class MainApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        
        final AirfluxOption option = new AirfluxOptionBuilder("YOUR_APP_NAME", "YOUR_APP_TOKEN")
            // Automatically enables the SDK on startup.
            .setSDKEnabled(true)
            // Automatically starts tracking events.
            .setAutoStartTrackingEnabled(true)
            // Sets the log level to debug for detailed logs.
            .setLogLevel(AirfluxLogLevel.DEBUG)
            // Sets the session timeout to 300 seconds (5 minutes).
            .setSessionTimeout(300)
            .setAllowEveryCountryEnabled(true)
            // Uncomment the line below to allow only specific countries.
            // .setCountryAllowlist(Arrays.asList("US", "KR", "JP"))
            .build();

        // Initializes the SDK using the configured option.
        Airflux.initializeSDK(this, option);
    }
}
```

{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

***

## 3. Verification

#### **Android Logcat check**

To view detailed log information for your app, use the Android Logcat tool.

{% hint style="warning" %}
You can set the log output level using the `setLogLevel` function. \
Setting it to `AirfluxLogLevel.DEBUG` allows you to see all Airflux logs.
{% endhint %}

***

## 4. Frequently Asked Questions

{% hint style="info" %}
Opt-in policy compliance

If player consent is required to send in-game data, implement the necessary setup by following the FAQ section below.&#x20;

[How can I set up the Airflux SDK to comply with the opt-in policy?](https://docs.airflux.ai/airflux-onboarding/airflux-integration/2.-install-the-airflux-sdk#how-can-i-set-up-the-airflux-sdk-to-comply-with-the-opt-in-policy)
{% endhint %}

<details>

<summary>How can I set up the Airflux SDK to comply with the opt-in policy?</summary>

The opt-in policy requires user consent before collecting and using player data. To adhere to this policy, implement the following methods.

1. **SDK opt-in setup**

Upon initialization of the Airflux SDK, set the initialization option `setAutoStartTrackingEnabled()`to `false` and call the `startTracking()` function at the point where you have received user consent for data tracking. The Airflux SDK will collect data after the `startTracking()` function is called.

{% hint style="warning" %}
**Attention**

Although event data is not tracked before `startTracking()` is triggered and after `stopTracking()` is triggered, player attribute data is aggregated and anonymized for transmission upon calling the Inference API.
{% endhint %}

```csharp
// After player provided consent to data tracking
Airflux.startTracking()

// If player withdraws consent to data tracking
Airflux.stopTracking()
```

2. **Initializing the Airflux SDK in inactive mode**

{% hint style="warning" %}
**Attention**

If the SDK is not enabled immediately after the SDK initialization, the Install and Open events may not be collected.
{% endhint %}

Set the initialization option `setSDKEnabled()` to `false` to initialize the SDK with all functions disabled until user consent for data tracking is obtained. Through this method, you can adhere to privacy policies to the highest level. Note that when the SDK is set in inactive mode, all features are disabled and no events and player attribute data is sent to Airflux.

</details>

<details>

<summary>How can I configure the Airflux SDK to call the Inference API only in specific countries</summary>

Set the initialization opeion `setAllowEveryCountryEnabled()` to false and add the countries you want to allow to the "Country Allowlist". Country codes should follow the ISO 3166-1 alpha-2 format (e.g., "US", "KR"), and multiple countries can be specified by separating codes with commas. Country codes are not case-sensitive.

</details>

<details>

<summary>Can I use other mediation platforms, such as MAX and AdMob, with Airflux?</summary>

Yes, Airflux is designed to work alongside existing mediation platforms.

</details>

<details>

<summary>How does Airflux determine a user’s country?</summary>

Airflux distinguishes a country based on the value tied to the deivce.

</details>

<details>

<summary>Does Airflux collect the ADID (Advertising ID)?</summary>

No. The Airflux SDK operates without collecting or requiring the ADID.

</details>

## 5. Troubleshooting

<details>

<summary>[Android] A coroutine dependency error occurs during the build process.</summary>

#### Issue

A coroutine dependency error occurs during the build process with the following message.

```bash
java.lang.NoClassDefFoundError: kotlin/coroutines/AbstractCoroutineContextKey
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
  ...
```

#### Cause

If the kotlinx-coroutines-core library version is 1.3.5 or later, [the kotlin-stdlib library version must be at a certain level or later](https://github.com/Kotlin/kotlinx.coroutines/issues/1879).

#### Solution

Check whether the kotlin-stdlib library version is v.1.3.70 or later with the `gradlew dependencies` command. If the version is earlier than v.1.3.70, you need to update it

</details>


# 3. Send in-game event data

To enable optimal ad display and model training, it is crucial to send sufficient in-game event and player attribute data to Airflux. This guide details the core principles and implementation steps for data collection using the Airflux SDK.

<figure><img src="/files/zol4mAdTLCVez07be1F6" alt=""><figcaption></figcaption></figure>

***

## Understanding Data Collection

Airflux collects data through three primary methods to make the most optimal ad display decisions.

{% hint style="success" %}
Each type of data is collected at a different **time** and for a different **purpose**, so you must provide **all three** to maximize the performance of the AI model.
{% endhint %}

1. In-game event data

   Records user actions such as `ORDER_COMPLETED` and `ACHIEVE_LEVEL`.
2. Player Attribute Data

   Records the player's current status, such as their `level` or `currency` balance.
3. Inference Parameters

   Records contextual information at the time of an ad request, such as `adType` or `adPlacementId`.

***

## 1. Send in-game event data

Use the `Airflux.trackEvent()` function to record key player actions within your game. The collected event data plays a crucial role in enabling the Airflux AI model to learn player behavior patterns and make optimal decisions.

### Detailed Event Guide with Code Examples

The following events are essential for model training. Clearly understand the purpose and timing of collecting each event, and ensure proper implementation for data transmission.

<details>

<summary>Ad Impression</summary>

Track this event immediately after an in-app ad is shown to the user. Collect relevant data such as ad type, revenue, and placement details.

{% hint style="danger" %}
The in-app ad revenue data must be collected using the client-side SDK through mediation platform integrations and sent to Airflux.
{% endhint %}

<table><thead><tr><th>Name</th><th width="235.9296875">Description</th><th>Required</th><th>Sample Value</th></tr></thead><tbody><tr><td><code>value</code></td><td>The amount of ad revenue.</td><td><strong>Required</strong></td><td><code>1.99</code></td></tr><tr><td><code>currency</code></td><td>The currency code for the ad revenue (ISO 4217).</td><td><strong>Required</strong></td><td><code>“USD”</code></td></tr><tr><td><code>adType</code></td><td>The type of ad. You <strong>must</strong> use one of the pre-defined strings from the list below.<br>• <code>"interstitial_ad"</code><br>• <code>"rewarded_ad"</code><br>• <code>“other”</code></td><td><strong>Required</strong></td><td><code>“interstitial_ad”</code></td></tr><tr><td><code>adPlacementID</code></td><td>A unique identifier for the ad placement.</td><td><strong>Required</strong></td><td><code>“placement_3”</code></td></tr><tr><td><code>adPlacementType</code></td><td>The type of ad placement. You <strong>must</strong> use one of the pre-defined strings from the list below.<br>• <code>"static"</code><br>• <code>"dynamic"</code></td><td><strong>Required</strong></td><td><code>“static”</code></td></tr><tr><td><code>adPlacementPosition</code></td><td>The position of the ad placement within the game. You <strong>must</strong> use one of the pre-defined strings from the list below<br>• <code>"stage_start"</code><br>• <code>"stage_middle"</code><br>• <code>"stage_end"</code><br>• <code>"non_stage"</code></td><td>Nullable (if the placement is dynamic)</td><td><code>“stage_start”</code></td></tr><tr><td><code>level</code></td><td>The player's or character's current level.</td><td>Nullable<br>(only if no level system)</td><td><code>10</code></td></tr><tr><td><code>stage</code></td><td>The stage number the user has played. If not in a game, this should be the last known result.</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr><tr><td><code>stageType</code></td><td>The type of stage where the user has played. If not in a game, this should be the last known result. You <strong>must</strong> use one of the pre-defined strings from the list below.<br>• <code>"primary_stage"</code><br>• <code>"secondary_stage"</code><br>• <code>"extra_stage"</code></td><td>Nullable (if the game has no stages)</td><td><code>"primary_stage"</code></td></tr><tr><td><code>totalFinishedStage</code></td><td>The total number of stages a player has finished. For games without stages, this should be the total play count.</td><td>Nullable (if the information is not available)</td><td><code>10</code></td></tr><tr><td><code>stageResult</code></td><td>The result of the most recently completed stage.If the ad placement occurs at the end of a stage or game, this should reflect the result of that stage. If the placement is in the middle of a stage/game or the user is not currently in gameplay, provide the result of the last finished stage.<br>You <strong>must</strong> use one of the pre-defined strings from the list below.<br>• <code>"success"</code><br>• <code>"fail"</code><br>• <code>"giveup"</code><br>• <code>"retry"</code><br>• <code>"draw"</code><br>• <code>"exhausted"</code></td><td>Nullable (if the game has no stages)</td><td><code>"success"</code><br></td></tr><tr><td><code>adCooldownSeconds</code></td><td>When a cooldown is applied to an ad placement, this key-value map shows the trigger and duration of the cooldown. The key is omitted when no cooldown is applied.<br>• <code>interstitial_ad</code> : Cooldown calculated from the last interstitial ad<br>• <code>rewarded_ad</code> : Cooldown calculated from the last rewarded ad.<br>• <code>app_open</code> : Cooldown calculated from the app open event<br>• <code>install</code> : Cooldown calculated from the app install event.<br>• <code>other</code> : Cooldown calculated from other events<br>Example: If <code>{"rewarded_ad": 120}</code> is included, it means a 2-minute cooldown is applied from the last rewarded ad.</td><td>Nullable (if no cooldown is applied)</td><td><code>{"rewarded_ad":10,"interstitial_ad": 20}</code></td></tr><tr><td><code>rewardItems</code></td><td>A key-value map of items rewarded to the player. Only for rewarded ads; otherwise, <code>null</code>.</td><td>Nullable (if the ad has no rewards)</td><td><code>{"coin": 500, "gem": 10}</code></td></tr></tbody></table>

#### **Code Example**

{% code title="Android: Kotlin" overflow="wrap" %}

```kotlin
import ai.airflux.Airflux
import ai.airflux.common.AirfluxCategory
import ai.airflux.common.AirfluxAttribute

Airflux.trackEvent(
    category = AirfluxCategory.AD_IMPRESSION,
    semanticAttributes = mapOf(
        AirfluxAttribute.VALUE to 1.99,
        AirfluxAttribute.CURRENCY to "USD",
        AirfluxAttribute.AD_TYPE to "interstitial_ad",
        AirfluxAttribute.AD_PLACEMENT_ID to "placement_3",
        AirfluxAttribute.AD_PLACEMENT_TYPE to "static",
        AirfluxAttribute.AD_PLACEMENT_POSITION to "stage_start",
        AirfluxAttribute.LEVEL to 10,
        AirfluxAttribute.STAGE_TYPE to "primary_stage",
        AirfluxAttribute.STAGE to 10,
        AirfluxAttribute.TOTAL_FINISHED_STAGE to 10,
        AirfluxAttribute.STAGE_RESULT to "success",
        AirfluxAttribute.AD_COOLDOWN_SECONDS to mapOf(
            "rewarded_ad" to 10,
            "interstitial_ad" to 20
        ),
        AirfluxAttribute.REWARD_ITEMS to mapOf(
            "coin" to 500,
            "gem" to 10
        )
    )
)
```

{% endcode %}

{% code title="Android: Java" %}

```java
import ai.airflux.Airflux;
import ai.airflux.common.AirfluxCategory;
import ai.airflux.common.AirfluxAttribute;

Airflux.trackEvent(
    AirfluxCategory.AD_IMPRESSION,
    new HashMap<String, Object>() {{
        put(AirfluxAttribute.VALUE, 1.99);
        put(AirfluxAttribute.CURRENCY, "USD");
        put(AirfluxAttribute.AD_TYPE, "interstitial_ad");
        put(AirfluxAttribute.AD_PLACEMENT_ID, "placement_3");
        put(AirfluxAttribute.AD_PLACEMENT_TYPE, "static");
        put(AirfluxAttribute.AD_PLACEMENT_POSITION, "stage_start");
        put(AirfluxAttribute.LEVEL, 10);
        put(AirfluxAttribute.STAGE_TYPE, "primary_stage");
        put(AirfluxAttribute.STAGE, 10);
        put(AirfluxAttribute.TOTAL_FINISHED_STAGE, 10);
        put(AirfluxAttribute.STAGE_RESULT, "success");
        put(AirfluxAttribute.AD_COOLDOWN_SECONDS, new HashMap<String, Integer>() {{
            put("rewarded_ad", 10);
            put("interstitial_ad", 20);
        }});
        put(AirfluxAttribute.REWARD_ITEMS, new HashMap<String, Integer>() {{
            put("coin", 500);
            put("gem", 10);
        }});
    }}
);
```

{% endcode %}

</details>

<details>

<summary>Order Completed</summary>

Track this event when an in-app purchase is completed. Collect data such as transaction ID, purchase amount, currency, and product information.

{% hint style="danger" %}
The in-app purchase revenue data must be collected using the client-side SDK and sent to Airflux. There might be a slight gap between the data sent to Airflux and the revenue data provided by vendors.
{% endhint %}

| Name                      | Description                                                                                                                                                             | Required     | Sample Value              |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------- |
| `transactionID`           | A unique identifier for the transaction.                                                                                                                                | **Required** | `TXN-20250411-5F3C9A72B1` |
| `value`                   | The purchase amount.                                                                                                                                                    | **Required** | `1.99`                    |
| `currency`                | The currency code for the purchase amount.                                                                                                                              | **Required** | `"USD"`                   |
| `products`                | A list of the purchased products.                                                                                                                                       | **Required** | `[]`                      |
| `products[X].productID`   | The unique identifier of the purchased product.                                                                                                                         | **Required** | `1C569KY32P1`             |
| `products[X].productName` | The name of the purchased product.                                                                                                                                      | **Required** | `"welcome_pack"`          |
| `purchaseRoute`           | <p>The purchase path. You must use one of the pre-defined strings from the list below.<br>• <code>"shop"</code><br>• <code>"popup"</code><br>• <code>"other"</code></p> | **Required** | `"shop"`                  |
| `inAppPurchased`          | The status of in-app purchases                                                                                                                                          | **Required** | `True`                    |

#### **Code Example**

{% code title="Android: Kotlin" %}

```kotlin
import ai.airflux.Airflux
import ai.airflux.common.AirfluxCategory
import ai.airflux.common.AirfluxAttribute

Airflux.trackEvent(
    category = AirfluxCategory.ORDER_COMPLETED,
    semanticAttributes = mapOf(
        AirfluxAttribute.TRANSACTION_ID to "TXN-20250411-5F3C9A72B1",
        AirfluxAttribute.VALUE to 1.99,
        AirfluxAttribute.CURRENCY to "USD",
        AirfluxAttribute.PRODUCTS to listOf(
            mapOf(
                AirfluxAttribute.PRODUCT_ID to "1C569KY32P1",
                AirfluxAttribute.PRODUCT_NAME to "welcome_pack"
            )
        ),
        AirfluxAttribute.PURCHASE_ROUTE to "shop"
    )
)
```

{% endcode %}

{% code title="Android: Java" %}

```java
import ai.airflux.Airflux;
import ai.airflux.common.AirfluxCategory;
import ai.airflux.common.AirfluxAttribute;

Airflux.trackEvent(
    AirfluxCategory.ORDER_COMPLETED,
    new HashMap<String, Object>() {{
        put(AirfluxAttribute.TRANSACTION_ID, "TXN-20250411-5F3C9A72B1");
        put(AirfluxAttribute.VALUE, 1.99);
        put(AirfluxAttribute.CURRENCY, "USD");
        put(AirfluxAttribute.PRODUCTS, Arrays.asList(
            new HashMap<String, String>() {{
                put(AirfluxAttribute.PRODUCT_ID, "1C569KY32P1");
                put(AirfluxAttribute.PRODUCT_NAME, "welcome_pack");
            }}
        ));
        put(AirfluxAttribute.PURCHASE_ROUTE, "shop");
    }}
);
```

{% endcode %}

</details>

<details>

<summary>Start Stage</summary>

Track this event when a stage or a game session begins.

<table><thead><tr><th width="135.54296875">Name</th><th width="214.48828125">Description</th><th>Required</th><th>Sample Value</th></tr></thead><tbody><tr><td><code>stageType</code></td><td>The type of stage where the user has played.<br><br>You must use one of the pre-defined strings from the list below.<br>• <code>"primary_stage"</code><br>• <code>"secondary_stage"</code><br>• <code>"extra_stage"</code></td><td>Nullable (if the game has no stages)</td><td><code>"primary_stage"</code></td></tr><tr><td><code>stage</code></td><td>The stage number the user has played</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr></tbody></table>

#### **Code Example**

{% code title="Android: Kotlin" %}

```kotlin
import ai.airflux.Airflux
import ai.airflux.common.AirfluxCategory
import ai.airflux.common.AirfluxAttribute

Airflux.trackEvent(
    category = AirfluxCategory.START_STAGE,
    semanticAttributes = mapOf(
        AirfluxAttribute.STAGE_TYPE to "primary_stage",
        AirfluxAttribute.STAGE to 10
    )
)
```

{% endcode %}

{% code title="Android: Java" %}

```java
import ai.airflux.Airflux;
import ai.airflux.common.AirfluxCategory;
import ai.airflux.common.AirfluxAttribute;

Airflux.trackEvent(
    AirfluxCategory.START_STAGE,
    new HashMap<String, Object>() {{
        put(AirfluxAttribute.STAGE_TYPE, "primary_stage");
        put(AirfluxAttribute.STAGE, 10);
    }}
);
```

{% endcode %}

</details>

<details>

<summary>Finish Stage</summary>

Track this event when a stage or game ends.

<table><thead><tr><th>Name</th><th width="192.68359375">Description</th><th>Required</th><th>Sample Value</th></tr></thead><tbody><tr><td><code>stageType</code></td><td>The type of stage where the user has played.<br><br>You must use one of the pre-defined strings from the list below.<br>• <code>"primary_stage"</code><br>• <code>"secondary_stage"</code><br>• <code>"extra_stage"</code></td><td>Nullable (if the game has no stages)</td><td><code>"primary_stage"</code></td></tr><tr><td><code>stage</code></td><td>The stage number the user has played</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr><tr><td><code>totalFinishedStage</code></td><td>The total number of stages a player has finished.<br>For games without stages, this should be the total play count.</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr><tr><td><code>stageResult</code></td><td>The result of the most recently completed stage.<br>If the ad placement occurs at the end of a stage or game, this should reflect the result of that stage. If the placement is in the middle of a stage/game or the user is not currently in gameplay, provide the result of the last finished stage.<br>You must use one of the pre-defined strings from the list below.<br>• <code>"success"</code><br>• <code>"fail"</code><br>• <code>"giveup"</code><br>• <code>"retry"</code><br>• <code>"draw"</code><br>• <code>"exhausted"</code></td><td>Nullable (if the game has no stages)</td><td><code>"success"</code></td></tr></tbody></table>

#### Code Example

{% code title="Android: Kotlin" %}

```kotlin
import ai.airflux.Airflux
import ai.airflux.common.AirfluxCategory
import ai.airflux.common.AirfluxAttribute

Airflux.trackEvent(
    category = AirfluxCategory.FINISH_STAGE,
    semanticAttributes = mapOf(
        AirfluxAttribute.STAGE_TYPE to "primary_stage",
        AirfluxAttribute.STAGE to 10,
        AirfluxAttribute.TOTAL_FINISHED_STAGE to 10,
        AirfluxAttribute.STAGE_RESULT to "success"
    )
)
```

{% endcode %}

{% code title="Android: Java" %}

```java
import ai.airflux.Airflux;
import ai.airflux.common.AirfluxCategory;
import ai.airflux.common.AirfluxAttribute;

Airflux.trackEvent(
    AirfluxCategory.FINISH_STAGE,
    new HashMap<String, Object>() {{
        put(AirfluxAttribute.STAGE_TYPE, "primary_stage");
        put(AirfluxAttribute.STAGE, 10);
        put(AirfluxAttribute.TOTAL_FINISHED_STAGE, 10);
        put(AirfluxAttribute.STAGE_RESULT, "success");
    }}
);
```

{% endcode %}

</details>

<details>

<summary>Achieve Level</summary>

Track this event when a player's or character's level changes, including when it decreases. This event can be omitted for games without a level system.

| Name    | Description                                | Required                                     | Sample Value |
| ------- | ------------------------------------------ | -------------------------------------------- | ------------ |
| `level` | The player's or character's current level. | <p>Nullable<br>(only if no level system)</p> | `10`         |

#### **Code Example**

{% code title="Android: Kotlin" %}

```kotlin
import ai.airflux.Airflux
import ai.airflux.common.AirfluxCategory
import ai.airflux.common.AirfluxAttribute

Airflux.trackEvent(
    category = AirfluxCategory.ACHIEVE_LEVEL,
    semanticAttributes = mapOf(
        AirfluxAttribute.LEVEL to 10
    )
)
```

{% endcode %}

{% code title="Android: Java" %}

```java
import ai.airflux.Airflux;
import ai.airflux.common.AirfluxCategory;
import ai.airflux.common.AirfluxAttribute;

Airflux.trackEvent(
    AirfluxCategory.ACHIEVE_LEVEL,
    new HashMap<String, Object>() {{
        put(AirfluxAttribute.LEVEL, 10);
    }}
);
```

{% endcode %}

</details>

### Verification

<details>

<summary>Using the App Real-Time Log</summary>

Trigger events based on your test scenarios and check the corresponding logs in the \[Raw Data] > \[App Real-time Log] menu. The event data will be displayed in JSON format, allowing you to confirm that the data type and structure of each field match the predefined format.

<table data-header-hidden><thead><tr><th width="221.7890625">Field</th><th>Validation Criteria</th></tr></thead><tbody><tr><td>eventData.goal.category</td><td>Verify that the event name exactly matches the string defined in the taxonomy.</td></tr><tr><td>semanticAttributes</td><td>• All keys must match those defined in the taxonomy. <br>• Value types must match the defined types (string, number, boolean). <br>• For revenue events (ad_impression, order_completed), values must be positive numbers.</td></tr><tr><td>originalCurrency</td><td>Must be a 3-letter uppercase code defined by ISO-4217 (e.g., USD, KRW)</td></tr></tbody></table>

</details>

***

## 2. Send player attribute data

Player attribute data provides a crucial snapshot of a player's status at a given time. This data is used to fine-tune player segmentation and personalize ad experiences. There are two primary functions for sending this data: `Airflux.setUser()` and `Airflux.setContext()`.

{% hint style="warning" %}
Player attribute data is only transmitted to the server when an event is tracked or an inference API is called. Ensure this data is set before making any inference API requests.
{% endhint %}

### Send User ID

Send the player's unique User ID when they sign up or sign in. This ensures all subsequent events and attributes are properly linked to that user. The User ID must be sent before the event data.

<details>

<summary>Send User ID </summary>

#### **When to trigger**

* When a player signs up or signs in.

| Name | Description                 | Example                   |
| ---- | --------------------------- | ------------------------- |
| `ID` | The player’s unique User ID | `"your_internal_user_id"` |

{% hint style="danger" %}
If the User ID is not sent before the event data, the User ID cannot be linked to the event data.
{% endhint %}

#### **Code Examples**

{% code title="Android: Kotlin" %}

```kotlin
import ai.airflux.Airflux
import ai.airflux.common.AirfluxUser

// Call when a player signs in
Airflux.setUser(AirfluxUser.ID, "your_internal_user_id")
```

{% endcode %}

{% code title="Android: Java" %}

```java
import ai.airflux.Airflux;
import ai.airflux.common.AirfluxUser;

Airflux.setUser(AirfluxUser.ID, "your_internal_user_id");
```

{% endcode %}

</details>

### Send Contextual Data

`Airflux.setContext()` is used to pass player attributes that are not tied to a specific event. This is crucial for providing the AI model with a complete snapshot of the player's status, such as their current level or currency balance at app launch.

<details>

<summary>Level Attributes</summary>

#### **When to trigger**

* When the game app opens
* When the player logs in

| Name    | Description                                | Example | Skip Case                 |
| ------- | ------------------------------------------ | ------- | ------------------------- |
| `Level` | The player's or character's current level. | `10`    | If the game has no levels |

{% hint style="danger" %}
The player attribute data must be passed to the SDK before the inference API request.
{% endhint %}

#### **Code Example**

{% code title="Android: Kotlin" %}

```kotlin
import ai.airflux.Airflux
import ai.airflux.common.AirfluxContext

Airflux.setContext(AirfluxContext.LEVEL, 10)
```

{% endcode %}

{% code title="Android: Java" %}

```java
import ai.airflux.Airflux;
import ai.airflux.common.AirfluxContext;

Airflux.setContext(AirfluxContext.LEVEL, 10);
```

{% endcode %}

</details>

<details>

<summary>Stage Attributes</summary>

#### **When to Trigger**

* When the game app opens.
* When a player logs in.

| Name                   | Description                                                                                                      | Example | Skip Case                       |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------- |
| `START_STAGE`          | The last stage the player started.                                                                               | `10`    | If the game has no stages       |
| `FINISH_STAGE`         | The last stage the player finished.                                                                              | `10`    | If the game has no stages       |
| `TOTAL_FINISHED_STAGE` | The total number of stages a player has finished. For games without stages, this should be the total play count. | `10`    | If information is not available |

#### **Code Example**

{% code title="Android: Kotlin" %}

```kotlin
import ai.airflux.Airflux
import ai.airflux.common.AirfluxContext

Airflux.setContext(AirfluxContext.START_STAGE, "primary_stage", 10)
Airflux.setContext(AirfluxContext.FINISH_STAGE, "primary_stage", 10)
Airflux.setContext(AirfluxContext.TOTAL_FINISHED_STAGE, "primary_stage", 10)
```

{% endcode %}

{% code title="Android: Java" %}

```java
import ai.airflux.Airflux;
import ai.airflux.common.AirfluxContext;

Airflux.setContext(AirfluxContext.START_STAGE, "primary_stage", 10);
Airflux.setContext(AirfluxContext.FINISH_STAGE, "primary_stage", 10);
Airflux.setContext(AirfluxContext.TOTAL_FINISHED_STAGE, "primary_stage", 10);
```

{% endcode %}

</details>

<details>

<summary>Other Attributes</summary>

#### **When to Trigger**

When other custom game attributes are updated.&#x20;

#### **Note**

* Attributes can have up to 100 key-value pairs.
* Keys must satisfy the regex ^\[a-zA-Z\_]\[a-zA-Z0-9\_]\*$.
* The maximum length of keys is 128 characters.
* Values type must be string, numeric, or boolean.
* The maximum length of string values is 1024 characters.

| Name        | Description                                                       | Example                   | Skip Case     |
| ----------- | ----------------------------------------------------------------- | ------------------------- | ------------- |
| `Attribute` | A key-value map for other custom attributes and game information. | `"battlePass", "premium”` | If not needed |

#### **Code Examples**

{% code title="Android: Kotlin" %}

```kotlin
import ai.airflux.Airflux
import ai.airflux.common.AirfluxContext

Airflux.setContext(AirfluxContext.ATTRIBUTE, "battlePass", "premium")
```

{% endcode %}

{% code title="Android: Java" %}

```java
import ai.airflux.Airflux;
import ai.airflux.common.AirfluxContext;

Airflux.setContext(AirfluxContext.ATTRIBUTE, "battlePass", "premium");
```

{% endcode %}

</details>

***

## Frequently Asked Questions

<details>

<summary>How should I use the <code>category</code>, <code>semanticAttributes</code>, and <code>customAttributes</code> parameters for event data collection?</summary>

| Name                 | Type                         | Description                                                                                                                                                                                                                                                                                                          |
| -------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `category`           | `String`                     | <p><strong>The event's unique name</strong> (e.g., <code>AD\_IMPRESSION</code>).<br>Only underscores are permitted as special characters; colons and other special characters are not allowed. If the collected data exceeds the maximum limit of 128 characters, only the initial 128 characters will be saved.</p> |
| `semanticAttributes` | `Dictionary<string, object>` | <p><strong>Semantic attributes of the event</strong><br>Semantic attribute data collection is limited by type: up to 1024 characters for strings, and 64 bits for integers or floats.</p>                                                                                                                            |
| `customAttributes`   | `Dictionary<string, object>` | **Custom attributes of the event** Custom attribute data collection is limited to 2,048 characters; exceeding the limit results in ERROR\_MAX\_LENGTH\_EXCEEDED.                                                                                                                                                     |

</details>

<details>

<summary>When a player completes a stage and levels up at the same time, how should I track it?</summary>

Use the `TrackEvent()` function to track the player's action of completing a stage as the Achieve Level event, and use the `SetLevel()` function to track the player's updated level as the player attribute.

</details>

<details>

<summary>Can I use the Airflux SDK to collect and send game store payment data?</summary>

No. The game store payment data must be collected and sent using the client-side SDK.

</details>

<details>

<summary>After restarting the game, the Airflux SDK stops sending events. How can I fix this?</summary>

Airflux Native SDK v1.0 Guide: \
The Airflux Native SDK automatically tracks app lifecycle events, so you do not need to manually call `startTracking()`. This issue typically occurs if the SDK is disabled, either by:

1. Setting `setSDKEnabled(false)` in the SDK initialization options.
2. Calling the `Airflux.disableSDK()` function during runtime.

When the SDK is disabled, both event tracking and inference requests are halted. To resolve this, you must explicitly call `Airflux.enableSDK()` to re-activate the SDK's features.

</details>


# 4. Call the Inference API

Configure the Airflux SDK to request an inference decision before displaying an interstitial ad. Based on the AI's response, you can decide whether to proceed with showing the ad. This process allows for smarter, more revenue-optimized ad delivery.

***

<figure><img src="/files/36MUXoSd2P6ZMABnOVjQ" alt=""><figcaption></figcaption></figure>

## 1. Set up the API Call and callbacks

Use the `Airflux.requestInference()` function with the `SHOW_INTERSTITIAL_AD` method to request a real-time ad decision. To ensure the AI makes an accurate decision, you must provide detailed contextual parameters and implement the appropriate callbacks.

<details>

<summary>Step 1 : Set Inference Parameters</summary>

To make an accurate decision, Airflux requires detailed **contextual parameters** related to the player state, ad type, and placement. These must be passed into the function via the `parameters` object using `AirfluxParameter.*` keys.

{% hint style="danger" %}
**Important**

All required fields must always be collected, and nullable fields should also be collected whenever available. Failure to provide these values may reduce optimization performance and lead to skewed experimental results.
{% endhint %}

<table><thead><tr><th>Name</th><th width="255.56640625">Description</th><th>Type</th><th>Required</th><th>Sample Value</th></tr></thead><tbody><tr><td><code>currency</code></td><td>The currency code for the ad revenue.(ISO 4217)</td><td>string</td><td>Required</td><td><code>“USD”</code></td></tr><tr><td><code>adType</code></td><td>The type of ad. You must use one of the pre-defined strings from the list below.<br>• <code>"interstitial_ad"</code><br>• <code>"rewarded_ad"</code><br>• <code>“other”</code></td><td>string</td><td>Required</td><td><code>“interstitial_ad”</code></td></tr><tr><td><code>adPlacementID</code></td><td>A unique identifier for the ad placement.</td><td>string</td><td>Required</td><td><code>“placement_3”</code></td></tr><tr><td><code>adPlacementType</code></td><td>The type of ad placement. You must use one of the pre-defined strings from the list below.<br>• <code>"static"</code><br>• <code>"dynamic"</code></td><td>string</td><td>Required</td><td><code>“static”</code></td></tr><tr><td><code>adPlacementPosition</code></td><td>The position of the ad placement within the game. You must use one of the pre-defined strings from the list below<br>• <code>"stage_start"</code><br>• <code>"stage_middle"</code><br>• <code>"stage_end"</code><br>• <code>"non_stage"</code></td><td>string</td><td>Nullable (if the placement is dynamic)</td><td><code>“stage_start"</code></td></tr><tr><td><code>level</code></td><td>The player's or character's current level.</td><td>int</td><td>Nullable<br>(only if no level system)</td><td><code>10</code></td></tr><tr><td><code>stage</code></td><td>The stage number where the ad was presented.<br>If not in a game, this should be the last known result.</td><td>int</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr><tr><td><code>stageType</code></td><td>The type of stage where the ad was presented.<br>If not in a game, this should be the last known result.<br>You must use one of the pre-defined strings from the list below.<br>• <code>"primary_stage"</code><br>• <code>"secondary_stage"</code><br>• <code>"extra_stage"</code></td><td>string</td><td>Nullable (if the game has no stages)</td><td><code>"primary_stage"</code></td></tr><tr><td><code>totalFinishedStage</code></td><td>Number of stages played so far (or play count if the game has no stages)</td><td>int</td><td>Nullable (if the information is not available)</td><td><code>10</code></td></tr><tr><td><code>stageResult</code></td><td>The result of the most recently completed stage.<br>If the ad placement occurs at the end of a stage or game, this should reflect the result of that stage. If the placement is in the middle of a stage/game or the user is not currently in gameplay, provide the result of the last finished stage.<br>You must use one of the pre-defined strings from the list below.<br>• <code>"success"</code><br>• <code>"fail"</code><br>• <code>"giveup"</code><br>• <code>"retry"</code><br>• <code>"draw"</code><br>• <code>"exhausted"</code></td><td>string</td><td>Nullable (if the game has no stages)</td><td><code>"success"</code></td></tr><tr><td><code>adCooldownSeconds</code></td><td>When a cooldown is applied to an ad placement, this key-value map shows the trigger and duration of the cooldown. The key is omitted when no cooldown is applied.<br>• <code>interstitial_ad</code> : Cooldown calculated from the last interstitial ad<br>• <code>rewarded_ad</code> : Cooldown calculated from the last rewarded ad.<br>• <code>app_open</code> : Cooldown calculated from the app open event<br>• <code>install</code> : Cooldown calculated from the app install event.<br>• <code>other</code> : Cooldown calculated from other events<br>Example: If <code>{"rewarded_ad": 120}</code> is included, it means a 2-minute cooldown is applied from the last rewarded ad.</td><td>map&#x3C;string, int></td><td>Nullable (if no cooldown is applied)</td><td><code>{”rewarded_ad”: 10, “interstitial_ad”: 20”}</code></td></tr><tr><td><code>rewardItems</code></td><td>A key-value map of items rewarded to the player. Only for rewarded ads; otherwise, null.</td><td>map&#x3C;string, int></td><td>Nullable (if the ad has no rewards)</td><td><code>{ "coin": 500, "gem": 10 }</code></td></tr></tbody></table>

</details>

<details>

<summary>Step 2 : Implement the Ad Display Logic</summary>

Once the inference request is sent, the SDK will trigger one of the following callbacks. You must implement the appropriate logic in each case to ensure a smooth player experience and stable ad revenue.

| Name                | Description                                                                                                                                                                                                                       | What your app should do                                                                  | Required     |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------ |
| `onShowAd`          | Triggered when the API determines the ad must be shown.                                                                                                                                                                           | Display the interstitial ad.                                                             | **Required** |
| `onSkipAd`          | Triggered when the API determines the ad must be skipped. Continue gameplay without showing ads.                                                                                                                                  | Skip the ad and continue gameplay seamlessly.                                            | **Required** |
| `onDefaultAdPolicy` | Triggered when the ad serving should not be decided by the Airflux policy. You must implement your internal logic to decide whether to show or skip the ad.                                                                       | Apply your internal ad policy logic and proceed accordingly.                             | **Required** |
| `onFailure`         | <p>Triggered when the inference request fails. This may happen if:<br>• The player's country is not supported (not in countryAllowlist)<br>• The server returns a 4XX or 5XX error<br>• The request times out after 3 seconds</p> | Use your fallback logic to decide whether to show or skip the ad. Retry is not required. | **Required** |

{% hint style="warning" %}
**Timeout and Fallback Recommendation**

Failure to handle the inference request properly may lead to degraded play experience and loss of ad revenue. To minimize potential negative effects, it is recommended to set a **3-second (default) timeout** and **implement `onFailure` code** in case the API call fails. Retry attempts upon function call failure are not required. In this case, your internal ad display logic should be included within the `onFailure` block.
{% endhint %}

</details>

#### Code Example

{% tabs %}
{% tab title="Android: Kotlin" %}

```kotlin
import ai.airflux.Airflux
import ai.airflux.AirfluxInference
import ai.airflux.AirfluxParameter
import android.util.Log

Airflux.requestInference(
    inference = AirfluxInference.SHOW_INTERSTITIAL_AD(
        parameters = mapOf(
            AirfluxParameter.AD_TYPE to "interstitial_ad",
	    AirfluxParameter.AD_PLACEMENT_ID to "placement_3", 
            AirfluxParameter.AD_PLACEMENT_TYPE to "static",
            AirfluxParameter.AD_PLACEMENT_POSITION to "stage_start", 
            AirfluxParameter.STAGE to 10,
            AirfluxParameter.STAGE_TYPE to "primary_stage",
            AirfluxParameter.TOTAL_FINISHED_STAGE to 10, 
            AirfluxParameter.STAGE_RESULT to "success",
            AirfluxParameter.AD_COOLDOWN_SECONDS to mapOf( 
                "rewarded_ad" to 10,
                "interstitial_ad" to 20
            ),
            AirfluxParameter.REWARD_ITEMS to mapOf( 
                "coin" to 500,
                "gem" to 10
            ),
            AirfluxParameter.FORCE_RESPONSE to mapOf( 
                "action" to "showAd",
                "parameters" to mapOf<String, Any>() 
            )
        ),
        onShowAd = {
            // AI determined to show the ad. Implement your logic here.
            Log.d("Airflux", "AI decided to show ad.")
            // Your function to display the interstitial ad
        },
        onSkipAd = {
            // AI determined to skip the ad. Implement your logic here.
            Log.d("Airflux", "AI decided to skip ad.")
            // Your function to continue gameplay without showing ads
        },
        onDefaultAdPolicy = {
            // Implement your internal logic to decide whether to show or skip the ad.
            Log.d("Airflux", "AI returned default policy. Implementing internal logic.")
            // Your function for default ad decision logic
        },
        onFailure = { error ->
            // Inference request failed. Implement your internal logic to handle the error
            // and decide whether to show or skip the ad based on your game's policy.
            Log.e("Airflux", "Inference request failed: ${error.message}. Falling back to default policy.")
            // Your function for default ad decision logic on error
        }
    )
)

```

{% endtab %}

{% tab title="Android: Java" %}

```java
import ai.airflux.Airflux;
import ai.airflux.AirfluxInference;
import ai.airflux.AirfluxParameter;
import android.util.Log;

Airflux.requestInference(
    AirfluxInference.SHOW_INTERSTITIAL_AD(
        new HashMap<String, Object>() {{
            put(AirfluxParameter.AD_TYPE, "interstitial_ad");
            put(AirfluxParameter.AD_PLACEMENT_ID, "placement_3");
            put(AirfluxParameter.AD_PLACEMENT_TYPE, "static");
            put(AirfluxParameter.AD_PLACEMENT_POSITION, "stage_start");
            put(AirfluxParameter.STAGE, 10);
            put(AirfluxParameter.STAGE_TYPE, "primary_stage");
            put(AirfluxParameter.TOTAL_FINISHED_STAGE, 10);
            put(AirfluxParameter.STAGE_RESULT, "success");
            put(AirfluxParameter.AD_COOLDOWN_SECONDS, new HashMap<String, Integer>() {{
                put("rewarded_ad", 10);
                put("interstitial_ad", 20);
            }});
            put(AirfluxParameter.REWARD_ITEMS, new HashMap<String, Integer>() {{
                put("coin", 500);
                put("gem", 10);
            }});
            put(AirfluxParameter.FORCE_RESPONSE, new HashMap<String, Object>() {{
                put("action", "showAd");
                put("parameters", new HashMap<String, Object>());
            }});
        }},
        () -> {
            // AI determined to show the ad. Implement your logic here.
            Log.d("Airflux", "AI decided to show ad.");
            // Your function to display the interstitial ad
        },
        () -> {
            // AI determined to skip the ad. Implement your logic here.
            Log.d("Airflux", "AI decided to skip ad.");
            // Your function to continue gameplay without showing ads
        },
        () -> {
            // Implement your internal logic to decide whether to show or skip the ad.
            Log.d("Airflux", "AI returned default policy. Implementing internal logic.");
            // Your function for default ad decision logic
        },
        airfluxError -> {
            // Inference request failed. Implement your internal logic to handle the error
            // and decide whether to show or skip the ad based on your game's policy.
            Log.e("Airflux", String.format("Inference request failed: %s. Falling back to default policy.", airfluxError.getMessage()));
            // Your function for default ad decision logic on error
        }
    )
);

```

{% endtab %}
{% endtabs %}

#### **Verification**

<details>

<summary>Testing with Force responses</summary>

Ensure that your app correctly calls the inference API at each interstitial placement and follows the returned decision reliably.

**What to test**

1. The inference request includes all required parameters.
2. Exactly one of the decision callbacks is triggered per request.

**Testing with Force responses**

To simulate various responses during development or QA, you can use the `AirfluxParameter.FORCE_RESPONSE` parameter in your inference request:

{% hint style="info" %}
It’s **recommended to implement handling for all four cases** to ensure consistent ad delivery across various environments and edge cases.
{% endhint %}

| Simulated value     | Callback triggered    | Purpose                                      | Expected Behavior                   |
| ------------------- | --------------------- | -------------------------------------------- | ----------------------------------- |
| `“showAd”`          | `onShowAd()`          | Test ad display flow                         | Ad is displayed to the player       |
| `“skipAd”`          | `onSkipAd()`          | Test ad skip behavior                        | Ad is skipped and gameplay resumes  |
| `“defaultAdPolicy”` | `onDefaultAdPolicy()` | Test your ad decision logic                  | Your ad decision logic is executed  |
| `“failure”`         | `onFailure(error)`    | Test fallback handling for failure scenarios | Custom policy determines ad display |

```kotlin
[AirfluxParameter.FORCE_RESPONSE]: {
    "action": "showAd",
    "parameters": {}
}
```

{% hint style="info" %}
The `FORCE_RESPONSE` parameter is intended for testing purposes only and **must be removed from production builds**. If left active, it may cause ads to be **always shown or always skipped**, regardless of actual inference decisions.
{% endhint %}

**Code Example**

{% code title="Android: Kotlin" %}

```kotlin
AirfluxParameter.FORCE_RESPONSE to mapOf(
    "action" to "showAd",
    "parameters" to mapOf<String, Any>()
)
```

{% endcode %}

{% code title="Android: Java" %}

```java
put(AirfluxParameter.FORCE_RESPONSE, new HashMap<String, Object>() {{
    put("action", "showAd");
    put("parameters", new HashMap<String, Object>());
}});
```

{% endcode %}

</details>

***

## 2. Deploy the app

After completing sufficient QA and crash testing, deploy your gaming app. To ensure proper integration with Airflux, make sure the deployment follows the timeline coordinated with the Growth Manager.

<details>

<summary>Where can I get guidance for the app store review?</summary>

Click [here](https://docs.airflux.ai/airflux-reference/preparing-for-the-app-store-review) for guidance on preparing for the Google Play and App Store reviews.

</details>

***

## Next steps

Congratulations! If you have completed the steps above, you are all set to optimize your in-game advertising with Airflux. Click [here](https://docs.airflux.ai/reporting) to learn how to receive your optimization results.

***

## Frequently Asked Questions

<details>

<summary>What is the difference between <code>onSkipAd()</code> and <code>onFailure()</code> callbacks?</summary>

* The `onSkipAd()` callback function is triggered when the API call is successful, and the inference result from the Airflux AI model indicates that an ad should not be displayed to enhance the play experience and maximize ad revenue.
* The `onFailure()` callback function is triggered when the API call fails. This includes situations such as the device's country not being in the allowlist (`countryAllowlist`), network issues, server errors, or request validation failures, where no response is received.

</details>

<details>

<summary>What is the response time of the API by country?</summary>

Airflux aims to deliver reliable service to users worldwide and typically maintains quick response times in most regions. However, minor delays may arise based on the network environment.

</details>


# Airflux Integration (iOS)

This guide will walk you through the entire process of integrating the Airflux SDK into your application. It covers everything from installation and initialization to implementing core functionalities.

***

## **Guide Outline**

You can use the following outline to navigate this guide.

{% stepper %}
{% step %}
[Add your app to the dashboard](/airflux-onboarding/airflux-integration-ios/1.-add-your-app-to-the-dashboard)
{% endstep %}

{% step %}
[Install the Airflux SDK](/airflux-onboarding/airflux-integration-ios/2.-install-the-airflux-sdk)
{% endstep %}

{% step %}
[Send in-game event data](/airflux-onboarding/airflux-integration-ios/3.-send-in-game-event-data)
{% endstep %}

{% step %}
[Call the Inference API](/airflux-onboarding/airflux-integration-ios/4.-call-the-inference-api)
{% endstep %}
{% endstepper %}

***

## Understanding Data Collection

Airflux collects data through three primary methods to make the most optimal ad display decisions.

{% hint style="success" %}
Each type of data is collected at a different **time** and for a different **purpose**, so you must provide **all three** to maximize the performance of the AI model.
{% endhint %}

1. In-game event data

   Records user actions such as `ORDER_COMPLETED` and `ACHIEVE_LEVEL`.
2. Player Attribute Data

   Records the player's current status, such as their `level` or `currency` balance.
3. Inference Parameters

   Records contextual information at the time of an ad request, such as `adType` or `adPlacementId`.


# 1. Add your app to the dashboard

Before installing the Airflux SDK in your gaming app, you need to add your app to the Airbridge dashboard. This step will take approximately 5 minutes.

***

## 1. Create an account and add your app to Airbridge

{% hint style="info" %}
Why is this step necessary?

Airflux is built on the Airbridge platform. The **App Name** and **App SDK Token** you get from Airbridge are required for initializing the Airflux SDK.
{% endhint %}

1. Visit the [Airflux Dashboard](https://app.airflux.ai/) and create an account via the Airbridge Dashboard.
2. Select the **Growth Plan** on the Airbridge plan intro page.
3. Set the **Organization name**.
4. Choose **Production mode** as the app mode and add your app per platform.
5. Set the **App Name**. This must be unique and cannot be changed once the app is added.
6. Set the **Time zone** and **Standard Currency**. Standard Currency should be set to ‘USD’.  Choose carefully, as they cannot be changed once the app is added to Airbridge.
7. Click **Submit** to finish registering your app.

If you have finished adding an app and want to add additional apps, refer to this Airbridge [article](https://help.airbridge.io/en/guides/register-a-new-app).

## 2. Find the information required for SDK setup

Once your gaming app is added to Airbridge, visit the [Airflux Dashboard](https://app.airflux.ai/), navigate to **\[Tokens]** from the sidebar to find the following information. You will need these to initialize the SDK.

* App Name
* App SDK Token

## Frequently Asked Questions

<details>

<summary>Why sign up via Airbridge?</summary>

Airflux and Airbridge share a single sign-on (SSO) system managed by AB180. Once you've registered on Airbridge, you're all set to use Airflux as well.

</details>

<details>

<summary>My app is not live yet. Can I still add it to the dashboard?</summary>

If your app is not live yet, choose `Production mode` as the app mode, skip adding the app per platform, and move on to the next steps. Once your app goes live, go to the Airbridge dashboard, select **\[Settings]>\[App Settings]**, and add the app by using search or entering the app store URL.

</details>

<details>

<summary>Can I change the app mode after the app is added to Airbridge?</summary>

No, the app mode cannot be changed later. If you want to change the app mode, you need to add the app as a new app and choose a different app mode.

</details>


# 2. Install the Airflux SDK

## 1. SDK Installation

The Airflux SDK can be installed using various dependency management tools. Choose one of the following methods to add the SDK to your project.

{% tabs %}
{% tab title="Swift Package Manager" %}

1. In the Xcode menu bar, click \[File] > \[Add Packages...]
2. In the top-right search bar, enter the following URL and then click \[Add Package].

   <https://github.com/ab180/airflux-ios-sdk-deployment>
3. Confirm the detected package and continue to click \[Add Package] to complete the installation.
4. You can confirm that the Airbridge SDK has been added under Package Dependencies in the Project Navigator.&#x20;
   {% endtab %}

{% tab title="CocoaPods" %}

#### 1.1 User Script Sandboxing

In Xcode's `Build Settings`, set `User Script Sandboxing` to `No`. For more details, refer to the [CocoaPods](https://github.com/CocoaPods/CocoaPods/issues/11946) documentation.

#### 1.2 Install CocoaPods and Set Up the Podfile

1. In your macOS Terminal, install CocoaPods using the following command: `brew install cocoapods`
2. Navigate to your Xcode project folder. The easiest way is to type `cd` and then drag your project folder directly into the Terminal window: `cd /path/to/your/XcodeProject`
3. Run this command to create a `Podfile`: `pod init`
4. Open the `Podfile` and add the following code to the `target` block.&#x20;

   Replace `$HERE_LATEST_VERSION` with the latest SDK version, available on the [Airflux SDK Versions page](/sdk-release-note/ios-sdk-release-note).

{% code title="Podfile" %}

```kotlin
target '[Project Name]' do
    ...
    # Replace $HERE_LATEST_VERSION with latest version
    # - Example: pod 'airflux-ios-sdk', '4.X.X'
    pod 'airflux-ios-sdk', '$HERE_LATEST_VERSION'
    ...
end
```

{% endcode %}

#### 1.3 Open Your Xcode Project

After installation, you must open the `YOUR_PROJECT.xcworkspace` file from now on. This file contains both your app project and the installed Pods. **Do not open the old `.xcodeproj` file**
{% endtab %}

{% tab title="Tuist" %}
{% hint style="info" %}
The Airflux iOS SDK cannot be installed using Tuist's [XcodeProj-based integration](https://docs.tuist.dev/en/guides/features/projects/dependencies#tuists-xcodeprojbased-integration). You must use Xcode's [default integration method](https://docs.tuist.dev/en/guides/features/projects/dependencies#xcodes-default-integration) for installation.
{% endhint %}

1. Run the `tuist edit` command in your Terminal.
2. Add the SDK as a remote package in the `project.packages` block. Then, add the SDK as a `package` dependency to `project.targets[...].target.dependencies`.

   * Replace `$HERE_LATEST_VERSION` with the latest SDK version, available on the [Airflux SDK Versions page](/sdk-release-note/ios-sdk-release-note).

   ```kotlin
   import ProjectDescription

   let project = Project(
       packages: [
           .remote(
               url: "https://github.com/ab180/airflux-ios-sdk-deployment",
               // Replace $HERE_LATEST_VERSION with latest version
               // - Example: requirement: .exact(from: "4.X.X")
               requirement: .exact(from: "$HERE_LATEST_VERSION")
           ),
           ...
       ],
       targets: [
           .target(
               dependencies: [
                   .package(product: "Airflux", type: .runtime),
                   ...
               ]
           ),
           ...
       ],
       ...
   )
   ```
3. Run the `tuist generate` command.
4. You can confirm that Airflux has been added to the **Package Dependencies** in Xcode.
   {% endtab %}

{% tab title="Manual Installation" %}
If you prefer a manual approach, you can directly download and add the SDK to your Xcode project.

1. Download the Airflux iOS SDK from the following URL:
   * [https://](https://sdk-internal.airbridge.io/build/airflux-ios-sdk/airflux-1.0.0-build-17/airflux-ios-sdk-deployment.zip)[sdk-download.airflux.ai/airflux-ios-sdk/latest/Airflux.zip](https://sdk-download.airflux.ai/airflux-ios-sdk/latest/Airflux.zip)
2. Unzip the file and find `Airflux.xcframework`.
3. In Xcode, go to your project file and navigate to the **\[General]** tab.
4. Scroll down to the **\[Frameworks, Libraries, and Embedded Content]** section and click the `+` button.
5. Click **\[Add Other...]**, then select **\[Add Files...]** and choose the `Airflux.xcframework` folder you downloaded.
6. Ensure that `Airflux.xcframework`'s Embed setting is configured to -`Embed & Sign`.
   {% endtab %}
   {% endtabs %}

***

## 2. SDK Initialization

Initialize the SDK by referring to the code below.&#x20;

{% hint style="info" %}
Your `YOUR_APP_NAME` and `YOUR_APP_SDK_TOKEN` credentials are available in the Airbridge dashboard via \[Settings] > \[Token Management].
{% endhint %}

<table><thead><tr><th>SDK Option</th><th width="216">Method</th><th>Data Type</th><th width="205.55078125">Description</th><th>Required</th></tr></thead><tbody><tr><td>App Name</td><td><code>AirfluxOptionBuilder()</code></td><td><code>string</code></td><td>Input the App Name from the Airbridge dashboard.</td><td><strong>Required</strong></td></tr><tr><td>App Token</td><td><code>AirfluxOptionBuilder()</code></td><td><code>string</code></td><td>Input the App Token from the Airbridge dashboard.</td><td><strong>Required</strong></td></tr><tr><td>SDK Enabled</td><td><code>setSDKEnabled()</code></td><td><code>boolean</code></td><td><p>Set whether to enable the SDK upon initialization.</p><ul><li><code>true</code>: The SDK is initialized in active mode.</li><li><code>false</code>: The SDK is initialized in inactive mode and is enabled upon calling the Airflux.<code>EnableSDK()</code>function.</li></ul></td><td>Optional</td></tr><tr><td>Auto Start Tracking Enabled</td><td><code>setAutoStartTrackingEnabled()</code></td><td><code>boolean</code></td><td><p>Set whether to collect events automatically upon SDK initialization.</p><ul><li><code>true</code>: Event collection starts automatically upon initialization.</li><li><code>false</code>: Event collection starts upon calling the <code>Airflux.StartTracking()</code> function.</li></ul></td><td>Optional</td></tr><tr><td>Log Level</td><td><code>setLogLevel()</code></td><td><code>AirfluxLogLevel</code></td><td>Set the log level for the Airflux SDK. Choose from <code>debug</code>, <code>info</code>, <code>warning</code>, <code>error</code>, <code>fault</code> .</td><td>Optional</td></tr><tr><td>Session Timeout</td><td><code>setSessionTimeout()</code></td><td><code>double</code></td><td>The default value is 300 seconds. Modify if needed.</td><td>Optional</td></tr><tr><td>Allow Every Country Enabled</td><td><code>setAllowEveryCountryEnabled()</code></td><td><code>boolean</code></td><td>When set to true, all countries are allowed to follow Airflux's optimization policies. To use a specific list of countries, set it to false and provide a list using <code>setCountryAllowlist()</code>.</td><td>Optional</td></tr><tr><td>Country Allowlist</td><td><code>setCountryAllowlist()</code></td><td><code>List&#x3C;String></code></td><td>Set the countries where calling the Airflux's inference API should be allowed. Use country codes following the ISO 3166-1 alpha-2 format (e.g., US, KR). You can set multiple countries using the Country Allowlist array. Airflux identifies a country based on the value tied to the device.</td><td>Optional</td></tr></tbody></table>

{% tabs %}
{% tab title="AppDelegate (Swift)" %}

```swift
import UIKit
import Airflux

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Creates the option for initializing the Airflux SDK.
        // Replace "YOUR_APP_NAME" and "YOUR_APP_TOKEN" with your actual credentials.
        let option = AirfluxOptionBuilder(name: "YOUR_APP_NAME", token: "YOUR_APP_TOKEN")
            // Automatically enables the SDK on startup.
            .setSDKEnabled(true)
            // Automatically starts tracking events.
            .setAutoStartTrackingEnabled(true)
            // Sets the log level to debug for detailed logs.
            .setLogLevel(AirfluxLogLevel.debug)
            // Sets the session timeout to 300 seconds (5 minutes).
            .setSessionTimeout(second: 300)
            // Allows Airflux features in all countries.
            .setAllowEveryCountryEnabled(true)
            // Uncomment the line below to allow only specific countries.
            // .setCountryAllowlist(["US", "KR", "JP"])
            .build()

        // Initializes the SDK with the configured option.
        Airflux.initializeSDK(option: option)
        
        return true
    }
}
```

{% endtab %}

{% tab title="AppDelegate (Objective-C)" %}

```objectivec
#import "AppDelegate.h"
#import <airflux/Airflux.h> 

@interface AppDelegate ()
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Creates the option for initializing the Airflux SDK.
    // Replace "YOUR_APP_NAME" and "YOUR_APP_TOKEN" with your actual credentials.
    AirfluxOptionBuilder *optionBuilder = [AirfluxOptionBuilder alloc] initWithName:@"YOUR_APP_NAME" token:@"YOUR_APP_TOKEN"];
    // Automatically enables the SDK on startup.
    [optionBuilder setSDKEnabled:YES];
    // Automatically starts tracking events.
    [optionBuilder setAutoStartTrackingEnabled:YES];
    // Sets the log level to debug for detailed logs.
    [optionBuilder setLogLevel:AirfluxLogLevelDebug];
    // Sets the session timeout to 300 seconds (5 minutes).
    [optionBuilder setSessionTimeoutWithSecond:300];
    // Allows Airflux features in all countries.
    [optionBuilder setAllowEveryCountryEnabled:YES];
    // Uncomment the line below to allow only specific countries.
    // [optionBuilder setCountryAllowlist:@[@"US", @"KR", @"JP"]];
    AirfluxOption *option = [optionBuilder build];

    // Initializes the SDK with the configured option.
    [Airflux initializeSDKWithOption:option];

    return YES;
}
```

{% endtab %}

{% tab title="SwiftUI (Swift)" %}

```swift
import SwiftUI
import Airflux

@main
struct YourApp: App {
    init() {
        // Creates the option for initializing the Airflux SDK.
        // Replace "YOUR_APP_NAME" and "YOUR_APP_TOKEN" with your actual credentials.
        let option = AirfluxOptionBuilder(name: "YOUR_APP_NAME", token: "YOUR_APP_TOKEN")
            // Automatically enables the SDK on startup.
            .setSDKEnabled(true)
            // Automatically starts tracking events.
            .setAutoStartTrackingEnabled(true)
            // Sets the log level to debug for detailed logs.
            .setLogLevel(AirfluxLogLevel.debug)
            // Sets the session timeout to 300 seconds (5 minutes).
            .setSessionTimeout(second: 300)
            // Allows Airflux features in all countries.
            .setAllowEveryCountryEnabled(true)
            // Uncomment the line below to allow only specific countries.
            // .setCountryAllowlist(["US", "KR", "JP"])
            .build()
        
        // Initializes the SDK with the configured option.
        Airflux.initializeSDK(option: option)
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
```

{% endtab %}
{% endtabs %}

***

## 3. Verification

#### **iOS Console Log Check**

To view detailed log information for your app in Xcode, use the following method before initializing the SDK

{% hint style="warning" %}
You can set the log output level using the `setLogLevel` function. Setting it to `AirfluxLogLevel.DEBUG` allows you to see all Airflux logs.
{% endhint %}

## 4. Frequently Asked Questions

{% hint style="info" %}
Opt-in policy compliance

If player consent is required to send in-game data, implement the necessary setup by following the FAQ section below.&#x20;

[How can I set up the Airflux SDK to comply with the opt-in policy?](https://docs.airflux.ai/airflux-onboarding/airflux-integration/2.-install-the-airflux-sdk#how-can-i-set-up-the-airflux-sdk-to-comply-with-the-opt-in-policy)
{% endhint %}

<details>

<summary>How can I set up the Airflux SDK to comply with the opt-in policy?</summary>

The opt-in policy requires user consent before collecting and using player data. To adhere to this policy, implement the following methods.

1. **SDK opt-in setup**

Upon initialization of the Airflux SDK, set the initialization option `setAutoStartTrackingEnabled()`to `false` and call the `startTracking()` function at the point where you have received user consent for data tracking. The Airflux SDK will collect data after the `startTracking()` function is called.

{% hint style="warning" %}
**Attention**

Although event data is not tracked before `startTracking()` is triggered and after `stopTracking()` is triggered, player attribute data is aggregated and anonymized for transmission upon calling the Inference API.
{% endhint %}

```csharp
// After player provided consent to data tracking
Airflux.startTracking()

// If player withdraws consent to data tracking
Airflux.stopTracking()
```

2. **Initializing the Airflux SDK in inactive mode**

{% hint style="warning" %}
**Attention**

If the SDK is not enabled immediately after the SDK initialization, the Install and Open events may not be collected.
{% endhint %}

Set the initialization option `setSDKEnabled()` to `false` to initialize the SDK with all functions disabled until user consent for data tracking is obtained. Through this method, you can adhere to privacy policies to the highest level. Note that when the SDK is set in inactive mode, all features are disabled and no events and player attribute data is sent to Airflux.

</details>

<details>

<summary>How can I configure the Airflux SDK to call the Inference API only in specific countries</summary>

Set the initialization opeion `setAllowEveryCountryEnabled()` to false and add the countries you want to allow to the "Country Allowlist". Country codes should follow the ISO 3166-1 alpha-2 format (e.g., "US", "KR"), and multiple countries can be specified by separating codes with commas. Country codes are not case-sensitive.

</details>

<details>

<summary>Can I use other mediation platforms, such as MAX and AdMob, with Airflux?</summary>

Yes, Airflux is designed to work alongside existing mediation platforms.

</details>

<details>

<summary>How does Airflux determine a user’s country?</summary>

Airflux distinguishes a country based on the value tied to the deivce.

</details>

<details>

<summary>Does Airflux collect the ADID (Advertising ID)?</summary>

No. The Airflux SDK operates without collecting or requiring the ADID.

</details>


# 3. Send in-game event data

To enable optimal ad display and model training, it is crucial to send sufficient in-game event and player attribute data to Airflux. This guide details the core principles and implementation steps for data collection using the Airflux SDK.

<figure><img src="/files/zol4mAdTLCVez07be1F6" alt=""><figcaption></figcaption></figure>

***

## Understanding Data Collection

Airflux collects data through three primary methods to make the most optimal ad display decisions.

{% hint style="success" %}
Each type of data is collected at a different **time** and for a different **purpose**, so you must provide **all three** to maximize the performance of the AI model.
{% endhint %}

1. In-game event data

   Records user actions such as `ORDER_COMPLETED` and `ACHIEVE_LEVEL`.
2. Player Attribute Data

   Records the player's current status, such as their `level` or `currency` balance.
3. Inference Parameters

   Records contextual information at the time of an ad request, such as `adType` or `adPlacementId`.

***

## 1. Send in-game event data

Use the `Airflux.trackEvent()` function to record key player actions within your game. The collected event data plays a crucial role in enabling the Airflux AI model to learn player behavior patterns and make optimal decisions.

### Detailed Event Guide with Code Examples

The following events are essential for model training. Clearly understand the purpose and timing of collecting each event, and ensure proper implementation for data transmission.

<details>

<summary>Ad Impression</summary>

Track this event immediately after an in-app ad is shown to the user. Collect relevant data such as ad type, revenue, and placement details.

{% hint style="danger" %}
The in-app ad revenue data must be collected using the client-side SDK through mediation platform integrations and sent to Airflux.
{% endhint %}

<table><thead><tr><th>Name</th><th width="235.9296875">Description</th><th>Required</th><th>Sample Value</th></tr></thead><tbody><tr><td><code>value</code></td><td>The amount of ad revenue.</td><td><strong>Required</strong></td><td><code>1.99</code></td></tr><tr><td><code>currency</code></td><td>The currency code for the ad revenue (ISO 4217).</td><td><strong>Required</strong></td><td><code>“USD”</code></td></tr><tr><td><code>adType</code></td><td>The type of ad. You <strong>must</strong> use one of the pre-defined strings from the list below.<br>• <code>"interstitial_ad"</code><br>• <code>"rewarded_ad"</code><br>• <code>“other”</code></td><td><strong>Required</strong></td><td><code>“interstitial_ad”</code></td></tr><tr><td><code>adPlacementID</code></td><td>A unique identifier for the ad placement.</td><td><strong>Required</strong></td><td><code>“placement_3”</code></td></tr><tr><td><code>adPlacementType</code></td><td>The type of ad placement. You <strong>must</strong> use one of the pre-defined strings from the list below.<br>• <code>"static"</code><br>• <code>"dynamic"</code></td><td><strong>Required</strong></td><td><code>“static”</code></td></tr><tr><td><code>adPlacementPosition</code></td><td>The position of the ad placement within the game. You <strong>must</strong> use one of the pre-defined strings from the list below<br>• <code>"stage_start"</code><br>• <code>"stage_middle"</code><br>• <code>"stage_end"</code><br>• <code>"non_stage"</code></td><td>Nullable (if the placement is dynamic)</td><td><code>“stage_start”</code></td></tr><tr><td><code>level</code></td><td>The player's or character's current level.</td><td>Nullable<br>(only if no level system)</td><td><code>10</code></td></tr><tr><td><code>stage</code></td><td>The stage number the user has played If not in a game, this should be the last known result.</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr><tr><td><code>stageType</code></td><td>The stage number the user has played. If not in a game, this should be the last known result. You <strong>must</strong> use one of the pre-defined strings from the list below.<br>• <code>"primary_stage"</code><br>• <code>"secondary_stage"</code><br>• <code>"extra_stage"</code></td><td>Nullable (if the game has no stages)</td><td><code>"primary_stage"</code></td></tr><tr><td><code>totalFinishedStage</code></td><td>The total number of stages a player has finished. For games without stages, this should be the total play count.</td><td>Nullable (if the information is not available)</td><td><code>10</code></td></tr><tr><td><code>stageResult</code></td><td>The result of the most recently completed stage.If the ad placement occurs at the end of a stage or game, this should reflect the result of that stage. If the placement is in the middle of a stage/game or the user is not currently in gameplay, provide the result of the last finished stage.<br>You <strong>must</strong> use one of the pre-defined strings from the list below.<br>• <code>"success"</code><br>• <code>"fail"</code><br>• <code>"giveup"</code><br>• <code>"retry"</code><br>• <code>"draw"</code><br>• <code>"exhausted"</code></td><td>Nullable (if the game has no stages)</td><td><code>"success"</code><br></td></tr><tr><td><code>adCooldownSeconds</code></td><td>When a cooldown is applied to an ad placement, this key-value map shows the trigger and duration of the cooldown. The key is omitted when no cooldown is applied.<br>• <code>interstitial_ad</code> : Cooldown calculated from the last interstitial ad<br>• <code>rewarded_ad</code> : Cooldown calculated from the last rewarded ad.<br>• <code>app_open</code> : Cooldown calculated from the app open event<br>• <code>install</code> : Cooldown calculated from the app install event.<br>• <code>other</code> : Cooldown calculated from other events<br>Example: If <code>{"rewarded_ad": 120}</code> is included, it means a 2-minute cooldown is applied from the last rewarded ad.</td><td>Nullable (if no cooldown is applied)</td><td><code>{"rewarded_ad":10,"interstitial_ad": 20}</code></td></tr><tr><td><code>rewardItems</code></td><td>A key-value map of items rewarded to the player. Only for rewarded ads; otherwise, <code>null</code>.</td><td>Nullable (if the ad has no rewards)</td><td><code>{"coin": 500, "gem": 10}</code></td></tr></tbody></table>

#### **Code Example**

{% code title="iOS: Swift" %}

```swift
import Airflux

Airflux.trackEvent(
    category: AirfluxCategory.AD_IMPRESSION,
    semanticAttributes: [
        AirfluxAttribute.VALUE: 1.99,
        AirfluxAttribute.CURRENCY: "USD",
        AirfluxAttribute.AD_TYPE: "interstitial_ad",
        AirfluxAttribute.AD_PLACEMENT_ID: "placement_3",
        AirfluxAttribute.AD_PLACEMENT_TYPE: "static",
        AirfluxAttribute.AD_PLACEMENT_POSITION: "stage_start",
        AirfluxAttribute.LEVEL: 10,
        AirfluxAttribute.STAGE_TYPE: "primary_stage",
        AirfluxAttribute.STAGE: 10,
        AirfluxAttribute.TOTAL_FINISHED_STAGE: 10,
        AirfluxAttribute.STAGE_RESULT: "success",
        AirfluxAttribute.AD_COOLDOWN_SECONDS: [
            "rewarded_ad": 10,
            "interstitial_ad": 20
        ],
        AirfluxAttribute.REWARD_ITEMS: [
            "coin": 500,
            "gem": 10
        ]
    ]
)
```

{% endcode %}

{% code title="iOS: Objective-C" %}

```objectivec
#import <Airflux/Airflux.h>

[Airflux trackEventWithCategory:AirfluxCategory.AD_IMPRESSION semanticAttributes:@{
    AirfluxAttribute.VALUE: @(1.99),
    AirfluxAttribute.CURRENCY: @"USD",
    AirfluxAttribute.AD_TYPE: @"interstitial_ad",
    AirfluxAttribute.AD_PLACEMENT_ID: @"placement_3",
    AirfluxAttribute.AD_PLACEMENT_TYPE: @"static",
    AirfluxAttribute.AD_PLACEMENT_POSITION: @"stage_start",
    AirfluxAttribute.LEVEL: @(10),
    AirfluxAttribute.STAGE_TYPE: @"primary_stage",
    AirfluxAttribute.STAGE: @(10),
    AirfluxAttribute.TOTAL_FINISHED_STAGE: @(10),
    AirfluxAttribute.STAGE_RESULT: @"success",
    AirfluxAttribute.AD_COOLDOWN_SECONDS: @{
        @"rewarded_ad": @(10),
        @"interstitial_ad": @(20)
    },
    AirfluxAttribute.REWARD_ITEMS: @{
        @"coin": @(500),
        @"gem": @(10)
    }
}];
```

{% endcode %}

</details>

<details>

<summary>Order Completed</summary>

Track this event when an in-app purchase is completed. Collect data such as transaction ID, purchase amount, currency, and product information.

{% hint style="danger" %}
The in-app purchase revenue data must be collected using the client-side SDK and sent to Airflux. There might be a slight gap between the data sent to Airflux and the revenue data provided by vendors.
{% endhint %}

| Name                      | Description                                                                                                                                                             | Required     | Sample Value              |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------- |
| `transactionID`           | A unique identifier for the transaction.                                                                                                                                | **Required** | `TXN-20250411-5F3C9A72B1` |
| `value`                   | The purchase amount.                                                                                                                                                    | **Required** | `1.99`                    |
| `currency`                | The currency code for the purchase amount.                                                                                                                              | **Required** | `"USD"`                   |
| `products`                | A list of the purchased products.                                                                                                                                       | **Required** | `[]`                      |
| `products[X].productID`   | The unique identifier of the purchased product.                                                                                                                         | **Required** | `1C569KY32P1`             |
| `products[X].productName` | The name of the purchased product.                                                                                                                                      | **Required** | `"welcome_pack"`          |
| `purchaseRoute`           | <p>The purchase path. You must use one of the pre-defined strings from the list below.<br>• <code>"shop"</code><br>• <code>"popup"</code><br>• <code>"other"</code></p> | **Required** | `"shop"`                  |
| `inAppPurchased`          | The status of in-app purchases                                                                                                                                          | **Required** | `True`                    |

#### **Code Example**

{% code title="iOS: Swift" %}

```swift
import Airflux

Airflux.trackEvent(
    category: AirfluxCategory.ORDER_COMPLETED,
    semanticAttributes: [
        AirfluxAttribute.TRANSACTION_ID: "TXN-20250411-5F3C9A72B1",
        AirfluxAttribute.VALUE: 1.99,
        AirfluxAttribute.CURRENCY: "USD",
        AirfluxAttribute.PRODUCTS: [
            [
                AirfluxAttribute.PRODUCT_ID: "1C569KY32P1",
                AirfluxAttribute.PRODUCT_NAME: "welcome_pack"
            ]
        ],
        AirfluxAttribute.PURCHASE_ROUTE: "shop"
    ]
)
```

{% endcode %}

{% code title="iOS: Objective-C" %}

```objectivec
#import <Airflux/Airflux.h>

[Airflux trackEventWithCategory:AirfluxCategory.ORDER_COMPLETED semanticAttributes:@{
    AirfluxAttribute.TRANSACTION_ID: @"TXN-20250411-5F3C9A72B1",
    AirfluxAttribute.VALUE: @(1.99),
    AirfluxAttribute.CURRENCY: @"USD",
    AirfluxAttribute.PRODUCTS: @[
        @{
            AirfluxAttribute.PRODUCT_ID: @"1C569KY32P1",
            AirfluxAttribute.PRODUCT_NAME: @"welcome_pack"
        }
    ],
    AirfluxAttribute.PURCHASE_ROUTE: @"shop"
}];
```

{% endcode %}

</details>

<details>

<summary>Start Stage</summary>

Track this event when a stage or a game session begins.

<table><thead><tr><th width="135.54296875">Name</th><th width="214.48828125">Description</th><th>Required</th><th>Sample Value</th></tr></thead><tbody><tr><td><code>stageType</code></td><td>The type of stage where the user has played.<br><br>You must use one of the pre-defined strings from the list below.<br>• <code>"primary_stage"</code><br>• <code>"secondary_stage"</code><br>• <code>"extra_stage"</code></td><td>Nullable (if the game has no stages)</td><td><code>"primary_stage"</code></td></tr><tr><td><code>stage</code></td><td>The stage number the user has played</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr></tbody></table>

#### **Code Example**

{% code title="iOS: Swift" %}

```swift
import Airflux

Airflux.trackEvent(
    category: AirfluxCategory.START_STAGE,
    semanticAttributes: [
        AirfluxAttribute.STAGE_TYPE: "primary_stage",
        AirfluxAttribute.STAGE: 10
    ]
)
```

{% endcode %}

{% code title="iOS: Objective-C" %}

```objectivec
#import <Airflux/Airflux.h>

[Airflux trackEventWithCategory:AirfluxCategory.START_STAGE semanticAttributes:@{
    AirfluxAttribute.STAGE_TYPE: @"primary_stage",
    AirfluxAttribute.STAGE: @(10)
}];
```

{% endcode %}

</details>

<details>

<summary>Finish Stage</summary>

Track this event when a stage or game ends.

<table><thead><tr><th>Name</th><th width="192.68359375">Description</th><th>Required</th><th>Sample Value</th></tr></thead><tbody><tr><td><code>stageType</code></td><td>The type of stage where the user has played.<br><br>You must use one of the pre-defined strings from the list below.<br>• <code>"primary_stage"</code><br>• <code>"secondary_stage"</code><br>• <code>"extra_stage"</code></td><td>Nullable (if the game has no stages)</td><td><code>"primary_stage"</code></td></tr><tr><td><code>stage</code></td><td>The stage number the user has played</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr><tr><td><code>totalFinishedStage</code></td><td>The total number of stages a player has finished.<br>For games without stages, this should be the total play count.</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr><tr><td><code>stageResult</code></td><td>The result of the most recently completed stage.<br>If the ad placement occurs at the end of a stage or game, this should reflect the result of that stage. If the placement is in the middle of a stage/game or the user is not currently in gameplay, provide the result of the last finished stage.<br>You must use one of the pre-defined strings from the list below.<br>• <code>"success"</code><br>• <code>"fail"</code><br>• <code>"giveup"</code><br>• <code>"retry"</code><br>• <code>"draw"</code><br>• <code>"exhausted"</code></td><td>Nullable (if the game has no stages)</td><td><code>"success"</code></td></tr></tbody></table>

#### Code Example

{% code title="iOS: Swift" %}

```swift
import Airflux

Airflux.trackEvent(
    category: AirfluxCategory.FINISH_STAGE,
    semanticAttributes: [
        AirfluxAttribute.STAGE_TYPE: "primary_stage",
        AirfluxAttribute.STAGE: 10,
        AirfluxAttribute.TOTAL_FINISHED_STAGE: 10,
        AirfluxAttribute.STAGE_RESULT: "success"
    ]
)
```

{% endcode %}

{% code title="iOS: Objective-C" %}

```objectivec
#import <Airflux/Airflux.h>

[Airflux trackEventWithCategory:AirfluxCategory.FINISH_STAGE semanticAttributes:@{
    AirfluxAttribute.STAGE_TYPE: @"primary_stage",
    AirfluxAttribute.STAGE: @(10),
    AirfluxAttribute.TOTAL_FINISHED_STAGE: @(10),
    AirfluxAttribute.STAGE_RESULT: @"success"
}];
```

{% endcode %}

</details>

<details>

<summary>Achieve Level</summary>

Track this event when a player's or character's level changes, including when it decreases. This event can be omitted for games without a level system.

| Name    | Description                                | Required                                     | Sample Value |
| ------- | ------------------------------------------ | -------------------------------------------- | ------------ |
| `level` | The player's or character's current level. | <p>Nullable<br>(only if no level system)</p> | `10`         |

#### **Code Example**

{% code title="iOS: Swift" %}

```swift
import Airflux

Airflux.trackEvent(
    category: AirfluxCategory.ACHIEVE_LEVEL,
    semanticAttributes: [
        AirfluxAttribute.LEVEL: 10
    ]
)
```

{% endcode %}

{% code title="iOS: Objective-C" %}

```objectivec
#import <Airflux/Airflux.h>

[Airflux trackEventWithCategory:AirfluxCategory.ACHIEVE_LEVEL semanticAttributes:@{
    AirfluxAttribute.LEVEL: @(10)
}];
```

{% endcode %}

</details>

### Verification

<details>

<summary>Using the App Real-Time Log</summary>

Trigger events based on your test scenarios and check the corresponding logs in the \[Raw Data] > \[App Real-time Log] menu. The event data will be displayed in JSON format, allowing you to confirm that the data type and structure of each field match the predefined format.

<table data-header-hidden><thead><tr><th width="221.7890625">Field</th><th>Validation Criteria</th></tr></thead><tbody><tr><td>eventData.goal.category</td><td>Verify that the event name exactly matches the string defined in the taxonomy.</td></tr><tr><td>semanticAttributes</td><td>• All keys must match those defined in the taxonomy. <br>• Value types must match the defined types (string, number, boolean). <br>• For revenue events (ad_impression, order_completed), values must be positive numbers.</td></tr><tr><td>originalCurrency</td><td>Must be a 3-letter uppercase code defined by ISO-4217 (e.g., USD, KRW)</td></tr></tbody></table>

</details>

***

## 2. Send player attribute data

Player attribute data provides a crucial snapshot of a player's status at a given time. This data is used to fine-tune player segmentation and personalize ad experiences. There are two primary functions for sending this data: `Airflux.setUser()` and `Airflux.setContext()`.

{% hint style="warning" %}
Player attribute data is only transmitted to the server when an event is tracked or an inference API is called. Ensure this data is set before making any inference API requests.
{% endhint %}

### Send User ID

Send the player's unique User ID when they sign up or sign in. This ensures all subsequent events and attributes are properly linked to that user. The User ID must be sent before the event data.

<details>

<summary>Send User ID </summary>

#### **When to trigger**

* When a player signs up or signs in.

| Name | Description                 | Example                   |
| ---- | --------------------------- | ------------------------- |
| `ID` | The player’s unique User ID | `"your_internal_user_id"` |

{% hint style="danger" %}
If the User ID is not sent before the event data, the User ID cannot be linked to the event data.
{% endhint %}

#### **Code Examples**

{% code title="iOS: Swift" %}

```swift
import Airflux

// Call when a player signs in
Airflux.setUser(AirfluxUser.ID, value: "your_internal_user_id")
```

{% endcode %}

{% code title="iOS: Objective-C" %}

```objectivec
#import <Airflux/Airflux.h>

[Airflux setUser:AirfluxUser.ID value:@"your_internal_user_id"];
```

{% endcode %}

</details>

### Send Contextual Data

`Airflux.setContext()` is used to pass player attributes that are not tied to a specific event. This is crucial for providing the AI model with a complete snapshot of the player's status, such as their current level or currency balance at app launch.

<details>

<summary>Level Attributes</summary>

#### **When to trigger**

* When the game app opens
* When the player logs in

| Name    | Description                                | Example | Skip Case                 |
| ------- | ------------------------------------------ | ------- | ------------------------- |
| `Level` | The player's or character's current level. | `10`    | If the game has no levels |

{% hint style="danger" %}
The player attribute data must be passed to the SDK before the inference API request.
{% endhint %}

#### **Code Example**

{% code title="iOS: Swift" %}

```swift
import Airflux

Airflux.setContext(AirfluxContext.LEVEL, value: 10)
```

{% endcode %}

{% code title="iOS: Objective-C" %}

```objectivec
#import <Airflux/Airflux.h>

[Airflux setContext:AirfluxContext.LEVEL value:@(10)];
```

{% endcode %}

</details>

<details>

<summary>Stage Attributes</summary>

#### **When to Trigger**

* When the game app opens.
* When a player logs in.

| Name                   | Description                                                                                                      | Example | Skip Case                       |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------- |
| `START_STAGE`          | The last stage the player started.                                                                               | `10`    | If the game has no stages       |
| `FINISH_STAGE`         | The last stage the player finished.                                                                              | `10`    | If the game has no stages       |
| `TOTAL_FINISHED_STAGE` | The total number of stages a player has finished. For games without stages, this should be the total play count. | `10`    | If information is not available |

#### **Code Example**

{% code title="iOS: Swift" %}

```swift
import Airflux

Airflux.setContext(AirfluxContext.START_STAGE, key: "primary_stage", value: 10)
Airflux.setContext(AirfluxContext.FINISH_STAGE, key: "primary_stage", value: 10)
Airflux.setContext(AirfluxContext.TOTAL_FINISHED_STAGE, key: "primary_stage", value: 10)
```

{% endcode %}

{% code title="iOS: Objective-C" %}

```objectivec
#import <Airflux/Airflux.h>

[Airflux setContext:AirfluxContext.START_STAGE key:@"primary_stage" value:@(10)];
[Airflux setContext:AirfluxContext.FINISH_STAGE key:@"primary_stage" value:@(10)];
[Airflux setContext:AirfluxContext.TOTAL_FINISHED_STAGE key:@"primary_stage" value:@(10)];
```

{% endcode %}

</details>

<details>

<summary>Other Attributes</summary>

#### **When to Trigger**

When other custom game attributes are updated.&#x20;

#### **Note**

* Attributes can have up to 100 key-value pairs.
* Keys must satisfy the regex ^\[a-zA-Z\_]\[a-zA-Z0-9\_]\*$.
* The maximum length of keys is 128 characters.
* Values type must be string, numeric, or boolean.
* The maximum length of string values is 1024 characters.

| Name        | Description                                                       | Example                   | Skip Case     |
| ----------- | ----------------------------------------------------------------- | ------------------------- | ------------- |
| `Attribute` | A key-value map for other custom attributes and game information. | `"battlePass", "premium”` | If not needed |

#### **Code Examples**

{% code title="iOS: Swift" %}

```swift
import Airflux

Airflux.setContext(AirfluxContext.ATTRIBUTE, key: "battlePass", value: "premium")
```

{% endcode %}

{% code title="iOS: Objective-C" %}

```objectivec
#import <Airflux/Airflux.h>

[Airflux setContext:AirfluxContext.ATTRIBUTE key:@"battlePass" value:@"premium"];
```

{% endcode %}

</details>

***

## Frequently Asked Questions

<details>

<summary>How should I use the <code>category</code>, <code>semanticAttributes</code>, and <code>customAttributes</code> parameters for event data collection?</summary>

| Name                 | Type                         | Description                                                                                                                                                                                                                                                                                                          |
| -------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `category`           | `String`                     | <p><strong>The event's unique name</strong> (e.g., <code>AD\_IMPRESSION</code>).<br>Only underscores are permitted as special characters; colons and other special characters are not allowed. If the collected data exceeds the maximum limit of 128 characters, only the initial 128 characters will be saved.</p> |
| `semanticAttributes` | `Dictionary<string, object>` | <p><strong>Semantic attributes of the event</strong><br>Semantic attribute data collection is limited by type: up to 1024 characters for strings, and 64 bits for integers or floats.</p>                                                                                                                            |
| `customAttributes`   | `Dictionary<string, object>` | **Custom attributes of the event** Custom attribute data collection is limited to 2,048 characters; exceeding the limit results in ERROR\_MAX\_LENGTH\_EXCEEDED.                                                                                                                                                     |

</details>

<details>

<summary>When a player completes a stage and levels up at the same time, how should I track it?</summary>

Use the `TrackEvent()` function to track the player's action of completing a stage as the Achieve Level event, and use the `SetLevel()` function to track the player's updated level as the player attribute.

</details>

<details>

<summary>Can I use the Airflux SDK to collect and send game store payment data?</summary>

No. The game store payment data must be collected and sent using the client-side SDK.

</details>

<details>

<summary>After restarting the game, the Airflux SDK stops sending events. How can I fix this?</summary>

Airflux Native SDK v1.0 Guide: \
The Airflux Native SDK automatically tracks app lifecycle events, so you do not need to manually call `startTracking()`. This issue typically occurs if the SDK is disabled, either by:

1. Setting `setSDKEnabled(false)` in the SDK initialization options.
2. Calling the `Airflux.disableSDK()` function during runtime.

When the SDK is disabled, both event tracking and inference requests are halted. To resolve this, you must explicitly call `Airflux.enableSDK()` to re-activate the SDK's features.

</details>


# 4. Call the Inference API

Configure the Airflux SDK to request an inference decision before displaying an interstitial ad. Based on the AI's response, you can decide whether to proceed with showing the ad. This process allows for smarter, more revenue-optimized ad delivery.

***

<figure><img src="/files/36MUXoSd2P6ZMABnOVjQ" alt=""><figcaption></figcaption></figure>

## 1. Set up the API Call and callbacks

Use the `Airflux.requestInference()` function with the `SHOW_INTERSTITIAL_AD` method to request a real-time ad decision. To ensure the AI makes an accurate decision, you must provide detailed contextual parameters and implement the appropriate callbacks.

<details>

<summary>Step 1 : Set Inference Parameters</summary>

To make an accurate decision, Airflux requires detailed **contextual parameters** related to the player state, ad type, and placement. These must be passed into the function via the `parameters` object using `AirfluxParameter.*` keys.

{% hint style="danger" %}
**Important**

All required fields must always be collected, and nullable fields should also be collected whenever available. Failure to provide these values may reduce optimization performance and lead to skewed experimental results.
{% endhint %}

<table><thead><tr><th>Name</th><th width="255.56640625">Description</th><th>Type</th><th>Required</th><th>Sample Value</th></tr></thead><tbody><tr><td><code>currency</code></td><td>The currency code for the ad revenue.(ISO 4217)</td><td>string</td><td>Required</td><td><code>“USD”</code></td></tr><tr><td><code>adType</code></td><td>The type of ad. You must use one of the pre-defined strings from the list below.<br>• <code>"interstitial_ad"</code><br>• <code>"rewarded_ad"</code><br>• <code>“other”</code></td><td>string</td><td>Required</td><td><code>“interstitial_ad”</code></td></tr><tr><td><code>adPlacementID</code></td><td>A unique identifier for the ad placement.</td><td>string</td><td>Required</td><td><code>“placement_3”</code></td></tr><tr><td><code>adPlacementType</code></td><td>The type of ad placement. You must use one of the pre-defined strings from the list below.<br>• <code>"static"</code><br>• <code>"dynamic"</code></td><td>string</td><td>Required</td><td><code>“static”</code></td></tr><tr><td><code>adPlacementPosition</code></td><td>The position of the ad placement within the game. You must use one of the pre-defined strings from the list below<br>• <code>"stage_start"</code><br>• <code>"stage_middle"</code><br>• <code>"stage_end"</code><br>• <code>"non_stage"</code></td><td>string</td><td>Nullable (if the placement is dynamic)</td><td><code>“stage_start"</code></td></tr><tr><td><code>level</code></td><td>The player's or character's current level.</td><td>int</td><td>Nullable<br>(only if no level system)</td><td><code>10</code></td></tr><tr><td><code>stage</code></td><td>The stage number where the ad was presented.<br>If not in a game, this should be the last known result.</td><td>int</td><td>Nullable (if the game has no stages)</td><td><code>10</code></td></tr><tr><td><code>stageType</code></td><td>The type of stage where the ad was presented.<br>If not in a game, this should be the last known result.<br>You must use one of the pre-defined strings from the list below.<br>• <code>"primary_stage"</code><br>• <code>"secondary_stage"</code><br>• <code>"extra_stage"</code></td><td>string</td><td>Nullable (if the game has no stages)</td><td><code>"primary_stage"</code></td></tr><tr><td><code>totalFinishedStage</code></td><td>Number of stages played so far (or play count if the game has no stages)</td><td>int</td><td>Nullable (if the information is not available)</td><td><code>10</code></td></tr><tr><td><code>stageResult</code></td><td>The result of the most recently completed stage.<br>If the ad placement occurs at the end of a stage or game, this should reflect the result of that stage. If the placement is in the middle of a stage/game or the user is not currently in gameplay, provide the result of the last finished stage.<br>You must use one of the pre-defined strings from the list below.<br>• <code>"success"</code><br>• <code>"fail"</code><br>• <code>"giveup"</code><br>• <code>"retry"</code><br>• <code>"draw"</code><br>• <code>"exhausted"</code></td><td>string</td><td>Nullable (if the game has no stages)</td><td><code>"success"</code></td></tr><tr><td><code>adCooldownSeconds</code></td><td>When a cooldown is applied to an ad placement, this key-value map shows the trigger and duration of the cooldown. The key is omitted when no cooldown is applied.<br>• <code>interstitial_ad</code> : Cooldown calculated from the last interstitial ad<br>• <code>rewarded_ad</code> : Cooldown calculated from the last rewarded ad.<br>• <code>app_open</code> : Cooldown calculated from the app open event<br>• <code>install</code> : Cooldown calculated from the app install event.<br>• <code>other</code> : Cooldown calculated from other events<br>Example: If <code>{"rewarded_ad": 120}</code> is included, it means a 2-minute cooldown is applied from the last rewarded ad.</td><td>map&#x3C;string, int></td><td>Nullable (if no cooldown is applied)</td><td><code>{”rewarded_ad”: 10, “interstitial_ad”: 20”}</code></td></tr><tr><td><code>rewardItems</code></td><td>A key-value map of items rewarded to the player. Only for rewarded ads; otherwise, null.</td><td>map&#x3C;string, int></td><td>Nullable (if the ad has no rewards)</td><td><code>{ "coin": 500, "gem": 10 }</code></td></tr></tbody></table>

</details>

<details>

<summary>Step 2 : Implement the Ad Display Logic</summary>

Once the inference request is sent, the SDK will trigger one of the following callbacks. You must implement the appropriate logic in each case to ensure a smooth player experience and stable ad revenue.

| Name                | Description                                                                                                                                                                                                                       | What your app should do                                                                  | Required     |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------ |
| `onShowAd`          | Triggered when the API determines the ad must be shown.                                                                                                                                                                           | Display the interstitial ad.                                                             | **Required** |
| `onSkipAd`          | Triggered when the API determines the ad must be skipped. Continue gameplay without showing ads.                                                                                                                                  | Skip the ad and continue gameplay seamlessly.                                            | **Required** |
| `onDefaultAdPolicy` | Triggered when the ad serving should not be decided by the Airflux policy. You must implement your internal logic to decide whether to show or skip the ad.                                                                       | Apply your internal ad policy logic and proceed accordingly.                             | **Required** |
| `onFailure`         | <p>Triggered when the inference request fails. This may happen if:<br>• The player's country is not supported (not in countryAllowlist)<br>• The server returns a 4XX or 5XX error<br>• The request times out after 3 seconds</p> | Use your fallback logic to decide whether to show or skip the ad. Retry is not required. | **Required** |

{% hint style="warning" %}
**Timeout and Fallback Recommendation**

Failure to handle the inference request properly may lead to degraded play experience and loss of ad revenue. To minimize potential negative effects, it is recommended to set a **3-second (default) timeout** and **implement `onFailure` code** in case the API call fails. Retry attempts upon function call failure are not required. In this case, your internal ad display logic should be included within the `onFailure` block.
{% endhint %}

</details>

#### Code Example

{% tabs %}
{% tab title="iOS: Swift" %}

```swift
import Airflux

Airflux.requestInference(
    AirfluxInference.SHOW_INTERSTITIAL_AD(
        parameters: [
            AirfluxParameter.AD_TYPE: "interstitial_ad", 
            AirfluxParameter.AD_PLACEMENT_ID: "placement_3", 
            AirfluxParameter.AD_PLACEMENT_TYPE: "static",
            AirfluxParameter.AD_PLACEMENT_POSITION: "stage_start",
            AirfluxParameter.STAGE: 10, 
            AirfluxParameter.STAGE_TYPE: "primary_stage",
            AirfluxParameter.TOTAL_FINISHED_STAGE: 10, 
            AirfluxParameter.STAGE_RESULT: "success",
            AirfluxParameter.AD_COOLDOWN_SECONDS: [ 
                "rewarded_ad": 10,
                "interstitial_ad": 20
            ],
            AirfluxParameter.REWARD_ITEMS: [ 
                "coin": 500,
                "gem": 10
            ],
            AirfluxParameter.FORCE_RESPONSE: [ 
                "action": "showAd",
                "parameters": [String: Any]() 
            ]
        ],
        onShowAd: {
            // AI determined to show the ad. Implement your logic here.
            print("AI decided to show ad.")
        },
        onSkipAd: {
            // AI determined to skip the ad. Implement your logic here.
            print("AI decided to skip ad.")
        },
        onDefaultAdPolicy: {
            // Implement your internal logic to decide whether to show or skip the ad.
            print("AI returned default policy. Implementing internal logic.")
        },
        onFailure: { error in
            // Inference request failed. Implement your internal logic to handle the error
            // and decide whether to show or skip the ad based on your game's policy.
            print("Inference request failed: \(error.message). Falling back to default policy.")
        }
    )
)

```

{% endtab %}

{% tab title="iOS: Objective-C" %}

```objectivec
#import <Airflux/Airflux.h>

[Airflux requestInference:[AirfluxInference SHOW_INTERSTITIAL_AD:@{
    AirfluxParameter.AD_TYPE: @"interstitial_ad",
    AirfluxParameter.AD_PLACEMENT_ID: @"placement_3",
    AirfluxParameter.AD_PLACEMENT_TYPE: @"static",
    AirfluxParameter.AD_PLACEMENT_POSITION: @"stage_start",
    AirfluxParameter.STAGE_TYPE: @"primary_stage",
    AirfluxParameter.STAGE: @(10),
    AirfluxParameter.TOTAL_FINISHED_STAGE: @(10),
    AirfluxParameter.STAGE_RESULT: @"success",
    AirfluxParameter.AD_COOLDOWN_SECONDS: @{
        @"rewarded_ad": @(10),
        @"interstitial_ad": @(20)
    },
    AirfluxParameter.REWARD_ITEMS: @{
        @"coin": @(500),
        @"gem": @(10)
    },
    AirfluxParameter.FORCE_RESPONSE: @{
        @"action": @"showAd",
        @"parameters": @{}
    }
} onShowAd:^{
    // AI determined to show the ad. Implement your logic here.
    NSLog(@"AI decided to show ad.");
} onSkipAd:^{
    // AI determined to skip the ad. Implement your logic here.
    NSLog(@"AI decided to skip ad.");
} onDefaultAdPolicy:^{
    // Implement your internal logic to decide whether to show or skip the ad.
    NSLog(@"AI returned default policy. Implementing internal logic.");
} onFailure:^(NSError * _Nonnull error) {
    // Inference request failed. Implement your internal logic to handle the error
    // and decide whether to show or skip the ad based on your game's policy.
    NSLog(@"Inference request failed: %@. Falling back to default policy.", error.localizedDescription);
}]];
```

{% endtab %}
{% endtabs %}

#### **Verification**

<details>

<summary>Testing with Force responses</summary>

Ensure that your app correctly calls the inference API at each interstitial placement and follows the returned decision reliably.

**What to test**

1. The inference request includes all required parameters.
2. Exactly one of the decision callbacks is triggered per request.

**Testing with Force responses**

To simulate various responses during development or QA, you can use the `AirfluxParameter.FORCE_RESPONSE` parameter in your inference request:

{% hint style="info" %}
It’s **recommended to implement handling for all four cases** to ensure consistent ad delivery across various environments and edge cases.
{% endhint %}

| Simulated value     | Callback triggered    | Purpose                                      | Expected Behavior                   |
| ------------------- | --------------------- | -------------------------------------------- | ----------------------------------- |
| `“showAd”`          | `onShowAd()`          | Test ad display flow                         | Ad is displayed to the player       |
| `“skipAd”`          | `onSkipAd()`          | Test ad skip behavior                        | Ad is skipped and gameplay resumes  |
| `“defaultAdPolicy”` | `onDefaultAdPolicy()` | Test your ad decision logic                  | Your ad decision logic is executed  |
| `“failure”`         | `onFailure(error)`    | Test fallback handling for failure scenarios | Custom policy determines ad display |

```kotlin
[AirfluxParameter.FORCE_RESPONSE]: {
    "action": "showAd",
    "parameters": {}
}
```

{% hint style="info" %}
The `FORCE_RESPONSE` parameter is intended for testing purposes only and **must be removed from production builds**. If left active, it may cause ads to be **always shown or always skipped**, regardless of actual inference decisions.
{% endhint %}

**Code Example**

{% code title="iOS: Swift" %}

```swift
AirfluxParameter.FORCE_RESPONSE: [ 
    "action": "showAd",
    "parameters": [:]
]
```

{% endcode %}

{% code title="iOS: Objective-C" %}

```objectivec
AirfluxParameter.FORCE_RESPONSE: @{
    "action": @"showAd",
    "parameters": @{}
}
```

{% endcode %}

</details>

***

## 2. Deploy the app

After completing sufficient QA and crash testing, deploy your gaming app. To ensure proper integration with Airflux, make sure the deployment follows the timeline coordinated with the Growth Manager.

<details>

<summary>Where can I get guidance for the app store review?</summary>

Click [here](https://docs.airflux.ai/airflux-reference/preparing-for-the-app-store-review) for guidance on preparing for the Google Play and App Store reviews.

</details>

***

## Next steps

Congratulations! If you have completed the steps above, you are all set to optimize your in-game advertising with Airflux. Click [here](https://docs.airflux.ai/reporting) to learn how to receive your optimization results.

***

## Frequently Asked Questions

<details>

<summary>What is the difference between <code>onSkipAd()</code> and <code>onFailure()</code> callbacks?</summary>

* The `onSkipAd()` callback function is triggered when the API call is successful, and the inference result from the Airflux AI model indicates that an ad should not be displayed to enhance the play experience and maximize ad revenue.
* The `onFailure()` callback function is triggered when the API call fails. This includes situations such as the device's country not being in the allowlist (`countryAllowlist`), network issues, server errors, or request validation failures, where no response is received.

</details>

<details>

<summary>What is the response time of the API by country?</summary>

Airflux aims to deliver reliable service to users worldwide and typically maintains quick response times in most regions. However, minor delays may arise based on the network environment.

</details>


# Reporting

This guide provides an overview of the AI-powered ad optimization process following Airflux integration and how to access the initial report.

***

## 1. Model training

Once the integration with Airflux is complete, your game data will be fed into the Airflux AI for model training, which will take approximately 2 weeks.

{% hint style="info" %}
**Why does it take 2 weeks for model training?**

Airflux requires a sufficient amount of data for player segmentation to develop optimized advertising strategies tailored to each profile. This initial training period is an essential process for personalized ad display optimization.
{% endhint %}

The following data sets are key to model training:

* **Player behavior:** Play patterns, such as game playtime, play frequency, purchase history, and ad viewing history.
* **Game progress:** Player’s current level, in-game status, etc.
* **Ad attributes:** Ad types, frequency, rewards players receive after watching rewarded ads, etc.

Using anonymized data in adherence to privacy regulations, Airflux effectively trains its models to determine the best timing for ad displays, enhancing LTV and retention without affecting the gameplay experience.&#x20;

To learn more about how inference works in Airflux, click [here](/airflux-reference/how-inference-works).&#x20;

## 2. Reporting and Dashboard Access

An Integration Success Report is issued via email as soon as data integration is confirmed, typically within two weeks of the initial setup. This report serves as a confirmation that the data is flowing correctly into the Airflux system.

Following the commencement of model training and optimization, Weekly Performance Reports are delivered to provide ongoing insights. These reports highlight key metric changes, such as shifts in LTV and retention, alongside the specific Airflux settings customized for the game.

For a more comprehensive analysis, the [dashboard](https://app.airflux.ai/) offers full access to all optimization results and real-time data, allowing for a detailed review of the AI’s performance and its impact on game growth.

## Frequently Asked Questions

<details>

<summary>How can additional users be granted access to the dashboard?</summary>

User management is handled through the [Airbridge dashboard](https://app.airbridge.io/). By inviting users under **\[Settings] > \[User Management]** and granting them permissions for the specific app, those users will automatically be authorized to access the Airflux dashboard as well.

</details>

<details>

<summary>What if initial performance does not meet expectations?</summary>

It may take additional time for Airflux to fully analyze data and generate significant results. Typically, noticeable improvements in key metrics such as ARPU and retention are observed after a continuous optimization process of 4 to 6 weeks.

</details>

## Have questions?

If you have any questions regarding model training and reporting, contact us at <support@airflux.ai>.


# Raw Data Export

This guide explains how to download experiment-grouped player data as a CSV file from the Airflux Dashboard so you can review Airflux's impact in your own analysis tools.

***

### 1. What you can do

Raw Data Export gives you a CSV of the players in your app, tagged by the experiment group they were in:

* `Default` — players who continued under your existing ad-serving logic.
* `Airflux` — players who saw Airflux's AI-optimized ad policy.

Compare metrics between the two groups in your own tooling — for example, joining the list with your purchase data to verify whether Airflux moved revenue, or feeding it back into your CRM cohorts for retention analysis.

Quick facts:

* All times are in **UTC**.
* Each export file is kept for **1 day**. After that, re-submit with the same filters to regenerate.
* One app per export. Switch apps in the dashboard's app picker to export from another.
* On-demand only — no scheduled or recurring exports yet.

<figure><img src="/files/D1UW891R28c1D1tZw2ir" alt=""><figcaption></figcaption></figure>

### 2. How to export

1. Sign in to the [Airflux Dashboard](https://app.airflux.ai/) and confirm the correct app is selected in the left sidebar app picker.
2. From the left sidebar, open **\\\[Exports]**.
3. Pick your filters:
   * **Date range** — interpreted as UTC, limited to dates where your app has data.
   * **OS** — iOS, Android, or both.
   * **App version** — pick from the list, or type a version and use **Add** to include one that isn't shown.
     * **Note:** If the value appears as *Unknown*, it means the app version was not sent to the Airflux server. This typically occurs when using an older version of the SDK.
   * **Country** — search by country name or two-letter code; type and **Add** if a country you want isn't listed.
4. Click **Export**. A notification appears with the request status.
5. When the file is ready (typically under a minute), click **Download** in the notification to save the CSV.

You can submit several exports back-to-back. Each job appears in the **Export History** panel below the form, where you can re-download recent files or use the **⋯ menu → Reuse filters** to repopulate the form.

### 3. About the CSV

| Column               | What it means                                                             |
| -------------------- | ------------------------------------------------------------------------- |
| `APP_NAME`           | Your app's registered name.                                               |
| `EXPERIMENT_GROUP`   | `Default` (existing logic) or `Airflux` (optimized policy).               |
| `USER_ID`            | The player identifier your SDK sends to Airflux.                          |
| `SDK_INIT_TIMESTAMP` | When the player first triggered an event through the Airflux SDK, in UTC. |

```
APP_NAME,EXPERIMENT_GROUP,USER_ID,SDK_INIT_TIMESTAMP
mygame,Default,user_abc123,2026-03-01T09:15:30Z
mygame,Default,user_def456,2026-03-01T10:22:15Z
mygame,Airflux,user_ghi789,2026-03-01T08:45:00Z
```

Each player appears once. The **date range** filter selects players based on their activity within that window, while `SDK_INIT_TIMESTAMP` shows when the player first triggered an event through the Airflux SDK, which can be earlier than the date range.

### Have questions?

If you have any questions about Raw Data Export, contact us at <support@airflux.ai>.


# How inference works

<figure><img src="/files/mYJFo2RmjGAbL8AJIjoS" alt=""><figcaption></figcaption></figure>

The Airflux inference engine is an AI-powered system that makes real-time decisions on whether to display an ad or not at a particular moment for maximum ad revenue. When a player reaches a point where an ad is supposed to be displayed, the Airflux SDK sends the in-game events and player attribute data to the Inference server. Using the data, a decision regarding ad display is made and returned to the client. The inferencing process includes the following steps:

<table><thead><tr><th width="149.1875">Steps</th><th>Description</th></tr></thead><tbody><tr><td><ol><li>Data Collection</li></ol></td><td>The SDK gathers data such as in-game events, player attributes, revenue, device information, and other relevant data in real-time.</td></tr><tr><td><ol start="2"><li>Data Preprocessing</li></ol></td><td>The collected data is converted into formats usable by the AI engine, including recent event count, session information, and eCPM metrics.</td></tr><tr><td><ol start="3"><li>Model Inference</li></ol></td><td>The AI engine selects a model based on player profile and game context, and performs inference to determine optimal ad display timing. </td></tr><tr><td><ol start="4"><li>Ad Display Decision</li></ol></td><td>The AI engine returns a result aligned with the optimization goal, indicating whether or not to display an ad.</td></tr></tbody></table>

## **Data used by the model**

The Airflux AI engine takes into account a variety of variables. The variables detailed in the table below are used to identify the best timing and approach for displaying ads, maximizing ad revenue while ensuring a positive play experience.

<table><thead><tr><th width="116.66796875">Category</th><th>Example</th><th>Purpose</th></tr></thead><tbody><tr><td>Player profile</td><td>Country, OS, Language</td><td>Player segmentation</td></tr><tr><td>In-session behavior</td><td>Accumulated playtime, recent session duration, event count</td><td>Churn and retention prediction</td></tr><tr><td>Ad response</td><td>Recent responses to ads </td><td>Ad resistance assessment</td></tr><tr><td>Revenue signal</td><td>The recent average of in-app revenue amount, eCPM</td><td>High-value user identification</td></tr><tr><td>Game context</td><td>Current stage, currency inventory</td><td>Adjustment for optimal play experience </td></tr><tr><td>Time series signal</td><td>Current date, day of week</td><td>Optimization around holidays and weekends</td></tr></tbody></table>

## Model updates

The models used by the Airflux AI engine evolve through several training loops:

* **Online feedback loop:** Internal model parameters are adjusted based on ad responses to each API call. Underperforming policies are discarded.
* **Model retraining loop:** When a large volume of events is aggregated or significant pattern changes are detected (e.g., new stages, patches), retraining is triggered.
* **Version update:** The data science team continues to develop and deploy new, improved models that outperform the previous versions.

## Performance & reliability

The Airflux inference engine’s architecture is built on the following key components:

* **CloudFront Global CDN:** All inference requests are routed through CloudFront, a top-tier global CDN, to the nearest PoP (Point of Presence), minimizing latency.
* **AWS Services:** Airflux uses services such as Application Load Balancer, Lambda\@Edge, Aurora (RDS), DynamoDB, and S3 to ensure high durability and scalability across multiple Availability Zones (AZs).


# Required event data for Airflux integration

This section outlines the event data that must be collected and sent using the Airflux SDK to ensure proper AI model training and achieve optimal results.&#x20;

## Required event data per revenue model

Depending on the revenue model of your game, different event data is required for utilizing Airflux. Refer to the table below.&#x20;

<table><thead><tr><th width="250.82421875">Revenue Model	</th><th>Required Events </th><th data-hidden></th></tr></thead><tbody><tr><td>In-app advertising only</td><td><code>AD_IMPRESSION</code>, <code>ACHIEVE_LEVEL</code>, <code>SPEND_CREDITS</code></td><td></td></tr><tr><td>In-app purchase only</td><td><code>ACHIEVE_LEVEL</code>, <code>ORDER_COMPLETED</code>, <code>SPEND_CREDITS</code></td><td></td></tr><tr><td>In-app advertising + In-app purchase </td><td><code>AD_IMPRESSION</code>, <code>ACHIEVE_LEVEL</code>, <code>ORDER_COMPLETED</code>, <code>SPEND_CREDITS</code></td><td></td></tr></tbody></table>

Note that the Install and Open events are automatically collected by the Airbridge SDK upon installation and no additional setup is needed.&#x20;

## Detailed schema per event

Find the key attributes and code examples for collecting the required event data below. &#x20;

<details>

<summary>Ad Impression (IAA-related event)</summary>

{% hint style="danger" %}
**Attention**

The in-app ad revenue data must be collected using the client-side SDK through mediation platform integrations and sent to Airflux.&#x20;
{% endhint %}

Track ad impressions when ad is shown to the player and collect relevant data, such as ad type, ad revenue, placement, and more.

For example, when ad revenue is generated after an interstitial ad is shown, call the `TrackEvent()` function, set the event category to `AirfluxCategory.AD_IMPRESSION`, and add `"adType"` to `customAttributes` to send `interstitial` as a value.

<table><thead><tr><th width="169.89453125">Attribute Type</th><th width="134.97265625">Name</th><th width="127.4609375">Semantic Attributes Description</th><th>Sample Value</th></tr></thead><tbody><tr><td>Semantic Attribute</td><td>Currency</td><td>Currency for ad revenue</td><td>USD</td></tr><tr><td>Semantic Attribute</td><td>Value</td><td>Ad revenue amount</td><td>1.99</td></tr><tr><td>Custom Attribute</td><td>adType</td><td>The type of the ad</td><td>reward: Rewarded ad<br>interstitial: Interstitial ad<br>banner: Banner ad</td></tr><tr><td>Custom Attribute</td><td>ad_placement</td><td>Ad placement</td><td>rw_offline: Offline reward ad<br>rw_get_item: Rewarded ad for obtaining items like weapons, skins, etc.<br>rw_get_coin: Rewarded ad for earning coins<br>rw_get_gem: Rewarded ad for earning gems<br>rw_time_skip: Rewarded ad for reducing recovery time<br>int_next_stage: Interstitial ad that is presented when advancing to the next stage<br>bn_next_stage: Banner ad that is presented when advancing to the next stage</td></tr><tr><td>Custom Attribute</td><td>stage_type</td><td>The type of the stage</td><td>main: Main stage<br>promotion: Seasonal promotion stage (updated every 3 months)</td></tr><tr><td>Custom Attribute</td><td>stage_number</td><td>The stage number where interstitial or rewarded ads are presented after Success, Fail, Give-up, or Retry. Otherwise, null is collected.</td><td>main: 1, 2, ..., 550 (30 new stages added every months)<br>promotion: 1, 2, ...,100</td></tr><tr><td>Custom Attribute</td><td>reward_item</td><td>The reward earned by the player after engaging with a rewarded ad. For other ad types, null is collected.</td><td>coin: Number of coins earned<br>gem: Number of gems earned</td></tr></tbody></table>

#### Code example

```csharp
// Example: Ad revenue transmission from AdMob
Airflux.TrackEvent(
    category: AirfluxCategory.AD_IMPRESSION,
    semanticAttributes: new Dictionary<string, object>()
    {
        { AirfluxAttribute.VALUE, 0.01 }, // Required: Ad revenue
        { AirfluxAttribute.CURRENCY, "USD" }, // Required: Currency code
        {
            AirfluxAttribute.AD_PARTNERS, new Dictionary<string, object>()
            {
                {
                    "mopub", new Dictionary<string, object>()
                    {
                        { "app_version", "5.18.0" },
                        { "adunit_id", "12345" },
                        { "adunit_name", "12345" },
                        { "adunit_format", "Banner" },
                        { "id", "12345" },
                        { "currency", "USD" },
                        { "publisher_revenue", 12345.123 },
                        { "adgroup_id", "12345" },
                        { "adgroup_name", "12345" },
                        { "adgroup_type", "12345" },
                        { "adgroup_priority", "12345" },
                        { "country", "kr" },
                        { "precision", "publisher_defined" },
                        { "network_name", "12345" },
                        { "network_placement_id", "12345" },
                        { "demand_partner_data", "12345" },
                    }
                }
            }
        },
    },
    customAttributes: new Dictionary<string, object>()
    {
        { "adType", "reward" }, // Ad type (e.g., rewarded, interstitial)
        { "ad_placement", "main_banner" }, // Ad placement (Detailed explanation of the ad_context value required after SDK installation is complete)
        { "stage_type", "Main" }, // Main or promotional stage (e.g., Main, Event)
        { "stage_number", "1" }, // The stage number where interstitial or rewarded ads are shown after Success, Fail, Give-up, or Retry. Otherwise, null is collected. 
        { "reward_item", new Dictionary<string, object> {{"coin", 10}, {"gem", 20}} } // The reward earned by the player after engaging with a rewarded ad. For other ad types, null is collected.
    }
);

```

</details>

<details>

<summary>Order Completed (IAP-related event)</summary>

{% hint style="danger" %}
**Attention**

The in-app purchase revenue data must be collected using the client-side SDK and sent to Airflux. There might be a slight gap between the data sent to Airflux and the revenue data provided by vendors.
{% endhint %}

Track in-app purchases and relevant data such as item information, transaction ID, the purchased amount, the payment currency, and more. &#x20;

For example, when a dialog is prompted confirming a purchase of an item, call the `TrackEvent()` function, set the event category to `AirfluxCategory.ORDER_COMPLETED`, and add `AirfluxAttribute.PRODUCT_ID` and `AirfluxAttribute.PRODUCT_NAME` to send information of the purchase item.&#x20;

{% hint style="warning" %}
The payment currency information (`AirfluxAttribute.CURRENCY` )and the purchase amount  (`AirfluxAttribute.VALUE)` must be included in the event data for accurate revenue analysis.&#x20;
{% endhint %}

<table><thead><tr><th width="169.89453125">Attribute Type</th><th width="115.31640625">Name</th><th width="127.4609375">Semantic Attributes Description</th><th>Sample Value</th></tr></thead><tbody><tr><td>Semantic Attribute</td><td>Transaction ID</td><td>Transaction ID</td><td>TXN-20250411-5F3C9A72B1</td></tr><tr><td>Semantic Attribute</td><td>Currency</td><td>Currency for ad revenue</td><td>USD</td></tr><tr><td>Semantic Attribute</td><td>Value</td><td>Ad revenue amount</td><td>10.99</td></tr><tr><td>Semantic Attribute</td><td>Product ID</td><td>Product ID</td><td>1C569KY32P1</td></tr><tr><td>Semantic Attribute</td><td>Product Name</td><td>Product Name</td><td>remove_ads: ""Ad Removal"" as a purchase item<br>welcome_pack: Item package for newly acquired players<br>starter_pack: Item package for beginners<br>coin_pack_1: Coin package<br>gem_pack_2: Gem package<br>limited_skin_1: Time-limited skin</td></tr><tr><td>Custom Attribute</td><td>purchase_route</td><td>The source of the purchase</td><td>shop: Purchased from the shop<br>popup: Purchased from a pop-up</td></tr></tbody></table>

#### Code example

```csharp
Airflux.TrackEvent(
    // StandardCategory
    category: AirfluxCategory.ORDER_COMPLETED, // or "CustomEvent" (CustomCategory)
    // SemanticAttributes
    semanticAttributes: new Dictionary<string, object>()
    {
        { AirfluxAttribute.VALUE, 11 }, // Required: Actual purchase amount
        { AirfluxAttribute.TRANSACTION_ID, "8065ef16-162b-4a82-b683-e51aefdda7d5" }, // Required: Transaction ID
        { AirfluxAttribute.CURRENCY, "USD" }, // Required: Currency code 
        {
            AirfluxAttribute.PRODUCTS, new List<object>()
            {
                new Dictionary<string, object>()
                {
                    { AirfluxAttribute.PRODUCT_ID, "1C569KY32P1" }, // Required, Product ID
                    { AirfluxAttribute.PRODUCT_NAME, "remove_ads" } // Required, Product name (welcome_back, remove_ads, starter_pack ...)
                } 
            }
        }
    },
    // CustomAttributes
    customAttributes: new Dictionary<string, object>()
    {
        { "purchase_route", "shop" } // (Optional) shop / popup
    }
```

</details>

<details>

<summary>Achieve Level</summary>

Track player game progress and how a stage ended.&#x20;

For example, when a stage ends, call the `TrackEvent()` function, set the event category to `AirfluxCategory.ACHIEVE_LEVEL`, and add `"stage_type"` and `"stage_number"` to track the stage type and stage number.

Additionally, use `"result"` to send information on how the stage ended, such as `success`, `fail`, `giveup`, and `retry` .

<table><thead><tr><th width="169.89453125">Attribute Type</th><th width="115.31640625">Name</th><th width="127.4609375">Semantic Attributes Description</th><th>Sample Value</th></tr></thead><tbody><tr><td>Custom Attribute</td><td>stage_type</td><td>The type of the stage</td><td>main: Main stage<br>promotion: Seasonal promotion stage (updated every 3 months)</td></tr><tr><td>Custom Attribute</td><td>stage_number</td><td>The stage number where Success, Fail, Give-up, or Retry occurred.</td><td>main: 1, 2, ..., 550 (30 new stages added every months)<br>promotion: 1, 2, ...,100 </td></tr><tr><td>Custom Attribute</td><td>result</td><td>The result of the stage</td><td>success: Stage completed successfully<br>fail: Stage failed<br>giveup: Stage abandoned<br>retry: Stage retried after failure or exit</td></tr></tbody></table>

#### Code example

```csharp
Airflux.TrackEvent(
    // StandardCategory
    category: AirfluxCategory.ACHIEVE_LEVEL, // or "CustomEvent" (CustomCategory)
    // SemanticAttributes
    semanticAttributes: new Dictionary<string, object>(),
    // CustomAttributes
    customAttributes: new Dictionary<string, object>()
    {
        { "stage_type", "main"},
        { "stage_number", 13  },
        { "result", "success" }      
    }
);
```

</details>

<details>

<summary>Spend Credits</summary>

Track in-game currency spending and relevant data, such as in-game currency information, spending amount, and more.

For example, when the player spends in-game currency, such as coins and gems, call the `TrackEvent()` function, set the event category to `AirfluxCategory.SPEND_CREDITS` and add `"item_type"` and `"item_amount"` to send the in-game currency type and spending amount.

Additionally, use `"stage_type"` and `"stage_number"`to to track the stage type and stage number.

<table><thead><tr><th width="169.89453125">Attribute Type</th><th width="115.31640625">Name</th><th width="253.59765625">Semantic Attributes Description</th><th>Sample Value</th></tr></thead><tbody><tr><td>Custom Attribute</td><td>item_type</td><td>The type of the in-game currency spent</td><td>coin<br>gem</td></tr><tr><td>Custom Attribute</td><td>item_amount</td><td>The amount of the in-game currency spent</td><td>10, 20</td></tr><tr><td>Custom Attribute</td><td>stage_type</td><td>The type of the stage</td><td>main: Main stage<br>promotion: Seasonal promotion stage (updated every 3 months)</td></tr><tr><td>Custom Attribute</td><td>stage_number</td><td>The current stage of the player</td><td>main: 1, 2, ~ , 550 (30 new stages added every months)<br>promotion: 1, 2, ~ ,100</td></tr></tbody></table>

#### Code example

```csharp
Airflux.TrackEvent(
    // StandardCategory
    category: AirfluxCategory.SPEND_CREDITS, // or "CustomEvent" (CustomCategory)
    // SemanticAttributes
    semanticAttributes: new Dictionary<string, object>(),
    // CustomAttributes
    customAttributes: new Dictionary<string, object>()
    {
        { "item_type", "coin" },
        { "item_amount", 100  },
        { "stage_type", "main"},
        { "stage_number", 13 }
    }
);
```

</details>


# Preparing for the App Store Review

This article is provided to assist you in preparing for the Google Play and App Store reviews. However, kindly regard it as a supplementary resource intended for your convenience rather than tailored legal advice. We recommend that you consult with legal professionals to address any of your specific needs.

## Requirements for Google Play

According to [Google's guidelines](https://support.google.com/googleplay/android-developer/answer/10787469?hl=en), app developers publishing on Google Play are required to disclose the app’s privacy practices by completing the data safety form. This allows Google to inform users of how an app, including through its use of third-party SDKs, collects and shares user data.

The Airflux SDK, which is highly configurable, collects user data necessary for measuring ad performance. When submitting your Airflux SDK-integrated app to Google Play for review, make sure to provide details on the data collected by your app and the custom-configured Airflux SDK.

### Complete the data safety form

To fill out the data safety form and disclose your app’s privacy practices, select your app in Google Play Console and navigate to the **\[Policy]>\[App content]** page.

The tables below will help you complete the form with respect to your use of Airflux. However, it is your responsibility to respond in accordance with your specific data practices, configurations, and integrations.

<details>

<summary>Data collection and security</summary>

| Question                                                              | Answer for the Airflux SDK |
| --------------------------------------------------------------------- | -------------------------- |
| Does your app collect or share any of the required user data types?   | Yes                        |
| Is all of the user data collected by your app encrypted in transit?   | Yes                        |
| Do you provide a way for users to request that their data be deleted? | No                         |

</details>

<details>

<summary>Data types</summary>

The table below lists the data types the Airflux SDK collects on your behalf by default. If your app collects any other data types, adjust your responses accordingly.

| Data Type                    | Airflux Data Collection |
| ---------------------------- | ----------------------- |
| **App activity**             |                         |
| App interactions             | Yes                     |
| **App info and performance** |                         |
| Crash logs                   | Yes                     |
| **Device or other IDs**      |                         |
| Device or other IDs          | Yes                     |

<br>

</details>

<details>

<summary>Data usage and handling</summary>

For every selected data type, you need to declare whether it is shared with a third party and how it is handled. The table below provides information on the data types collected by the Airflux SDK. If your app collects any other data types, also provide the information accordingly.

| Question                                                                        | Answer for the Airflux SDK                                                                                                                                                                                                                                                  |
| ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Is this data collected, shared, or both?                                        | Collected; Shared                                                                                                                                                                                                                                                           |
| Is this data processed ephemerally?                                             | No, this collected data is not processed ephemerally                                                                                                                                                                                                                        |
| Is this data required for your app, or can users choose whether it’s collected? | Data collection is required (users can’t turn off this data collection) *\*Note: If you have enabled the privacy protection feature of the Airflux SDK and your app allows users to choose whether data is collected, select “Users can choose whether data is collected.”* |
| Why is this user data collected?                                                | Analytics; Advertising or marketing                                                                                                                                                                                                                                         |
| Why is this user data shared?                                                   | Analytics; Advertising or marketing                                                                                                                                                                                                                                         |

</details>

### Complete the advertising ID declaration

As an Airflux user, you also need to declare your app’s use of advertising ID. To fill out the form, select your app in Google Play Console and navigate to the **\[Policy]>\[App content]** page.

The table below will help you complete the form with respect to your use of Airflux. However, it is your responsibility to respond in accordance with your specific data practices, configurations, and integrations.

<details>

<summary>Declaration form</summary>

| Question                                                                                                                                       | Airflux Data Practices              |
| ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| Does your app use advertising ID?                                                                                                              | Yes                                 |
| Why does your app need to use an advertising ID? This includes any SDKs that your app imports that use advertising IDs. Select all that apply. | Analytics; Advertising or marketing |

</details>

## Requirements for the App Store

According to [Apple’s guidelines](https://developer.apple.com/app-store/app-privacy-details/), app developers publishing on the App Store are required to disclose the app’s privacy practices by answering app privacy questions. This allows Apple to inform users of how an app, including through its use of third-party SDKs, collects and shares user data in the format of a nutrition label.

The Airflux SDK, which is highly configurable, collects user data necessary for measuring ad performance. When submitting your Airflux SDK-integrated app to the App Store for review, make sure to provide details on the data collected by your app and the custom-configured Airflux SDK.

### Submit app privacy details

To answer app privacy questions and disclose your app’s privacy practices, select your app in App Store Connect and navigate to the **\[General]>\[App Privacy]** page in the sidebar.

The tables below will help you answer app privacy questions with respect to your use of Airflux. However, it is your responsibility to respond in accordance with your specific data practices, configurations, and integrations.

<details>

<summary>Data Types</summary>

The table below lists the data types the Airflux SDK collects on your behalf by default. If your app collects any other data types, adjust your responses accordingly.

| Data Type                     | Airflux Data Collection | Notes                                                                                                                                                                                                                                        |
| ----------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Contact info**              |                         |                                                                                                                                                                                                                                              |
| Name                          | No                      |                                                                                                                                                                                                                                              |
| Email address, including hash | See notes               | Airflux does not collect contact information by default. However, if you have configured to send hashed email addresses to Airflux, select this data type.                                                                                   |
| Phone number, including hash  | See notes               | Airflux does not collect contact information by default. However, if you have configured to send hashed phone numbers to Airflux, select this data type.                                                                                     |
| Other user contact info       | No                      |                                                                                                                                                                                                                                              |
| **Health & fitness**          |                         |                                                                                                                                                                                                                                              |
| Health                        | No                      |                                                                                                                                                                                                                                              |
| Fitness                       | No                      |                                                                                                                                                                                                                                              |
| **Financial info**            |                         |                                                                                                                                                                                                                                              |
| Payment info                  | No                      |                                                                                                                                                                                                                                              |
| Credit info                   | No                      |                                                                                                                                                                                                                                              |
| Other financial info          | No                      |                                                                                                                                                                                                                                              |
| **Location**                  |                         |                                                                                                                                                                                                                                              |
| Precise location              | No                      |                                                                                                                                                                                                                                              |
| Coarse location               | No                      |                                                                                                                                                                                                                                              |
| **Sensitive info**            |                         |                                                                                                                                                                                                                                              |
| Sensitive info                | No                      |                                                                                                                                                                                                                                              |
| **Contacts**                  |                         |                                                                                                                                                                                                                                              |
| Contacts                      | No                      |                                                                                                                                                                                                                                              |
| **User content**              |                         |                                                                                                                                                                                                                                              |
| Emails or text messages       | No                      |                                                                                                                                                                                                                                              |
| Photos or videos              | No                      |                                                                                                                                                                                                                                              |
| Audio data                    | No                      |                                                                                                                                                                                                                                              |
| Gameplay content              | No                      |                                                                                                                                                                                                                                              |
| Customer support              | No                      |                                                                                                                                                                                                                                              |
| Other user content            | No                      |                                                                                                                                                                                                                                              |
| **Browsing history**          |                         |                                                                                                                                                                                                                                              |
| Browsing history              | No                      |                                                                                                                                                                                                                                              |
| **Search history**            |                         |                                                                                                                                                                                                                                              |
| Search history                | No                      |                                                                                                                                                                                                                                              |
| **Identifiers**               |                         |                                                                                                                                                                                                                                              |
| User ID                       | Optional                | If you have configured to send User IDs to Airflux, select this data type.                                                                                                                                                                   |
| Device ID                     | Optional                | Device IDs, or Advertising IDs (IDFA), are collected only when accessible.                                                                                                                                                                   |
| **Purchases**                 |                         |                                                                                                                                                                                                                                              |
| Purchase history              | Optional                | If you have configured to measure purchase events in Airflux, select this data type.                                                                                                                                                         |
| **Usage data**                |                         |                                                                                                                                                                                                                                              |
| Product interaction           | Yes                     | Airflux measures app launches by default.                                                                                                                                                                                                    |
| Advertising data              | No                      |                                                                                                                                                                                                                                              |
| Other usage data              | Optional                | If you have configured to measure any other user interactions, such as button clicks and Wi-Fi data usage, select this data type.                                                                                                            |
| **Diagnostics**               |                         |                                                                                                                                                                                                                                              |
| Crash data                    | No                      |                                                                                                                                                                                                                                              |
| Performance data              | No                      |                                                                                                                                                                                                                                              |
| Other diagnostic data         | No                      |                                                                                                                                                                                                                                              |
| **Other data**                |                         |                                                                                                                                                                                                                                              |
| Other data types              | Yes                     | Airflux collects IDFV, some device information such as the OS version, device type, and device language, and some network information such as the IP address. Airflux applies a random unique ID to each user to utilize the data collected. |

</details>

<details>

<summary>Data use purposes</summary>

For every selected data type, you need to declare how it is used. The table below provides information on the data types collected by the Airflux SDK. If your app collects any other data types, also provide the information accordingly.<br>

| Data Use Purpose                     | Answer for the Airflux SDK | Notes                                                                                                                                                                                  |
| ------------------------------------ | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Third-party advertising              | Optional                   | Select this if you share data with entities that display third-party ads in your app.                                                                                                  |
| Developer’s advertising or marketing | Optional                   | Select this if you use first-party data, share data with third parties to display first-party ads in your app, or use the collected data for other marketing and advertising purposes. |
| Analytics                            | Yes                        |                                                                                                                                                                                        |
| Product personalization              | Optional                   |                                                                                                                                                                                        |
| App functionality                    | Yes                        | Select this if you use data for any product personalization.                                                                                                                           |
| Other purposes                       | Optional                   | Select this if you use data for any purpose not otherwise listed.                                                                                                                      |

</details>

<details>

<summary>Data linking and tracking</summary>

For every selected data type, you need to declare whether it is linked to the user’s identity and whether it is used for tracking purposes. The table below provides information on the data types collected by the Airflux SDK. If your app collects any other data types, also provide the information accordingly.

| Question                                                                    | Answer for the Airflux SDK                                                   |
| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| Are the \[data type] collected from this app linked to the user’s identity? | Yes, \[data type] collected from this app are linked to the user’s identity. |
| Do you or your third-party partners use \[data type] for tracking purposes? | No, we do not use \[data type] for tracking purposes.                        |

</details>

### Attention

<details>

<summary>Entities subject to Apple's policies</summary>

It is crucial to provide all necessary details on the data collected by your app. Make sure that your integrated ad channels, as well as any of your third-party partners that require SDK integration, are complying with Apple’s policies.

</details>

<details>

<summary>Privacy protection for minors and opt-out users</summary>

You must enable the privacy protection feature of the Airflux SDK for children under age 14 or for users who have not authorized tracking via the AppTrackingTransparency framework. Their user data should never be sent to Airflux.

</details>

## Apple's Privacy Manifest

{% hint style="info" %}
It is advised that you prepare the privacy manifest in advance for your convenience in case of App Store policy changes. However, this is not legal advice. Consult with legal professionals to address any of your specific needs.
{% endhint %}

Starting May 1, 2024, Apple requires app developers [to declare approved reasons for using a set of APIs in their app’s privacy manifest](https://developer.apple.com/news/?id=3d8a9yyh) to inform the App Store of their app’s privacy requirements. By adding the [privacy manifest](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files), app developers do not have to enter privacy details manually and can more readily address App Store policy changes.

Even though it is no longer necessary to enter privacy details manually, the app has to be resubmitted for review for every Airflux iOS SDK update, as it always has been.


# SDK Reference

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Unity SDK Reference</td><td><a href="/files/EiJJrt8k3roBq9bBBf6y">/files/EiJJrt8k3roBq9bBBf6y</a></td><td><a href="https://reference.airflux.ai/airflux-unity-sdk/latest">https://reference.airflux.ai/airflux-unity-sdk/latest</a></td></tr><tr><td>Android SDK Reference</td><td><a href="/files/EiJJrt8k3roBq9bBBf6y">/files/EiJJrt8k3roBq9bBBf6y</a></td><td><a href="https://reference.airflux.ai/airflux-android-sdk/latest/">https://reference.airflux.ai/airflux-android-sdk/latest/</a></td></tr><tr><td>iOS SDK Reference</td><td><a href="/files/EiJJrt8k3roBq9bBBf6y">/files/EiJJrt8k3roBq9bBBf6y</a></td><td><a href="https://reference.airflux.ai/airflux-ios-sdk/latest/">https://reference.airflux.ai/airflux-ios-sdk/latest/</a></td></tr></tbody></table>


# SDK Release Note

##

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Unity SDK Release Note</td><td><a href="/files/EiJJrt8k3roBq9bBBf6y">/files/EiJJrt8k3roBq9bBBf6y</a></td><td><a href="/pages/YSVWlUbEFx5MPthyXWLI">/pages/YSVWlUbEFx5MPthyXWLI</a></td></tr><tr><td>Android SDK Release Note</td><td><a href="/files/EiJJrt8k3roBq9bBBf6y">/files/EiJJrt8k3roBq9bBBf6y</a></td><td><a href="/pages/QcsYYjyzFWaRKssLRIMy">/pages/QcsYYjyzFWaRKssLRIMy</a></td></tr><tr><td>iOS SDK Release Note</td><td><a href="/files/EiJJrt8k3roBq9bBBf6y">/files/EiJJrt8k3roBq9bBBf6y</a></td><td><a href="/pages/gOqutDCAygnOfXJ9rrTy">/pages/gOqutDCAygnOfXJ9rrTy</a></td></tr></tbody></table>


# Unity SDK Release Note

## 2026

### v1.0.2 - Mar 11, 2026

**FIXED**

* Fixed an issue where the sessionUUID changed after installation.

## 2025

### v1.0.1 - Dec 22, 2025

**FIXED**

* Enhanced event collection stability

**CHANGED**

* Update Airflux Android SDK to 1.0.2

### v1.0.0

**CHANGED**

* Redesigned Airflux SDK to utilize native SDKs for improved performance and stability
* Dependencies: Airflux Android SDK 1.0.1, Airflux iOS SDK 1.0.1

### v0.3.3 - Sep 26, 2025

**FIXED**

* Fixed an issue with JSON parsing.

### v0.3.2 - Jul 15, 2025

**FIXED**

* Fixed crash issue that occurred during the inference process in the Unity iOS Development Build environment.

### v0.3.1 - Jul 11, 2025

**ADDED**

* Supports Android 16 KB page size.

### v0.3.0 - Jul 2, 2025

**CHANGED**

* Changed Country allowlist logic has been improved.

### v0.2.0 - Jun 9, 2025

**ADDED**

* Add Country Allow All feature for improved usability across all regions.

### v0.1.0 - May 28, 2025

**ADDED**

* Initial release of Airflux Unity SDK.
* Integration with Airbridge Unity SDK v4.4.0.


# Android SDK Release Note

## 2025

### v1.0.2 - Dec 22, 2025

**Fixed**

* Enhanced event collection stability

### v1.0.1 - Oct 29, 2025

**Fixed**

* Update event payload structure for multi-platform support

### v1.0.0 - Oct 13, 2025

**ADDED**

* Initial release of Airflux SDK


# iOS SDK Release Note

## 2026

### v1.0.2 - Feb 6, 2026

**Fixed**

* Fixed an issue where the sessionUUID changed after installation.

## 2025

### v1.0.1 - Oct 29, 2025

**ADDED**

* Initial release of Airflux SDK


# Country Tier Information

The cost per Inference API call varies depending on the country information of the traffic you want to apply Airflux. See the country tier information below.

<details>

<summary>Tier 1</summary>

| Country                   |
| ------------------------- |
| Australia (AU)            |
| Austria (AT)              |
| Belgium (BE)              |
| Canada (CA)               |
| Switzerland (CH)          |
| Germany (DE)              |
| Denmark (DK)              |
| Spain (ES)                |
| Finland (FI)              |
| France (FR)               |
| Ireland (IE)              |
| Israel (IL)               |
| Italy (IT)                |
| Japan (JP)                |
| Korea, Republic of (KR)   |
| Netherlands (NL)          |
| Norway (NO)               |
| New Zealand (NZ)          |
| Poland (PL)               |
| Portugal (PT)             |
| Saudi Arabia (SA)         |
| Singapore (SG)            |
| Sweden (SE)               |
| United Arab Emirates (AE) |
| United Kingdom (GB)       |
| United States (US)        |

</details>

<details>

<summary>Tier 2</summary>

| Country           |
| ----------------- |
| Argentina (AR)    |
| Bulgaria (BG)     |
| Brazil (BR)       |
| Chile (CL)        |
| Colombia (CO)     |
| Egypt (EG)        |
| Estonia (EE)      |
| Greece (GR)       |
| Hong Kong (HK)    |
| Croatia (HR)      |
| Hungary (HU)      |
| Indonesia (ID)    |
| Kazakhstan (KZ)   |
| Lithuania (LT)    |
| Latvia (LV)       |
| Morocco (MA)      |
| Mexico (MX)       |
| Malaysia (MY)     |
| Philippines (PH)  |
| Qatar (QA)        |
| Romania (RO)      |
| Slovakia (SK)     |
| Slovenia (SI)     |
| Thailand (TH)     |
| Turkey (TR)       |
| Ukraine (UA)      |
| South Africa (ZA) |

</details>

<details>

<summary>Tier 3</summary>

| Country                                           |
| ------------------------------------------------- |
| Aruba (AW)                                        |
| Afghanistan (AF)                                  |
| Angola (AO)                                       |
| Anguilla (AI)                                     |
| Åland Islands (AX)                                |
| Albania (AL)                                      |
| Andorra (AD)                                      |
| Armenia (AM)                                      |
| American Samoa (AS)                               |
| Antarctica (AQ)                                   |
| French Southern Territories (TF)                  |
| Antigua and Barbuda (AG)                          |
| Azerbaijan (AZ)                                   |
| Burundi (BI)                                      |
| Benin (BJ)                                        |
| Bonaire, Sint Eustatius and Saba (BQ)             |
| Burkina Faso (BF)                                 |
| Bangladesh (BD)                                   |
| Bahrain (BH)                                      |
| Bahamas (BS)                                      |
| Bosnia and Herzegovina (BA)                       |
| Saint Barthélemy (BL)                             |
| Belarus (BY)                                      |
| Belize (BZ)                                       |
| Bermuda (BM)                                      |
| Bolivia, Plurinational State of (BO)              |
| Barbados (BB)                                     |
| Brunei Darussalam (BN)                            |
| Bhutan (BT)                                       |
| Bouvet Island (BV)                                |
| Botswana (BW)                                     |
| Central African Republic (CF)                     |
| Cocos (Keeling) Islands (CC)                      |
| China (CN)                                        |
| Côte d'Ivoire (CI)                                |
| Cameroon (CM)                                     |
| Congo, The Democratic Republic of the (CD)        |
| Congo (CG)                                        |
| Cook Islands (CK)                                 |
| Comoros (KM)                                      |
| Cabo Verde (CV)                                   |
| Costa Rica (CR)                                   |
| Cuba (CU)                                         |
| Curaçao (CW)                                      |
| Christmas Island (CX)                             |
| Cayman Islands (KY)                               |
| Cyprus (CY)                                       |
| Czechia (CZ)                                      |
| Djibouti (DJ)                                     |
| Dominica (DM)                                     |
| Dominican Republic (DO)                           |
| Algeria (DZ)                                      |
| Ecuador (EC)                                      |
| Eritrea (ER)                                      |
| Western Sahara (EH)                               |
| Ethiopia (ET)                                     |
| Fiji (FJ)                                         |
| Falkland Islands (FK)                             |
| Faroe Islands (FO)                                |
| Micronesia, Federated States of (FM)              |
| Gabon (GA)                                        |
| Georgia (GE)                                      |
| Guernsey (GG)                                     |
| Ghana (GH)                                        |
| Gibraltar (GI)                                    |
| Guinea (GN)                                       |
| Guadeloupe (GP)                                   |
| Gambia (GM)                                       |
| Guinea-Bissau (GW)                                |
| Equatorial Guinea (GQ)                            |
| Grenada (GD)                                      |
| Greenland (GL)                                    |
| Guatemala (GT)                                    |
| French Guiana (GF)                                |
| Guam (GU)                                         |
| Guyana (GY)                                       |
| Heard Island and McDonald Islands (HM)            |
| Honduras (HN)                                     |
| Haiti (HT)                                        |
| Isle of Man (IM)                                  |
| India (IN)                                        |
| British Indian Ocean Territory (IO)               |
| Iran, Islamic Republic of (IR)                    |
| Iraq (IQ)                                         |
| Iceland (IS)                                      |
| Jamaica (JM)                                      |
| Jersey (JE)                                       |
| Jordan (JO)                                       |
| Kenya (KE)                                        |
| Kyrgyzstan (KG)                                   |
| Cambodia (KH)                                     |
| Kiribati (KI)                                     |
| Saint Kitts and Nevis (KN)                        |
| Kuwait (KW)                                       |
| Lao People's Democratic Republic (LA)             |
| Lebanon (LB)                                      |
| Liberia (LR)                                      |
| Libya (LY)                                        |
| Saint Lucia (LC)                                  |
| Liechtenstein (LI)                                |
| Sri Lanka (LK)                                    |
| Lesotho (LS)                                      |
| Luxembourg (LU)                                   |
| Macao (MO)                                        |
| Saint Martin - French part (MF)                   |
| Monaco (MC)                                       |
| Moldova, Republic of (MD)                         |
| Madagascar (MG)                                   |
| Maldives (MV)                                     |
| Marshall Islands (MH)                             |
| North Macedonia (MK)                              |
| Mali (ML)                                         |
| Malta (MT)                                        |
| Myanmar (MM)                                      |
| Montenegro (ME)                                   |
| Mongolia (MN)                                     |
| Northern Mariana Islands (MP)                     |
| Mozambique (MZ)                                   |
| Mauritania (MR)                                   |
| Montserrat (MS)                                   |
| Martinique (MQ)                                   |
| Mauritius (MU)                                    |
| Malawi (MW)                                       |
| Mayotte (YT)                                      |
| Namibia (NA)                                      |
| New Caledonia (NC)                                |
| Niger (NE)                                        |
| Norfolk Island (NF)                               |
| Nigeria (NG)                                      |
| Nicaragua (NI)                                    |
| Niue (NU)                                         |
| Nepal (NP)                                        |
| Nauru (NR)                                        |
| Oman (OM)                                         |
| Pakistan (PK)                                     |
| Panama (PA)                                       |
| Pitcairn (PN)                                     |
| Peru (PE)                                         |
| Palau (PW)                                        |
| Papua New Guinea (PG)                             |
| Puerto Rico (PR)                                  |
| Korea, Democratic People's Republic of (KP)       |
| Paraguay (PY)                                     |
| Palestine, State of (PS)                          |
| French Polynesia (PF)                             |
| Réunion (RE)                                      |
| Russian Federation (RU)                           |
| Rwanda (RW)                                       |
| Sudan (SD)                                        |
| Senegal (SN)                                      |
| South Georgia and the South Sandwich Islands (GS) |
| Saint Helena, Ascension and Tristan da Cunha (SH) |
| Svalbard and Jan Mayen (SJ)                       |
| Solomon Islands (SB)                              |
| Sierra Leone (SL)                                 |
| El Salvador (SV)                                  |
| San Marino (SM)                                   |
| Somalia (SO)                                      |
| Saint Pierre and Miquelon (PM)                    |
| Serbia (RS)                                       |
| South Sudan (SS)                                  |
| Sao Tome and Principe (ST)                        |
| Suriname (SR)                                     |
| Eswatini (SZ)                                     |
| Sint Maarten - Dutch part (SX)                    |
| Seychelles (SC)                                   |
| Syrian Arab Republic (SY)                         |
| Turks and Caicos Islands (TC)                     |
| Chad (TD)                                         |
| Togo (TG)                                         |
| Tajikistan (TJ)                                   |
| Tokelau (TK)                                      |
| Turkmenistan (TM)                                 |
| Timor-Leste (TL)                                  |
| Tonga (TO)                                        |
| Trinidad and Tobago (TT)                          |
| Tunisia (TN)                                      |
| Tuvalu (TV)                                       |
| Taiwan, Province of China (TW)                    |
| Tanzania, United Republic of (TZ)                 |
| Uganda (UG)                                       |
| United States Minor Outlying Islands (UM)         |
| Uruguay (UY)                                      |
| Uzbekistan (UZ)                                   |
| Holy See - Vatican City State (VA)                |
| Saint Vincent and the Grenadines (VC)             |
| Venezuela, Bolivarian Republic of (VE)            |
| Virgin Islands, British (VG)                      |
| Virgin Islands, U.S. (VI)                         |
| Viet Nam (VN)                                     |
| Vanuatu (VU)                                      |
| Wallis and Futuna (WF)                            |
| Samoa (WS)                                        |
| Yemen (YE)                                        |
| Zambia (ZM)                                       |
| Zimbabwe (ZW)                                     |

</details>


# Airflux Integration (Unity v0.x)

{% hint style="warning" %}
Deprecated: This section documents a legacy version (v0.x). New integrations should use the [Airflux Integration (Unity)](/airflux-onboarding/airflux-integration-unity) guide.
{% endhint %}

This section provides an overview of the Airflux integration process and the expected time from integration to first results.&#x20;

***

## About **the integration**

To fully integrate your game with Airflux, a Growth Manager will work with you to coordinate the timeline from SDK install through to app deployment.

### Expected time from integration to first results

* SDK installation: A quick start could take just 30 minutes, but it may require 3 to 7 days, depending on the number of in-game events you plan to include for machine learning.&#x20;
* Reporting: The initial results are released about 4 weeks after installing the Airflux SDK on your game app and deploying it. Typically, the first 2 weeks are necessary for data collection and processing before model training, which will take approximately 2 weeks to produce meaningful results.

### Implementation steps

The integration process consists of the following steps.

1. [Add your app to the dashboard](/airflux-integration-unity-v0.x/1.-add-your-app-to-the-dashboard) (approx. 2-5 min.)
2. [Install the Airflux SDK](/airflux-integration-unity-v0.x/2.-install-the-airflux-sdk) (approx. 5 min.)
3. [Send in-game data](/airflux-integration-unity-v0.x/3.-send-in-game-event-data) (approx. 30-60 min.)
4. [Call the Inference API](/airflux-integration-unity-v0.x/4.-call-the-inference-api) (approx. 15-60 min.)

## Frequently Asked Questions

<details>

<summary>Is it necessary to install the SDK?</summary>

Yes, installing the SDK is necessary to collect in-game events and player attribute data that are crucial for training and using Airflux AI.

</details>


# Unity v0.x → v1.0 Migration Guide

Airflux Unity SDK v1.0 introduces a redesigned API surface and updated data taxonomy for more accurate event/context aggregation and inference decisions. This guide explains how to migrate an existing Unity v0.x (including v0.1) integration to v1.0 safely.

{% hint style="warning" %}
Deprecated: Unity v0.x is a legacy integration. New integrations should use the [Airflux Integration (Unity)](/airflux-onboarding/airflux-integration-unity) guide.
{% endhint %}

***

#### Important notes before you migrate

> **Attention: Breaking change**\
> Airflux Unity SDK v1.0 is **not backward-compatible** with v0.x.
>
> * You **must** update your code to the new APIs and taxonomy.
> * You **must not** keep v0.x and v1.0 installed in the same Unity project.

> **Attention: Data reset**\
> When upgrading to v1.0, existing on-device Airflux data (stored context, counters, and any pending SDK data) should be treated as **reset**.\
> Plan your QA and roll-out assuming a clean slate for v1.0 devices.

#### Migration checklist

* [ ] Remove Airflux Unity SDK v0.x from the project
* [ ] Import Airflux Unity SDK v1.0 and reconfigure **Airflux Settings**
* [ ] Remove legacy **session tracking** calls (`NotifyAppForeground/NotifyAppBackground`)
* [ ] Replace v0.x APIs with v1.0 APIs (`SetUser`, `SetContext`, `RequestInference`)
* [ ] Update event taxonomy (especially **stage** and **ad impression** tracking)
* [ ] Verify logs, event payloads, and inference callback handling
* [ ] Run QA on Android & iOS builds and deploy

***

### 1. Update the SDK package

#### 1.1 Remove Airflux Unity SDK v0.x

1. In your Unity project, **delete the existing Airflux v0.x SDK files** (for example, the old `Assets/Airflux` folder and any related plugin files).
2. Confirm there is **no remaining v0.x assembly** or duplicated Airflux code in:
   * `Assets/Plugins/Android`
   * `Assets/Plugins/iOS`
   * `Assets/` (any legacy Airflux folders)

> **Attention**\
> Importing v1.0 without fully removing v0.x can cause compilation conflicts and duplicated classes.

#### 1.2 Install Airflux Unity SDK v1.0

1. Download the latest Airflux Unity SDK v1.0 `.unitypackage`. ([link](/airflux-onboarding/airflux-integration-unity/2.-install-the-airflux-sdk))
2. Import it via **Assets → Import Package → Custom Package…**
3. After import, open **Airflux → Airflux Settings** and configure:
   * **App Name** (required)
   * **App Token** (required)
   * **SDK Enabled / Auto Start Tracking Enabled** (optional, depending on consent flow)
   * **Allow Every Country Enabled / Country Allowlist** (optional but strongly recommended to confirm)
   * **Log Level** (recommend `debug` during QA)
   * **Session Timeout** (default 300 seconds)

These options are available in the v1.0 initialization settings.

***

### 2. Remove legacy session tracking (v0.x → v1.0)

In Unity v0.x, you typically implemented session tracking manually by calling `NotifyAppForeground()` / `NotifyAppBackground()` on app lifecycle callbacks.

In v1.0, the SDK automatically tracks app lifecycle events, so you **do not need** this manual wiring.

#### What to change

**Before (v0.x)**

```csharp
using UnityEngine;

public class AirfluxBehaviour : MonoBehaviour
{
    void OnApplicationPause(bool pauseStatus)
    {
        if (pauseStatus)
        {
            Airflux.NotifyAppBackground();
        }
        else
        {
            Airflux.NotifyAppForeground();
        }
    }
}
```

**After (v1.0)**\
Remove the `NotifyAppForeground/NotifyAppBackground` calls entirely.

```csharp
using UnityEngine;

public class AirfluxBehaviour : MonoBehaviour
{
    void OnApplicationPause(bool pauseStatus)
    {
        // No manual Airflux session tracking required in v1.0
    }
}
```

***

### 3. Migrate player attribute APIs (User & Context)

v1.0 consolidates “player attribute data” into:

* **User**: `Airflux.SetUser(...)`
* **Context**: `Airflux.SetContext(...)`

(These values are transmitted when you track an event or request an inference—set them **before** calling inference or tracking key events.)

#### 3.1 API mapping (v0.x → v1.0)

| What you were doing                       | v0.x API                            | v1.0 API                                                  |
| ----------------------------------------- | ----------------------------------- | --------------------------------------------------------- |
| Set User ID                               | `SetUserID(id)`                     | `SetUser(AirfluxUser.ID, id)`                             |
| Clear User ID                             | `ClearUserID()`                     | `ClearUser(AirfluxUser.ID)`                               |
| Set Level                                 | `SetLevel(level)`                   | `SetContext(AirfluxContext.LEVEL, level)`                 |
| Set Hard Currency                         | `SetHardCurrency(name, balance)`    | `SetContext(AirfluxContext.HARD_CURRENCY, name, balance)` |
| Set Soft Currency                         | `SetSoftCurrency(name, balance)`    | `SetContext(AirfluxContext.SOFT_CURRENCY, name, balance)` |
| Set “inference attributes” / custom state | `SetInferenceAttribute(key, value)` | `SetContext(AirfluxContext.ATTRIBUTE, key, value)`        |

> **Attention**\
> In v1.0, “inference attributes” are modeled as a **context category** (`AirfluxContext.ATTRIBUTE`).\
> If you used `SetInferenceAttribute(...)` in v0.x for things like `battlePass`, migrate them to `SetContext(AirfluxContext.ATTRIBUTE, ...)`.

#### 3.2 Example: User ID

**Before (v0.x)**

```csharp
Airflux.SetUserID("your_user_id");
```

**After (v1.0)**

```csharp
Airflux.SetUser(AirfluxUser.ID, "your_user_id");
```

#### 3.3 Example: Level & Currency

**After (v1.0)**

```csharp
// Level
Airflux.SetContext(AirfluxContext.LEVEL, 10);

// Currency
Airflux.SetContext(AirfluxContext.HARD_CURRENCY, "gem", 500);
Airflux.SetContext(AirfluxContext.SOFT_CURRENCY, "gold", 54000);

// Custom attributes
Airflux.SetContext(AirfluxContext.ATTRIBUTE, "battlePass", "premium");
```

***

### 4. Update event tracking (taxonomy changes)

The `TrackEvent(category, semanticAttributes, customAttributes)` signature remains conceptually the same, but **what you put into semantic vs custom attributes—and which events you should send—has changed in v1.0**.

Below are the most important migration points.

{% hint style="warning" %}
Important: It is essential to consult the [Airflux Integration (Unity](/airflux-onboarding/airflux-integration-unity/3.-send-in-game-event-data)) section regarding all required fields.
{% endhint %}

#### 4.1 Ad Impression: move `adType` and key fields into semantic attributes

In v0.x, `adType` was sent as a **custom attribute** (e.g., `customAttributes.adType`).

In v1.0, `AD_TYPE`, `AD_PLACEMENT_*`, `STAGE_*`, etc. are tracked as **semantic attributes** using `AirfluxAttribute.*`.

**After (v1.0) example**

```csharp
Airflux.TrackEvent(
    category: AirfluxCategory.AD_IMPRESSION,
    semanticAttributes: new Dictionary<string, object>
    {
        { AirfluxAttribute.VALUE, 0.025 },
        { AirfluxAttribute.CURRENCY, "USD" },

        // moved into semantic attributes in v1.0
        { AirfluxAttribute.AD_TYPE, "rewarded_ad" },              // or "interstitial_ad"
        { AirfluxAttribute.AD_PLACEMENT_ID, "stage_clear_reward" },

        // collect if available
        { AirfluxAttribute.STAGE_TYPE, "primary_stage" },
        { AirfluxAttribute.STAGE, 15 },
    }
);
```

> **Attention: Allowed values changed**\
> v1.0 requires pre-defined strings for many fields (e.g., `adType`, `stageType`, `stageResult`). Make sure your old values are mapped to the v1.0 allowed set.

#### 4.2 Stage tracking: replace “stage end via ACHIEVE\_LEVEL” with START\_STAGE / FINISH\_STAGE

In v0.x, some integrations used `ACHIEVE_LEVEL` to represent stage completion (with stage info and result).\
In v1.0, stage progression is tracked explicitly with:

* `START_STAGE`
* `FINISH_STAGE`

And `ACHIEVE_LEVEL` is used for actual level progression (if your game has a level system).

**After (v1.0) examples**

```csharp
// Stage start
Airflux.TrackEvent(
    category: AirfluxCategory.START_STAGE,
    semanticAttributes: new Dictionary<string, object>
    {
        { AirfluxAttribute.STAGE_TYPE, "primary_stage" },
        { AirfluxAttribute.STAGE, 10 }
    }
);

// Stage finish
Airflux.TrackEvent(
    category: AirfluxCategory.FINISH_STAGE,
    semanticAttributes: new Dictionary<string, object>
    {
        { AirfluxAttribute.STAGE_TYPE, "primary_stage" },
        { AirfluxAttribute.STAGE, 10 },
        { AirfluxAttribute.TOTAL_FINISHED_STAGE, 10 },
        { AirfluxAttribute.STAGE_RESULT, "success" } // success/fail/giveup/retry/draw/exhausted
    }
);
```

#### 4.3 Spend Credits: use `CREDIT_*` fields

v1.0 standardizes currency spending into `SPEND_CREDITS` with semantic attributes:

```csharp
Airflux.TrackEvent(
    category: AirfluxCategory.SPEND_CREDITS,
    semanticAttributes: new Dictionary<string, object>
    {
        { AirfluxAttribute.CREDIT_CLASS, "hard" },    // "hard" or "soft"
        { AirfluxAttribute.CREDIT_TYPE, "coin" },
        { AirfluxAttribute.CREDIT_SPENT, 10 },
        { AirfluxAttribute.CREDIT_CURRENT, 990 },

        // stage info if available
        { AirfluxAttribute.STAGE_TYPE, "primary_stage" },
        { AirfluxAttribute.STAGE, 10 }
    }
);
```

#### 4.4 Order Completed: send product list and purchase route

v1.0 expects `PRODUCTS` as a list, along with `TRANSACTION_ID`, `VALUE`, `CURRENCY`, and `PURCHASE_ROUTE`.

```csharp
Airflux.TrackEvent(
    category: AirfluxCategory.ORDER_COMPLETED,
    semanticAttributes: new Dictionary<string, object>
    {
        { AirfluxAttribute.TRANSACTION_ID, "TXN-20250411-0001" },
        { AirfluxAttribute.VALUE, 4.99 },
        { AirfluxAttribute.CURRENCY, "USD" },
        {
            AirfluxAttribute.PRODUCTS, new List<object>
            {
                new Dictionary<string, object>
                {
                    { AirfluxAttribute.PRODUCT_ID, "welcome_pack" },
                    { AirfluxAttribute.PRODUCT_NAME, "Welcome Pack" }
                }
            }
        },
        { AirfluxAttribute.PURCHASE_ROUTE, "shop" } // shop/popup/other
    }
);
```

***

### 5. Migrate the Inference API call (Interstitial decision)

#### 5.1 What changed

**v0.x**

* You set placement context via `setInferenceAttributes()`
* You called `InferenceShowAdInterstitial(onShowAd, onSkipAd, onFailure)`

**v1.0**

* You call `Airflux.RequestInference(...)` with `AirfluxInference.SHOW_INTERSTITIAL_AD(...)`
* You pass inference-time parameters via `AirfluxParameter.*`
* You must implement **four** callbacks:
  * `onShowAd`
  * `onSkipAd`
  * `onDefaultAdPolicy`
  * `onFailure`

> **Important**\
> v1.0 requires you to include **all required inference parameters**, and include nullable parameters whenever available.

#### 5.2 v1.0 example (recommended pattern)

```csharp
public void TryShowInterstitial()
{
    var parameters = new Dictionary<string, object>
    {
        // Required
        { AirfluxParameter.AD_TYPE, "interstitial_ad" },
        { AirfluxParameter.AD_PLACEMENT_ID, "end_of_stage_ad" },
        { AirfluxParameter.AD_PLACEMENT_TYPE, "static" },

        // Nullable (collect if available)
        { AirfluxParameter.AD_PLACEMENT_POSITION, "stage_end" },
        { AirfluxParameter.STAGE_TYPE, "primary_stage" },
        { AirfluxParameter.STAGE, 15 },
        { AirfluxParameter.STAGE_RESULT, "success" },
        { AirfluxParameter.TOTAL_FINISHED_STAGE, 42 },
        { AirfluxParameter.LEVEL, 10 }
    };

    Airflux.RequestInference(
        AirfluxInference.SHOW_INTERSTITIAL_AD(
            parameters: parameters,
            onShowAd: () =>
            {
                // AI decided to show the interstitial ad.
                ShowInterstitial();
            },
            onSkipAd: () =>
            {
                // AI decided to skip the ad.
                ContinueGameplay();
            },
            onDefaultAdPolicy: () =>
            {
                // AI requests your internal policy decision.
                ApplyMyDefaultAdPolicy();
            },
            onFailure: (error) =>
            {
                // Inference request failed (country allowlist, timeout, 4xx/5xx, etc.).
                // Apply your fallback policy. Retry is not required.
                ApplyMyDefaultAdPolicy();
            }
        )
    );
}
```

#### 5.3 Testing inference responses (QA only)

**v0.x** simulated responses via `contexts.inferenceAttributes.forceInferenceResponse`.

**v1.0** uses `AirfluxParameter.FORCE_RESPONSE` with one of:

* `"showAd"`
* `"skipAd"`
* `"defaultAdPolicy"`
* `"failure"`

Example:

```csharp
parameters[AirfluxParameter.FORCE_RESPONSE] = new Dictionary<string, object>
{
    { "action", "showAd" },
    { "parameters", new Dictionary<string, object>() }
};
```

> **Attention**\
> Force response parameters must be removed before production builds.

***

### 6. Verify your migration

#### 6.1 Check logs in Unity

* Set **Log Level = debug** during QA to see detailed Airflux logs.

#### 6.2 Verify event & inference payloads

At minimum, verify:

1. Required events are being tracked (AD\_IMPRESSION, START\_STAGE/FINISH\_STAGE, ORDER\_COMPLETED if applicable, etc.).
2. Inference requests include required parameters and trigger **exactly one** callback per request.

***

### Frequently Asked Questions

<details>

<summary>Do I still need ADID (IDFA/GAID) for Airflux?</summary>

* In Unity v0.x, Airflux required ADID collection.
* In Unity v1.0, Airflux does **not** collect ADID.

</details>

<details>

<summary>What’s the difference between onSkipAd() and onFailure()?</summary>

* `onSkipAd()` means the inference request succeeded and the model decided to skip the ad.
* `onFailure()` means the inference request failed (e.g., allowlist not matched, timeout, network/server error) and you should apply your fallback policy.

</details>


# 1. Add your app to the dashboard

Before installing the Airflux SDK in your gaming app, you need to add your app to the Airbridge dashboard. This step will take approximately 2-5 minutes.

***

## 1. Create an account and add your app to Airbridge

{% hint style="info" %}
**Why is this step necessary?**

Airflux is built on the Airbridge platform. The App Name used to add your gaming app to Airbridge and the App SDK Token assigned by Airbridge are required for initializing the Airflux SDK.
{% endhint %}

1. Visit the [Airbridge website](https://app.airbridge.io/app), click **Dashboard**, and create an account.&#x20;
2. Select the **Growth Plan** on the Airbridge plan intro page.
3. Set the Organization name.
4. Choose **Production mode** as the app mode and add your app per platform.

{% hint style="danger" %}
**Attention**

Although apps can be added in development mode for testing purposes, Airflux only runs on apps in production mode
{% endhint %}

5. Set the App Name. The App Name must be unique and cannot be changed once the app is added to Airbridge.&#x20;
6. Set the time zone and standard currency. Choose carefully, as they cannot be changed once the app is added to Airbridge.

* Time Zone: The time zone you choose is the baseline for the time of the event occurrence.
* Standard Currency: The currency you choose is the currency used for reporting.

7. Click **Submit**.

If you have finished adding an app and want to add additional apps, refer to this Airbridge [article](https://help.airbridge.io/en/guides/register-a-new-app). &#x20;

## 2. **Find the information required for SDK setup**

Once your gaming app is added to Airbridge, select **\[Settings]>\[Tokens]** from the sidebar and find the following information. Make sure to record them somewhere safe, as the information is required to initialize the SDK.

* App Name
* App SDK Token

## Frequently Asked Questions

<details>

<summary>My app is not live yet. Can I still add it to the dashboard?</summary>

If your app is not live yet, choose **Production mode** as the app mode, skip adding the app per platform, and move on to the next steps. Once your app goes live, go to the Airbridge dashboard, select **\[Settings]>\[App Settings]**, and add the app by using search or entering the app store URL.

</details>

<details>

<summary>Can I change the app mode after the app is added to Airbridge?</summary>

No, the app mode cannot be changed later. If you want to change the app mode, you need to add the app as a new app and choose a different app mode.&#x20;

</details>


# 2. Install the Airflux SDK

By installing the Airflux SDK in your app, you can collect in-game data required for Airflux's model training. The installation and setup will take approximately 5 minutes.

***

## 1. Import the Airflux package

Follow the steps below to add the Airbridge SDK package file to your project.

{% tabs %}
{% tab title="Unity" %}

1. Download the latest version of the[ Airflux Unity SDK package file](https://sdk-download.airflux.ai/unity/airflux-unity-0.3.3.unitypackage).
2. Import the package file by selecting the menu option **\[Assets]>\[Import Package]>\[Custom Package]**.
3. When the import is complete, the **\[Airflux]** tab will appear in the top menu bar of the Unity Editor.
   {% endtab %}
   {% endtabs %}

## 2. Set up SDK initialization

The SDK initializes automatically every time your app is opened.&#x20;

{% hint style="info" %}
**Opt-in policy compliance** &#x20;

If player consent is required to send in-game data, implement the necessary setup by following the FAQ section below.&#x20;

[#how-can-i-set-up-the-airflux-sdk-to-comply-with-the-opt-in-policy](#how-can-i-set-up-the-airflux-sdk-to-comply-with-the-opt-in-policy "mention")
{% endhint %}

{% tabs %}
{% tab title="Unity" %}
Select **\[Airflux]>\[Airflux Settings]** from the top menu bar in the Unity Editor and configure the keys as listed below.

{% hint style="danger" %}
**Attention: Check country allowlist settings when updating SDK**

When updating the Airflux SDK from version 0.1.0 to 0.2.0, the `Country Allow All` option is set to `true` by default. As a result, after the update, all countries regardless of your existing allowlist settings are allowed to follow Airflux's optimization policies.&#x20;

If you wish to restrict countries by sticking to your existing allowlist settings, be sure to explicitly set `Country Allow All` to `false` after the update.
{% endhint %}

<table><thead><tr><th width="114.9658203125">Key</th><th>Required / Optional</th><th width="95.533203125">Data Type</th><th width="92.53125">Default</th><th>Description</th></tr></thead><tbody><tr><td>App Name</td><td>Required</td><td>string</td><td>-</td><td>Input the App Name from the Airbridge dashboard.</td></tr><tr><td>App Token</td><td>Required</td><td>string</td><td>-</td><td>Input the SDK Token from the Airbridge dashboard.</td></tr><tr><td>Log Level</td><td>Optional </td><td>string</td><td>warning</td><td>Set the log level for the Airflux SDK. Choose from <code>debug</code>, <code>info</code>, <code>warning</code>, <code>error</code>, <code>fault</code> .</td></tr><tr><td>Session Timeout Seconds</td><td>Optional</td><td>double</td><td>300</td><td>The default value is 300 seconds. Modify if needed.</td></tr><tr><td>Country Allow All</td><td>Required</td><td>boolean</td><td>true</td><td>When set to <code>true</code>, all countries are allowed to follow Airflux's optimization policies. </td></tr><tr><td>Country Allowlist</td><td>Required</td><td>array</td><td>[ ]</td><td>Set the countries where calling the Airflux's inference API should be allowed. Use country codes following the ISO 3166-1 alpha-2 format (e.g., US, KR). You can set multiple countries using the Country Allowlist array. </td></tr><tr><td>Auto Start Tracking Enabled</td><td>Optional</td><td>boolean</td><td>false</td><td>Set whether to collect events automatically upon SDK initialization.<br>- <code>true</code>:  Event collection starts automatically upon initialization.<br>- <code>false</code>: Event collection starts upon calling the  <code>Airflux.StartTracking()</code> function.</td></tr><tr><td>SDK Enabled</td><td>Optional</td><td>boolean</td><td>false</td><td>Set whether to enable the SDK upon initialization. <br>-  <code>true</code>:  The SDK is initialized in active mode.<br>- <code>false</code>: The SDK is initialized in inactive mode and is enabled upon calling the<code>Airflux.EnableSDK()</code>function.</td></tr></tbody></table>
{% endtab %}
{% endtabs %}

## Frequently Asked Questions

<details>

<summary>How can I set up the Airflux SDK to comply with the opt-in policy?</summary>

The opt-in policy requires user consent before collecting and using player data. To adhere to this policy, implement the following methods.&#x20;

1. **SDK opt-in setup**

Upon initialization of the Airflux SDK, set `Auto Start Tracking Enabled` to `false` and call the `StartTracking()` function at the point where you received user consent for data tracking. The Airflux SDK will collect data after the `StartTracking()` function is called.&#x20;

{% hint style="danger" %}
**Attention**

Although event data is not tracked before `StartTracking()` is triggered and after `StopTracking()` is triggered, player attribute data is aggregated and anonymized for transmission upon calling the Inference API.
{% endhint %}

```csharp
// After player provided consent to data tracking
Airflux.StartTracking();

//If player withdraws consent to data tracking
Airflux.StopTracking();
```

2. **Initializing the Airflux SDK in inactive mode**

{% hint style="danger" %}
**Attention**

If the SDK is not enabled immediately after the SDK initialization, the Install and Open events may not be collected.
{% endhint %}

Set `SDK Enabled` to `false` to initialize the SDK with all functions disabled until user consent for data tracking is obtained. Through this method, you can adhere to privacy policies to the highest level. Note that when the SDK is set in inactive mode, all features are disabled and no events and player attribute data is sent to Airflux.&#x20;

```csharp
// Checks whether the SDK is currently enabled.
Airbridge.IsSDKEnabled()
// Enables the SDK.
Airbridge.EnableSDK()
// Disables the SDK.
Airbridge.DisableSDK()
```

</details>

<details>

<summary>How can I configure the Airflux SDK to call the Inference API only in specific countries?</summary>

Set the "Country Allow All" to `false` and add the countries you want to allow to the "Country Allowlist". Country codes should follow the ISO 3166-1 alpha-2 format (e.g., "US", "KR"), and multiple countries can be specified by separating codes with commas.

</details>

<details>

<summary>Can I use other mediation platforms, such as MAX and AdMob, with Airflux? </summary>

Yes, Airflux is designed to work alongside existing mediation platforms.&#x20;

</details>

<details>

<summary>Is IL2CPP supported? </summary>

Yes, Airflux supports IL2CPP.

</details>

<details>

<summary>Can I use Airflux without ADID collection? </summary>

Currently, Airflux cannot be used in environments where ADID collection is not allowed.\
For further discussion or support regarding the use of Airflux in environments with ADID collection restrictions, such as children's apps, contact us at <sales@airflux.ai>.

</details>

## Troubleshooting

<details>

<summary>[Android] A coroutine dependency error occurs during the build process. </summary>

#### Issue

A coroutine dependency error occurs during the build process with the following message.

```
java.lang.NoClassDefFoundError: kotlin/coroutines/AbstractCoroutineContextKey
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
  ...
```

#### Cause

If the kotlinx-coroutines-core library version is 1.3.5 or later, [the kotlin-stdlib library version must be at a certain level or later](https://github.com/Kotlin/kotlinx.coroutines/issues/1879).

#### Solution

Check whether the kotlin-stdlib library version is v.1.3.70 or later with the `gradlew dependencies` command. If the version is earlier than v.1.3.70, you need to update it.

</details>

<details>

<summary>[Android] The "Manifest merger failed" error occurs during the build process.</summary>

#### Issue&#x20;

The "Manifest merger failed" error occurs during the build process.

#### Cause

The Airbridge SDK's `AndroidManifest.xml` includes rules to opt out of backing up the Shared Preferences data. The purpose of this rule is to avoid retaining the same Airbridge settings during the reinstallation of the app so that new installs or reinstalls can be detected accurately.

Merging Airbridge SDK backup rules with your app backup rules can cause conflicts.

#### Solution

Below are the opt-out rules defined in the Airbridge SDK.

Backup on Android 12 or laterBackup on Android 11 and earlier.

```
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
    <cloud-backup>
        <exclude domain="sharedpref" path="airbridge-internal" />
        <exclude domain="sharedpref" path="airbridge-install" />
        <exclude domain="sharedpref" path="airbridge-user-info" />
        <exclude domain="sharedpref" path="airbridge-user-alias" />
        <exclude domain="sharedpref" path="airbridge-user-attributes" />
        <exclude domain="sharedpref" path="airbridge-device-alias" />
        <exclude domain="database" path="airbridge.db" />
    </cloud-backup>
    <device-transfer>
        <exclude domain="sharedpref" path="airbridge-internal" />
        <exclude domain="sharedpref" path="airbridge-install" />
        <exclude domain="sharedpref" path="airbridge-user-info" />
        <exclude domain="sharedpref" path="airbridge-user-alias" />
        <exclude domain="sharedpref" path="airbridge-user-attributes" />
        <exclude domain="sharedpref" path="airbridge-device-alias" />
        <exclude domain="database" path="airbridge.db" />
    </device-transfer>
</data-extraction-rules>
```

**Fix conflict with fullBackupContent="string"**

Adding `android:fullBackupContent="string"` to the `AndroidManifest.xml` file may cause an error like the following.

Build Output

```
Manifest merger failed : Attribute application@fullBackupContent value=(string) from AndroidManifest.xml
```

To fix this error,

* add `xmlns:tools="http://schemas.android.com/tools"` to the `<manifest>` tag
* add `tools:replace="android:fullBackupContent"` to the `<application>` tag

in the Custom Main Manifest(`Assets/Plugins/Android/AndroidManifest.xml`) file.

**Fix conflict with dataExtractionRules="string resource"**

Adding `android:dataExtractionRules="string resource"` to the `AndroidManifest.xml` file may cause an error like the following.

Build Output

```
Manifest merger failed : Attribute application@dataExtractionRules value=(string resource) from AndroidManifest.xml
```

To fix this error,

* add `xmlns:tools="http://schemas.android.com/tools"` to the `<manifest>` tag
* add `tools:replace="android:dataExtractionRules"` to the `<application>` tag

in the Custom Main Manifest(`Assets/Plugins/Android/AndroidManifest.xml`) file.

**Fix conflict with allowBackup="false"**

Adding `android:allowBackup="false"` to the `AndroidManifest.xml` file may cause an error like the following.

Build Output

```
Manifest merger failed : Attribute application@allowBackup value=(false) from AndroidManifest.xml:32:9-36
	is also present at [:airbridge] AndroidManifest.xml:27:9-35 value=(true).
	Suggestion: add 'tools:replace="android:allowBackup"' to <application> element at AndroidManifest.xml:30:5-250:19 to override.
```

To fix this error,

* add `xmlns:tools="http://schemas.android.com/tools"` to the `<manifest>` tag
* add `tools:replace="android:allowBackup"` to the `<application>` tag

in the Custom Main Manifest(`Assets/Plugins/Android/AndroidManifest.xml`) file.

**If compileSdkVersion is lower than 31**

The `android:dataExtractionRules` has been added in API Level 31. Therefore, if the compileSdkVersion is lower than 31, an error like the following may occur.

Build Output

```
AndroidManifest.xml: AAPT: error: attribute android:dataExtractionRules not found.
```

To fix this error,

* add `xmlns:tools="http://schemas.android.com/tools"` to the `<manifest>` tag
* add `tools:remove="android:dataExtractionRules"` to the `<application>` tag

in the Custom Main Manifest(`Assets/Plugins/Android/AndroidManifest.xml`) file.

For more guidance, refer to the articles below.

* [Android Developers Guide](https://developer.android.com/identity/data/autobackup)<br>

</details>

<details>

<summary>[Android] Conflict occurs when merging the Airflux SDK backup rules.</summary>

#### Issue

If an Airflux SDK backup rule and a backup rule for a different third-party SDK (e.g., AppsFlyer SDK) overlap, you will see the build error below.

```
Attribute application@fullBackupContent value=(@xml/appsflyer_backup_rules) from [com.appsflyer:af-android-sdk:6.6.1] AndroidManifest.xml:14:18-73
is also present at [io.airbridge:sdk-android:2.14.0] AndroidManifest.xml:27:18-78 value=(@xml/airbridge_auto_backup_rules).
Suggestion: add 'tools:replace="android:fullBackupContent"' to <application> element at AndroidManifest.xml:7:5-13:19 to override.
```

#### Cause

Overlapping of the Airflux SDK backup rules and third-party SDK backup rules can cause build errors.

#### Solution

**`backup_rules.xml` setup**

1. Create an Android Library project (`Assets/Plugins/Android/res.androidlib`) to store your resource files.
2. Add an `AndroidManifest.xml` file in the created Android Library Project as follows.

   123456

   ```
   <?xml version="1.0" encoding="utf-8"?>
   <manifest xmlns:android="http://schemas.android.com/apk/res/android"
             package="custom.android.res"
             android:versionCode="1"
             android:versionName="1.0">
   </manifest>
   ```
3. Create a `res/xml` folder inside the created Android Library Project.
4. Create a file (e.g., `custom_backup_rules.xml`) within the new xml folder.
5. Add the data backup rules defined by the Airflux SDK as follows.

   12345678910111213141516

   ```
   <?xml version="1.0" encoding="utf-8"?>
   <full-backup-content>
       <!-- Airbridge Backup Rules -->
       <exclude domain="sharedpref" path="airbridge-internal" />
       <exclude domain="sharedpref" path="airbridge-install" />
       <exclude domain="sharedpref" path="airbridge-user-info" />
       <exclude domain="sharedpref" path="airbridge-user-alias" />
       <exclude domain="sharedpref" path="airbridge-user-attributes" />
       <exclude domain="sharedpref" path="airbridge-device-alias" />
       <exclude domain="database" path="airbridge.db" />
     
       <!-- Appsflyer Backup Rules -->
       <exclude domain="sharedpref" path="appsflyer-data"/>
   	
   	<!-- Your Custom Backup Rules -->
   </full-backup-content>
   ```

**`data_extraction_rules.xml` setup**

1. Create a file (e.g., `custom_data_extraction_rules.xml`) within the new xml folder.
2. Add the data backup rules defined by the Airflux SDK as follows.

   ```
   <?xml version="1.0" encoding="utf-8"?>
   ```

   ```
   <data-extraction-rules>
       <cloud-backup>
           <!-- Airbridge Backup Rules -->
           <exclude domain="sharedpref" path="airbridge-internal" />
           <exclude domain="sharedpref" path="airbridge-install" />
           <exclude domain="sharedpref" path="airbridge-user-info" />
           <exclude domain="sharedpref" path="airbridge-user-alias" />
           <exclude domain="sharedpref" path="airbridge-user-attributes" />
           <exclude domain="sharedpref" path="airbridge-device-alias" />
           <exclude domain="database" path="airbridge.db" />

           <!-- Appsflyer Backup Rules -->
           <exclude domain="sharedpref" path="appsflyer-data"/>

   	    <!-- Your Custom Backup Rules -->
       </cloud-backup>
       <device-transfer>
           <!-- Airbridge Backup Rules -->
           <exclude domain="sharedpref" path="airbridge-internal" />
           <exclude domain="sharedpref" path="airbridge-install" />
           <exclude domain="sharedpref" path="airbridge-user-info" />
           <exclude domain="sharedpref" path="airbridge-user-alias" />
           <exclude domain="sharedpref" path="airbridge-user-attributes" />
           <exclude domain="sharedpref" path="airbridge-device-alias" />
           <exclude domain="database" path="airbridge.db" />

           <!-- Appsflyer Backup Rules -->
           <exclude domain="sharedpref" path="appsflyer-data"/>

   	    <!-- Your Custom Backup Rules -->
       </device-transfer>
   </data-extraction-rules>
   ```

**`AndroidManifest.xml` setup**

Apply the data backup rules to the Android App Manifest file (`Assets/Plugins/Android/AndroidManifest.xml`) as follows.

```
<manifest
    ...
    xmlns:tools="http://schemas.android.com/tools">

    <application
        ...
		android:allowBackup="true"
		android:fullBackupContent="@xml/custom_backup_rules"
        android:dataExtractionRules="@xml/custom_data_extraction_rules"
		tools:replace="android:fullBackupContent, android:dataExtractionRules">
```

For more guidance, refer to the articles below.

* [Import an Android Library Project](https://docs.unity3d.com/2022.3/Documentation/Manual/android-library-project-import.html)
* [Override the Android App Manifest](https://docs.unity3d.com/2022.3/Documentation/Manual/overriding-android-manifest.html)

</details>


# 3. Send in-game event data

Sending sufficient in-game event and player attribute data to Airflux is essential for model training and ad display optimization. This step may take approximately 30 minutes to 1 hour, depending on the number of events and attributes you want to track.

<figure><img src="/files/zol4mAdTLCVez07be1F6" alt=""><figcaption></figcaption></figure>

***

## 1. Implement session tracking

To implement session tracking within a Unity project, integrate the following methods into the Airflux SDK as shown below.

The `NotifyAppForeground()` function should be called when the app gains focus, signaling the start of a user session. Conversely, `NotifyAppBackground()` should be called when the application is sent to the background, indicating the end of the session.

By integrating these calls, Airflux can collect session information, including session count, duration, and maximum session length.

{% tabs %}
{% tab title="Unity" %}

```csharp
using UnityEngine;

public class AirfluxBehaviour : MonoBehaviour
{
    void OnApplicationPause(bool pauseStatus)
    {
        if (pauseStatus)
        {
            Airflux.NotifyAppBackground();
        }
        else
        {
            Airflux.NotifyAppForeground();
        }
    }
}
```

{% endtab %}
{% endtabs %}

## 2. Send in-game event data

Use the `TrackEvent()` function to track key player actions within the game. The in-game event data plays a crucial role in enabling the Airflux AI model to learn player behavior patterns.&#x20;

<table><thead><tr><th width="192.7109375">Event Name</th><th>When to trigger</th></tr></thead><tbody><tr><td>Ad Impression</td><td>When the ad is shown to the user</td></tr><tr><td>Order Completed</td><td>When the in-app purchase is completed and the user receives the item (e.g., when a dialog appears confirming the purchase)</td></tr><tr><td>Achieve Level</td><td>When a stage ends (e.g., Success, Fail, Give-up, Retry)</td></tr><tr><td>Spend Credits</td><td>When the player spends in-game currency</td></tr></tbody></table>

{% hint style="warning" %}
Collecting and sending the event data specified in the [Required event data for Airflux integration](/airflux-reference/required-event-data-for-airflux-integration) to Airflux is essential for using Airflux.&#x20;
{% endhint %}

{% tabs %}
{% tab title="Unity" %}
The `TrackEvent()` function should be called when a key event is triggered. Refer to the table below for the detailed requirements for the key components.

<table><thead><tr><th width="109.57421875">Name</th><th width="123.3515625">Type</th><th>Description</th></tr></thead><tbody><tr><td>category</td><td>String</td><td><p><strong>Name of the event</strong></p><p>Only underscores are permitted as special characters; colons and other special characters are not allowed. If the collected data exceeds the maximum limit of 128 characters, only the initial 128 characters will be saved.</p></td></tr><tr><td>semanticAttributes</td><td>Dictionary&#x3C;string, object></td><td><p><strong>Semantic attributes of the event</strong> </p><p>Semantic attribute data collection is limited by type: up to 1024 characters for strings, and 64 bits for integers or floats.</p></td></tr><tr><td>customAttributes</td><td>Dictionary&#x3C;string, object></td><td><p><strong>Custom attributes of the even</strong>t</p><p>Custom attribute data collection is limited to 2,048 characters; exceeding the limit results in ERROR_MAX_LENGTH_EXCEEDED.</p></td></tr></tbody></table>

```csharp
Airflux.TrackEvent(
    // StandardCategory
    category: AirfluxCategory.AD_IMPRESSION,
    // SemanticAttributes
    semanticAttributes: new Dictionary<string, object>()
    {
        { AirfluxAttribute.VALUE, 11 },
        { AirfluxAttribute.TRANSACTION_ID, "8065ef16-162b-4a82-b683-e51aefdda7d5" },
        { AirfluxAttribute.CURRENCY, "USD" }
    },
    // CustomAttributes
    customAttributes: new Dictionary<string, object>()
    {
        { "key", "value" }
    }
);
```

{% endtab %}
{% endtabs %}

### Sample codes for event

{% hint style="warning" %}
In-game events are essential data for model training. Clearly understand the purpose and timing of collecting each event, and ensure proper implementation for data transmission.&#x20;
{% endhint %}

<details>

<summary>Ad Impression (IAA-related event)</summary>

{% hint style="danger" %}
**Attention**

The in-app ad revenue data must be collected using the client-side SDK through mediation platform integrations and sent to Airflux.&#x20;
{% endhint %}

Track ad impressions when ad is shown to the player and collect relevant data, such as ad type, ad revenue, placement, and more. &#x20;

For example, when ad revenue is generated after an interstitial ad is shown, call the `TrackEvent()` function, set the event category to `AirfluxCategory.AD_IMPRESSION`, and add `"adType"` to `customAttributes` to send `interstitial` as a value.&#x20;

<table><thead><tr><th width="169.89453125">Attribute Type</th><th width="115.31640625">Name</th><th width="127.4609375">Semantic Attributes Description</th><th>Sample Value</th></tr></thead><tbody><tr><td>Semantic Attribute</td><td>Currency</td><td>Currency for ad revenue</td><td>USD</td></tr><tr><td>Semantic Attribute</td><td>Value</td><td>Ad revenue amount</td><td>1.99</td></tr><tr><td>Custom Attribute</td><td>adType</td><td>The type of the ad</td><td>reward: Rewarded ad<br>interstitial: Interstitial ad<br>banner: Banner ad</td></tr><tr><td>Custom Attribute</td><td>ad_placement</td><td>Ad placement</td><td>rw_offline: Offline reward ad<br>rw_get_item: Rewarded ad for obtaining items like weapons, skins, etc.<br>rw_get_coin: Rewarded ad for earning coins<br>rw_get_gem: Rewarded ad for earning gems<br>rw_time_skip: Rewarded ad for reducing recovery time<br>int_next_stage: Interstitial ad that is presented when advancing to the next stage<br>bn_next_stage: Banner ad that is presented when advancing to the next stage</td></tr><tr><td>Custom Attribute</td><td>stage_type</td><td>The type of the stage</td><td>main: Main stage<br>promotion: Seasonal promotion stage (updated every 3 month)</td></tr><tr><td>Custom Attribute</td><td>stage_number</td><td>The stage number where interstitial or rewarded ads are presented after Success, Fail, Give-up, or Retry. Otherwise, null is collected.</td><td>main: 1, 2, ..., 550 (30 new stages added every month)<br>promotion: 1, 2, ...,100</td></tr><tr><td>Custom Attribute</td><td>reward_item</td><td>The reward earned by the player after engaging with a rewarded ad. For other ad types, null is collected.</td><td>coin: Number of coins earned<br>gem: Number of gems earned</td></tr></tbody></table>

#### Code example

```csharp
// Example: Ad revenue transmission from AdMob
Airflux.TrackEvent(
    category: AirfluxCategory.AD_IMPRESSION,
    semanticAttributes: new Dictionary<string, object>()
    {
        { AirfluxAttribute.VALUE, 0.01 }, // Required: Ad revenue
        { AirfluxAttribute.CURRENCY, "USD" }, // Required: Currency code
        {
            AirfluxAttribute.AD_PARTNERS, new Dictionary<string, object>()
            {
                {
                    "mopub", new Dictionary<string, object>()
                    {
                        { "app_version", "5.18.0" },
                        { "adunit_id", "12345" },
                        { "adunit_name", "12345" },
                        { "adunit_format", "Banner" },
                        { "id", "12345" },
                        { "currency", "USD" },
                        { "publisher_revenue", 12345.123 },
                        { "adgroup_id", "12345" },
                        { "adgroup_name", "12345" },
                        { "adgroup_type", "12345" },
                        { "adgroup_priority", "12345" },
                        { "country", "kr" },
                        { "precision", "publisher_defined" },
                        { "network_name", "12345" },
                        { "network_placement_id", "12345" },
                        { "demand_partner_data", "12345" },
                    }
                }
            }
        },
    },
    customAttributes: new Dictionary<string, object>()
    {
        { "adType", "reward" }, // Ad type: reward, interstitial, etc.
        { "ad_placement", "main_banner" }, // Ad placement: main_banner,int_next_stage, etc.
        { "stage_type", "Main" }, // Stage type: Main, Event, etc.
        { "stage_number", "1" }, // Stage number where interstitial/rewarded ads are shown after Success, Fail, Give-up, or Retry; otherwise null
        { "reward_item", new Dictionary<string, object> {{"coin", 10}, {"gem", 20}} } // Reward from a rewarded ad; null for other ad types
    }
);

```

</details>

<details>

<summary>Order Completed (IAP-related event)</summary>

{% hint style="danger" %}
**Attention**

The in-app purchase revenue data must be collected using the client-side SDK and sent to Airflux. There might be a slight gap between the data sent to Airflux and the revenue data provided by vendors.
{% endhint %}

Track in-app purchases and relevant data such as item information, transaction ID, the purchased amount, the payment currency, and more. &#x20;

For example, when a dialog is prompted confirming a purchase of an item, call the `TrackEvent()` function, set the event category to `AirfluxCategory.ORDER_COMPLETED`, and add `AirfluxAttribute.PRODUCT_ID` and `AirfluxAttribute.PRODUCT_NAME` to send information of the purchase item.&#x20;

{% hint style="warning" %}
The payment currency information (`AirfluxAttribute.CURRENCY` )and the purchase amount  (`AirfluxAttribute.VALUE)` must be included in the event data for accurate revenue analysis.&#x20;
{% endhint %}

<table><thead><tr><th width="169.89453125">Attribute Type</th><th width="115.31640625">Name</th><th width="127.4609375">Semantic Attributes Description</th><th>Sample Value</th></tr></thead><tbody><tr><td>Semantic Attribute</td><td>Transaction ID</td><td>Transaction ID</td><td>TXN-20250411-5F3C9A72B1</td></tr><tr><td>Semantic Attribute</td><td>Currency</td><td>Currency for ad revenue</td><td>USD</td></tr><tr><td>Semantic Attribute</td><td>Value</td><td>Ad revenue amount</td><td>10.99</td></tr><tr><td>Semantic Attribute</td><td>Product ID</td><td>Product ID</td><td>1C569KY32P1</td></tr><tr><td>Semantic Attribute</td><td>Product Name</td><td>Product Name</td><td>remove_ads: ""Ad Removal"" as a purchase item<br>welcome_pack: Item package for newly acquired players<br>starter_pack: Item package for beginners<br>coin_pack_1: Coin package<br>gem_pack_2: Gem package<br>limited_skin_1: Time-limited skin</td></tr><tr><td>Custom Attribute</td><td>purchase_route</td><td>The source of the purchase</td><td>shop: Purchased from the shop<br>popup: Purchased from a pop-up</td></tr></tbody></table>

#### Code example

```csharp
Airflux.TrackEvent(
    // StandardCategory
    category: AirfluxCategory.ORDER_COMPLETED, // or "CustomEvent" (CustomCategory)
    // SemanticAttributes
    semanticAttributes: new Dictionary<string, object>()
    {
        { AirfluxAttribute.VALUE, 11 }, // Required: Actual purchase amount
        { AirfluxAttribute.TRANSACTION_ID, "8065ef16-162b-4a82-b683-e51aefdda7d5" }, // Required: Transaction ID
        { AirfluxAttribute.CURRENCY, "USD" }, // Required: Currency code 
        {
            AirfluxAttribute.PRODUCTS, new List<object>()
            {
                new Dictionary<string, object>()
                {
                    { AirfluxAttribute.PRODUCT_ID, "1C569KY32P1" }, // Required, Product ID
                    { AirfluxAttribute.PRODUCT_NAME, "remove_ads" } // Required, Product name (welcome_back, remove_ads, starter_pack ...)
                } 
            }
        }
    },
    // CustomAttributes
    customAttributes: new Dictionary<string, object>()
    {
        { "purchase_route", "shop" } // (Optional) shop / popup
    }
```

</details>

<details>

<summary>Achieve Level</summary>

Track player game progress and how a stage ended.&#x20;

For example, when a stage ends, call the `TrackEvent()` function, set the event category to `AirfluxCategory.ACHIEVE_LEVEL`,  and add `"stage_type"` and `"stage_number"` to track the stage type and stage number.&#x20;

Additionally, use `"result"` to send information on how the stage ended, such as `success`, `fail`, `giveup`, and `retry` .

<table><thead><tr><th width="169.89453125">Attribute Type</th><th width="115.31640625">Name</th><th width="127.4609375">Semantic Attributes Description</th><th>Sample Value</th></tr></thead><tbody><tr><td>Custom Attribute</td><td>stage_type</td><td>The type of the stage</td><td>main: Main stage<br>promotion: Seasonal promotion stage (updated every 3 month)</td></tr><tr><td>Custom Attribute</td><td>stage_number</td><td>The stage number where Success, Fail, Give-up, or Retry occurred.</td><td>main: 1, 2, ..., 550 (30 new stages added every month)<br>promotion: 1, 2, ...,100 </td></tr><tr><td>Custom Attribute</td><td>result</td><td>The result of the stage</td><td>success: Stage completed successfully<br>fail: Stage failed<br>giveup: Stage abandoned<br>retry: Stage retried after failure or exit</td></tr></tbody></table>

#### Code example

```csharp
Airflux.TrackEvent(
    // StandardCategory
    category: AirfluxCategory.ACHIEVE_LEVEL, // or "CustomEvent" (CustomCategory)
    // SemanticAttributes
    semanticAttributes: new Dictionary<string, object>(),
    // CustomAttributes
    customAttributes: new Dictionary<string, object>()
    {
        { "stage_type", "main"},
        { "stage_number", 13  },
        { "result", "success" }      
    }
);
```

</details>

<details>

<summary>Spend Credits</summary>

Track in-game currency spending and relevant data, such as in-game currency information, spending amount, and more.&#x20;

For example, when the player spends in-game currency, such as coins and gems, call the `TrackEvent()` function, set the event category to `AirfluxCategory.SPEND_CREDITS` and add `"item_type"` and `"item_amount"`  to send the in-game currency type and spending amount. &#x20;

Additionally, use `"stage_type"` and `"stage_number"`to to track the stage type and stage number.

<table><thead><tr><th width="169.89453125">Attribute Type</th><th width="115.31640625">Name</th><th width="253.59765625">Semantic Attributes Description</th><th>Sample Value</th></tr></thead><tbody><tr><td>Custom Attribute</td><td>item_type</td><td>The type of the in-game currency spent</td><td>coin<br>gem</td></tr><tr><td>Custom Attribute</td><td>item_amount</td><td>The amount of the in-game currency spent</td><td>10, 20</td></tr><tr><td>Custom Attribute</td><td>stage_type</td><td>The type of the stage</td><td>main: Main stage<br>promotion: Seasonal promotion stage (updated every 3 month)</td></tr><tr><td>Custom Attribute</td><td>stage_number</td><td>The current stage of the player</td><td>main: 1, 2, ~ , 550 (30 new stages added every month)<br>promotion: 1, 2, ~ ,100</td></tr></tbody></table>

#### Code example

```csharp
Airflux.TrackEvent(
    // StandardCategory
    category: AirfluxCategory.SPEND_CREDITS, // or "CustomEvent" (CustomCategory)
    // SemanticAttributes
    semanticAttributes: new Dictionary<string, object>(),
    // CustomAttributes
    customAttributes: new Dictionary<string, object>()
    {
        { "item_type", "coin" },
        { "item_amount", 100  },
        { "stage_type", "main"},
        { "stage_number", 13 }
    }
);
```

</details>

#### In-game event data verification

<details>

<summary>Using the Airflux Testing Console</summary>

For a more structured validation, use the **Event Data Test** area within the [Airflux Testing Console](https://testing-console.airflux.ai/).

Once all events have been collected successfully, the status for each event should be either **No Data** or **Nullable Fields Incomplete**.

</details>

## 3. Send player attribute data

In addition to tracking player actions through in-game events, Airflux also requires player attribute data, such as the player’s current level, in-game currency balance, and other contextual information to train the Airflux AI model. Sufficient player attribute data allows Airflux to fine-segment players and perform inferences for maximum LTV and retention.&#x20;

{% hint style="warning" %}
Attention

The player attribute data must be passed to the SDK before the inference API request.&#x20;
{% endhint %}

Use the following functions to track and pass the player attribute data to the SDK.

<details>

<summary>Player's Level Status </summary>

Use `Airflux.SetLevel()`  to pass the player's level status to the Airflux SDK. For accurate model training, the player's level status data must be aggregated to gather sufficient contextual information.&#x20;

Therefore, it is crucial to call the `Airflux.SetLevel()`  and aggregate the player's level status every time:

* the player opens the game
* the player logs in to the game&#x20;
* the player's game level is updated

For new players or sign-ups, the starting level should be passed.

<pre class="language-csharp"><code class="lang-csharp">// Trigger in the following cases
// - when the player starts the game
// - when the player logs in
<strong>// - when the player level is updated
</strong>Airflux.SetLevel(5);
</code></pre>

</details>

<details>

<summary>Player's Currency Status</summary>

Use `Airflux.SetHardCurrency()`  and `Airflux.SetSoftCurrency()`  to pass the player's hard currency (e.g., diamonds) and soft currency (e.g., coins) inventory status to the Airflux SDK.

For accurate model training, the player's currency status data must be aggregated to gather sufficient contextual information.&#x20;

Therefore, it is crucial to call the `Airflux.SetHardCurrency()`  or `Airflux.SetSoftCurrency()` and aggregate the player's currency status every time:

* a player purchased, earned, or spent in-game currency
* a player starts the game
* a player logs in to the game&#x20;

For new players or sign-ups, the game's default currency inventory status should be passed.

```csharp
// Trigger when hard/soft currency balances change. Input the final balance.
// - Each currency can have up to 100 key-value pairs.
// - The attribute names must satisfy the regex ^[a-zA-Z_][a-zA-Z0-9_]*$.
// - The maximum length of attribute names is 128 characters.
Airflux.SetHardCurrency("diamond", 1000);
Airflux.SetSoftCurrency("gold", 1000);
Airflux.SetSoftCurrency("wood", 1000);
Airflux.SetSoftCurrency("coal", 1000);
```

{% hint style="warning" %}
**Attention**&#x20;

Input the in-game currency balance for `Currency` . If a player had 500 diamonds and purchased 700 more, `Airflux.SetHardCurrency("diamond", 1200)` should be triggered.
{% endhint %}

</details>

<details>

<summary>Other Player Attribute</summary>

Use `Airflux.SetInferenceAttribute()` to pass the player's attributes other than the level and currency inventory status. Make sure to call the function whenever the attribute is updated.&#x20;

```csharp
// Trigger when player attributes change.
// - Attributes can have up to 100 key-value pairs.
// - Keys must satisfy the regex ^[a-zA-Z_][a-zA-Z0-9_]*$.
// - The maximum length of keys is 128 characters.
// - Values type must be string, numeric, or boolean.
// - The maximum length of string values is 1024 characters.
Airflux.SetInferenceAttribute("string", "string");
Airflux.RemoveInferenceAttribute("string");
Airflux.ClearInferenceAttributes();
```

</details>

#### Player attribute data verification

<details>

<summary>Using the Airflux Testing Console</summary>

For a more structured validation, use the **Player Attriubte Test** area within the [Airflux Testing Console](https://testing-console.airflux.ai/).

Once all events have been collected successfully, the status for each event should be either **No Data** or **Nullable Fields Incomplete**.

</details>

## 4. Send User ID

If your game issues a unique User ID for each player upon sign-up or sign-in, it is advised to send the data to Airflux. If your game server does not handle User IDs, a unique identifier can be generated upon sign-in and sent to Airflux. The User ID must be sent before the event data.

{% tabs %}
{% tab title="Unity" %}
{% hint style="danger" %}
**Attention**

If the User ID is not sent before the event data, the User ID cannot be linked to the event data.&#x20;
{% endhint %}

```csharp
// Send the User ID
Airflux.SetUserID("your_internal_user_id");

// Send the event data
Airflux.TrackEvent(
    // ... event related codes ...
);
```

{% endtab %}
{% endtabs %}

## 5. Verify data transmission

Ensure the payload transmitted by the Airflux SDK adheres to the predefined event taxonomy and that the session information, in-game events, and play attribute data are sent to the Airflux server as intended.

### How to verify event transmission

Trigger events based on the test scenarios listed below, and check the corresponding events in the **\[Raw Data]>\[App Real-time Log]** menu of the Airbridge dashboard. Event data transmitted through the Airflux SDK will be displayed in JSON format. You need to verify that the data type and structure of each field match the predefined format.

<figure><img src="/files/Oah7MQrlTytyNxzDcOmX" alt=""><figcaption></figcaption></figure>

### Taxonomy-based event and attribute format validation

Check the following items to ensure that the taxonomy definitions match the values configured in the SDK.

<table><thead><tr><th width="210">Field</th><th>Validation Criteria</th></tr></thead><tbody><tr><td>eventData.goal.category</td><td>Verify that the event name exactly matches the string defined in the taxonomy.</td></tr><tr><td>semanticAttributes</td><td>• All keys must match those defined in the taxonomy.<br>• Value types must match the defined types (string, number, boolean).<br>• For revenue events (ad_impression, order_completed), values must be positive numbers.</td></tr><tr><td>originalCurrency</td><td>Must be a 3-letter uppercase code defined by ISO-4217 (e.g., USD, KRW)</td></tr><tr><td>customAttributes</td><td>Key: string / Value: allowed types (string, number, boolean)</td></tr></tbody></table>

### Key event validation based on test scenarios

When the following key events are triggered, verify that they are properly logged in the **\[App Real-time Log]** page without omission and that related attributes are accurately included.&#x20;

Click [here](/airflux-reference/required-event-data-for-airflux-integration) to view the events and key attributes that must be sent to Airflux.&#x20;

<table><thead><tr><th width="254">Scenario</th><th>Key Event</th></tr></thead><tbody><tr><td>App installed</td><td>Install</td></tr><tr><td>App launched</td><td>Open</td></tr><tr><td>Ad viewing completed</td><td>Ad Impression</td></tr><tr><td>In-app purchase completed</td><td>Order Completed</td></tr><tr><td>Level achieved</td><td>Achieve Level</td></tr><tr><td>Currency spent</td><td>Spend Credits</td></tr></tbody></table>

In particular, revenue-related events such as **Ad Impression** and **Order Complete** are critical for the Airflux model training. Double-check the following points:

* **Ad Impression**
  * Confirm the revenue value in `eventData.originalValue`.
  * Confirm the currency in `originalCurrency`.
  * Confirm the ad type in `customAttributes.adType`.
* **Order Completed**
  * Confirm the revenue value in `eventData.originalValue`.
  * Confirm the currency in `originalCurrency`.

If you are sending User IDs, make sure the `externalUserID` is properly logged.

## Frequently Asked Questions

<details>

<summary>When a player completes a stage and levels up at the same time, how should I track it?</summary>

Use the `TrackEvent()` function to track the player's action of completing a stage as the Achieve Level event, and use the `SetLevel()` function to track the player's updated level as the player attribute.&#x20;

</details>

<details>

<summary>Can I use the Airflux SDK to collect and send game store payment data?</summary>

No. The game store payment data must be collected and sent using the client-side SDK.

</details>

<details>

<summary>After restarting the game on iOS, the Airflux SDK stops sending events. How can I fix this?</summary>

There are two things you need to check:

1. If `AutoStartTrackingEnabled` is set to `false` , make sure to manually call `Airflux.StartTracking()` every time the app launches.
2. Make sure `StartTracking()` is called after `application(_:didFinishLaunchingWithOptions:)`, the core method in the iOS app lifecycle that is executed once native initialization is complete.\
   Calling `StartTracking()` too early may result in all events being dropped starting from the second launch.

For Airflux Unity SDK integration, we recommend the following approach:

1. Create a `MonoBehaviour` script.
2. Call `Airflux.StartTracking()` once within `OnApplicationFocus(true)`.

* `OnApplicationFocus()` is called after `didBecomeActive`, and therefore, the call occurs after native initialization.
* Because `OnApplicationFocus()` is triggered every time the app returns to the foreground, make sure to add a flag so that `StartTracking()` is only called once.

</details>


# 4. Call the Inference API

Configure the Airflux SDK to call the Inference API before displaying ads, and use the API response to determine whether to proceed with the ad display. This step may take about 15 minutes to 1 hour.

<figure><img src="/files/0KBVGX3MBo3VHI6wJPZI" alt=""><figcaption></figcaption></figure>

***

## 1. Call the Inference API and configure callbacks

Airflux analyzes player behavior using in-game events and player attribute data to determine the optimal timing for ad displays to maximize revenue. When calling the Inference API before showing an ad to the player, the Airflux AI will return a decision on whether to display the ad or skip it.

{% tabs %}
{% tab title="Unity" %}
The `InferenceShowAdInterstitial()` function should be called when the ad is supposed to be shown. If the API call is successfully processed, the `onShowAd` callback function is triggered to show the ad to the player, or the `onSkipAd` callback function is triggered to skip the ad for the player.

***

**Set Ad Placement Context**

Before calling the function, **you must set the ad placement context** using `setInferenceAttributes()`. This attribute helps Airflux understand where the ad is shown (e.g., `"stage_start"` or `"stage_end"`), enabling proper feature logging and policy optimization.

{% hint style="danger" %}
Failure to correctly set ad\_placement may reduce optimization performance and skew experimental results.
{% endhint %}

```csharp
Airflux.setInferenceAttributes("ad_placement", "stage_start");

Airflux.InferenceShowAdInterstitial(
    onShowAd: () => { /* Show Ad */ },
    onSkipAd: () => { /* Skip Ad */ },
    onFailure: (AirfluxError error) => { /* Handle on failure */ }
);
```

When the API call fails, `onFailure` is returned. The API call may fail in the following cases:

* **The device's country is not in the countryAllowlist:** API calls may fail for players in countries not supported by Airflux.
* **The inference server returns a 4XX or 5XX error response:** API calls may fail due to internal server errors or invalid requests.&#x20;
* **The API call times out after 3 seconds without a response:** Network delays or other issues may cause the API response time to go beyond the limit.
* Alternatively, the API failure may be intentionally triggered to invoke the internal ad display logic as a fallback.

Failure to call the `InferenceShowAdInterstitial()` function may lead to degraded play experience and loss of ad revenue. Therefore, it is recommended that a 3-second timeout be set and a fallback be implemented to display ads to the player in case the API call fails, to minimize potential negative effects. Retry attempts upon function call failure are not required.&#x20;

{% hint style="warning" %} <mark style="color:red;">The fallback should incorporate your internal ad display logic.</mark>&#x20;
{% endhint %}
{% endtab %}
{% endtabs %}

### Frequently Asked Questions

<details>

<summary>What is the difference between <code>onSkipAd()</code> and <code>onFailure()</code> callbacks?</summary>

* The `onSkipAd()` callback function is triggered when the API call is successful, and the inference result from the Airflux AI model indicates that an ad should not be displayed to enhance the play experience and maximize ad revenue.
* The `onFailure()` callback function is triggered when the API call fails. This includes situations such as the device's country not being in the allowlist (`countryAllowlist`), network issues, server errors, or request validation failures, where no response is received.

</details>

<details>

<summary>What is the response time of the API by country?</summary>

Airflux aims to deliver reliable service to users worldwide and typically maintains quick response times in most regions. However, minor delays may arise based on the network environment.

</details>

## 2. Verify implementation

Ensure that the `InferenceShowAdInterstitial()` function is called when the ad is supposed to be shown to the player.&#x20;

Then, verify that the logic for proceeding with ad display based on the API response is properly implemented. Airflux supports testing API call response scenarios for you to verify proper implementation. The following settings should be used for verification purposes only and must be removed before deploying the app.&#x20;

### Test options

You can simulate specific test scenarios by setting the `contexts.inferenceAttributes.forceInferenceResponse` field in the Inference API request.

| Value     | Purpose                   | Expected Behavior                                |
| --------- | ------------------------- | ------------------------------------------------ |
| onShowAd  | Test ad display           | Always returns true → Ad is shown                |
| onSkipAd  | Test ad skip              | Always returns false → Ad is skipped             |
| onFailure | Test API failure scenario | Returns HTTP 422 → Client should handle fallback |

{% hint style="warning" %}
Attention

The `forceInferenceResponse` field MUST be removed in the production environment.
{% endhint %}

### Test setup

{% code overflow="wrap" %}

```csharp
Airflux.SetInferenceAttribute("forceInferenceResponse", "onShowAd");
// The following call to Airflux.InferenceShowAdInterstitial() will return "onShowAd"

Airflux.SetInferenceAttribute("forceInferenceResponse", "onSkipAd");
// The following call to Airflux.InferenceShowAdInterstitial() will return "onSkipAd"

Airflux.SetInferenceAttribute("forceInferenceResponse", "onFailure");
// The following call to Airflux.InferenceShowAdInterstitial() will return "onFailure"
```

{% endcode %}

{% hint style="warning" %}
Attention

This test setting forcibly manipulates inference responses. If it remains active in the production environment, ads may never be shown or may be constantly shown at every ad placement. Ensure this setting is removed before deploying the app.
{% endhint %}

Confirm that when the `onShowAd` callback is triggered, the ad is displayed, and when the `onSkipAd` callback is triggered, the ad is skipped. When the `onFailure` callback is triggered, the fallback procedure is operated as intended.&#x20;

## 3. Deploy the app

After completing sufficient QA and crash testing, deploy your gaming app. To ensure proper integration with Airflux, make sure the deployment follows the timeline coordinated with the Growth Manager.

<details>

<summary>Where can I get guidance for the app store review?</summary>

Click [here](/airflux-reference/preparing-for-the-app-store-review) for guidance on preparing for the Google Play and App Store reviews.

</details>

## Next steps

Congratulations! If you have completed the steps above, you are all set to optimize your in-game advertising with Airflux. Click [here](/reporting) to learn how to receive your optimization results.&#x20;


